Eth contract fix memory + new sdk + refactor (#257)

* Update sdk version

* Update the contract according to the sdk changes

- Change some memory modifiers to improve gas efficiency
- Implement getValidTimePeriod() and remove old staleness logic
- Update the tests

* Update latest migration descriptions

* Add version

* Update Deploying.md

* Add test to validate version of the contract

* Add deploy commit hash

* Rename the placeholder

* Fix placeholder
This commit is contained in:
Ali Behjati 2022-08-24 22:03:49 +04:30 committed by GitHub
parent 995c886804
commit a17b27f3d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 89 additions and 64 deletions

View File

@ -18,6 +18,9 @@ rm -rf build && npx truffle compile --all
# Merge the network addresses into the artifacts, if some contracts are already deployed. # Merge the network addresses into the artifacts, if some contracts are already deployed.
npx apply-registry npx apply-registry
# Set the deploy commit hash in the contract binary (used for debugging purposes)
sed -i "s/dead0beaf0deb10700c0331700da5d00deadbead/$(git rev-parse HEAD)/g" build/contracts/*
# Perform the migration # Perform the migration
npx truffle migrate --network $MIGRATIONS_NETWORK npx truffle migrate --network $MIGRATIONS_NETWORK
@ -60,6 +63,8 @@ const PythUpgradable = artifacts.require("PythUpgradable");
const { upgradeProxy } = require("@openzeppelin/truffle-upgrades"); const { upgradeProxy } = require("@openzeppelin/truffle-upgrades");
/** /**
* Version <x.y.z>.
*
* Briefly describe the changelog here. * Briefly describe the changelog here.
*/ */
module.exports = async function (deployer) { module.exports = async function (deployer) {
@ -86,6 +91,12 @@ make sure that your change to the contract won't cause any collision**. For exam
Anything other than the operations above will probably cause a collision. Please refer to Open Zeppelin Upgradeable Anything other than the operations above will probably cause a collision. Please refer to Open Zeppelin Upgradeable
(documentations)[https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable] for more information. (documentations)[https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable] for more information.
## Versioning
We use [Semantic Versioning](https://semver.org/) for our releases. When upgrading the contract, update the npm package version using
`npm version <new version number> --no-git-tag-version`. Also, modify the hard-coded value in `version()` method in
[the `Pyth.sol` contract](./contracts/pyth/Pyth.sol) to the new version. Then, after your PR is merged in main, create a release like with tag `pyth-evm-contract-v<x.y.z>`. This will help developers to be able to track code changes easier.
# Testing # Testing
The [pyth-js][] repository contains an example with documentation and a code sample showing how to relay your own prices to a The [pyth-js][] repository contains an example with documentation and a code sample showing how to relay your own prices to a

View File

@ -24,7 +24,7 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
setPyth2WormholeEmitter(pyth2WormholeEmitter); setPyth2WormholeEmitter(pyth2WormholeEmitter);
} }
function updatePriceBatchFromVm(bytes memory encodedVm) private returns (PythInternalStructs.BatchPriceAttestation memory bpa) { function updatePriceBatchFromVm(bytes calldata encodedVm) private returns (PythInternalStructs.BatchPriceAttestation memory bpa) {
(IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVm); (IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVm);
require(valid, reason); require(valid, reason);
@ -211,34 +211,29 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
} }
function queryPriceFeed(bytes32 id) public view override returns (PythStructs.PriceFeed memory priceFeed){ function queryPriceFeed(bytes32 id) public view override returns (PythStructs.PriceFeed memory priceFeed){
// Look up the latest price info for the given ID // Look up the latest price info for the given ID
PythInternalStructs.PriceInfo memory info = latestPriceInfo(id); PythInternalStructs.PriceInfo memory info = latestPriceInfo(id);
require(info.priceFeed.id != 0, "no price feed found for the given price id"); require(info.priceFeed.id != 0, "no price feed found for the given price id");
// Check that there is not a significant difference between this chain's time
// and the price publish time.
if (info.priceFeed.status == PythStructs.PriceStatus.TRADING &&
absDiff(block.timestamp, info.priceFeed.publishTime) > validTimePeriodSeconds()) {
info.priceFeed.status = PythStructs.PriceStatus.UNKNOWN;
// getLatestAvailablePrice* gets prevPrice when status is
// unknown. So, now that status is being set to unknown,
// we should move the current price to the previous
// price to ensure getLatestAvailablePrice* works
// as intended.
info.priceFeed.prevPrice = info.priceFeed.price;
info.priceFeed.prevConf = info.priceFeed.conf;
info.priceFeed.prevPublishTime = info.priceFeed.publishTime;
}
return info.priceFeed; return info.priceFeed;
} }
function absDiff(uint x, uint y) private pure returns (uint) { function priceFeedExists(bytes32 id) public override view returns (bool) {
if (x > y) { PythInternalStructs.PriceInfo memory info = latestPriceInfo(id);
return x - y; return (info.priceFeed.id != 0);
} else {
return y - x;
} }
function getValidTimePeriod() public override view returns (uint) {
return validTimePeriodSeconds();
}
function version() public pure returns (string memory) {
return "0.1.0";
}
function deployCommitHash() public pure returns (bytes20) {
// This is a place holder for the commit hash and will be replaced
// with the commit hash upon deployment.
return hex"dead0beaf0deb10700c0331700da5d00deadbead";
} }
} }

View File

@ -5,9 +5,15 @@ const PythUpgradable = artifacts.require("PythUpgradable");
const { upgradeProxy } = require("@openzeppelin/truffle-upgrades"); const { upgradeProxy } = require("@openzeppelin/truffle-upgrades");
/** /**
* Version 0.1.0
*
* This change: * This change:
* - Updates the interface, adds `updatePriceFeedsIfNecessary` that wraps * - Updates the interface, adds `updatePriceFeedsIfNecessary` that wraps
* `updatePriceFeeds` and rejects if the price update is not necessary. * `updatePriceFeeds` and rejects if the price update is not necessary.
* - Changes some memory modifiers to improve gas efficiency.
* - Changes staleness logic to be included in the sdk and bring
* more clarity to the existing code.
* - Adds version to the contract (which is hard coded)
*/ */
module.exports = async function (deployer) { module.exports = async function (deployer) {
const proxy = await PythUpgradable.deployed(); const proxy = await PythUpgradable.deployed();

View File

@ -5,9 +5,15 @@ const PythUpgradable = artifacts.require("PythUpgradable");
const { upgradeProxy } = require("@openzeppelin/truffle-upgrades"); const { upgradeProxy } = require("@openzeppelin/truffle-upgrades");
/** /**
* Version 0.1.0
*
* This change: * This change:
* - Updates the interface, adds `updatePriceFeedsIfNecessary` that wraps * - Updates the interface, adds `updatePriceFeedsIfNecessary` that wraps
* `updatePriceFeeds` and rejects if the price update is not necessary. * `updatePriceFeeds` and rejects if the price update is not necessary.
* - Changes some memory modifiers to improve gas efficiency.
* - Changes staleness logic to be included in the sdk and bring
* more clarity to the existing code.
* - Adds version to the contract (which is hard coded)
*/ */
module.exports = async function (deployer) { module.exports = async function (deployer) {
const proxy = await PythUpgradable.deployed(); const proxy = await PythUpgradable.deployed();

View File

@ -5,9 +5,15 @@ const PythUpgradable = artifacts.require("PythUpgradable");
const { upgradeProxy } = require("@openzeppelin/truffle-upgrades"); const { upgradeProxy } = require("@openzeppelin/truffle-upgrades");
/** /**
* Version 0.1.0
*
* This change: * This change:
* - Updates the interface, adds `updatePriceFeedsIfNecessary` that wraps * - Updates the interface, adds `updatePriceFeedsIfNecessary` that wraps
* `updatePriceFeeds` and rejects if the price update is not necessary. * `updatePriceFeeds` and rejects if the price update is not necessary.
* - Changes some memory modifiers to improve gas efficiency.
* - Changes staleness logic to be included in the sdk and bring
* more clarity to the existing code.
* - Adds version to the contract (which is hard coded)
*/ */
module.exports = async function (deployer) { module.exports = async function (deployer) {
const proxy = await PythUpgradable.deployed(); const proxy = await PythUpgradable.deployed();

View File

@ -1,17 +1,17 @@
{ {
"name": "wormhole", "name": "@pythnetwork/pyth-evm-contract",
"version": "1.0.0", "version": "0.1.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "wormhole", "name": "@pythnetwork/pyth-evm-contract",
"version": "1.0.0", "version": "0.1.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@openzeppelin/contracts": "^4.5.0", "@openzeppelin/contracts": "^4.5.0",
"@openzeppelin/contracts-upgradeable": "^4.5.2", "@openzeppelin/contracts-upgradeable": "^4.5.2",
"@pythnetwork/pyth-sdk-solidity": "^0.5.1", "@pythnetwork/pyth-sdk-solidity": "^0.5.2",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"elliptic": "^6.5.2", "elliptic": "^6.5.2",
"ganache-cli": "^6.12.1", "ganache-cli": "^6.12.1",
@ -3674,9 +3674,9 @@
"dev": true "dev": true
}, },
"node_modules/@pythnetwork/pyth-sdk-solidity": { "node_modules/@pythnetwork/pyth-sdk-solidity": {
"version": "0.5.1", "version": "0.5.2",
"resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-solidity/-/pyth-sdk-solidity-0.5.1.tgz", "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-solidity/-/pyth-sdk-solidity-0.5.2.tgz",
"integrity": "sha512-uvV48IPzkU4+q6PQZNbLnjT+xVaiLmnMEalMvLFK3+QRBpd8gTXm8izdO0ZPbKHdytOueTHxlFK5imo7hRabBA==" "integrity": "sha512-On0hyZcTuMsonSzLc+HHA/5npjwB9WlFZlLrXW8TUV+7WEKH4o73OYJy/b9197E7snTviDIASlVT+rmcC9b7rg=="
}, },
"node_modules/@redux-saga/core": { "node_modules/@redux-saga/core": {
"version": "1.1.3", "version": "1.1.3",
@ -39998,9 +39998,9 @@
"dev": true "dev": true
}, },
"@pythnetwork/pyth-sdk-solidity": { "@pythnetwork/pyth-sdk-solidity": {
"version": "0.5.1", "version": "0.5.2",
"resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-solidity/-/pyth-sdk-solidity-0.5.1.tgz", "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-solidity/-/pyth-sdk-solidity-0.5.2.tgz",
"integrity": "sha512-uvV48IPzkU4+q6PQZNbLnjT+xVaiLmnMEalMvLFK3+QRBpd8gTXm8izdO0ZPbKHdytOueTHxlFK5imo7hRabBA==" "integrity": "sha512-On0hyZcTuMsonSzLc+HHA/5npjwB9WlFZlLrXW8TUV+7WEKH4o73OYJy/b9197E7snTviDIASlVT+rmcC9b7rg=="
}, },
"@redux-saga/core": { "@redux-saga/core": {
"version": "1.1.3", "version": "1.1.3",

View File

@ -1,8 +1,7 @@
{ {
"name": "wormhole", "name": "@pythnetwork/pyth-evm-contract",
"version": "1.0.0", "version": "0.1.0",
"description": "", "description": "",
"main": "networks.js",
"devDependencies": { "devDependencies": {
"@chainsafe/truffle-plugin-abigen": "0.0.1", "@chainsafe/truffle-plugin-abigen": "0.0.1",
"@openzeppelin/cli": "^2.8.2", "@openzeppelin/cli": "^2.8.2",
@ -30,7 +29,7 @@
"dependencies": { "dependencies": {
"@openzeppelin/contracts": "^4.5.0", "@openzeppelin/contracts": "^4.5.0",
"@openzeppelin/contracts-upgradeable": "^4.5.2", "@openzeppelin/contracts-upgradeable": "^4.5.2",
"@pythnetwork/pyth-sdk-solidity": "^0.5.1", "@pythnetwork/pyth-sdk-solidity": "^0.5.2",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"elliptic": "^6.5.2", "elliptic": "^6.5.2",
"ganache-cli": "^6.12.1", "ganache-cli": "^6.12.1",

View File

@ -6,7 +6,7 @@ const PythStructs = artifacts.require("PythStructs");
const { deployProxy, upgradeProxy } = require("@openzeppelin/truffle-upgrades"); const { deployProxy, upgradeProxy } = require("@openzeppelin/truffle-upgrades");
const { expectRevert, expectEvent, time } = require("@openzeppelin/test-helpers"); const { expectRevert, expectEvent, time } = require("@openzeppelin/test-helpers");
const { assert } = require("chai"); const { assert, expect } = require("chai");
// Use "WormholeReceiver" if you are testing with Wormhole Receiver // Use "WormholeReceiver" if you are testing with Wormhole Receiver
const Wormhole = artifacts.require("Wormhole"); const Wormhole = artifacts.require("Wormhole");
@ -562,7 +562,7 @@ contract("Pyth", function () {
); );
}); });
it("should show stale cached prices as unknown", async function () { it("should revert on getting stale current prices", async function () {
let smallestTimestamp = 1; let smallestTimestamp = 1;
let rawBatch = generateRawBatchAttestation( let rawBatch = generateRawBatchAttestation(
smallestTimestamp, smallestTimestamp,
@ -575,15 +575,14 @@ contract("Pyth", function () {
const price_id = const price_id =
"0x" + "0x" +
(255 - (i % 256)).toString(16).padStart(2, "0").repeat(32); (255 - (i % 256)).toString(16).padStart(2, "0").repeat(32);
let priceFeedResult = await this.pythProxy.queryPriceFeed(price_id); expectRevert(
assert.equal( this.pythProxy.getCurrentPrice(price_id),
priceFeedResult.status.toString(), "current price unavailable"
PythStructs.PriceStatus.UNKNOWN.toString()
); );
} }
}); });
it("should show cached prices too far into the future as unknown", async function () { it("should revert on getting current prices too far into the future as they are considered unknown", async function () {
let largestTimestamp = 4294967295; let largestTimestamp = 4294967295;
let rawBatch = generateRawBatchAttestation( let rawBatch = generateRawBatchAttestation(
largestTimestamp - 5, largestTimestamp - 5,
@ -596,10 +595,9 @@ contract("Pyth", function () {
const price_id = const price_id =
"0x" + "0x" +
(255 - (i % 256)).toString(16).padStart(2, "0").repeat(32); (255 - (i % 256)).toString(16).padStart(2, "0").repeat(32);
let priceFeedResult = await this.pythProxy.queryPriceFeed(price_id); expectRevert(
assert.equal( this.pythProxy.getCurrentPrice(price_id),
priceFeedResult.status.toString(), "current price unavailable"
PythStructs.PriceStatus.UNKNOWN.toString()
); );
} }
}); });
@ -622,11 +620,9 @@ contract("Pyth", function () {
const price_id = const price_id =
"0x" + "0x" +
(255 - (i % 256)).toString(16).padStart(2, "0").repeat(32); (255 - (i % 256)).toString(16).padStart(2, "0").repeat(32);
let priceFeedResult = await this.pythProxy.queryPriceFeed(price_id);
assert.equal( // Expect getCurrentPrice to work (not revert)
priceFeedResult.status.toString(), await this.pythProxy.getCurrentPrice(price_id);
PythStructs.PriceStatus.TRADING.toString()
);
} }
// One minute passes // One minute passes
@ -637,10 +633,10 @@ contract("Pyth", function () {
const price_id = const price_id =
"0x" + "0x" +
(255 - (i % 256)).toString(16).padStart(2, "0").repeat(32); (255 - (i % 256)).toString(16).padStart(2, "0").repeat(32);
let priceFeedResult = await this.pythProxy.queryPriceFeed(price_id);
assert.equal( expectRevert(
priceFeedResult.status.toString(), this.pythProxy.getCurrentPrice(price_id),
PythStructs.PriceStatus.UNKNOWN.toString() "current price unavailable"
); );
} }
@ -653,10 +649,9 @@ contract("Pyth", function () {
"0x" + "0x" +
(255 - (i % 256)).toString(16).padStart(2, "0").repeat(32); (255 - (i % 256)).toString(16).padStart(2, "0").repeat(32);
let priceFeedResult = await this.pythProxy.queryPriceFeed(price_id); let priceFeedResult = await this.pythProxy.queryPriceFeed(price_id);
assert.equal(
priceFeedResult.status.toString(), // Expect getCurrentPrice to work (not revert)
PythStructs.PriceStatus.TRADING.toString() await this.pythProxy.getCurrentPrice(price_id);
);
} }
}); });
@ -724,6 +719,13 @@ contract("Pyth", function () {
"invalid data source chain/emitter ID" "invalid data source chain/emitter ID"
); );
}); });
it("Make sure version is the npm package version", async function () {
const contractVersion = await this.pythProxy.version();
const { version } = require('../package.json');
expect(contractVersion).equal(version);
});
}); });
const signAndEncodeVM = async function ( const signAndEncodeVM = async function (