pyth-crosschain/target_chains/ethereum/contracts/forge-test/Pyth.Aave.t.sol

369 lines
12 KiB
Solidity

// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
import "@pythnetwork/pyth-sdk-solidity/PythErrors.sol";
import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
import "./utils/WormholeTestUtils.t.sol";
import "./utils/PythTestUtils.t.sol";
import "./utils/RandTestUtils.t.sol";
import "../contracts/aave/interfaces/IPriceOracleGetter.sol";
import "../contracts/aave/PythPriceOracleGetter.sol";
import "./Pyth.WormholeMerkleAccumulator.t.sol";
contract PythAaveTest is PythWormholeMerkleAccumulatorTest {
IPriceOracleGetter public pythOracleGetter;
address[] assets;
bytes32[] priceIds;
uint constant NUM_PRICE_FEEDS = 5;
uint256 constant BASE_CURRENCY_UNIT = 1e8;
uint constant VALID_TIME_PERIOD_SECS = 60;
function setUp() public override {
pyth = IPyth(setUpPyth(setUpWormholeReceiver(1)));
assets = new address[](NUM_PRICE_FEEDS);
PriceFeedMessage[]
memory priceFeedMessages = generateRandomBoundedPriceFeedMessage(
NUM_PRICE_FEEDS
);
priceIds = new bytes32[](NUM_PRICE_FEEDS);
for (uint i = 0; i < NUM_PRICE_FEEDS; i++) {
assets[i] = address(
uint160(uint(keccak256(abi.encodePacked(i + NUM_PRICE_FEEDS))))
);
priceIds[i] = priceFeedMessages[i].priceId;
}
(
bytes[] memory updateData,
uint updateFee
) = createWormholeMerkleUpdateData(priceFeedMessages);
pyth.updatePriceFeeds{value: updateFee}(updateData);
pythOracleGetter = new PythPriceOracleGetter(
address(pyth),
assets,
priceIds,
address(0x0),
BASE_CURRENCY_UNIT,
VALID_TIME_PERIOD_SECS
);
}
function testConversion(
int64 pythPrice,
int32 pythExpo,
uint256 aavePrice,
uint256 baseCurrencyUnit
) private {
PriceFeedMessage[] memory priceFeedMessages = new PriceFeedMessage[](1);
PriceFeedMessage memory priceFeedMessage = PriceFeedMessage({
priceId: getRandBytes32(),
price: pythPrice,
conf: getRandUint64(),
expo: pythExpo,
publishTime: uint64(1),
prevPublishTime: getRandUint64(),
emaPrice: getRandInt64(),
emaConf: getRandUint64()
});
priceFeedMessages[0] = priceFeedMessage;
(
bytes[] memory updateData,
uint updateFee
) = createWormholeMerkleUpdateData(priceFeedMessages);
pyth.updatePriceFeeds{value: updateFee}(updateData);
priceIds = new bytes32[](1);
priceIds[0] = priceFeedMessage.priceId;
assets = new address[](1);
assets[0] = address(
uint160(uint(keccak256(abi.encodePacked(uint(100)))))
);
pythOracleGetter = new PythPriceOracleGetter(
address(pyth),
assets,
priceIds,
address(0x0),
baseCurrencyUnit,
VALID_TIME_PERIOD_SECS
);
assertEq(pythOracleGetter.getAssetPrice(assets[0]), aavePrice);
}
function testGetAssetPriceWorks() public {
// "display" price is 529.30903
testConversion(52_930_903, -5, 52_930_903_000, BASE_CURRENCY_UNIT);
}
function testGetAssetPriceWorksWithPositiveExponent() public {
// "display" price is 5_293_000
testConversion(5_293, 3, 529_300_000_000_000, BASE_CURRENCY_UNIT);
}
function testGetAssetPriceWorksWithZeroExponent() public {
// "display" price is 5_293
testConversion(5_293, 0, 529_300_000_000, BASE_CURRENCY_UNIT);
}
function testGetAssetPriceWorksWithNegativeNormalizerExponent() public {
// "display" price is 5_293
testConversion(
5_293_000_000_000_000,
-12,
529_300_000_000,
BASE_CURRENCY_UNIT
);
}
function testGetAssetPriceWorksWithBaseCurrencyUnitOfOne() public {
// "display" price is 529.30903
testConversion(52_930_903, -5, 529, 1);
}
function testGetAssetPriceWorksWithBoundedRandomValues(uint seed) public {
setRandSeed(seed);
for (uint i = 0; i < assets.length; i++) {
address asset = assets[i];
uint256 assetPrice = pythOracleGetter.getAssetPrice(asset);
uint256 aavePrice = assetPrice / BASE_CURRENCY_UNIT;
bytes32 priceId = priceIds[i];
PythStructs.Price memory price = pyth.getPrice(priceId);
int64 pythRawPrice = price.price;
uint pythNormalizer;
uint pythPrice;
if (price.expo < 0) {
pythNormalizer = 10 ** uint32(-price.expo);
pythPrice = uint64(pythRawPrice) / pythNormalizer;
} else {
pythNormalizer = 10 ** uint32(price.expo);
pythPrice = uint64(pythRawPrice) * pythNormalizer;
}
assertEq(aavePrice, pythPrice);
}
}
function testGetAssetPriceWorksIfGivenBaseCurrencyAddress() public {
address usdAddress = address(0x0);
uint256 assetPrice = pythOracleGetter.getAssetPrice(usdAddress);
assertEq(assetPrice, BASE_CURRENCY_UNIT);
}
function testGetAssetRevertsIfPriceNotRecentEnough() public {
uint timestamp = block.timestamp;
vm.warp(timestamp + VALID_TIME_PERIOD_SECS);
for (uint i = 0; i < assets.length; i++) {
pythOracleGetter.getAssetPrice(assets[i]);
}
vm.warp(timestamp + VALID_TIME_PERIOD_SECS + 1);
for (uint i = 0; i < assets.length; i++) {
vm.expectRevert(PythErrors.StalePrice.selector);
pythOracleGetter.getAssetPrice(assets[i]);
}
}
function testGetAssetRevertsIfPriceFeedNotFound() public {
address addr = address(
uint160(uint(keccak256(abi.encodePacked(uint(100)))))
);
vm.expectRevert(PythErrors.PriceFeedNotFound.selector);
pythOracleGetter.getAssetPrice(addr);
}
function testGetAssetPriceRevertsIfPriceIsNegative() public {
PriceFeedMessage[] memory priceFeedMessages = new PriceFeedMessage[](1);
PriceFeedMessage memory priceFeedMessage = PriceFeedMessage({
priceId: getRandBytes32(),
price: int64(-5),
conf: getRandUint64(),
expo: getRandInt32(),
publishTime: uint64(1),
prevPublishTime: getRandUint64(),
emaPrice: getRandInt64(),
emaConf: getRandUint64()
});
priceFeedMessages[0] = priceFeedMessage;
(
bytes[] memory updateData,
uint updateFee
) = createWormholeMerkleUpdateData(priceFeedMessages);
pyth.updatePriceFeeds{value: updateFee}(updateData);
priceIds = new bytes32[](1);
priceIds[0] = priceFeedMessage.priceId;
assets = new address[](1);
assets[0] = address(
uint160(uint(keccak256(abi.encodePacked(uint(100)))))
);
pythOracleGetter = new PythPriceOracleGetter(
address(pyth),
assets,
priceIds,
address(0x0),
BASE_CURRENCY_UNIT,
VALID_TIME_PERIOD_SECS
);
vm.expectRevert(abi.encodeWithSignature("InvalidNonPositivePrice()"));
pythOracleGetter.getAssetPrice(assets[0]);
}
function testGetAssetPriceRevertsIfNormalizerOverflows() public {
PriceFeedMessage[] memory priceFeedMessages = new PriceFeedMessage[](1);
PriceFeedMessage memory priceFeedMessage = PriceFeedMessage({
priceId: getRandBytes32(),
price: int64(1),
conf: getRandUint64(),
expo: int32(59), // type(uint192).max = ~6.27e58
publishTime: uint64(1),
prevPublishTime: getRandUint64(),
emaPrice: getRandInt64(),
emaConf: getRandUint64()
});
priceFeedMessages[0] = priceFeedMessage;
(
bytes[] memory updateData,
uint updateFee
) = createWormholeMerkleUpdateData(priceFeedMessages);
pyth.updatePriceFeeds{value: updateFee}(updateData);
priceIds = new bytes32[](1);
priceIds[0] = priceFeedMessage.priceId;
assets = new address[](1);
assets[0] = address(
uint160(uint(keccak256(abi.encodePacked(uint(100)))))
);
pythOracleGetter = new PythPriceOracleGetter(
address(pyth),
assets,
priceIds,
address(0x0),
BASE_CURRENCY_UNIT,
VALID_TIME_PERIOD_SECS
);
vm.expectRevert(abi.encodeWithSignature("NormalizationOverflow()"));
pythOracleGetter.getAssetPrice(assets[0]);
}
function testGetAssetPriceRevertsIfNormalizedToZero() public {
PriceFeedMessage[] memory priceFeedMessages = new PriceFeedMessage[](1);
PriceFeedMessage memory priceFeedMessage = PriceFeedMessage({
priceId: getRandBytes32(),
price: int64(1),
conf: getRandUint64(),
expo: int32(-75),
publishTime: uint64(1),
prevPublishTime: getRandUint64(),
emaPrice: getRandInt64(),
emaConf: getRandUint64()
});
priceFeedMessages[0] = priceFeedMessage;
(
bytes[] memory updateData,
uint updateFee
) = createWormholeMerkleUpdateData(priceFeedMessages);
pyth.updatePriceFeeds{value: updateFee}(updateData);
priceIds = new bytes32[](1);
priceIds[0] = priceFeedMessage.priceId;
assets = new address[](1);
assets[0] = address(
uint160(uint(keccak256(abi.encodePacked(uint(100)))))
);
pythOracleGetter = new PythPriceOracleGetter(
address(pyth),
assets,
priceIds,
address(0x0),
BASE_CURRENCY_UNIT,
VALID_TIME_PERIOD_SECS
);
vm.expectRevert(abi.encodeWithSignature("InvalidNonPositivePrice()"));
pythOracleGetter.getAssetPrice(assets[0]);
}
function testPythPriceOracleGetterConstructorRevertsIfAssetsAndPriceIdsLengthAreDifferent()
public
{
priceIds = new bytes32[](2);
priceIds[0] = getRandBytes32();
priceIds[1] = getRandBytes32();
assets = new address[](1);
assets[0] = address(
uint160(uint(keccak256(abi.encodePacked(uint(100)))))
);
vm.expectRevert(abi.encodeWithSignature("InconsistentParamsLength()"));
pythOracleGetter = new PythPriceOracleGetter(
address(pyth),
assets,
priceIds,
address(0x0),
BASE_CURRENCY_UNIT,
VALID_TIME_PERIOD_SECS
);
}
function testPythPriceOracleGetterConstructorRevertsIfInvalidBaseCurrencyUnit()
public
{
priceIds = new bytes32[](1);
priceIds[0] = getRandBytes32();
assets = new address[](1);
assets[0] = address(
uint160(uint(keccak256(abi.encodePacked(uint(100)))))
);
vm.expectRevert(abi.encodeWithSignature("InvalidBaseCurrencyUnit()"));
pythOracleGetter = new PythPriceOracleGetter(
address(pyth),
assets,
priceIds,
address(0x0),
0,
VALID_TIME_PERIOD_SECS
);
vm.expectRevert(abi.encodeWithSignature("InvalidBaseCurrencyUnit()"));
pythOracleGetter = new PythPriceOracleGetter(
address(pyth),
assets,
priceIds,
address(0x0),
11,
VALID_TIME_PERIOD_SECS
);
vm.expectRevert(abi.encodeWithSignature("InvalidBaseCurrencyUnit()"));
pythOracleGetter = new PythPriceOracleGetter(
address(pyth),
assets,
priceIds,
address(0x0),
20,
VALID_TIME_PERIOD_SECS
);
}
}