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.
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
npx truffle migrate --network $MIGRATIONS_NETWORK
@ -60,6 +63,8 @@ const PythUpgradable = artifacts.require("PythUpgradable");
const { upgradeProxy } = require("@openzeppelin/truffle-upgrades");
/**
* Version <x.y.z>.
*
* Briefly describe the changelog here.
*/
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
(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
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);
}
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);
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){
// Look up the latest price info for the given ID
PythInternalStructs.PriceInfo memory info = latestPriceInfo(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;
}
function absDiff(uint x, uint y) private pure returns (uint) {
if (x > y) {
return x - y;
} else {
return y - x;
function priceFeedExists(bytes32 id) public override view returns (bool) {
PythInternalStructs.PriceInfo memory info = latestPriceInfo(id);
return (info.priceFeed.id != 0);
}
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");
/**
* Version 0.1.0
*
* This change:
* - Updates the interface, adds `updatePriceFeedsIfNecessary` that wraps
* `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) {
const proxy = await PythUpgradable.deployed();

View File

@ -5,9 +5,15 @@ const PythUpgradable = artifacts.require("PythUpgradable");
const { upgradeProxy } = require("@openzeppelin/truffle-upgrades");
/**
* Version 0.1.0
*
* This change:
* - Updates the interface, adds `updatePriceFeedsIfNecessary` that wraps
* `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) {
const proxy = await PythUpgradable.deployed();

View File

@ -5,9 +5,15 @@ const PythUpgradable = artifacts.require("PythUpgradable");
const { upgradeProxy } = require("@openzeppelin/truffle-upgrades");
/**
* Version 0.1.0
*
* This change:
* - Updates the interface, adds `updatePriceFeedsIfNecessary` that wraps
* `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) {
const proxy = await PythUpgradable.deployed();

View File

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

View File

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

View File

@ -6,7 +6,7 @@ const PythStructs = artifacts.require("PythStructs");
const { deployProxy, upgradeProxy } = require("@openzeppelin/truffle-upgrades");
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
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 rawBatch = generateRawBatchAttestation(
smallestTimestamp,
@ -575,15 +575,14 @@ contract("Pyth", function () {
const price_id =
"0x" +
(255 - (i % 256)).toString(16).padStart(2, "0").repeat(32);
let priceFeedResult = await this.pythProxy.queryPriceFeed(price_id);
assert.equal(
priceFeedResult.status.toString(),
PythStructs.PriceStatus.UNKNOWN.toString()
expectRevert(
this.pythProxy.getCurrentPrice(price_id),
"current price unavailable"
);
}
});
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 rawBatch = generateRawBatchAttestation(
largestTimestamp - 5,
@ -596,10 +595,9 @@ contract("Pyth", function () {
const price_id =
"0x" +
(255 - (i % 256)).toString(16).padStart(2, "0").repeat(32);
let priceFeedResult = await this.pythProxy.queryPriceFeed(price_id);
assert.equal(
priceFeedResult.status.toString(),
PythStructs.PriceStatus.UNKNOWN.toString()
expectRevert(
this.pythProxy.getCurrentPrice(price_id),
"current price unavailable"
);
}
});
@ -622,11 +620,9 @@ contract("Pyth", function () {
const price_id =
"0x" +
(255 - (i % 256)).toString(16).padStart(2, "0").repeat(32);
let priceFeedResult = await this.pythProxy.queryPriceFeed(price_id);
assert.equal(
priceFeedResult.status.toString(),
PythStructs.PriceStatus.TRADING.toString()
);
// Expect getCurrentPrice to work (not revert)
await this.pythProxy.getCurrentPrice(price_id);
}
// One minute passes
@ -637,10 +633,10 @@ contract("Pyth", function () {
const price_id =
"0x" +
(255 - (i % 256)).toString(16).padStart(2, "0").repeat(32);
let priceFeedResult = await this.pythProxy.queryPriceFeed(price_id);
assert.equal(
priceFeedResult.status.toString(),
PythStructs.PriceStatus.UNKNOWN.toString()
expectRevert(
this.pythProxy.getCurrentPrice(price_id),
"current price unavailable"
);
}
@ -653,10 +649,9 @@ contract("Pyth", function () {
"0x" +
(255 - (i % 256)).toString(16).padStart(2, "0").repeat(32);
let priceFeedResult = await this.pythProxy.queryPriceFeed(price_id);
assert.equal(
priceFeedResult.status.toString(),
PythStructs.PriceStatus.TRADING.toString()
);
// Expect getCurrentPrice to work (not revert)
await this.pythProxy.getCurrentPrice(price_id);
}
});
@ -724,6 +719,13 @@ contract("Pyth", function () {
"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 (