Skip to content

Commit 5a1902c

Browse files
authored
Merge pull request #1031 from reserve-protocol/3.2.0
3.2.0
2 parents f04be8e + 07f6f59 commit 5a1902c

File tree

133 files changed

+3060
-1024
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

133 files changed

+3060
-1024
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ jobs:
8383
restore-keys: |
8484
hardhat-network-fork-${{ runner.os }}-
8585
hardhat-network-fork-
86-
- run: npx hardhat test ./test/plugins/individual-collateral/{cbeth,aave-v3,compoundv3,stargate}/*.test.ts
86+
- run: npx hardhat test ./test/plugins/individual-collateral/{cbeth,aave-v3,compoundv3,stargate,lido}/*.test.ts
8787
env:
8888
NODE_OPTIONS: '--max-old-space-size=8192'
8989
TS_NODE_SKIP_IGNORE: true

CHANGELOG.md

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,68 @@
11
# Changelog
22

3+
# 3.2.0
4+
5+
This release makes bidding in dutch auctions easier for MEV searchers and gives new RTokens being deployed the option to enable a variable target basket, or to be "reweightable". An RToken that is not reweightable cannot have its target basket changed in terms of quantities of target units.
6+
7+
## Upgrade Steps
8+
9+
Upgrade BasketHandler, BackingManager, and Distributor.
10+
11+
Call `broker.setDutchTradeImplementation(newGnosisTrade)` with the new `DutchTrade` contract address.
12+
13+
If this is the first upgrade to a >= 3.0.0 token, call `*.cacheComponents()` on all components.
14+
15+
For plugins, upgrade all plugins that contain an appreciating asset (not FiatCollateral. AppreciatingFiatCollateral etc) OR contain multiple oracle feeds.
16+
17+
## Core Protocol Contracts
18+
19+
New governance param added: `reweightable`
20+
21+
- `BackingManager`
22+
- Track basket nonce last collateralized at end of `settleTrade()`
23+
- `BasketHandler` [+1 slot]
24+
- Restrict `redeemCustom()` to nonces after `lastCollateralized`
25+
- New `LastCollateralizedChanged()` event -- track to determine earliest basket nonce to use for `redeemCustom()`
26+
- Add concept of a reweightable basket: a basket that can have its target amounts (once grouped by target unit) changed
27+
- Add `reweightable()` view
28+
- Add `forceSetPrimeBasket()` to allow setting a new prime basket without normalizing by USD value
29+
- Alter `setPrimeBasket()` to enforce basket normalization for reweightable RTokens
30+
- `BackingManager`
31+
- Minor gas optimization
32+
- `Deployer`
33+
- New boolean field `reweightable` added to `IDeployer.DeploymentParams`
34+
- `Distributor`
35+
- Minor gas optimization
36+
37+
## Plugins
38+
39+
### Assets
40+
41+
- frax-eth: Add new `sFrxETH` plugin that leverages a curve EMA
42+
- stargate: Continue transfers of wrapper tokens if stargate rewards break
43+
- All plugins with variable refPerTok(): do not revert refresh() when underlying protocol reverts
44+
- All plugins with multiple chainlink feeds will now timeout over the maximum of the feeds' timeouts
45+
- Add ORACLE_TIMEOUT_BUFFER to all usages of chainlink feeds
46+
47+
### Facades
48+
49+
- `FacadeRead`
50+
- Use avg prices instead of low prices in `backingOverview()` and `basketBreakdown()`
51+
52+
### Trading
53+
54+
- `DutchTrade`
55+
56+
- Add new `bidTradeCallback()` function to allow payment of tokens at the _end_ of the tx, removing need for flash loans. Example of how-to-use in `contracts/plugins/mocks/DutchTradeRouter.sol`
57+
58+
### Facades
59+
60+
- `FacadeRead`
61+
- Add `maxIssuableByAmounts()` function to provide an estimation independent of account balances
62+
363
# 3.1.0
464

5-
### Upgrade Steps -- Required
65+
## Upgrade Steps
666

767
Upgrade all core contracts and _all_ assets. Most ERC20s do not need to be upgraded. Use `Deployer.deployRTokenAsset()` to create a new `RTokenAsset` instance. This asset should be swapped too.
868

@@ -16,7 +76,7 @@ Then, call `Broker.cacheComponents()`.
1676

1777
Finally, call `Broker.setBatchTradeImplementation(newGnosisTrade)`.
1878

19-
### Core Protocol Contracts
79+
## Core Protocol Contracts
2080

2181
- `BackingManager` [+2 slots]
2282
- Replace use of `lotPrice()` with `price()` everywhere
@@ -48,7 +108,7 @@ Finally, call `Broker.setBatchTradeImplementation(newGnosisTrade)`.
48108
- Use correct era in `UnstakingStarted` event
49109
- Expose `draftEra` via `getDraftEra()` view
50110

51-
### Facades
111+
## Facades
52112

53113
- `FacadeMonitor`
54114
- Add `batchAuctionsDisabled()` view

audits/Reserve Audit Report 3_1_0 - Trust Security.pdf renamed to audits/Trust Security - Reserve Audit Report 3_1_0.pdf

File renamed without changes.
593 KB
Binary file not shown.

common/configuration.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ export interface IFeeds {
8888
stETHUSD?: string
8989
wstETHstETHexr?: string
9090
cbETHETHexr?: string
91+
ETHUSD?: string
92+
wstETHstETH?: string
9193
}
9294

9395
export interface IPools {
@@ -124,6 +126,7 @@ interface INetworkConfig {
124126
AAVE_V3_INCENTIVES_CONTROLLER?: string
125127
AAVE_V3_POOL?: string
126128
STARGATE_STAKING_CONTRACT?: string
129+
CURVE_POOL_WETH_FRXETH?: string
127130
}
128131

129132
export const networkConfig: { [key: string]: INetworkConfig } = {
@@ -220,6 +223,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = {
220223
stETHUSD: '0xCfE54B5cD566aB89272946F602D76Ea879CAb4a8', // stETH/USD
221224
rETH: '0x536218f9E9Eb48863970252233c8F271f554C2d0', // rETH/ETH
222225
cbETH: '0xf017fcb346a1885194689ba23eff2fe6fa5c483b', // cbETH/ETH
226+
frxETH: '0xc58f3385fbc1c8ad2c0c9a061d7c13b141d7a5df', // frxETH/ETH
223227
},
224228
AAVE_LENDING_POOL: '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9',
225229
AAVE_INCENTIVES: '0xd784927Ff2f95ba542BfC824c8a8a98F3495f6b5',
@@ -240,6 +244,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = {
240244
AAVE_V3_POOL: '0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2',
241245
AAVE_V3_INCENTIVES_CONTROLLER: '0x8164Cc65827dcFe994AB23944CBC90e0aa80bFcb',
242246
STARGATE_STAKING_CONTRACT: '0xB0D502E938ed5f4df2E681fE6E419ff29631d62b',
247+
CURVE_POOL_WETH_FRXETH: '0x9c3b46c0ceb5b9e304fcd6d88fc50f7dd24b31bc',
243248
},
244249
'1': {
245250
name: 'mainnet',
@@ -345,6 +350,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = {
345350
AAVE_V3_POOL: '0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2',
346351
AAVE_V3_INCENTIVES_CONTROLLER: '0x8164Cc65827dcFe994AB23944CBC90e0aa80bFcb',
347352
STARGATE_STAKING_CONTRACT: '0xB0D502E938ed5f4df2E681fE6E419ff29631d62b',
353+
CURVE_POOL_WETH_FRXETH: '0x9c3b46c0ceb5b9e304fcd6d88fc50f7dd24b31bc',
348354
},
349355
'3': {
350356
name: 'tenderly',
@@ -445,6 +451,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = {
445451
AAVE_V3_POOL: '0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2',
446452
AAVE_V3_INCENTIVES_CONTROLLER: '0x8164Cc65827dcFe994AB23944CBC90e0aa80bFcb',
447453
STARGATE_STAKING_CONTRACT: '0xB0D502E938ed5f4df2E681fE6E419ff29631d62b',
454+
CURVE_POOL_WETH_FRXETH: '0xa1F8A6807c402E4A15ef4EBa36528A3FED24E577',
448455
},
449456
'5': {
450457
name: 'goerli',
@@ -548,6 +555,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = {
548555
aWETHv3: '0xD4a0e0b9149BCee3C920d2E00b5dE09138fd8bb7',
549556
acbETHv3: '0xcf3D55c10DB69f28fD1A75Bd73f3D8A2d9c595ad',
550557
sUSDbC: '0x4c80e24119cfb836cdf0a6b53dc23f04f7e652ca',
558+
wstETH: '0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452',
551559
STG: '0xE3B53AF74a4BF62Ae5511055290838050bf764Df',
552560
},
553561
chainlinkFeeds: {
@@ -562,6 +570,9 @@ export const networkConfig: { [key: string]: INetworkConfig } = {
562570
wstETHstETHexr: '0xB88BAc61a4Ca37C43a3725912B1f472c9A5bc061', // 0.5%, 24hr
563571
cbETHETHexr: '0x868a501e68F3D1E89CfC0D22F6b22E8dabce5F04', // 0.5%, 24hr
564572
STG: '0x63Af8341b62E683B87bB540896bF283D96B4D385',
573+
stETHETH: '0xf586d0728a47229e747d824a939000Cf21dEF5A0', // 0.5%, 24h
574+
ETHUSD: '0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70', // 0.15%, 20min
575+
wstETHstETH: '0xB88BAc61a4Ca37C43a3725912B1f472c9A5bc061', // 0.5%, 24h
565576
},
566577
GNOSIS_EASY_AUCTION: '0xb1875Feaeea32Bbb02DE83D81772e07E37A40f02', // mock
567578
COMET_REWARDS: '0x123964802e6ABabBE1Bc9547D72Ef1B69B00A6b1',
@@ -596,6 +607,7 @@ export interface IConfig {
596607
unstakingDelay: BigNumber
597608
withdrawalLeak: BigNumber
598609
warmupPeriod: BigNumber
610+
reweightable: boolean
599611
tradingDelay: BigNumber
600612
batchAuctionLength: BigNumber
601613
dutchAuctionLength: BigNumber

common/constants.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ export enum TradeKind {
5656
BATCH_AUCTION,
5757
}
5858

59+
export enum BidType {
60+
NONE,
61+
CALLBACK,
62+
TRANSFER,
63+
}
64+
5965
export const FURNACE_DEST = '0x0000000000000000000000000000000000000001'
6066
export const STRSR_DEST = '0x0000000000000000000000000000000000000002'
6167

contracts/facade/FacadeRead.sol

Lines changed: 56 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -28,26 +28,46 @@ contract FacadeRead is IFacadeRead {
2828
/// @return {qRTok} How many RToken `account` can issue given current holdings
2929
/// @custom:static-call
3030
function maxIssuable(IRToken rToken, address account) external returns (uint256) {
31+
(address[] memory erc20s, ) = rToken.main().basketHandler().quote(FIX_ONE, FLOOR);
32+
uint256[] memory balances = new uint256[](erc20s.length);
33+
for (uint256 i = 0; i < erc20s.length; ++i) {
34+
balances[i] = IERC20(erc20s[i]).balanceOf(account);
35+
}
36+
return maxIssuableByAmounts(rToken, balances);
37+
}
38+
39+
/// @param amounts {qTok} Amounts per basket ERC20
40+
/// Assumes same order as current basket ERC20s given by bh.quote()
41+
/// @return {qRTok} How many RToken `account` can issue given current holdings
42+
/// @custom:static-call
43+
function maxIssuableByAmounts(IRToken rToken, uint256[] memory amounts)
44+
public
45+
returns (uint256)
46+
{
3147
IMain main = rToken.main();
3248

3349
require(!main.frozen(), "frozen");
3450

3551
// Poke Main
3652
main.assetRegistry().refresh();
3753

38-
// {BU}
39-
BasketRange memory basketsHeld = main.basketHandler().basketsHeldBy(account);
40-
uint192 needed = rToken.basketsNeeded();
41-
42-
int8 decimals = int8(rToken.decimals());
43-
44-
// return {qRTok} = {BU} * {(1 RToken) qRTok/BU)}
45-
if (needed.eq(FIX_ZERO)) return basketsHeld.bottom.shiftl_toUint(decimals);
54+
// Get basket ERC20s
55+
IBasketHandler bh = main.basketHandler();
56+
(address[] memory erc20s, uint256[] memory quantities) = bh.quote(FIX_ONE, CEIL);
4657

47-
uint192 totalSupply = shiftl_toFix(rToken.totalSupply(), -decimals); // {rTok}
58+
// Compute how many baskets we can mint with the collateral amounts
59+
uint192 baskets = type(uint192).max;
60+
for (uint256 i = 0; i < erc20s.length; ++i) {
61+
// {BU} = {tok} / {tok/BU}
62+
uint192 inBUs = divuu(amounts[i], quantities[i]); // FLOOR
63+
baskets = fixMin(baskets, inBUs);
64+
}
4865

49-
// {qRTok} = {BU} * {rTok} / {BU} * {qRTok/rTok}
50-
return basketsHeld.bottom.mulDiv(totalSupply, needed).shiftl_toUint(decimals);
66+
// Convert baskets to RToken
67+
// {qRTok} = {qRTok/BU} * {qRTok} / {BU}
68+
uint256 totalSupply = rToken.totalSupply();
69+
if (totalSupply == 0) return baskets;
70+
return baskets.muluDivu(rToken.basketsNeeded(), rToken.totalSupply(), FLOOR);
5171
}
5272

5373
/// Do no use inifite approvals. Instead, use BasketHandler.quote() to determine the amount
@@ -210,13 +230,17 @@ contract FacadeRead is IFacadeRead {
210230
targets = new bytes32[](erc20s.length);
211231
for (uint256 i = 0; i < erc20s.length; ++i) {
212232
ICollateral coll = assetRegistry.toColl(IERC20(erc20s[i]));
233+
targets[i] = coll.targetName();
234+
213235
int8 decimals = int8(IERC20Metadata(erc20s[i]).decimals());
214-
(uint192 lowPrice, ) = coll.price();
236+
(uint192 low, uint192 high) = coll.price();
237+
if (low == 0 || high == FIX_MAX) continue;
238+
239+
uint192 avg = (low + high) / 2; // {UoA/tok}
215240

216241
// {UoA} = {qTok} * {tok/qTok} * {UoA/tok}
217-
uoaAmts[i] = shiftl_toFix(deposits[i], -decimals).mul(lowPrice);
242+
uoaAmts[i] = shiftl_toFix(deposits[i], -decimals).mul(avg);
218243
uoaSum += uoaAmts[i];
219-
targets[i] = coll.targetName();
220244
}
221245

222246
uoaShares = new uint192[](erc20s.length);
@@ -268,8 +292,7 @@ contract FacadeRead is IFacadeRead {
268292

269293
/// @param draftEra {draftEra} The draft era to query unstakings for
270294
/// @param account The account for the query
271-
/// @dev Use stRSR.draftRate() to convert {qDrafts} to {qRSR}
272-
/// @return unstakings {qDrafts} All the pending StRSR unstakings for an account, in drafts
295+
/// @return unstakings {qRSR} All the pending StRSR unstakings for an account, in RSR
273296
function pendingUnstakings(
274297
RTokenP1 rToken,
275298
uint256 draftEra,
@@ -278,6 +301,7 @@ contract FacadeRead is IFacadeRead {
278301
StRSRP1 stRSR = StRSRP1(address(rToken.main().stRSR()));
279302
uint256 left = stRSR.firstRemainingDraft(draftEra, account);
280303
uint256 right = stRSR.draftQueueLen(draftEra, account);
304+
uint192 draftRate = stRSR.draftRate();
281305

282306
unstakings = new Pending[](right - left);
283307
for (uint256 i = 0; i < right - left; i++) {
@@ -289,7 +313,8 @@ contract FacadeRead is IFacadeRead {
289313
diff = drafts - prevDrafts;
290314
}
291315

292-
unstakings[i] = Pending(i + left, availableAt, diff);
316+
// {qRSR} = {qDrafts} / {qDrafts/qRSR}
317+
unstakings[i] = Pending(i + left, availableAt, diff.div(draftRate));
293318
}
294319
}
295320

@@ -358,17 +383,19 @@ contract FacadeRead is IFacadeRead {
358383
for (uint256 i = 0; i < basketERC20s.length; i++) {
359384
IAsset asset = reg.toAsset(IERC20(basketERC20s[i]));
360385

361-
// {UoA/tok}
362-
(uint192 low, ) = asset.price();
363-
364386
// {tok}
365387
uint192 needed = shiftl_toFix(quantities[i], -int8(asset.erc20Decimals()));
366388

389+
// {UoA/tok}
390+
(uint192 low, uint192 high) = asset.price();
391+
if (low == 0 || high == FIX_MAX) continue;
392+
uint192 avg = (low + high) / 2;
393+
367394
// {UoA} = {UoA} + {tok}
368-
uoaNeeded += needed.mul(low);
395+
uoaNeeded += needed.mul(avg);
369396

370397
// {UoA} = {UoA} + {tok} * {UoA/tok}
371-
uoaHeldInBaskets += fixMin(needed, asset.bal(address(bm))).mul(low);
398+
uoaHeldInBaskets += fixMin(needed, asset.bal(address(bm))).mul(avg);
372399
}
373400

374401
backing = uoaHeldInBaskets.div(uoaNeeded);
@@ -382,13 +409,14 @@ contract FacadeRead is IFacadeRead {
382409
rsrAsset.bal(address(rToken.main().stRSR()))
383410
);
384411

385-
(uint192 lowPrice, ) = rsrAsset.price();
412+
(uint192 lowPrice, uint192 highPrice) = rsrAsset.price();
413+
if (lowPrice > 0 && highPrice < FIX_MAX) {
414+
// {UoA} = {tok} * {UoA/tok}
415+
uint192 rsrUoA = rsrBal.mul((lowPrice + highPrice) / 2);
386416

387-
// {UoA} = {tok} * {UoA/tok}
388-
uint192 rsrUoA = rsrBal.mul(lowPrice);
389-
390-
// {1} = {UoA} / {UoA}
391-
overCollateralization = rsrUoA.div(uoaNeeded);
417+
// {1} = {UoA} / {UoA}
418+
overCollateralization = rsrUoA.div(uoaNeeded);
419+
}
392420
}
393421

394422
/// @return low {UoA/tok} The low price of the RToken as given by the relevant RTokenAsset

contracts/facade/FacadeWrite.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ contract FacadeWrite is IFacadeWrite {
7777
}
7878

7979
// Set basket
80-
basketHandler.setPrimeBasket(basketERC20s, setup.weights);
80+
basketHandler.forceSetPrimeBasket(basketERC20s, setup.weights);
8181
basketHandler.refreshBasket();
8282
}
8383

contracts/interfaces/IAsset.sol

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ interface TestIAsset is IAsset {
6969
/// @return {s} Seconds that an oracle value is considered valid
7070
function oracleTimeout() external view returns (uint48);
7171

72+
/// @return {s} The maximum of all oracle timeouts on the plugin
73+
function maxOracleTimeout() external view returns (uint48);
74+
7275
/// @return {s} Seconds that the price() should decay over, after stale price
7376
function priceTimeout() external view returns (uint48);
7477

@@ -136,4 +139,7 @@ interface TestICollateral is TestIAsset, ICollateral {
136139

137140
/// @return The amount of time a collateral must be in IFFY status until being DISABLED
138141
function delayUntilDefault() external view returns (uint48);
142+
143+
/// @return The underlying refPerTok, likely not included in all collaterals however.
144+
function underlyingRefPerTok() external view returns (uint192);
139145
}

0 commit comments

Comments
 (0)