diff --git a/contracts/interfaces/IAsset.sol b/contracts/interfaces/IAsset.sol index b5423ab2b5..a1c68a305e 100644 --- a/contracts/interfaces/IAsset.sol +++ b/contracts/interfaces/IAsset.sol @@ -32,6 +32,13 @@ interface IAsset is IRewardable { /// @return high {UoA/tok} The upper end of the price estimate function price() external view returns (uint192 low, uint192 high); + /// Should not revert + /// lotLow should be nonzero when the asset might be worth selling + /// @dev Deprecated. Phased out in 3.1.0, but left on interface for backwards compatibility + /// @return lotLow {UoA/tok} The lower end of the lot price estimate + /// @return lotHigh {UoA/tok} The upper end of the lot price estimate + function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh); + /// @return {tok} The balance of the ERC20 in whole tokens function bal(address account) external view returns (uint192); diff --git a/contracts/interfaces/IBasketHandler.sol b/contracts/interfaces/IBasketHandler.sol index e43cf6735f..2ed829d1b9 100644 --- a/contracts/interfaces/IBasketHandler.sol +++ b/contracts/interfaces/IBasketHandler.sol @@ -138,6 +138,13 @@ interface IBasketHandler is IComponent { /// @return high {UoA/BU} The upper end of the price estimate function price() external view returns (uint192 low, uint192 high); + /// Should not revert + /// lotLow should be nonzero if a BU could be worth selling + /// @dev Deprecated. Phased out in 3.1.0, but left on interface for backwards compatibility + /// @return lotLow {UoA/tok} The lower end of the lot price estimate + /// @return lotHigh {UoA/tok} The upper end of the lot price estimate + function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh); + /// @return timestamp The timestamp at which the basket was last set function timestamp() external view returns (uint48); diff --git a/contracts/p0/BasketHandler.sol b/contracts/p0/BasketHandler.sol index 03e99b1401..998c25e65f 100644 --- a/contracts/p0/BasketHandler.sol +++ b/contracts/p0/BasketHandler.sol @@ -370,11 +370,27 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { } /// Should not revert - /// low should be nonzero when the asset might be worth selling /// @return low {UoA/BU} The lower end of the price estimate /// @return high {UoA/BU} The upper end of the price estimate // returns sum(quantity(erc20) * price(erc20) for erc20 in basket.erc20s) function price() external view returns (uint192 low, uint192 high) { + return _price(false); + } + + /// Should not revert + /// lowLow should be nonzero when the asset might be worth selling + /// @dev Deprecated. Phased out in 3.1.0, but left on interface for backwards compatibility + /// @return lotLow {UoA/BU} The lower end of the lot price estimate + /// @return lotHigh {UoA/BU} The upper end of the lot price estimate + // returns sum(quantity(erc20) * lotPrice(erc20) for erc20 in basket.erc20s) + function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh) { + return _price(true); + } + + /// Returns the price of a BU, using the lot prices if `useLotPrice` is true + /// @return low {UoA/BU} The lower end of the lot price estimate + /// @return high {UoA/BU} The upper end of the lot price estimate + function _price(bool useLotPrice) internal view returns (uint192 low, uint192 high) { IAssetRegistry reg = main.assetRegistry(); uint256 low256; @@ -384,7 +400,9 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler { uint192 qty = quantity(basket.erc20s[i]); if (qty == 0) continue; - (uint192 lowP, uint192 highP) = reg.toAsset(basket.erc20s[i]).price(); + (uint192 lowP, uint192 highP) = useLotPrice + ? reg.toAsset(basket.erc20s[i]).lotPrice() + : reg.toAsset(basket.erc20s[i]).price(); low256 += qty.safeMul(lowP, RoundingMode.FLOOR); diff --git a/contracts/p1/BasketHandler.sol b/contracts/p1/BasketHandler.sol index 6a0753c1b6..fa076253bd 100644 --- a/contracts/p1/BasketHandler.sol +++ b/contracts/p1/BasketHandler.sol @@ -311,11 +311,28 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { } /// Should not revert - /// low should be nonzero when BUs are worth selling /// @return low {UoA/BU} The lower end of the price estimate /// @return high {UoA/BU} The upper end of the price estimate // returns sum(quantity(erc20) * price(erc20) for erc20 in basket.erc20s) function price() external view returns (uint192 low, uint192 high) { + return _price(false); + } + + /// Should not revert + /// lowLow should be nonzero when the asset might be worth selling + /// @dev Deprecated. Phased out in 3.1.0, but left on interface for backwards compatibility + /// @return lotLow {UoA/BU} The lower end of the lot price estimate + /// @return lotHigh {UoA/BU} The upper end of the lot price estimate + // returns sum(quantity(erc20) * lotPrice(erc20) for erc20 in basket.erc20s) + function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh) { + return _price(true); + } + + /// Returns the price of a BU, using the lot prices if `useLotPrice` is true + /// @param useLotPrice Whether to use lotPrice() or price() + /// @return low {UoA/BU} The lower end of the price estimate + /// @return high {UoA/BU} The upper end of the price estimate + function _price(bool useLotPrice) internal view returns (uint192 low, uint192 high) { uint256 low256; uint256 high256; @@ -324,7 +341,9 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler { uint192 qty = quantity(basket.erc20s[i]); if (qty == 0) continue; - (uint192 lowP, uint192 highP) = assetRegistry.toAsset(basket.erc20s[i]).price(); + (uint192 lowP, uint192 highP) = useLotPrice + ? assetRegistry.toAsset(basket.erc20s[i]).lotPrice() + : assetRegistry.toAsset(basket.erc20s[i]).price(); low256 += qty.safeMul(lowP, RoundingMode.FLOOR); diff --git a/contracts/plugins/assets/Asset.sol b/contracts/plugins/assets/Asset.sol index 3b858a9eab..302a6a6731 100644 --- a/contracts/plugins/assets/Asset.sol +++ b/contracts/plugins/assets/Asset.sol @@ -168,6 +168,15 @@ contract Asset is IAsset, VersionedAsset { assert(_low <= _high); } + /// Should not revert + /// lotLow should be nonzero when the asset might be worth selling + /// @dev Deprecated. Phased out in 3.1.0, but left on interface for backwards compatibility + /// @return lotLow {UoA/tok} The lower end of the lot price estimate + /// @return lotHigh {UoA/tok} The upper end of the lot price estimate + function lotPrice() external view virtual returns (uint192 lotLow, uint192 lotHigh) { + return price(); + } + /// @return {tok} The balance of the ERC20 in whole tokens function bal(address account) external view virtual returns (uint192) { return shiftl_toFix(erc20.balanceOf(account), -int8(erc20Decimals)); diff --git a/contracts/plugins/assets/RTokenAsset.sol b/contracts/plugins/assets/RTokenAsset.sol index fdacf7ddb5..e9487fe671 100644 --- a/contracts/plugins/assets/RTokenAsset.sol +++ b/contracts/plugins/assets/RTokenAsset.sol @@ -105,6 +105,15 @@ contract RTokenAsset is IAsset, VersionedAsset, IRTokenOracle { } } + /// Should not revert + /// lotLow should be nonzero when the asset might be worth selling + /// @dev Deprecated. Phased out in 3.1.0, but left on interface for backwards compatibility + /// @return lotLow {UoA/tok} The lower end of the lot price estimate + /// @return lotHigh {UoA/tok} The upper end of the lot price estimate + function lotPrice() external view virtual returns (uint192 lotLow, uint192 lotHigh) { + return price(); + } + /// @return {tok} The balance of the ERC20 in whole tokens function bal(address account) external view returns (uint192) { // The RToken has 18 decimals, so there's no reason to waste gas here doing a shiftl_toFix diff --git a/docs/collateral.md b/docs/collateral.md index 9655107da4..e6ae0e039c 100644 --- a/docs/collateral.md +++ b/docs/collateral.md @@ -47,6 +47,13 @@ interface IAsset is IRewardable { /// @return high {UoA/tok} The upper end of the price estimate function price() external view returns (uint192 low, uint192 high); + /// Should not revert + /// lotLow should be nonzero when the asset might be worth selling + /// @dev Deprecated. Phased out in 3.1.0, but left on interface for backwards compatibility + /// @return lotLow {UoA/tok} The lower end of the lot price estimate + /// @return lotHigh {UoA/tok} The upper end of the lot price estimate + function lotPrice() external view returns (uint192 lotLow, uint192 lotHigh); + /// @return {tok} The balance of the ERC20 in whole tokens function bal(address account) external view returns (uint192); @@ -367,6 +374,12 @@ Should return `(0, FIX_MAX)` if pricing data is _completely_ unavailable or stal Should be gas-efficient. +### lotPrice() `{UoA/tok}` + +Deprecated. Phased out in 3.1.0, but left on interface for backwards compatibility. + +Recommend implement `lotPrice()` by calling `price()`. If you are inheriting from any of our existing collateral plugins, this is already done for you. See [Asset.sol](../contracts/plugins/Asset.sol) for the implementation. + ### refPerTok() `{ref/tok}` Should never revert. diff --git a/test/Main.test.ts b/test/Main.test.ts index de7d519b73..9d00c3b0a6 100644 --- a/test/Main.test.ts +++ b/test/Main.test.ts @@ -3150,6 +3150,15 @@ describe(`MainP${IMPLEMENTATION} contract`, () => { await expectPrice(basketHandler.address, fp('0.75'), ORACLE_ERROR, true) }) + it('lotPrice (deprecated) is equal to price()', async () => { + const lotPrice = await basketHandler.lotPrice() + const price = await basketHandler.price() + expect(price.length).to.equal(2) + expect(lotPrice.length).to.equal(price.length) + expect(lotPrice[0]).to.equal(price[0]) + expect(lotPrice[1]).to.equal(price[1]) + }) + it('Should not put backup tokens with different targetName in the basket', async () => { // Swap out collateral for bad target name const CollFactory = await ethers.getContractFactory('FiatCollateral') diff --git a/test/plugins/Asset.test.ts b/test/plugins/Asset.test.ts index ad8c967def..5d801071a7 100644 --- a/test/plugins/Asset.test.ts +++ b/test/plugins/Asset.test.ts @@ -805,6 +805,17 @@ describe('Assets contracts #fast', () => { expect(lowPrice5).to.be.equal(bn(0)) expect(highPrice5).to.be.equal(MAX_UINT192) }) + + it('lotPrice (deprecated) is equal to price()', async () => { + for (const asset of [rsrAsset, compAsset, aaveAsset, rTokenAsset]) { + const lotPrice = await asset.lotPrice() + const price = await asset.price() + expect(price.length).to.equal(2) + expect(lotPrice.length).to.equal(price.length) + expect(lotPrice[0]).to.equal(price[0]) + expect(lotPrice[1]).to.equal(price[1]) + } + }) }) describe('Constructor validation', () => { diff --git a/test/plugins/Collateral.test.ts b/test/plugins/Collateral.test.ts index e52458f39e..21ca93859c 100644 --- a/test/plugins/Collateral.test.ts +++ b/test/plugins/Collateral.test.ts @@ -818,6 +818,17 @@ describe('Collateral contracts', () => { expect(await unpricedAppFiatCollateral.savedHighPrice()).to.equal(highPrice) expect(await unpricedAppFiatCollateral.lastSave()).to.equal(currBlockTimestamp) }) + + it('lotPrice (deprecated) is equal to price()', async () => { + for (const coll of [tokenCollateral, usdcCollateral, aTokenCollateral, cTokenCollateral]) { + const lotPrice = await coll.lotPrice() + const price = await coll.price() + expect(price.length).to.equal(2) + expect(lotPrice.length).to.equal(price.length) + expect(lotPrice[0]).to.equal(price[0]) + expect(lotPrice[1]).to.equal(price[1]) + } + }) }) describe('Status', () => { diff --git a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts index 0ba9baf74e..7a4a52862c 100644 --- a/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/aave/ATokenFiatCollateral.test.ts @@ -747,6 +747,15 @@ describeFork(`ATokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi await zeropriceCtokenCollateral.refresh() expect(await zeropriceCtokenCollateral.status()).to.equal(CollateralStatus.IFFY) }) + + it('lotPrice (deprecated) is equal to price()', async () => { + const lotPrice = await aDaiCollateral.lotPrice() + const price = await aDaiCollateral.price() + expect(price.length).to.equal(2) + expect(lotPrice.length).to.equal(price.length) + expect(lotPrice[0]).to.equal(price[0]) + expect(lotPrice[1]).to.equal(price[1]) + }) }) // Note: Here the idea is to test all possible statuses and check all possible paths to default diff --git a/test/plugins/individual-collateral/collateralTests.ts b/test/plugins/individual-collateral/collateralTests.ts index da6b098630..dcb238c332 100644 --- a/test/plugins/individual-collateral/collateralTests.ts +++ b/test/plugins/individual-collateral/collateralTests.ts @@ -441,6 +441,15 @@ export default function fn( await advanceTime(priceTimeout / 2) await expectUnpriced(collateral.address) }) + + it('lotPrice (deprecated) is equal to price()', async () => { + const lotPrice = await collateral.lotPrice() + const price = await collateral.price() + expect(price.length).to.equal(2) + expect(lotPrice.length).to.equal(price.length) + expect(lotPrice[0]).to.equal(price[0]) + expect(lotPrice[1]).to.equal(price[1]) + }) }) describe('status', () => { diff --git a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts index aefae34d5a..921b3f1bec 100644 --- a/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts +++ b/test/plugins/individual-collateral/compoundv2/CTokenFiatCollateral.test.ts @@ -760,6 +760,15 @@ describeFork(`CTokenFiatCollateral - Mainnet Forking P${IMPLEMENTATION}`, functi await zeropriceCtokenCollateral.refresh() expect(await zeropriceCtokenCollateral.status()).to.equal(CollateralStatus.IFFY) }) + + it('lotPrice (deprecated) is equal to price()', async () => { + const lotPrice = await cDaiCollateral.lotPrice() + const price = await cDaiCollateral.price() + expect(price.length).to.equal(2) + expect(lotPrice.length).to.equal(price.length) + expect(lotPrice[0]).to.equal(price[0]) + expect(lotPrice[1]).to.equal(price[1]) + }) }) // Note: Here the idea is to test all possible statuses and check all possible paths to default diff --git a/test/plugins/individual-collateral/curve/collateralTests.ts b/test/plugins/individual-collateral/curve/collateralTests.ts index cd7d2d4e69..c016642708 100644 --- a/test/plugins/individual-collateral/curve/collateralTests.ts +++ b/test/plugins/individual-collateral/curve/collateralTests.ts @@ -505,6 +505,15 @@ export default function fn( await advanceTime(priceTimeout / 2) await expectUnpriced(ctx.collateral.address) }) + + it('lotPrice (deprecated) is equal to price()', async () => { + const lotPrice = await ctx.collateral.lotPrice() + const price = await ctx.collateral.price() + expect(price.length).to.equal(2) + expect(lotPrice.length).to.equal(price.length) + expect(lotPrice[0]).to.equal(price[0]) + expect(lotPrice[1]).to.equal(price[1]) + }) }) describe('status', () => {