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:
parent
995c886804
commit
a17b27f3d1
|
@ -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
|
||||||
|
|
|
@ -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";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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,12 +595,11 @@ 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("changing validity time works", async function() {
|
it("changing validity time works", async function() {
|
||||||
|
@ -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 (
|
||||||
|
|
Loading…
Reference in New Issue