Omniscia Swisscoast Audit

PythCaller Manual Review Findings

PythCaller Manual Review Findings

PCR-01M: Improper Revert Error

Description:

The PythCaller::convertToUint function will revert if the conversion cannot occur due to a misbehaving Pyth oracle instead of bubbling up the error to the PythCaller::getPythCurrentValue caller and thus permitting them to f.e. invoke the SupraCaller as an alternative.

Impact:

The PriceFeed may never fallback to the Supra oracle system if the Pyth oracle misbehaves with an incorrect exponent or a negative price.

Example:

packages/contracts/contracts/Dependencies/PythCaller.sol
35function getPythCurrentValue(bytes32 HBARUSD, bytes32 USHCHF)
36external
37view
38override
39returns (
40 bool ifRetrieve,
41 uint256 value,
42 uint256 _timestampRetrieved
43)
44{
45 (int64 priceHBARUSD, , int32 expoHBARUSD, uint publishTimeHBARUSD) = pyth.getPriceUnsafe(HBARUSD);
46 (int64 priceUSHCHF, , int32 expoUSHCHF, uint publishTimeUSHCHF ) = pyth.getPriceUnsafe(USHCHF);
47
48 uint256 basePriceHBARUSD = convertToUint(priceHBARUSD, expoHBARUSD, 8);
49 uint256 basePriceUSHCHF = convertToUint(priceUSHCHF, expoUSHCHF, 8);
50
51 uint256 hbarChfPrice = basePriceHBARUSD * basePriceUSHCHF;
52
53 // Using the smaller of the two timestamps as reference
54 uint256 publishTime = publishTimeHBARUSD < publishTimeUSHCHF ? publishTimeHBARUSD : publishTimeUSHCHF;
55
56 if (hbarChfPrice > 0) return (true, hbarChfPrice, publishTime);
57 return (false, 0, publishTime);
58}
59
60function convertToUint(
61 int64 price,
62 int32 expo,
63 uint8 targetDecimals
64) private pure returns (uint256) {
65 if (price < 0 || expo > 0 || expo < -255) {
66 revert();
67 }
68
69 uint8 priceDecimals = uint8(uint32(-1 * expo));
70
71 if (targetDecimals >= priceDecimals) {
72 return
73 uint(uint64(price)) *
74 10 ** uint32(targetDecimals - priceDecimals);
75 } else {
76 return
77 uint(uint64(price)) /
78 10 ** uint32(priceDecimals - targetDecimals);
79 }
80}

Recommendation:

We advise the PythCaller::convertToUint function to be revised, yielding a bool indicating whether the conversion could be safely performed.

Alleviation (04618e407bddce5b22e9cadd787fd3334bd3afe6):

The code was updated to bubble up a false value for the ifRetrieve value of the top-level PythCaller::getPythCurrentValue call, rendering this exhibit fully alleviated.

PCR-02M: Insecure Price Acquisition

Description:

The referenced function integrated will not apply data staleness checks which can significantly hurt the integrity of the yielded values.

Impact:

The data staleness check imposed by the PriceFeed contract is insufficient in protecting against stale prices based on the Pyth Network's configuration, causing prices yielded by the PythCaller::getPythCurrentValue function to potentially be stale.

Example:

packages/contracts/contracts/Dependencies/PythCaller.sol
35function getPythCurrentValue(bytes32 HBARUSD, bytes32 USHCHF)
36external
37view
38override
39returns (
40 bool ifRetrieve,
41 uint256 value,
42 uint256 _timestampRetrieved
43)
44{
45 (int64 priceHBARUSD, , int32 expoHBARUSD, uint publishTimeHBARUSD) = pyth.getPriceUnsafe(HBARUSD);
46 (int64 priceUSHCHF, , int32 expoUSHCHF, uint publishTimeUSHCHF ) = pyth.getPriceUnsafe(USHCHF);
47
48 uint256 basePriceHBARUSD = convertToUint(priceHBARUSD, expoHBARUSD, 8);
49 uint256 basePriceUSHCHF = convertToUint(priceUSHCHF, expoUSHCHF, 8);
50
51 uint256 hbarChfPrice = basePriceHBARUSD * basePriceUSHCHF;
52
53 // Using the smaller of the two timestamps as reference
54 uint256 publishTime = publishTimeHBARUSD < publishTimeUSHCHF ? publishTimeHBARUSD : publishTimeUSHCHF;
55
56 if (hbarChfPrice > 0) return (true, hbarChfPrice, publishTime);
57 return (false, 0, publishTime);
58}

Recommendation:

We advise the normal IPyth::getPrice function to be invoked instead, guaranteeing that the prices yielded are not stale.

Alleviation (04618e407bddce5b22e9cadd787fd3334bd3afe6):

The Swisscoast team has clarified that the HLiquity system is meant to impose its own set of data staleness checks, rendering validation of such a trait at the PythCaller level incorrect.

We concur with this assessment and have thus marked the exhibit as nullified.

PCR-03M: Incorrect Accuracy of Yielded Value

Description:

The PythCaller::getPythCurrentValue function will yield a value with 1e16 accuracy instead of the expected 1e8 accuracy by the HLiquity system.

Impact:

Functions such as BorrowerOperations::_getUSDValue will yield significantly higher values, causing the HCHF token to be minted at abnormal amounts and thus to be devalued in relation to its "real" decimals.

Example:

packages/contracts/contracts/Dependencies/PythCaller.sol
26/*
27* getPythCurrentValue()
28*
29* @dev Allows the user to get the latest value for the requestId specified
30* @param _requestId is the requestId to look up the value for
31* @return ifRetrieve bool true if it is able to retrieve a value, the value, and the value's timestamp
32* @return value the value retrieved
33* @return _timestampRetrieved the value's timestamp
34*/
35function getPythCurrentValue(bytes32 HBARUSD, bytes32 USHCHF)
36external
37view
38override
39returns (
40 bool ifRetrieve,
41 uint256 value,
42 uint256 _timestampRetrieved
43)
44{
45 (int64 priceHBARUSD, , int32 expoHBARUSD, uint publishTimeHBARUSD) = pyth.getPriceUnsafe(HBARUSD);
46 (int64 priceUSHCHF, , int32 expoUSHCHF, uint publishTimeUSHCHF ) = pyth.getPriceUnsafe(USHCHF);
47
48 uint256 basePriceHBARUSD = convertToUint(priceHBARUSD, expoHBARUSD, 8);
49 uint256 basePriceUSHCHF = convertToUint(priceUSHCHF, expoUSHCHF, 8);
50
51 uint256 hbarChfPrice = basePriceHBARUSD * basePriceUSHCHF;
52
53 // Using the smaller of the two timestamps as reference
54 uint256 publishTime = publishTimeHBARUSD < publishTimeUSHCHF ? publishTimeHBARUSD : publishTimeUSHCHF;
55
56 if (hbarChfPrice > 0) return (true, hbarChfPrice, publishTime);
57 return (false, 0, publishTime);
58}

Recommendation:

We advise the result of the referenced multiplication to be divided by 1e8, ensuring its accuracy is normalized.

Alleviation (04618e407bddce5b22e9cadd787fd3334bd3afe6):

The yielded price is properly divided by 1e8 as advised, ensuring the accuracy of the PythCaller matches the one expected by the HLiquity system.