Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/swift-heron-flips.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@exactly/protocol": patch
---

✨ price-feeds: add inverse feed
70 changes: 41 additions & 29 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ DebtManagerTest:testFloatingToFixedRollHigherThanAvailableLiquidityWithSlippage(
DebtManagerTest:testFloatingToFixedRollHigherThanAvailableLiquidityWithSlippageWithThreePools() (gas: 2819298)
DebtManagerTest:testFloatingToFixedRollWithAccurateSlippage() (gas: 1733293)
DebtManagerTest:testFloatingToFixedRollWithAccurateSlippageWithPreviousPosition() (gas: 1955460)
DebtManagerTest:testFuzzRolls(uint8[4],uint8[4],uint256[4],uint40[4],uint8[4]) (runs: 256, μ: 8031885, ~: 8086009)
DebtManagerTest:testFuzzRolls(uint8[4],uint8[4],uint256[4],uint40[4],uint8[4]) (runs: 256, μ: 8004418, ~: 8031281)
DebtManagerTest:testLateFixedRoll() (gas: 1455061)
DebtManagerTest:testLateFixedRollWithThreeLoops() (gas: 2044387)
DebtManagerTest:testLateFixedToFloatingRoll() (gas: 1449884)
Expand Down Expand Up @@ -220,19 +220,19 @@ IntegrationPreviewerTest:test_fixedRepayPosition_beforeMaturity() (gas: 580013)
IntegrationPreviewerTest:test_fixedRepayPosition_maxUint() (gas: 79490)
IntegrationPreviewerTest:test_fixedRepayPosition_saturatedFallback() (gas: 63069)
IntegrationPreviewerTest:test_healthFactor() (gas: 464287)
IntegrationPreviewerTest:test_previewDeposit_(uint256) (runs: 256, μ: 1595909, ~: 1599768)
IntegrationPreviewerTest:test_previewDeposit_(uint256) (runs: 256, μ: 1595663, ~: 1599768)
IntegrationPreviewerTest:test_previewHealthFactor() (gas: 5827588)
IntegrationPreviewerTest:test_previewHealthFactor_nonCollateralMarket() (gas: 3511184)
IntegrationPreviewerTest:test_previewWithdraw_(uint256) (runs: 256, μ: 1656041, ~: 1660104)
IntegrationPreviewerTest:test_previewWithdraw_(uint256) (runs: 256, μ: 1655792, ~: 1660104)
InterestRateModelTest:testFixedBorrowRate() (gas: 2088868)
InterestRateModelTest:testFixedRateRevertAlreadyMatured() (gas: 2083135)
InterestRateModelTest:testFixedRateRevertUtilizationExceeded() (gas: 2097203)
InterestRateModelTest:testFloatingBorrowRate() (gas: 2082456)
InterestRateModelTest:testFuzzFixedRateGrowth(uint256,uint256,uint256,uint256) (runs: 256, μ: 2108979, ~: 2105195)
InterestRateModelTest:testFuzzFixedRateTimeSensitivity(uint256,uint256,uint256) (runs: 256, μ: 2117002, ~: 2117161)
InterestRateModelTest:testFuzzReferenceLegacyRateFixed(uint32,uint256,uint256[2],uint256[2],uint256,uint256,uint256) (runs: 256, μ: 12926388, ~: 13131329)
InterestRateModelTest:testFuzzReferenceRateFixed(uint256,uint256,uint256,uint256,uint256,uint256,(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,int256,uint256,uint256)) (runs: 256, μ: 2384553, ~: 2386829)
InterestRateModelTest:testFuzzReferenceRateFloating(uint256,uint256,(uint256,uint256,uint256,uint256,uint256,uint256,uint256)) (runs: 256, μ: 2324290, ~: 2324936)
InterestRateModelTest:testFuzzFixedRateGrowth(uint256,uint256,uint256,uint256) (runs: 256, μ: 2109336, ~: 2112192)
InterestRateModelTest:testFuzzFixedRateTimeSensitivity(uint256,uint256,uint256) (runs: 256, μ: 2117022, ~: 2117161)
InterestRateModelTest:testFuzzReferenceLegacyRateFixed(uint32,uint256,uint256[2],uint256[2],uint256,uint256,uint256) (runs: 256, μ: 12931216, ~: 13130880)
InterestRateModelTest:testFuzzReferenceRateFixed(uint256,uint256,uint256,uint256,uint256,uint256,(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,int256,uint256,uint256)) (runs: 256, μ: 2384630, ~: 2386873)
InterestRateModelTest:testFuzzReferenceRateFloating(uint256,uint256,(uint256,uint256,uint256,uint256,uint256,uint256,uint256)) (runs: 256, μ: 2324355, ~: 2324942)
InterestRateModelTest:testMinTimeToMaturity() (gas: 2107232)
InterestRateModelTest:testRevertMaxUtilizationLowerThanWad() (gas: 268402)
MarketTest:testAccountLiquidityAdjustedDebt() (gas: 654879)
Expand Down Expand Up @@ -396,7 +396,7 @@ PoolLibTest:testAtomicDepositBorrowRepayWithdraw() (gas: 45922)
PoolLibTest:testBackupBorrow() (gas: 33624)
PoolLibTest:testEarningsAccrual() (gas: 38952)
PoolLibTest:testEarningsDistribution() (gas: 32679)
PoolLibTest:testFuzzAddRemoveAll(uint8[12]) (runs: 256, μ: 104450, ~: 105534)
PoolLibTest:testFuzzAddRemoveAll(uint8[12]) (runs: 256, μ: 104690, ~: 106307)
PoolLibTest:testMaturityRangeLimit() (gas: 7866)
PoolLibTest:testMaturityRangeTooWide() (gas: 11656)
PreviewerTest:testAccountsReturningAccurateAmounts() (gas: 1687100)
Expand Down Expand Up @@ -478,6 +478,18 @@ PriceFeedDoubleTest:testPriceFeedDoubleReturningPrice() (gas: 76193)
PriceFeedDoubleTest:testPriceFeedDoubleWithActualOnChainValues() (gas: 85326)
PriceFeedDoubleTest:testPriceFeedDoubleWithNegativePriceShouldRevert() (gas: 212681)
PriceFeedDoubleTest:testPriceFeedDoubleWithUsdPriceFeed() (gas: 498675)
PriceFeedInverseTest:testFuzz_latestAnswer_matchesGreaterScale(int256) (runs: 256, μ: 55881, ~: 56215)
PriceFeedInverseTest:testFuzz_latestAnswer_returnsOne_whenAnswerIsAboveBaseUnitSquared(int256) (runs: 256, μ: 2531209, ~: 2531318)
PriceFeedInverseTest:testFuzz_latestAnswer_reverts_whenAnswerIsNegative(int256) (runs: 256, μ: 54861, ~: 54945)
PriceFeedInverseTest:testFuzz_latestAnswer_roundsUp(int256) (runs: 256, μ: 55095, ~: 55431)
PriceFeedInverseTest:test_assetPrice_returnsPrice_whenAnswerIsAboveBaseUnitSquared() (gas: 2509699)
PriceFeedInverseTest:test_constructor_setsPriceFeedDecimalsAndBaseUnit() (gas: 26853)
PriceFeedInverseTest:test_latestAnswer_matchesGreaterScale_withHighPrices() (gas: 229965)
PriceFeedInverseTest:test_latestAnswer_returnsExactInverse_whenDivisionIsExact() (gas: 91181)
PriceFeedInverseTest:test_latestAnswer_returnsInverse() (gas: 57926)
PriceFeedInverseTest:test_latestAnswer_returnsOne_whenAnswerIsAboveBaseUnitSquared() (gas: 91218)
PriceFeedInverseTest:test_latestAnswer_reverts_whenAnswerIsNegative() (gas: 94442)
PriceFeedInverseTest:test_latestAnswer_reverts_whenAnswerIsZero() (gas: 37201)
PriceFeedPoolTest:testPriceFeedPoolReturningPrice() (gas: 40917)
PriceFeedPoolTest:testPriceFeedPoolReturningPriceWithToken0False() (gas: 2181230)
PriceFeedPoolTest:testPriceFeedPoolWithAllDifferentDecimals() (gas: 2159319)
Expand Down Expand Up @@ -577,8 +589,8 @@ StakedEXATest:invariantNoDuplicatedReward() (runs: 10, calls: 5000, reverts: 0)
StakedEXATest:invariantRewardsUpOnly() (runs: 10, calls: 5000, reverts: 0)
StakedEXATest:invariantShareValueIsOne() (runs: 10, calls: 5000, reverts: 0)
StakedEXATest:testAlreadyListedError() (gas: 43677)
StakedEXATest:testAvgIndex(uint256[3],uint256[2]) (runs: 256, μ: 1322403, ~: 1370843)
StakedEXATest:testAvgStartTime(uint256[3],uint256[2]) (runs: 256, μ: 1250469, ~: 1298909)
StakedEXATest:testAvgIndex(uint256[3],uint256[2]) (runs: 256, μ: 1319458, ~: 1370678)
StakedEXATest:testAvgStartTime(uint256[3],uint256[2]) (runs: 256, μ: 1247524, ~: 1298744)
StakedEXATest:testBalanceOfDeposit(uint80) (runs: 256, μ: 331003, ~: 339807)
StakedEXATest:testBalanceOfWithdraw(uint256) (runs: 256, μ: 82393, ~: 82407)
StakedEXATest:testCanChangeRewardsDurationWhenDisabled() (gas: 176331)
Expand All @@ -587,12 +599,12 @@ StakedEXATest:testClaimAndUnstake() (gas: 1622618)
StakedEXATest:testClaimAndWithdrawAfterRefTime() (gas: 1029325)
StakedEXATest:testClaimBeforeFirstHarvest() (gas: 527334)
StakedEXATest:testDepositClaimsRewardsToReceiver() (gas: 1121108)
StakedEXATest:testDepositEvent(uint256) (runs: 256, μ: 335230, ~: 334966)
StakedEXATest:testDepositShouldClaim(uint256[2],uint32) (runs: 256, μ: 822456, ~: 754904)
StakedEXATest:testDepositEvent(uint256) (runs: 256, μ: 335228, ~: 334966)
StakedEXATest:testDepositShouldClaim(uint256[2],uint32) (runs: 256, μ: 826654, ~: 754952)
StakedEXATest:testDepositToAnotherWithAllowance() (gas: 375284)
StakedEXATest:testDepositToAnotherWithoutAllowanceShouldFail() (gas: 121959)
StakedEXATest:testDepositWithdrawAvgStartTimeAndIndex(uint256[3],uint256,uint256[5]) (runs: 256, μ: 1879175, ~: 1904532)
StakedEXATest:testEarnedWithTime(uint256) (runs: 256, μ: 86134, ~: 86326)
StakedEXATest:testDepositWithdrawAvgStartTimeAndIndex(uint256[3],uint256,uint256[5]) (runs: 256, μ: 1875287, ~: 1904448)
StakedEXATest:testEarnedWithTime(uint256) (runs: 256, μ: 86108, ~: 86326)
StakedEXATest:testEmergencyAdminCanPauseNotUnpause() (gas: 175029)
StakedEXATest:testFinishDistributionEmitEvent() (gas: 240752)
StakedEXATest:testFinishDistributionLetsClaimUnclaimed() (gas: 1784877)
Expand All @@ -603,9 +615,9 @@ StakedEXATest:testGrantRevokeEmergencyAdmin() (gas: 117824)
StakedEXATest:testGrantRevokePauser() (gas: 117836)
StakedEXATest:testHandlerClaim(uint8) (runs: 256, μ: 485340, ~: 485340)
StakedEXATest:testHandlerDeposit(uint80) (runs: 256, μ: 1240331, ~: 1276242)
StakedEXATest:testHandlerHarvest(uint64) (runs: 256, μ: 402355, ~: 411143)
StakedEXATest:testHandlerNotifyRewardAmount(uint64) (runs: 256, μ: 166056, ~: 156797)
StakedEXATest:testHandlerSetDuration(uint32) (runs: 256, μ: 196224, ~: 218711)
StakedEXATest:testHandlerHarvest(uint64) (runs: 256, μ: 402351, ~: 411143)
StakedEXATest:testHandlerNotifyRewardAmount(uint64) (runs: 256, μ: 165113, ~: 156797)
StakedEXATest:testHandlerSetDuration(uint32) (runs: 256, μ: 198473, ~: 218711)
StakedEXATest:testHandlerSetMarket() (gas: 144553)
StakedEXATest:testHandlerWithdraw(uint256) (runs: 256, μ: 91497, ~: 91511)
StakedEXATest:testHarvestAmountWithReducedAllowance() (gas: 184605)
Expand All @@ -616,13 +628,13 @@ StakedEXATest:testHarvestWhenFinished() (gas: 326352)
StakedEXATest:testHarvestX() (gas: 180952)
StakedEXATest:testHarvestZero() (gas: 255436)
StakedEXATest:testInitialValues() (gas: 196484)
StakedEXATest:testInsufficientBalanceError(uint256) (runs: 256, μ: 63929, ~: 64151)
StakedEXATest:testInsufficientBalanceError(uint256) (runs: 256, μ: 63958, ~: 64151)
StakedEXATest:testMaxRewardsGasConsumption() (gas: 144674963)
StakedEXATest:testMultipleClaimsVsOne() (gas: 21936440)
StakedEXATest:testMultipleHarvests() (gas: 284083)
StakedEXATest:testNoRewardsAfterPeriod(uint256) (runs: 256, μ: 1809884, ~: 1804427)
StakedEXATest:testNoRewardsAfterPeriod(uint256) (runs: 256, μ: 1809893, ~: 1804427)
StakedEXATest:testNotPausingRoleError() (gas: 39124)
StakedEXATest:testNotifyRewardAmount(uint256,uint256) (runs: 256, μ: 155618, ~: 155564)
StakedEXATest:testNotifyRewardAmount(uint256,uint256) (runs: 256, μ: 155601, ~: 155564)
StakedEXATest:testNotifyRewardWithUnderlyingAsset() (gas: 460608)
StakedEXATest:testOnlyAdminEnableReward() (gas: 1297302)
StakedEXATest:testOnlyAdminFinishDistribution() (gas: 213640)
Expand All @@ -640,13 +652,13 @@ StakedEXATest:testPenaltyThresholdRange() (gas: 36989)
StakedEXATest:testPermitAndDeposit() (gas: 452146)
StakedEXATest:testPermitFailKeepsFlow() (gas: 484480)
StakedEXATest:testRemoveDepositAllowance() (gas: 466913)
StakedEXATest:testResetDepositAfterRefTime(uint256) (runs: 256, μ: 1033242, ~: 1032994)
StakedEXATest:testRewardAmountNotifiedEvent(uint256) (runs: 256, μ: 105549, ~: 105281)
StakedEXATest:testResetDepositAfterRefTime(uint256) (runs: 256, μ: 1033239, ~: 1032994)
StakedEXATest:testRewardAmountNotifiedEvent(uint256) (runs: 256, μ: 105547, ~: 105293)
StakedEXATest:testRewardNotListedError() (gas: 1177184)
StakedEXATest:testRewardPaidEvent(uint256,uint256) (runs: 256, μ: 810714, ~: 854321)
StakedEXATest:testRewardsAmounts(uint256) (runs: 256, μ: 1707512, ~: 1707287)
StakedEXATest:testRewardPaidEvent(uint256,uint256) (runs: 256, μ: 809092, ~: 854321)
StakedEXATest:testRewardsAmounts(uint256) (runs: 256, μ: 1707506, ~: 1707287)
StakedEXATest:testRewardsDurationSetEvent(uint40) (runs: 256, μ: 52173, ~: 52149)
StakedEXATest:testSetDuration(uint256,uint40) (runs: 256, μ: 66005, ~: 64453)
StakedEXATest:testSetDuration(uint256,uint40) (runs: 256, μ: 65997, ~: 64453)
StakedEXATest:testSetMarketAddressZero() (gas: 36979)
StakedEXATest:testSetMarketOnlyAdmin() (gas: 1345554)
StakedEXATest:testSetMaxRewardsTokensExceeded() (gas: 111958823)
Expand All @@ -658,12 +670,12 @@ StakedEXATest:testSetSavingsZeroAddressError() (gas: 37006)
StakedEXATest:testTotalSupplyDeposit(uint80) (runs: 256, μ: 330389, ~: 339193)
StakedEXATest:testTotalSupplyWithdraw(uint256) (runs: 256, μ: 81753, ~: 81767)
StakedEXATest:testUntransferable(uint80) (runs: 256, μ: 338580, ~: 348672)
StakedEXATest:testWithdrawEvent(uint256) (runs: 256, μ: 483396, ~: 483140)
StakedEXATest:testWithdrawEvent(uint256) (runs: 256, μ: 483393, ~: 483140)
StakedEXATest:testWithdrawRewardUnderlyingAsset() (gas: 456617)
StakedEXATest:testWithdrawRewardsOnlyAdmin() (gas: 242200)
StakedEXATest:testWithdrawRewardsOnlyReward() (gas: 1177174)
StakedEXATest:testWithdrawSameAmountRewardsShouldEqual(uint256,uint256) (runs: 256, μ: 1065777, ~: 1107874)
StakedEXATest:testWithdrawWithRewards(uint256) (runs: 256, μ: 895620, ~: 895364)
StakedEXATest:testWithdrawSameAmountRewardsShouldEqual(uint256,uint256) (runs: 256, μ: 1063385, ~: 1107803)
StakedEXATest:testWithdrawWithRewards(uint256) (runs: 256, μ: 895617, ~: 895364)
StakedEXATest:testZeroRateError() (gas: 58274)
StakingPreviewerTest:testAllClaimable() (gas: 769205)
StakingPreviewerTest:testAllClaimed() (gas: 818952)
Expand Down
31 changes: 31 additions & 0 deletions contracts/PriceFeedInverse.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.17;

import { FixedPointMathLib } from "solady/src/utils/FixedPointMathLib.sol";
Comment thread
patitonar marked this conversation as resolved.
import { SafeCastLib } from "solady/src/utils/SafeCastLib.sol";
import { IPriceFeed } from "./utils/IPriceFeed.sol";

contract PriceFeedInverse is IPriceFeed {
using FixedPointMathLib for uint256;
using SafeCastLib for int256;

/// @notice Price feed whose answer is inverted.
IPriceFeed public immutable priceFeed;
/// @notice Number of decimals that the answer of this price feed has, same as the inverted feed's.
uint8 public immutable decimals;
/// @notice Base unit of the inverted feed's answer.
uint256 public immutable baseUnit;

constructor(IPriceFeed priceFeed_) {
priceFeed = priceFeed_;
decimals = priceFeed_.decimals();
baseUnit = 10 ** decimals;
}

/// @notice Returns the inverse of the price feed's latest answer.
/// @dev Rounds up, the conservative direction for debt-only markets, so the inverse can't truncate to zero.
/// Reverts on a zero or negative answer.
function latestAnswer() external view returns (int256) {
return int256(baseUnit.mulDivUp(baseUnit, priceFeed.latestAnswer().toUint256()));
Comment thread
patitonar marked this conversation as resolved.
}
}
11 changes: 11 additions & 0 deletions deploy/PriceFeeds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ const func: DeployFunction = async ({ ethers, network, deployments: { deploy, ge
log: true,
}),
);
} else if (priceFeed === "inverse") {
await tenderlify(
"PriceFeedInverse",
await deploy(`PriceFeed${symbol}`, {
skipIfAlreadyDeployed,
contract: "PriceFeedInverse",
args: [(await get(`PriceFeed${symbol}Inverted`)).address],
from: deployer,
log: true,
}),
);
} else if (priceFeed.wrapper) {
const { address, abi } = await get(priceFeed.wrapper);
await tenderlify(
Expand Down
6 changes: 4 additions & 2 deletions deploy/mocks/Assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const func: DeployFunction = async ({ deployments: { deploy, log }, getNamedAcco
from: deployer,
log: true,
});
} else if (priceFeed) {
} else if (typeof priceFeed === "object") {
await deploy(priceFeed.wrapper, {
skipIfAlreadyDeployed: true,
contract: "MockStETH",
Expand All @@ -52,7 +52,9 @@ const func: DeployFunction = async ({ deployments: { deploy, log }, getNamedAcco
});
}

await deploy(`PriceFeed${symbol}${priceFeed ? (priceFeed === "double" ? "One" : "Main") : ""}`, {
const sourceSuffix =
priceFeed === "double" ? "One" : priceFeed === "inverse" ? "Inverted" : priceFeed ? "Main" : "";
await deploy(`PriceFeed${symbol}${sourceSuffix}`, {
Comment thread
patitonar marked this conversation as resolved.
skipIfAlreadyDeployed: true,
contract: "MockPriceFeed",
args: [priceDecimals, parseUnits({ WBTC: "63000", WETH: "1000", OP: "3" }[symbol] ?? "1", priceDecimals)],
Comment thread
patitonar marked this conversation as resolved.
Expand Down
2 changes: 1 addition & 1 deletion hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ declare module "hardhat/types/config" {
frozen?: boolean;
nonCollateral?: boolean;
adjustFactor: number;
priceFeed?: "double" | { wrapper: string; fn: string; baseUnit: bigint };
priceFeed?: "double" | "inverse" | { wrapper: string; fn: string; baseUnit: bigint };
penaltyRatePerDay: number;
treasuryFeeRate: number;
backupFeeRate: number;
Expand Down
Loading
Loading