[eth] Use PythErrors everywhere (#404)

* Remove unnecessary check
* Use PythErrors everywhere
This commit is contained in:
Ali Behjati 2022-11-29 18:30:45 +01:00 committed by GitHub
parent b31f768d37
commit c3461e5e1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 164 additions and 214 deletions

View File

@ -7,6 +7,7 @@ import "../libraries/external/UnsafeBytesLib.sol";
import "@pythnetwork/pyth-sdk-solidity/AbstractPyth.sol"; import "@pythnetwork/pyth-sdk-solidity/AbstractPyth.sol";
import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol"; import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
import "@pythnetwork/pyth-sdk-solidity/PythErrors.sol";
import "./PythGetters.sol"; import "./PythGetters.sol";
import "./PythSetters.sol"; import "./PythSetters.sol";
import "./PythInternalStructs.sol"; import "./PythInternalStructs.sol";
@ -21,11 +22,10 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
) internal { ) internal {
setWormhole(wormhole); setWormhole(wormhole);
require( if (
dataSourceEmitterChainIds.length == dataSourceEmitterChainIds.length !=
dataSourceEmitterAddresses.length, dataSourceEmitterAddresses.length
"data source arguments should have the same length" ) revert PythErrors.InvalidArgument();
);
for (uint i = 0; i < dataSourceEmitterChainIds.length; i++) { for (uint i = 0; i < dataSourceEmitterChainIds.length; i++) {
PythInternalStructs.DataSource memory ds = PythInternalStructs PythInternalStructs.DataSource memory ds = PythInternalStructs
@ -34,10 +34,8 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
dataSourceEmitterAddresses[i] dataSourceEmitterAddresses[i]
); );
require( if (PythGetters.isValidDataSource(ds.chainId, ds.emitterAddress))
!PythGetters.isValidDataSource(ds.chainId, ds.emitterAddress), revert PythErrors.InvalidArgument();
"Data source already added"
);
_state.isValidDataSource[hashDataSource(ds)] = true; _state.isValidDataSource[hashDataSource(ds)] = true;
_state.validDataSources.push(ds); _state.validDataSources.push(ds);
@ -57,7 +55,7 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
bytes[] calldata updateData bytes[] calldata updateData
) public payable override { ) public payable override {
uint requiredFee = getUpdateFee(updateData); uint requiredFee = getUpdateFee(updateData);
require(msg.value >= requiredFee, "insufficient paid fee amount"); if (msg.value < requiredFee) revert PythErrors.InsufficientFee();
for (uint i = 0; i < updateData.length; ) { for (uint i = 0; i < updateData.length; ) {
updatePriceBatchFromVm(updateData[i]); updatePriceBatchFromVm(updateData[i]);
@ -245,10 +243,8 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
} }
} }
require( if (attestationIndex > attestationSize)
attestationIndex <= attestationSize, revert PythErrors.InvalidUpdateData();
"INTERNAL: Consumed more than `attestationSize` bytes"
);
} }
} }
@ -259,10 +255,8 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
bytes32[] calldata priceIds, bytes32[] calldata priceIds,
uint64[] calldata publishTimes uint64[] calldata publishTimes
) external payable override { ) external payable override {
require( if (priceIds.length != publishTimes.length)
priceIds.length == publishTimes.length, revert PythErrors.InvalidArgument();
"priceIds and publishTimes arrays should have same length"
);
for (uint i = 0; i < priceIds.length; ) { for (uint i = 0; i < priceIds.length; ) {
// If the price does not exist, then the publish time is zero and // If the price does not exist, then the publish time is zero and
@ -277,9 +271,7 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
} }
} }
revert( revert PythErrors.NoFreshUpdate();
"no prices in the submitted batch have fresh prices, so this update will have no effect"
);
} }
// This is an overwrite of the same method in AbstractPyth.sol // This is an overwrite of the same method in AbstractPyth.sol
@ -295,10 +287,7 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
price.price = info.price; price.price = info.price;
price.conf = info.conf; price.conf = info.conf;
require( if (price.publishTime == 0) revert PythErrors.PriceFeedNotFound();
price.publishTime != 0,
"price feed for the given id is not pushed or does not exist"
);
} }
// This is an overwrite of the same method in AbstractPyth.sol // This is an overwrite of the same method in AbstractPyth.sol
@ -314,10 +303,7 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
price.price = info.emaPrice; price.price = info.emaPrice;
price.conf = info.emaConf; price.conf = info.emaConf;
require( if (price.publishTime == 0) revert PythErrors.PriceFeedNotFound();
price.publishTime != 0,
"price feed for the given id is not pushed or does not exist"
);
} }
function parseBatchAttestationHeader( function parseBatchAttestationHeader(
@ -334,18 +320,20 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
{ {
uint32 magic = UnsafeBytesLib.toUint32(encoded, index); uint32 magic = UnsafeBytesLib.toUint32(encoded, index);
index += 4; index += 4;
require(magic == 0x50325748, "invalid magic value"); if (magic != 0x50325748) revert PythErrors.InvalidUpdateData();
uint16 versionMajor = UnsafeBytesLib.toUint16(encoded, index); uint16 versionMajor = UnsafeBytesLib.toUint16(encoded, index);
index += 2; index += 2;
require(versionMajor == 3, "invalid version major, expected 3"); if (versionMajor != 3) revert PythErrors.InvalidUpdateData();
uint16 versionMinor = UnsafeBytesLib.toUint16(encoded, index); // This value is only used as the check below which currently
// never reverts
// uint16 versionMinor = UnsafeBytesLib.toUint16(encoded, index);
index += 2; index += 2;
require(
versionMinor >= 0, // This check is always false as versionMinor is 0, so it is commented.
"invalid version minor, expected 0 or more" // in the future that the minor version increases this will have effect.
); // if(versionMinor < 0) revert InvalidUpdateData();
uint16 hdrSize = UnsafeBytesLib.toUint16(encoded, index); uint16 hdrSize = UnsafeBytesLib.toUint16(encoded, index);
index += 2; index += 2;
@ -370,10 +358,7 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
index += hdrSize; index += hdrSize;
// Payload ID of 2 required for batch headerBa // Payload ID of 2 required for batch headerBa
require( if (payloadId != 2) revert PythErrors.InvalidUpdateData();
payloadId == 2,
"invalid payload ID, expected 2 for BatchPriceAttestation"
);
} }
// Parse the number of attestations // Parse the number of attestations
@ -386,10 +371,8 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
// Given the message is valid the arithmetic below should not overflow, and // Given the message is valid the arithmetic below should not overflow, and
// even if it overflows then the require would fail. // even if it overflows then the require would fail.
require( if (encoded.length != (index + (attestationSize * nAttestations)))
encoded.length == (index + (attestationSize * nAttestations)), revert PythErrors.InvalidUpdateData();
"invalid BatchPriceAttestation size"
);
} }
} }
@ -398,12 +381,11 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
) internal view returns (IWormhole.VM memory vm) { ) internal view returns (IWormhole.VM memory vm) {
{ {
bool valid; bool valid;
string memory reason; (vm, valid, ) = wormhole().parseAndVerifyVM(encodedVm);
(vm, valid, reason) = wormhole().parseAndVerifyVM(encodedVm); if (!valid) revert PythErrors.InvalidWormholeVaa();
require(valid, reason);
} }
require(verifyPythVM(vm), "invalid data source chain/emitter ID"); if (!verifyPythVM(vm)) revert PythErrors.InvalidUpdateDataSource();
} }
function parsePriceFeedUpdates( function parsePriceFeedUpdates(
@ -420,10 +402,8 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
unchecked { unchecked {
{ {
uint requiredFee = getUpdateFee(updateData); uint requiredFee = getUpdateFee(updateData);
require( if (msg.value < requiredFee)
msg.value >= requiredFee, revert PythErrors.InsufficientFee();
"insufficient paid fee amount"
);
} }
priceFeeds = new PythStructs.PriceFeed[](priceIds.length); priceFeeds = new PythStructs.PriceFeed[](priceIds.length);
@ -482,10 +462,6 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
index, index,
attestationSize attestationSize
); );
require(
info.publishTime != 0,
"price feed for the given id is not pushed or does not exist"
);
priceFeeds[k].id = priceId; priceFeeds[k].id = priceId;
priceFeeds[k].price.price = info.price; priceFeeds[k].price.price = info.price;
@ -513,10 +489,9 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
} }
for (uint k = 0; k < priceIds.length; k++) { for (uint k = 0; k < priceIds.length; k++) {
require( if (priceFeeds[k].id == 0) {
priceFeeds[k].id != 0, revert PythErrors.PriceFeedNotFoundWithinRange();
"1 or more price feeds are not found in the updateData or they are out of the given time range" }
);
} }
} }
} }
@ -526,10 +501,7 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
) public view override returns (PythStructs.PriceFeed memory priceFeed) { ) 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( if (info.publishTime == 0) revert PythErrors.PriceFeedNotFound();
info.publishTime != 0,
"price feed for the given id is not pushed or does not exist"
);
priceFeed.id = id; priceFeed.id = id;
priceFeed.price.price = info.price; priceFeed.price.price = info.price;

View File

@ -7,6 +7,7 @@ import "./PythGovernanceInstructions.sol";
import "./PythInternalStructs.sol"; import "./PythInternalStructs.sol";
import "./PythGetters.sol"; import "./PythGetters.sol";
import "./PythSetters.sol"; import "./PythSetters.sol";
import "@pythnetwork/pyth-sdk-solidity/PythErrors.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol"; import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol";
@ -37,19 +38,17 @@ abstract contract PythGovernance is
function verifyGovernanceVM( function verifyGovernanceVM(
bytes memory encodedVM bytes memory encodedVM
) internal returns (IWormhole.VM memory parsedVM) { ) internal returns (IWormhole.VM memory parsedVM) {
(IWormhole.VM memory vm, bool valid, string memory reason) = wormhole() (IWormhole.VM memory vm, bool valid, ) = wormhole().parseAndVerifyVM(
.parseAndVerifyVM(encodedVM); encodedVM
require(valid, reason);
require(
isValidGovernanceDataSource(vm.emitterChainId, vm.emitterAddress),
"VAA is not coming from the governance data source"
); );
require( if (!valid) revert PythErrors.InvalidWormholeVaa();
vm.sequence > lastExecutedGovernanceSequence(),
"VAA is older than the last executed governance VAA" if (!isValidGovernanceDataSource(vm.emitterChainId, vm.emitterAddress))
); revert PythErrors.InvalidGovernanceDataSource();
if (vm.sequence <= lastExecutedGovernanceSequence())
revert PythErrors.OldGovernanceMessage();
setLastExecutedGovernanceSequence(vm.sequence); setLastExecutedGovernanceSequence(vm.sequence);
@ -63,16 +62,12 @@ abstract contract PythGovernance is
vm.payload vm.payload
); );
require( if (gi.targetChainId != chainId() && gi.targetChainId != 0)
gi.targetChainId == chainId() || gi.targetChainId == 0, revert PythErrors.InvalidGovernanceTarget();
"invalid target chain for this governance instruction"
);
if (gi.action == GovernanceAction.UpgradeContract) { if (gi.action == GovernanceAction.UpgradeContract) {
require( if (gi.targetChainId == 0)
gi.targetChainId != 0, revert PythErrors.InvalidGovernanceTarget();
"upgrade with chain id 0 is not possible"
);
upgradeContract(parseUpgradeContractPayload(gi.payload)); upgradeContract(parseUpgradeContractPayload(gi.payload));
} else if ( } else if (
gi.action == GovernanceAction.AuthorizeGovernanceDataSourceTransfer gi.action == GovernanceAction.AuthorizeGovernanceDataSourceTransfer
@ -89,11 +84,10 @@ abstract contract PythGovernance is
} else if ( } else if (
gi.action == GovernanceAction.RequestGovernanceDataSourceTransfer gi.action == GovernanceAction.RequestGovernanceDataSourceTransfer
) { ) {
revert( // RequestGovernanceDataSourceTransfer can be only part of AuthorizeGovernanceDataSourceTransfer message
"RequestGovernanceDataSourceTransfer can be only part of AuthorizeGovernanceDataSourceTransfer message" revert PythErrors.InvalidGovernanceMessage();
);
} else { } else {
revert("invalid governance action"); revert PythErrors.InvalidGovernanceMessage();
} }
} }
@ -119,21 +113,19 @@ abstract contract PythGovernance is
// If it's valid then its emitter can take over the governance from the current emitter. // If it's valid then its emitter can take over the governance from the current emitter.
// The VAA is checked here to ensure that the new governance data source is valid and can send message // The VAA is checked here to ensure that the new governance data source is valid and can send message
// through wormhole. // through wormhole.
(IWormhole.VM memory vm, bool valid, string memory reason) = wormhole() (IWormhole.VM memory vm, bool valid, ) = wormhole().parseAndVerifyVM(
.parseAndVerifyVM(payload.claimVaa); payload.claimVaa
require(valid, reason); );
if (!valid) revert PythErrors.InvalidWormholeVaa();
GovernanceInstruction memory gi = parseGovernanceInstruction( GovernanceInstruction memory gi = parseGovernanceInstruction(
vm.payload vm.payload
); );
require( if (gi.targetChainId != chainId() && gi.targetChainId != 0)
gi.targetChainId == chainId() || gi.targetChainId == 0, revert PythErrors.InvalidGovernanceTarget();
"invalid target chain for this governance instruction"
); if (gi.action != GovernanceAction.RequestGovernanceDataSourceTransfer)
require( revert PythErrors.InvalidGovernanceMessage();
gi.action == GovernanceAction.RequestGovernanceDataSourceTransfer,
"governance data source change inner vaa is not of claim action type"
);
RequestGovernanceDataSourceTransferPayload RequestGovernanceDataSourceTransferPayload
memory claimPayload = parseRequestGovernanceDataSourceTransferPayload( memory claimPayload = parseRequestGovernanceDataSourceTransferPayload(
@ -141,11 +133,10 @@ abstract contract PythGovernance is
); );
// Governance data source index is used to prevent replay attacks, so a claimVaa cannot be used twice. // Governance data source index is used to prevent replay attacks, so a claimVaa cannot be used twice.
require( if (
governanceDataSourceIndex() < governanceDataSourceIndex() >=
claimPayload.governanceDataSourceIndex, claimPayload.governanceDataSourceIndex
"cannot upgrade to an older governance data source" ) revert PythErrors.OldGovernanceMessage();
);
setGovernanceDataSourceIndex(claimPayload.governanceDataSourceIndex); setGovernanceDataSourceIndex(claimPayload.governanceDataSourceIndex);

View File

@ -5,6 +5,7 @@ pragma solidity ^0.8.0;
import "../libraries/external/BytesLib.sol"; import "../libraries/external/BytesLib.sol";
import "./PythInternalStructs.sol"; import "./PythInternalStructs.sol";
import "@pythnetwork/pyth-sdk-solidity/PythErrors.sol";
/** /**
* @dev `PythGovernanceInstructions` defines a set of structs and parsing functions * @dev `PythGovernanceInstructions` defines a set of structs and parsing functions
@ -75,17 +76,16 @@ contract PythGovernanceInstructions {
uint index = 0; uint index = 0;
uint32 magic = encodedInstruction.toUint32(index); uint32 magic = encodedInstruction.toUint32(index);
require(magic == MAGIC, "invalid magic for GovernanceInstruction");
if (magic != MAGIC) revert PythErrors.InvalidGovernanceMessage();
index += 4; index += 4;
uint8 modNumber = encodedInstruction.toUint8(index); uint8 modNumber = encodedInstruction.toUint8(index);
gi.module = GovernanceModule(modNumber); gi.module = GovernanceModule(modNumber);
index += 1; index += 1;
require( if (gi.module != MODULE) revert PythErrors.InvalidGovernanceTarget();
gi.module == MODULE,
"invalid module for GovernanceInstruction"
);
uint8 actionNumber = encodedInstruction.toUint8(index); uint8 actionNumber = encodedInstruction.toUint8(index);
gi.action = GovernanceAction(actionNumber); gi.action = GovernanceAction(actionNumber);
@ -112,10 +112,8 @@ contract PythGovernanceInstructions {
uc.newImplementation = address(encodedPayload.toAddress(index)); uc.newImplementation = address(encodedPayload.toAddress(index));
index += 20; index += 20;
require( if (encodedPayload.length != index)
encodedPayload.length == index, revert PythErrors.InvalidGovernanceMessage();
"invalid length for UpgradeContractPayload"
);
} }
/// @dev Parse a AuthorizeGovernanceDataSourceTransferPayload (action 2) with minimal validation /// @dev Parse a AuthorizeGovernanceDataSourceTransferPayload (action 2) with minimal validation
@ -142,10 +140,8 @@ contract PythGovernanceInstructions {
sgdsClaim.governanceDataSourceIndex = encodedPayload.toUint32(index); sgdsClaim.governanceDataSourceIndex = encodedPayload.toUint32(index);
index += 4; index += 4;
require( if (encodedPayload.length != index)
encodedPayload.length == index, revert PythErrors.InvalidGovernanceMessage();
"invalid length for RequestGovernanceDataSourceTransferPayload"
);
} }
/// @dev Parse a SetDataSourcesPayload (action 3) with minimal validation /// @dev Parse a SetDataSourcesPayload (action 3) with minimal validation
@ -169,10 +165,8 @@ contract PythGovernanceInstructions {
index += 32; index += 32;
} }
require( if (encodedPayload.length != index)
encodedPayload.length == index, revert PythErrors.InvalidGovernanceMessage();
"invalid length for SetDataSourcesPayload"
);
} }
/// @dev Parse a SetFeePayload (action 4) with minimal validation /// @dev Parse a SetFeePayload (action 4) with minimal validation
@ -189,10 +183,8 @@ contract PythGovernanceInstructions {
sf.newFee = uint256(val) * uint256(10) ** uint256(expo); sf.newFee = uint256(val) * uint256(10) ** uint256(expo);
require( if (encodedPayload.length != index)
encodedPayload.length == index, revert PythErrors.InvalidGovernanceMessage();
"invalid length for SetFeePayload"
);
} }
/// @dev Parse a SetValidPeriodPayload (action 5) with minimal validation /// @dev Parse a SetValidPeriodPayload (action 5) with minimal validation
@ -204,9 +196,7 @@ contract PythGovernanceInstructions {
svp.newValidPeriod = uint256(encodedPayload.toUint64(index)); svp.newValidPeriod = uint256(encodedPayload.toUint64(index));
index += 8; index += 8;
require( if (encodedPayload.length != index)
encodedPayload.length == index, revert PythErrors.InvalidGovernanceMessage();
"invalid length for SetValidPeriodPayload"
);
} }
} }

View File

@ -11,6 +11,7 @@ import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "./PythGovernance.sol"; import "./PythGovernance.sol";
import "./Pyth.sol"; import "./Pyth.sol";
import "@pythnetwork/pyth-sdk-solidity/PythErrors.sol";
contract PythUpgradable is contract PythUpgradable is
Initializable, Initializable,
@ -126,11 +127,10 @@ contract PythUpgradable is
_upgradeToAndCallUUPS(payload.newImplementation, new bytes(0), false); _upgradeToAndCallUUPS(payload.newImplementation, new bytes(0), false);
// Calling a method using `this.<method>` will cause a contract call that will use // Calling a method using `this.<method>` will cause a contract call that will use
// the new contract. // the new contract. This call will fail if the method does not exists or the magic
require( // is different.
this.pythUpgradableMagic() == 0x97a6f304, if (this.pythUpgradableMagic() != 0x97a6f304)
"the new implementation is not a Pyth contract" revert PythErrors.InvalidGovernanceMessage();
);
emit ContractUpgraded(oldImplementation, _getImplementation()); emit ContractUpgraded(oldImplementation, _getImplementation());
} }

View File

@ -6,6 +6,7 @@ import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "forge-std/Test.sol"; import "forge-std/Test.sol";
import "@pythnetwork/pyth-sdk-solidity/IPyth.sol"; import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
import "@pythnetwork/pyth-sdk-solidity/PythErrors.sol";
import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol"; import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
import "./utils/WormholeTestUtils.t.sol"; import "./utils/WormholeTestUtils.t.sol";
import "./utils/PythTestUtils.t.sol"; import "./utils/PythTestUtils.t.sol";
@ -139,11 +140,7 @@ contract GasBenchmark is Test, WormholeTestUtils, PythTestUtils {
function testBenchmarkUpdatePriceFeedsIfNecessaryNotFresh() public { function testBenchmarkUpdatePriceFeedsIfNecessaryNotFresh() public {
// Since the price is not advanced, the publishTimes are the same as the // Since the price is not advanced, the publishTimes are the same as the
// ones in the contract. // ones in the contract.
vm.expectRevert( vm.expectRevert(PythErrors.NoFreshUpdate.selector);
bytes(
"no prices in the submitted batch have fresh prices, so this update will have no effect"
)
);
pyth.updatePriceFeedsIfNecessary{value: cachedPricesUpdateFee}( pyth.updatePriceFeedsIfNecessary{value: cachedPricesUpdateFee}(
cachedPricesUpdateData, cachedPricesUpdateData,
@ -183,11 +180,7 @@ contract GasBenchmark is Test, WormholeTestUtils, PythTestUtils {
bytes32[] memory ids = new bytes32[](1); bytes32[] memory ids = new bytes32[](1);
ids[0] = priceIds[0]; ids[0] = priceIds[0];
vm.expectRevert( vm.expectRevert(PythErrors.PriceFeedNotFoundWithinRange.selector);
bytes(
"1 or more price feeds are not found in the updateData or they are out of the given time range"
)
);
pyth.parsePriceFeedUpdates{value: freshPricesUpdateFee}( pyth.parsePriceFeedUpdates{value: freshPricesUpdateFee}(
freshPricesUpdateData, freshPricesUpdateData,
ids, ids,

View File

@ -6,6 +6,7 @@ import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "forge-std/Test.sol"; import "forge-std/Test.sol";
import "@pythnetwork/pyth-sdk-solidity/IPyth.sol"; import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
import "@pythnetwork/pyth-sdk-solidity/PythErrors.sol";
import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol"; import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
import "./utils/WormholeTestUtils.t.sol"; import "./utils/WormholeTestUtils.t.sol";
import "./utils/PythTestUtils.t.sol"; import "./utils/PythTestUtils.t.sol";
@ -364,7 +365,7 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils, RandTestUtils {
// Since attestations are not empty the fee should be at least 1 // Since attestations are not empty the fee should be at least 1
assertGe(updateFee, 1); assertGe(updateFee, 1);
vm.expectRevert(bytes("insufficient paid fee amount")); vm.expectRevert(PythErrors.InsufficientFee.selector);
pyth.parsePriceFeedUpdates{value: updateFee - 1}( pyth.parsePriceFeedUpdates{value: updateFee - 1}(
updateData, updateData,
@ -425,7 +426,7 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils, RandTestUtils {
uint updateFee = pyth.getUpdateFee(updateData); uint updateFee = pyth.getUpdateFee(updateData);
vm.expectRevert(bytes("invalid data source chain/emitter ID")); vm.expectRevert(PythErrors.InvalidUpdateDataSource.selector);
pyth.parsePriceFeedUpdates{value: updateFee}( pyth.parsePriceFeedUpdates{value: updateFee}(
updateData, updateData,
priceIds, priceIds,
@ -455,7 +456,7 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils, RandTestUtils {
uint updateFee = pyth.getUpdateFee(updateData); uint updateFee = pyth.getUpdateFee(updateData);
vm.expectRevert(bytes("invalid data source chain/emitter ID")); vm.expectRevert(PythErrors.InvalidUpdateDataSource.selector);
pyth.parsePriceFeedUpdates{value: updateFee}( pyth.parsePriceFeedUpdates{value: updateFee}(
updateData, updateData,
priceIds, priceIds,
@ -480,11 +481,7 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils, RandTestUtils {
bytes32[] memory priceIds = new bytes32[](1); bytes32[] memory priceIds = new bytes32[](1);
priceIds[0] = bytes32(uint(2)); priceIds[0] = bytes32(uint(2));
vm.expectRevert( vm.expectRevert(PythErrors.PriceFeedNotFoundWithinRange.selector);
bytes(
"1 or more price feeds are not found in the updateData or they are out of the given time range"
)
);
pyth.parsePriceFeedUpdates{value: updateFee}( pyth.parsePriceFeedUpdates{value: updateFee}(
updateData, updateData,
priceIds, priceIds,
@ -520,11 +517,7 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils, RandTestUtils {
); );
// Request for parse after the time range should revert. // Request for parse after the time range should revert.
vm.expectRevert( vm.expectRevert(PythErrors.PriceFeedNotFoundWithinRange.selector);
bytes(
"1 or more price feeds are not found in the updateData or they are out of the given time range"
)
);
pyth.parsePriceFeedUpdates{value: updateFee}( pyth.parsePriceFeedUpdates{value: updateFee}(
updateData, updateData,
priceIds, priceIds,

View File

@ -13,7 +13,7 @@
"@certusone/wormhole-sdk-wasm": "^0.0.1", "@certusone/wormhole-sdk-wasm": "^0.0.1",
"@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": "^2.1.0", "@pythnetwork/pyth-sdk-solidity": "^2.2.0",
"@pythnetwork/xc-governance-sdk": "file:../third_party/pyth/xc-governance-sdk-js", "@pythnetwork/xc-governance-sdk": "file:../third_party/pyth/xc-governance-sdk-js",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"elliptic": "^6.5.2", "elliptic": "^6.5.2",
@ -5642,9 +5642,9 @@
"integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
}, },
"node_modules/@pythnetwork/pyth-sdk-solidity": { "node_modules/@pythnetwork/pyth-sdk-solidity": {
"version": "2.1.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-solidity/-/pyth-sdk-solidity-2.1.0.tgz", "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-solidity/-/pyth-sdk-solidity-2.2.0.tgz",
"integrity": "sha512-jHzqw+BHaCOAYwRNCgAUhcbNZrB5f3Arly3PaYN3/Tg7/5RQ95a9FD15XvJB1DB3yymUPIkmLYMur7Sh+e1G4A==" "integrity": "sha512-LsRMmaf9MTflGSymqOJMepFk/3R7DyxMOJfLDB5RDSieyiq+RJ5IYIYnXAFsMrqkjibOtVxARcortHtE9VWwhw=="
}, },
"node_modules/@pythnetwork/xc-governance-sdk": { "node_modules/@pythnetwork/xc-governance-sdk": {
"resolved": "../third_party/pyth/xc-governance-sdk-js", "resolved": "../third_party/pyth/xc-governance-sdk-js",
@ -45038,9 +45038,9 @@
"integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
}, },
"@pythnetwork/pyth-sdk-solidity": { "@pythnetwork/pyth-sdk-solidity": {
"version": "2.1.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-solidity/-/pyth-sdk-solidity-2.1.0.tgz", "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-solidity/-/pyth-sdk-solidity-2.2.0.tgz",
"integrity": "sha512-jHzqw+BHaCOAYwRNCgAUhcbNZrB5f3Arly3PaYN3/Tg7/5RQ95a9FD15XvJB1DB3yymUPIkmLYMur7Sh+e1G4A==" "integrity": "sha512-LsRMmaf9MTflGSymqOJMepFk/3R7DyxMOJfLDB5RDSieyiq+RJ5IYIYnXAFsMrqkjibOtVxARcortHtE9VWwhw=="
}, },
"@pythnetwork/xc-governance-sdk": { "@pythnetwork/xc-governance-sdk": {
"version": "file:../third_party/pyth/xc-governance-sdk-js", "version": "file:../third_party/pyth/xc-governance-sdk-js",

View File

@ -32,7 +32,7 @@
"@certusone/wormhole-sdk-wasm": "^0.0.1", "@certusone/wormhole-sdk-wasm": "^0.0.1",
"@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": "^2.1.0", "@pythnetwork/pyth-sdk-solidity": "^2.2.0",
"@pythnetwork/xc-governance-sdk": "file:../third_party/pyth/xc-governance-sdk-js", "@pythnetwork/xc-governance-sdk": "file:../third_party/pyth/xc-governance-sdk-js",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"elliptic": "^6.5.2", "elliptic": "^6.5.2",

View File

@ -39,7 +39,6 @@ contract("Pyth", function () {
"0x71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b"; "0x71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b";
const notOwnerError = const notOwnerError =
"Ownable: caller is not the owner -- Reason given: Ownable: caller is not the owner."; "Ownable: caller is not the owner -- Reason given: Ownable: caller is not the owner.";
const insufficientFeeError = "insufficient paid fee amount";
// Place all atomic operations that are done within migrations here. // Place all atomic operations that are done within migrations here.
beforeEach(async function () { beforeEach(async function () {
@ -433,9 +432,9 @@ contract("Pyth", function () {
assert.equal(feeInWei, 20); assert.equal(feeInWei, 20);
// When a smaller fee is payed it reverts // When a smaller fee is payed it reverts
await expectRevert( await expectRevertCustomError(
updatePriceFeeds(this.pythProxy, [rawBatch1, rawBatch2], feeInWei - 1), updatePriceFeeds(this.pythProxy, [rawBatch1, rawBatch2], feeInWei - 1),
insufficientFeeError "InsufficientFee"
); );
}); });
@ -571,11 +570,11 @@ contract("Pyth", function () {
}); });
it("should fail transaction if a price is not found", async function () { it("should fail transaction if a price is not found", async function () {
await expectRevert( await expectRevertCustomError(
this.pythProxy.queryPriceFeed( this.pythProxy.queryPriceFeed(
"0xdeadfeeddeadfeeddeadfeeddeadfeeddeadfeeddeadfeeddeadfeeddeadfeed" "0xdeadfeeddeadfeeddeadfeeddeadfeeddeadfeeddeadfeeddeadfeeddeadfeed"
), ),
"price feed for the given id is not pushed or does not exist" "PriceFeedNotFound"
); );
}); });
@ -591,10 +590,7 @@ contract("Pyth", function () {
for (var i = 1; i <= RAW_BATCH_ATTESTATION_COUNT; i++) { for (var i = 1; i <= RAW_BATCH_ATTESTATION_COUNT; i++) {
const price_id = const price_id =
"0x" + (255 - (i % 256)).toString(16).padStart(2, "0").repeat(32); "0x" + (255 - (i % 256)).toString(16).padStart(2, "0").repeat(32);
expectRevert( expectRevertCustomError(this.pythProxy.getPrice(price_id), "StalePrice");
this.pythProxy.getPrice(price_id),
"no price available which is recent enough"
);
} }
}); });
@ -610,10 +606,7 @@ contract("Pyth", function () {
for (var i = 1; i <= RAW_BATCH_ATTESTATION_COUNT; i++) { for (var i = 1; i <= RAW_BATCH_ATTESTATION_COUNT; i++) {
const price_id = const price_id =
"0x" + (255 - (i % 256)).toString(16).padStart(2, "0").repeat(32); "0x" + (255 - (i % 256)).toString(16).padStart(2, "0").repeat(32);
expectRevert( expectRevertCustomError(this.pythProxy.getPrice(price_id), "StalePrice");
this.pythProxy.getPrice(price_id),
"no price available which is recent enough"
);
} }
}); });
@ -643,10 +636,7 @@ contract("Pyth", function () {
const price_id = const price_id =
"0x" + (255 - (i % 256)).toString(16).padStart(2, "0").repeat(32); "0x" + (255 - (i % 256)).toString(16).padStart(2, "0").repeat(32);
expectRevert( expectRevertCustomError(this.pythProxy.getPrice(price_id), "StalePrice");
this.pythProxy.getPrice(price_id),
"no price available which is recent enough"
);
} }
// Setting the validity time to 120 seconds // Setting the validity time to 120 seconds
@ -753,9 +743,9 @@ contract("Pyth", function () {
0 0
); );
await expectRevert( await expectRevertCustomError(
this.pythProxy.updatePriceFeeds(["0x" + vm]), this.pythProxy.updatePriceFeeds(["0x" + vm]),
"invalid data source chain/emitter ID" "InvalidUpdateDataSource"
); );
}); });
@ -779,9 +769,9 @@ contract("Pyth", function () {
1 1
); );
await expectRevert( await expectRevertCustomError(
this.pythProxy.executeGovernanceInstruction(vaaWrongMagic), this.pythProxy.executeGovernanceInstruction(vaaWrongMagic),
"invalid magic for GovernanceInstruction" "InvalidGovernanceMessage"
); );
const wrongModule = Buffer.from(data); const wrongModule = Buffer.from(data);
@ -794,9 +784,9 @@ contract("Pyth", function () {
1 1
); );
await expectRevert( await expectRevertCustomError(
this.pythProxy.executeGovernanceInstruction(vaaWrongModule), this.pythProxy.executeGovernanceInstruction(vaaWrongModule),
"invalid module for GovernanceInstruction" "InvalidGovernanceTarget"
); );
const outOfBoundModule = Buffer.from(data); const outOfBoundModule = Buffer.from(data);
@ -828,9 +818,9 @@ contract("Pyth", function () {
1 1
); );
await expectRevert( await expectRevertCustomError(
this.pythProxy.executeGovernanceInstruction(vaaWrongEmitter), this.pythProxy.executeGovernanceInstruction(vaaWrongEmitter),
"VAA is not coming from the governance data source" "InvalidGovernanceDataSource"
); );
const vaaWrongChain = await createVAAFromUint8Array( const vaaWrongChain = await createVAAFromUint8Array(
@ -840,9 +830,9 @@ contract("Pyth", function () {
1 1
); );
await expectRevert( await expectRevertCustomError(
this.pythProxy.executeGovernanceInstruction(vaaWrongChain), this.pythProxy.executeGovernanceInstruction(vaaWrongChain),
"VAA is not coming from the governance data source" "InvalidGovernanceDataSource"
); );
}); });
@ -859,9 +849,9 @@ contract("Pyth", function () {
1 1
); );
await expectRevert( await expectRevertCustomError(
this.pythProxy.executeGovernanceInstruction(wrongChainVaa), this.pythProxy.executeGovernanceInstruction(wrongChainVaa),
"invalid target chain for this governance instruction" "InvalidGovernanceTarget"
); );
const dataForAllChains = new governance.SetValidPeriodInstruction( const dataForAllChains = new governance.SetValidPeriodInstruction(
@ -908,9 +898,9 @@ contract("Pyth", function () {
await this.pythProxy.executeGovernanceInstruction(vaaSeq1), await this.pythProxy.executeGovernanceInstruction(vaaSeq1),
// Replaying shouldn't work // Replaying shouldn't work
await expectRevert( await expectRevertCustomError(
this.pythProxy.executeGovernanceInstruction(vaaSeq1), this.pythProxy.executeGovernanceInstruction(vaaSeq1),
"VAA is older than the last executed governance VAA" "OldGovernanceMessage"
); );
const vaaSeq2 = await createVAAFromUint8Array( const vaaSeq2 = await createVAAFromUint8Array(
@ -922,13 +912,13 @@ contract("Pyth", function () {
await this.pythProxy.executeGovernanceInstruction(vaaSeq2), await this.pythProxy.executeGovernanceInstruction(vaaSeq2),
// Replaying shouldn't work // Replaying shouldn't work
await expectRevert( await expectRevertCustomError(
this.pythProxy.executeGovernanceInstruction(vaaSeq1), this.pythProxy.executeGovernanceInstruction(vaaSeq1),
"VAA is older than the last executed governance VAA" "OldGovernanceMessage"
); );
await expectRevert( await expectRevertCustomError(
this.pythProxy.executeGovernanceInstruction(vaaSeq2), this.pythProxy.executeGovernanceInstruction(vaaSeq2),
"VAA is older than the last executed governance VAA" "OldGovernanceMessage"
); );
}); });
@ -948,9 +938,9 @@ contract("Pyth", function () {
1 1
); );
await expectRevert( await expectRevertCustomError(
this.pythProxy.executeGovernanceInstruction(vaa), this.pythProxy.executeGovernanceInstruction(vaa),
"upgrade with chain id 0 is not possible" "InvalidGovernanceTarget"
); );
}); });
@ -1020,9 +1010,9 @@ contract("Pyth", function () {
1 1
); );
await expectRevert( await expectRevertCustomError(
this.pythProxy.executeGovernanceInstruction(claimVaaHexString), this.pythProxy.executeGovernanceInstruction(claimVaaHexString),
"VAA is not coming from the governance data source" "InvalidGovernanceDataSource"
); );
const claimVaa = Buffer.from(claimVaaHexString.substring(2), "hex"); const claimVaa = Buffer.from(claimVaaHexString.substring(2), "hex");
@ -1055,9 +1045,9 @@ contract("Pyth", function () {
expect(newGovernanceDataSource.emitterAddress).equal(newEmitterAddress); expect(newGovernanceDataSource.emitterAddress).equal(newEmitterAddress);
// Verifies the data source has changed. // Verifies the data source has changed.
await expectRevert( await expectRevertCustomError(
this.pythProxy.executeGovernanceInstruction(vaa), this.pythProxy.executeGovernanceInstruction(vaa),
"VAA is not coming from the governance data source" "InvalidGovernanceDataSource"
); );
// Make sure a claim vaa does not get executed // Make sure a claim vaa does not get executed
@ -1075,9 +1065,9 @@ contract("Pyth", function () {
2 2
); );
await expectRevert( await expectRevertCustomError(
this.pythProxy.executeGovernanceInstruction(claimLonelyVaa), this.pythProxy.executeGovernanceInstruction(claimLonelyVaa),
"RequestGovernanceDataSourceTransfer can be only part of AuthorizeGovernanceDataSourceTransfer message" "InvalidGovernanceMessage"
); );
// Transfer back the ownership to the old governance data source without increasing // Transfer back the ownership to the old governance data source without increasing
@ -1115,9 +1105,15 @@ contract("Pyth", function () {
2 2
); );
await expectRevert( // This test fails without the hard coded gas limit.
this.pythProxy.executeGovernanceInstruction(transferBackVaaWrong), // Without gas limit, it fails on a random place (in wormhole sig verification) which
"cannot upgrade to an older governance data source" // is probably because truffle cannot estimate the gas usage correctly. So the gas is
// hard-coded to a high value of 6.7m gas (close to ganache limit).
await expectRevertCustomError(
this.pythProxy.executeGovernanceInstruction(transferBackVaaWrong, {
gas: 6700000,
}),
"OldGovernanceMessage"
); );
}); });
@ -1163,9 +1159,9 @@ contract("Pyth", function () {
); );
let rawBatch = generateRawBatchAttestation(100, 100, 1337); let rawBatch = generateRawBatchAttestation(100, 100, 1337);
await expectRevert( await expectRevertCustomError(
updatePriceFeeds(this.pythProxy, [rawBatch]), updatePriceFeeds(this.pythProxy, [rawBatch]),
"invalid data source chain/emitter ID" "InvalidUpdateDataSource"
); );
await updatePriceFeeds( await updatePriceFeeds(
@ -1202,9 +1198,9 @@ contract("Pyth", function () {
assert.equal(await this.pythProxy.singleUpdateFeeInWei(), "5000"); assert.equal(await this.pythProxy.singleUpdateFeeInWei(), "5000");
let rawBatch = generateRawBatchAttestation(100, 100, 1337); let rawBatch = generateRawBatchAttestation(100, 100, 1337);
await expectRevert( await expectRevertCustomError(
updatePriceFeeds(this.pythProxy, [rawBatch], 0), updatePriceFeeds(this.pythProxy, [rawBatch], 0),
insufficientFeeError "InsufficientFee"
); );
await updatePriceFeeds(this.pythProxy, [rawBatch], 5000); await updatePriceFeeds(this.pythProxy, [rawBatch], 5000);
@ -1385,3 +1381,18 @@ function expectEventMultipleTimes(receipt, eventName, args, cnt) {
const matches = getNumMatchingEvents(receipt, eventName, args); const matches = getNumMatchingEvents(receipt, eventName, args);
assert(matches === cnt, `Expected ${cnt} event matches, found ${matches}.`); assert(matches === cnt, `Expected ${cnt} event matches, found ${matches}.`);
} }
async function expectRevertCustomError(promise, reason) {
try {
await promise;
expect.fail("Expected promise to throw but it didn't");
} catch (revert) {
if (reason) {
const reasonId = web3.utils.keccak256(reason + "()").substr(0, 10);
expect(
JSON.stringify(revert),
`Expected custom error ${reason} (${reasonId})`
).to.include(reasonId);
}
}
}