Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Single price #900

Merged
merged 45 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
6e2e4f0
draft single price(); compiling
tbrent Aug 15, 2023
dc0faad
decay high price upwards to 3x
tbrent Aug 16, 2023
e054ccc
small comment nits for clarity
tbrent Aug 16, 2023
cf94383
enforce dutch auctions under live prices only
tbrent Aug 16, 2023
5dbe101
put 3.1.0 upgrade instructions in CHANGELOG
tbrent Aug 16, 2023
d51e25f
update confirmation scripts + docs
tbrent Aug 16, 2023
6341fff
convert tests to at least run + purge more lotPrice mentions
tbrent Aug 16, 2023
67ce35e
Asset.test.ts
tbrent Aug 17, 2023
7ba785d
Collateral.test.ts
tbrent Aug 17, 2023
d7b04fe
revert basketRange() when BU unpriced
tbrent Aug 17, 2023
7aa5daa
unit tests
tbrent Aug 17, 2023
2a14c68
integration tests
tbrent Aug 17, 2023
3119276
fix generic suite
tbrent Aug 18, 2023
fb983f1
Merge branch '3.0.0-rc6' into single-price
tbrent Aug 18, 2023
dc4fcd7
fix Broker gas test
tbrent Aug 18, 2023
38edc34
check-in bug
tbrent Aug 18, 2023
f60eff4
Merge branch 'single-price' of github.com:reserve-protocol/protocol i…
tbrent Aug 18, 2023
ea9cdfb
Collateral.test.ts gas tests
tbrent Aug 18, 2023
550dd24
print more
tbrent Aug 18, 2023
fc32a85
fix plugin tests
tbrent Aug 19, 2023
ee5de23
draft single price(); compiling
tbrent Aug 15, 2023
2480ff2
decay high price upwards to 3x
tbrent Aug 16, 2023
79339fc
small comment nits for clarity
tbrent Aug 16, 2023
bd51df5
enforce dutch auctions under live prices only
tbrent Aug 16, 2023
c6447ef
put 3.1.0 upgrade instructions in CHANGELOG
tbrent Aug 16, 2023
f068784
update confirmation scripts + docs
tbrent Aug 16, 2023
b69b0fd
convert tests to at least run + purge more lotPrice mentions
tbrent Aug 16, 2023
e47eff1
Asset.test.ts
tbrent Aug 17, 2023
f6732dc
Collateral.test.ts
tbrent Aug 17, 2023
160b05a
revert basketRange() when BU unpriced
tbrent Aug 17, 2023
f35a534
unit tests
tbrent Aug 17, 2023
83c87a9
integration tests
tbrent Aug 17, 2023
0c96777
fix generic suite
tbrent Aug 18, 2023
7c06e1e
fix Broker gas test
tbrent Aug 18, 2023
57d6311
check-in bug
tbrent Aug 18, 2023
a2afae9
Collateral.test.ts gas tests
tbrent Aug 18, 2023
31dad0f
print more
tbrent Aug 18, 2023
7c88501
fix plugin tests
tbrent Aug 19, 2023
48cdc1e
Merge branch 'single-price' of github.com:reserve-protocol/protocol i…
tbrent Aug 21, 2023
a096822
fix yarn gas target
tbrent Aug 21, 2023
8e3381e
new gas snapshots
tbrent Aug 21, 2023
bce9d8e
remove .only
tbrent Aug 21, 2023
bea56a3
updated recollat gas snapshot
tbrent Aug 21, 2023
70f8de8
updated gas snapshots
tbrent Aug 21, 2023
da8388c
updated gas snapshots
tbrent Aug 21, 2023
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
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
# Changelog

# 3.1.0 - Unreleased

### Upgrade Steps -- Required

Upgrade `BackingManager`, `Broker`, and _all_ assets

Then call `Broker.cacheComponents()`.

### Core Protocol Contracts

- `BackingManager`
- Replace use of `lotPrice()` with `price()`
- `Broker` [+1 slot]
- Disallow starting dutch trades with non-RTokenAsset assets when `lastSave() != block.timestamp`

## Plugins

### Assets

- Remove `lotPrice()`
- Alter `price().high` to decay upwards to 3x over the price timeout

# 3.0.0 - Unreleased

### Upgrade Steps
Expand Down
6 changes: 3 additions & 3 deletions contracts/facade/FacadeAct.sol
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,11 @@ contract FacadeAct is IFacadeAct, Multicall {
}

surpluses[i] = erc20s[i].balanceOf(address(revenueTrader));
(uint192 lotLow, ) = reg.assets[i].lotPrice(); // {UoA/tok}
if (lotLow == 0) continue;
(uint192 low, ) = reg.assets[i].price(); // {UoA/tok}
if (low == 0) continue;

// {qTok} = {UoA} / {UoA/tok}
minTradeAmounts[i] = minTradeVolume.safeDiv(lotLow, FLOOR).shiftl_toUint(
minTradeAmounts[i] = minTradeVolume.safeDiv(low, FLOOR).shiftl_toUint(
int8(reg.assets[i].erc20Decimals())
);

Expand Down
15 changes: 8 additions & 7 deletions contracts/interfaces/IAsset.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,11 @@ interface IAsset is IRewardable {
function refresh() external;

/// Should not revert
/// low should be nonzero if the asset could be worth selling
/// @return low {UoA/tok} The lower end of the price estimate
/// @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
/// @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);

Expand Down Expand Up @@ -67,8 +62,14 @@ interface TestIAsset is IAsset {
/// @return {s} Seconds that an oracle value is considered valid
function oracleTimeout() external view returns (uint48);

/// @return {s} Seconds that the lotPrice should decay over, after stale price
/// @return {s} Seconds that the price().low should decay over, after stale price
function priceTimeout() external view returns (uint48);

/// @return {UoA/tok} The last saved low price
function savedLowPrice() external view returns (uint192);

/// @return {UoA/tok} The last saved high price
function savedHighPrice() external view returns (uint192);
}

/// CollateralStatus must obey a linear ordering. That is:
Expand Down
7 changes: 1 addition & 6 deletions contracts/interfaces/IBasketHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -133,16 +133,11 @@ interface IBasketHandler is IComponent {
function basketsHeldBy(address account) external view returns (BasketRange memory);

/// 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
function price() external view returns (uint192 low, uint192 high);

/// Should not revert
/// lotLow should be nonzero if a BU could be worth selling
/// @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);

Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/IBroker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ enum TradeKind {
BATCH_AUCTION
}

/// Cache of all (lot) prices for a pair to prevent re-lookup
/// Cache of all prices for a pair to prevent re-lookup
struct TradePrices {
uint192 sellLow; // {UoA/sellTok} can be 0
uint192 sellHigh; // {UoA/sellTok} should not be 0
Expand Down
21 changes: 2 additions & 19 deletions contracts/p0/BasketHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -368,26 +368,11 @@ 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
/// @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;
Expand All @@ -397,9 +382,7 @@ contract BasketHandlerP0 is ComponentP0, IBasketHandler {
uint192 qty = quantity(basket.erc20s[i]);
if (qty == 0) continue;

(uint192 lowP, uint192 highP) = useLotPrice
? reg.toAsset(basket.erc20s[i]).lotPrice()
: reg.toAsset(basket.erc20s[i]).price();
(uint192 lowP, uint192 highP) = reg.toAsset(basket.erc20s[i]).price();

low256 += qty.safeMul(lowP, RoundingMode.FLOOR);

Expand Down
11 changes: 11 additions & 0 deletions contracts/p0/Broker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,11 @@ contract BrokerP0 is ComponentP0, IBroker {
"dutch auctions disabled for token pair"
);
require(dutchAuctionLength > 0, "dutch auctions not enabled");
require(
priceIsCurrent(req.sell) && priceIsCurrent(req.buy),
"dutch auctions require live prices"
);

DutchTrade trade = DutchTrade(Clones.clone(address(dutchTradeImplementation)));
trades[address(trade)] = true;

Expand All @@ -248,4 +253,10 @@ contract BrokerP0 is ComponentP0, IBroker {
emit DutchTradeDisabledSet(erc20, dutchTradeDisabled[erc20], disabled);
dutchTradeDisabled[erc20] = disabled;
}

/// @return true if the price is current, or it's the RTokenAsset
function priceIsCurrent(IAsset asset) private view returns (bool) {
return
asset.lastSave() == block.timestamp || address(asset.erc20()) == address(main.rToken());
}
}
4 changes: 2 additions & 2 deletions contracts/p0/RevenueTrader.sol
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader {
main.assetRegistry().refresh();

IAsset assetToBuy = main.assetRegistry().toAsset(tokenToBuy);
(uint192 buyLow, uint192 buyHigh) = assetToBuy.lotPrice(); // {UoA/tok}
(uint192 buyLow, uint192 buyHigh) = assetToBuy.price(); // {UoA/tok}
require(buyHigh > 0 && buyHigh < FIX_MAX, "buy asset price unknown");

// For each ERC20: start auction of given kind
Expand All @@ -99,7 +99,7 @@ contract RevenueTraderP0 is TradingP0, IRevenueTrader {
require(address(trades[erc20]) == address(0), "trade open");
require(erc20.balanceOf(address(this)) > 0, "0 balance");

(uint192 sellLow, uint192 sellHigh) = assetToSell.lotPrice(); // {UoA/tok}
(uint192 sellLow, uint192 sellHigh) = assetToSell.price(); // {UoA/tok}

TradingLibP0.TradeInfo memory trade = TradingLibP0.TradeInfo({
sell: assetToSell,
Expand Down
53 changes: 23 additions & 30 deletions contracts/p0/mixins/TradingLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ library TradingLibP0 {
view
returns (BasketRange memory range)
{
(uint192 buPriceLow, uint192 buPriceHigh) = ctx.bh.lotPrice(); // {UoA/BU}
(uint192 buPriceLow, uint192 buPriceHigh) = ctx.bh.price(); // {UoA/BU}

// Cap ctx.basketsHeld.top
if (ctx.basketsHeld.top > ctx.rToken.basketsNeeded()) {
Expand Down Expand Up @@ -303,21 +303,14 @@ library TradingLibP0 {
bal = bal.plus(asset.bal(address(ctx.stRSR)));
}

{
// Skip over dust-balance assets not in the basket
(uint192 lotLow, ) = asset.lotPrice(); // {UoA/tok}

// Intentionally include value of IFFY/DISABLED collateral
if (
ctx.bh.quantity(erc20s[i]) == 0 &&
!isEnoughToSell(asset, bal, lotLow, ctx.minTradeVolume)
) continue;
}

(uint192 low, uint192 high) = asset.price(); // {UoA/tok}
// price() is better than lotPrice() here: it's important to not underestimate how
// much value could be in a token that is unpriced by using a decaying high lotPrice.
// price() will return [0, FIX_MAX] in this case, which is preferable.

// Skip over dust-balance assets not in the basket
// Intentionally include value of IFFY/DISABLED collateral
if (
ctx.bh.quantity(erc20s[i]) == 0 &&
!isEnoughToSell(asset, bal, low, ctx.minTradeVolume)
) continue;

// throughout these sections +/- is same as Fix.plus/Fix.minus and </> is Fix.gt/.lt

Expand Down Expand Up @@ -354,7 +347,7 @@ library TradingLibP0 {

// (2) Lose minTradeVolume to dust (why: auctions can return tokens)
// Q: Why is this precisely where we should take out minTradeVolume?
// A: Our use of isEnoughToSell always uses the low price (lotLow, technically),
// A: Our use of isEnoughToSell always uses the low price (low, technically),
// so min trade volumes are always assesed based on low prices. At this point
// in the calculation we have already calculated the UoA amount corresponding to
// the excess token balance based on its low price, so we are already set up
Expand Down Expand Up @@ -453,19 +446,19 @@ library TradingLibP0 {
// {tok} = {BU} * {tok/BU}
uint192 needed = range.top.mul(ctx.bh.quantity(erc20s[i]), CEIL); // {tok}
if (bal.gt(needed)) {
(uint192 lotLow, uint192 lotHigh) = asset.lotPrice(); // {UoA/sellTok}
if (lotHigh == 0) continue; // Skip worthless assets
(uint192 low, uint192 high) = asset.price(); // {UoA/sellTok}
if (high == 0) continue; // Skip worthless assets

// by calculating this early we can duck the stack limit but be less gas-efficient
bool enoughToSell = isEnoughToSell(
asset,
bal.minus(needed),
lotLow,
low,
ctx.minTradeVolume
);

// {UoA} = {sellTok} * {UoA/sellTok}
uint192 delta = bal.minus(needed).mul(lotLow, FLOOR);
uint192 delta = bal.minus(needed).mul(low, FLOOR);

// status = asset.status() if asset.isCollateral() else SOUND
CollateralStatus status; // starts SOUND
Expand All @@ -476,8 +469,8 @@ library TradingLibP0 {
if (isBetterSurplus(maxes, status, delta) && enoughToSell) {
trade.sell = asset;
trade.sellAmount = bal.minus(needed);
trade.prices.sellLow = lotLow;
trade.prices.sellHigh = lotHigh;
trade.prices.sellLow = low;
trade.prices.sellHigh = high;

maxes.surplusStatus = status;
maxes.surplus = delta;
Expand All @@ -487,17 +480,17 @@ library TradingLibP0 {
needed = range.bottom.mul(ctx.bh.quantity(erc20s[i]), CEIL); // {buyTok};
if (bal.lt(needed)) {
uint192 amtShort = needed.minus(bal); // {buyTok}
(uint192 lotLow, uint192 lotHigh) = asset.lotPrice(); // {UoA/buyTok}
(uint192 low, uint192 high) = asset.price(); // {UoA/buyTok}

// {UoA} = {buyTok} * {UoA/buyTok}
uint192 delta = amtShort.mul(lotHigh, CEIL);
uint192 delta = amtShort.mul(high, CEIL);

// The best asset to buy is whichever asset has the largest deficit
if (delta.gt(maxes.deficit)) {
trade.buy = ICollateral(address(asset));
trade.buyAmount = amtShort;
trade.prices.buyLow = lotLow;
trade.prices.buyHigh = lotHigh;
trade.prices.buyLow = low;
trade.prices.buyHigh = high;

maxes.deficit = delta;
}
Expand All @@ -512,13 +505,13 @@ library TradingLibP0 {
uint192 rsrAvailable = rsrAsset.bal(address(ctx.bm)).plus(
rsrAsset.bal(address(ctx.stRSR))
);
(uint192 lotLow, uint192 lotHigh) = rsrAsset.lotPrice(); // {UoA/RSR}
(uint192 low, uint192 high) = rsrAsset.price(); // {UoA/RSR}

if (lotHigh > 0 && isEnoughToSell(rsrAsset, rsrAvailable, lotLow, ctx.minTradeVolume)) {
if (high > 0 && isEnoughToSell(rsrAsset, rsrAvailable, low, ctx.minTradeVolume)) {
trade.sell = rsrAsset;
trade.sellAmount = rsrAvailable;
trade.prices.sellLow = lotLow;
trade.prices.sellHigh = lotHigh;
trade.prices.sellLow = low;
trade.prices.sellHigh = high;
}
}
}
Expand Down
22 changes: 2 additions & 20 deletions contracts/p1/BasketHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -309,27 +309,11 @@ 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
/// @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;

Expand All @@ -338,9 +322,7 @@ contract BasketHandlerP1 is ComponentP1, IBasketHandler {
uint192 qty = quantity(basket.erc20s[i]);
if (qty == 0) continue;

(uint192 lowP, uint192 highP) = useLotPrice
? assetRegistry.toAsset(basket.erc20s[i]).lotPrice()
: assetRegistry.toAsset(basket.erc20s[i]).price();
(uint192 lowP, uint192 highP) = assetRegistry.toAsset(basket.erc20s[i]).price();

low256 += qty.safeMul(lowP, RoundingMode.FLOOR);

Expand Down
Loading