Skip to content

Commit

Permalink
Merge pull request #835 from reserve-protocol/3.0.0-docs
Browse files Browse the repository at this point in the history
3.0.0 docs
  • Loading branch information
tbrent authored May 23, 2023
2 parents 57a77ff + be89c47 commit 0c11e56
Show file tree
Hide file tree
Showing 11 changed files with 106 additions and 68 deletions.
103 changes: 74 additions & 29 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,106 +1,155 @@
# Changelog

## 3.0.0
## 3.0.0 - Unreleased

Warning: RTokens upgrading to this major release should proceed carefully. In addition to updating all component addresses:

- `cacheComponents()` MUST be called to prevent certain functions from reverting, on both the BackingManager and both RevenueTraders
- `setWarmupPeriod()` can be called on the BasketHandler to turn on the warmup period, optionally
- `setWithdrawalLeak()` can be called on StRSR to start saving gas on withdrawals, optionally
- `setDutchAuctionLength()` can be called (with a value such as 1800) or left at duration 0s to keep dutch auctions disabled

Collateral / Asset plugins from 2.1.0 do not need to be upgraded

#### Core Protocol Contracts

- `AssetRegistry` [+1 slot]
Summary: Other component contracts need to know when refresh() was last called
- Add last refresh timestamp tracking and expose via `lastRefresh()` getter
- Add `size()` getter for number of registered assets
- `BackingManager` [+2 slots]
Summary: manageTokens was broken out into rebalancing and surplus-forwarding functions to allow users to more precisely call the protocol

- Remove `delegatecall` during reward claiming
- Modify `settleTrade(IERC20 sell)` to call `rebalance()` when caller is a trade it deployed.
- Remove `manageTokensSortedOrder(IERC20[] memory erc20s)`
- Replace `manageTokens(IERC20[] memory erc20s)` with:
- `rebalance() ` - the argument-agnostic trading part of the logic
- `rebalance(TradeKind)` + `RecollateralizationLibP1`
- Modify trading algorithm to not trade RToken, and instead dissolve it when it has a balance above ~1e6. "dissolve" = melt() with a basketsNeeded change, like redemption.
- Add significant caching of asset state to `RecollateralizationLibP1` in addition to removing RToken trading (due to dissolve)
- `forwardRevenue(IERC20[] memory erc20s)` - the revenue distributing part
- Modify backingBuffer logic to keep the backing buffer in collateral tokens only. Fix subtle and inconsequential bug that resulted in not maximizing RToken minting locally.
- Add significant caching to save gas
- `forwardRevenue(IERC20[] memory erc20s)`
- Modify backingBuffer logic to keep the backing buffer in collateral tokens only. Fix subtle and inconsequential bug that resulted in not maximizing RToken minting locally, though overall RToken production would not have been lower.
- Use `nonReentrant` over CEI pattern for gas improvement. related to discussion of [this](https://github.com/code-423n4/2023-01-reserve-findings/issues/347) cross-contract reentrancy risk
- move `nonReentrant` up outside `tryTrade` internal helper
- Remove `manageTokensSortedOrder(IERC20[] memory erc20s)`
- Modify `settleTrade(IERC20 sell)` to call `rebalance()` when caller is a trade it deployed.
- Remove all `delegatecall` during reward claiming
- Functions now revert on unproductive executions, instead of no-op
- Do not trade until a warmupPeriod (last time SOUND was newly attained) has passed
- Add `cacheComponents()` refresher to be called on upgrade
- Bugfix: consider `maxTradeVolume()` from both assets on a trade, not just 1

- `BasketHandler` [+5 slots]
Summary: Introduces a notion of basket warmup to defend against short-term oracle manipulation attacks. Prevent RTokens from changing in value due to governance

- Add new gov param: `warmupPeriod`. Has `setWarmupPeriod(..)`
- Add new gov param: `warmupPeriod` with setter `setWarmupPeriod(..)` and event `WarmupPeriodSet()`
- Add `isReady()` view
- Extract basket switching logic out into external library `BasketLibP1`
- Enforce `setPrimeBasket()` does not change the net value of a basket in terms of its target units
- Add `quoteCustomRedemption(uint48[] basketNonces, uint192[] memory portions, ..)` to quote a linear combination of current-or-previous baskets.
- Add `quoteCustomRedemption(uint48[] basketNonces, uint192[] memory portions, ..)` to quote a linear combination of current-or-previous baskets for redemption
- Add `getHistoricalBasket(uint48 basketNonce)` view

- `Broker` [+1 slot]
Summary: Add a new trading plugin that performs single-lot dutch auctions. Batch auctions via Gnosis EasyAuction are expected to be the backup auction (can be faster if more gas costly) going forward.

- Add `TradeKind` enum to track multiple trading types
- Add new dutch auction `DutchTrade`
- Add minimum auction length of 24s; applies to all auction types
- Add `setDutchAuctionLength(..)` governance setter
- Rename variable `auctionLength` -> `batchAuctionLength`
- Rename setter `setAuctionLength()` -> `setBatchAuctionLength()`
- Rename event `AuctionLengthSet()` -> `BatchAuctionLengthSet()`
- Add `dutchAuctionLength` and `setDutchAuctionLength()` setter and `DutchAuctionLengthSet()` event
- Add `dutchTradeImplementation` and `setDutchTradeImplementation()` setter and `DutchTradeImplementationSet()` event
- Modify `openTrade(TradeRequest memory reg)` -> `openTrade(TradeKind kind, TradeRequest memory req)`
- Allow when paused / frozen, since caller must be in-system

- `Deployer` [+0 slots]
Summary: Support new governance params

- Modify to handle new gov params: `warmupPeriod`, `dutchAuctionLength`, and `withdrawalLeak`
- Do not grant OWNER any of the roles other than ownership

- `Distributor` [+0 slots]
- Remove `notPausedOrFrozen` modifier from `distribute()`; caller only hurts themselves
Summary: Waste of gas to double-check this, since caller is another component
- Remove `notPausedOrFrozen` modifier from `distribute()`
- `Furnace` [+0 slots]
Summary: Should be able to melting while redeeming when frozen
- Modify `melt()` modifier: `notPausedOrFrozen` -> `notFrozen`
- `Main` [+0 slots]
Summary: Breakup pausing into two types of pausing: issuance and trading

- Break `paused` into `issuancePaused` and `tradingPaused`
- `pause()` -> `pauseTrading()` and `pauseIssuance()`
- `unpause()` -> `unpauseTrading()` and `unpauseIssuance()`
- `pausedOrFrozen()` -> `tradingPausedOrFrozen()` and `issuancePausedOrFrozen()`
- `PausedSet()` event -> `TradingPausedSet()` and `IssuancePausedSet()`

- `RevenueTrader` [+3 slots]
Summary: QoL improvements. Make compatible with new dutch auction trading method

- Remove `delegatecall` during reward claiming
- Add `cacheComponents()` refresher to be called on upgrade
- `manageToken(IERC20 sell)` -> `manageToken(IERC20 sell, TradeKind kind)`
- Allow `manageToken(..)` to open dust auctions
- Revert on 0 balance or collision auction, instead of no-op
- Refresh buy and sell asset
- Refresh buy and sell asset before trade
- `settleTrade(IERC20)` now distributes `tokenToBuy`, instead of requiring separate `manageToken(IERC20)` call

- `RToken` [+0 slots]
Summary: Provide multiple redemption methods for when fullyCollateralized vs not. Should support a higher RToken price during basket changes.

- Remove `exchangeRateIsValidAfter` modifier from all functions except `setBasketsNeeded()`
- Modify `issueTo()` to revert before `warmupPeriod`
- Remove `redeem(uint256 amount, uint48 basketNonce)` and `redeemTo(address recipient, uint256 amount, uint48 basketNonce)`
- Add `redeem(uint256 amount)` and `redeemTo(address recipient, uint256 amount)` - always on current basket nonce; reverts on partial redemption
- Add new `redeemCustom(.., uint256 amount, uint48[] memory basketNonces, uint192[] memory portions, ..)` function to allow redemption from a linear combination of current and previous baskets. All non-standard possibly lossy redemptions must go through this function
- Modify `redeem(uint256 amount, uint48 basketNonce)` -> `redeem(uint256 amount)`. Redemptions are on the current basket nonce and revert under partial redemption
- Modify `redeemTo(address recipient, uint256 amount, uint48 basketNonce)` -> `redeemTo(address recipient, uint256 amount)`. Redemptions are on the current basket nonce and revert under partial redemption
- Add new `redeemCustom(.., uint256 amount, uint48[] memory basketNonces, uint192[] memory portions, ..)` function to allow redemption from a linear combination of current and previous baskets. During rebalancing this method of redemption will provide a higher overall redemption value than prorata redemption on the current basket nonce would.
- `mint(address recipient, uint256 amtRToken)` -> `mint(uint256 amtRToken)`, since recipient is _always_ BackingManager. Expand scope to include adjustments to `basketsNeeded`
- Add `dissolve(uint256 amount)`: burns RToken and reduces `basketsNeeded`, similar to redemption. Only callable by BackingManager
- Modify `setBasketsNeeded(..)` to revert when supply is 0

- `StRSR` [+2 slots]
- Remove duplicate `stakeRate()` getter (it's 1 / exchangeRate())
- Add `withdrawalLeak` gov param, with `setWithdrawalLeak(..)` setter and `leakyRefresh()` helper
Summary: Add the ability to cancel unstakings and a withdrawal() gas-saver to allow small RSR amounts to be exempt from refreshes

- Remove duplicate `stakeRate()` getter (same as `1 / exchangeRate()`)
- Add `withdrawalLeak` gov param, with `setWithdrawalLeak(..)` setter and `WithdrawalLeakSet()` event
- Modify `withdraw()` to allow a small % of RSR too exit without paying to refresh all assets
- Modify `withdraw()` to check for `warmupPeriod`
- Add ability to re-stake during a withdrawal via `cancelUnstake(uint256 endId)`
- Add `UnstakingCancelled()` event

- `StRSRVotes` [+0 slots]
- Add `stakeAndDelegate(uint256 rsrAmount, address delegate)` function, to encourage people to receive voting weight upon staking

#### Facades

- `FacadeWrite`
Summary: More expressive and fine-grained control over the set of pausers and freezers

- Do not automatically grant Guardian PAUSER/SHORT_FREEZER/LONG_FREEZER
- Do not automatically grant Owner PAUSER/SHORT_FREEZER/LONG_FREEZER
- Add ability to initialize with multiple pausers, short freezers, and long freezers
- Modify `setupGovernance(.., address owner, address guardian, address pauser)` -> `setupGovernance(.., GovernanceRoles calldata govRoles)`
- Update `DeploymentParams` and `Implementations` struct to contain new gov params and dutch trade plugin

- `FacadeAct`
Summary: Remove unused getActCalldata and add way to run revenue auctions

- Remove `getActCalldata(..)`
- Modify `runRevenueAuctions(..)` to work with both 3.0.0 and 2.1.0 interfaces

- `FacadeRead`
Summary: Add new data summary views frontends may be interested in

- Remove `basketNonce` from `redeem(.., uint48 basketNonce)`
- Remove `traderBalances(..)`
- `balancesAcrossAllTraders(IBackingManager) returns (IERC20[] memory erc20s, uint256[] memory balances, uint256[] memory balancesNeededByBackingManager)`
- Add `nextRecollateralizationAuction(..) returns (bool canStart, IERC20 sell, IERC20 buy, uint256 sellAmount)`
- Add `revenueOverview(IRevenueTrader) returns ( IERC20[] memory erc20s, bool[] memory canStart, uint256[] memory surpluses, uint256[] memory minTradeAmounts)`
- Remove `FacadeMonitor`

- Remove `FacadeMonitor` - redundant with `nextRecollateralizationAuction()` and `revenueOverview()`

### Plugins

#### DutchTrade

Implements a new, simpler, trading method. Intended to be the new dominant trading method, with GnosisTrade (batch auctions) available as a faster-but-more-gas-expensive backup option.
A cheaper, simpler, trading method. Intended to be the new dominant trading method, with GnosisTrade (batch auctions) available as a faster-but-more-gas-expensive backup option.

DutchTrade implements a two-stage, single-lot, falling price dutch auction. In the first 40% of the auction, the price falls from 1000x to the best-case price in a geometric/exponential decay as a price manipulation defense mechanism. Bids are not expected to occur (but note: unlike the GnosisTrade batch auction, this mechanism is not resistant to _arbitrary_ price manipulation).

Expand All @@ -111,7 +160,8 @@ Duration: 30 min (default)
#### Assets and Collateral

- Add `version() return (string)` getter to pave way for separation of asset versioning and core protocol versioning
- Remove expectation of `delegatecall` during `claimRewards()` call, though assets can still do it and it won't break anything
- Update `claimRewards()` on all assets to 3.0.0-style, without `delegatecall`
- Add `lastSave()` to `RTokenAsset`

## 2.1.0

Expand Down Expand Up @@ -235,8 +285,6 @@ Candidate release for the "all clear" milestone. There wasn't any real usage of
- Add `FacadeRead.redeem(IRToken rToken, uint256 amount, uint48 basketNonce)` to return the expected redemption quantities on the basketNonce, or revert
- Integrate with OZ 4.7.3 Governance (changes to `quorum()`/t`proposalThreshold()`)

TODO

## 1.1.0

- Introduce semantic versioning to the Deployer and RToken
Expand Down Expand Up @@ -267,20 +315,17 @@ event RTokenCreated(

- Add `version()` getter on Deployer, Main, and all Components, via mix-in. To be updated with each subsequent release.

Deploy commit [d757d3a5a6097ae42c71fc03a7c787ec001d2efc](https://github.com/reserve-protocol/protocol/commit/d757d3a5a6097ae42c71fc03a7c787ec001d2efc)
[d757d3a5a6097ae42c71fc03a7c787ec001d2efc](https://github.com/reserve-protocol/protocol/commit/d757d3a5a6097ae42c71fc03a7c787ec001d2efc)

## 1.0.0

(This release is the one from the canonical lauch onstage in Bogota. We were missing semantic versioning at the time, but we call this the 1.0.0 release retroactively.)

Deploy commit [eda322894a5ed379bbda2b399c9d1cc65aa8c132](https://github.com/reserve-protocol/protocol/commit/eda322894a5ed379bbda2b399c9d1cc65aa8c132)
[eda322894a5ed379bbda2b399c9d1cc65aa8c132](https://github.com/reserve-protocol/protocol/commit/eda322894a5ed379bbda2b399c9d1cc65aa8c132)

# Links

<!-- - [[Unreleased]](https://github.com/reserve-protocol/protocol) -->
<!-- - https://github.com/reserve-protocol/protocol/compare/3.0.0-rc1...HEAD -->

- [[3.0.0]](https://github.com/reserve-protocol/protocol/releases/tag/3.0.0)
- [[Unreleased]](https://github.com/reserve-protocol/protocol/releases/tag/3.0.0-rc1)
- https://github.com/reserve-protocol/protocol/compare/2.1.0-rc4...3.0.0
- [[2.1.0]](https://github.com/reserve-protocol/protocol/releases/tag/2.1.0-rc4)
- https://github.com/reserve-protocol/protocol/compare/2.0.0-candidate-4...2.1.0-rc4
Expand Down
7 changes: 2 additions & 5 deletions contracts/interfaces/IBackingManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,9 @@ import "./ITrading.sol";
* @title IBackingManager
* @notice The BackingManager handles changes in the ERC20 balances that back an RToken.
* - It computes which trades to perform, if any, and initiates these trades with the Broker.
* - rebalance()
* - If already collateralized, excess assets are transferred to RevenueTraders.
*
* `manageTokens(erc20s)` and `manageTokensSortedOrder(erc20s)` are handles for getting at the
* same underlying functionality. The former allows an ERC20 list in any order, while the
* latter requires a sorted array, and executes in O(n) rather than O(n^2) time. In the
* vast majority of cases we expect the the O(n^2) function to be acceptable.
* - forwardRevenue(IERC20[] calldata erc20s)
*/
interface IBackingManager is IComponent, ITrading {
/// Emitted when the trading delay is changed
Expand Down
2 changes: 0 additions & 2 deletions contracts/p1/BackingManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ contract BackingManagerP1 is TradingP1, IBackingManager {

// ==== Invariants ====
// tradingDelay <= MAX_TRADING_DELAY and backingBuffer <= MAX_BACKING_BUFFER
//
// ... and the *much* more complicated temporal properties for _manageTokens()

function init(
IMain main_,
Expand Down
4 changes: 3 additions & 1 deletion contracts/p1/StRSR.sol
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab

/// Begins a delayed unstaking for `amount` StRSR
/// @param stakeAmount {qStRSR}
/// @custom:interaction
// checks:
// not paused (trading) or frozen
// 0 < stakeAmount <= bal[caller]
Expand Down Expand Up @@ -333,6 +334,8 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab
require(basketHandler.isReady(), "basket not ready");
}

/// Cancel an ongoing unstaking; resume staking
/// @custom:interaction CEI
function cancelUnstake(uint256 endId) external {
requireNotFrozen();
address account = _msgSender();
Expand Down Expand Up @@ -408,7 +411,6 @@ abstract contract StRSRP1 is Initializable, ComponentP1, IStRSR, EIP712Upgradeab
//
// other properties:
// seized >= rsrAmount, which should be a logical consequence of the above effects

function seizeRSR(uint256 rsrAmount) external {
requireNotTradingPausedOrFrozen();

Expand Down
6 changes: 2 additions & 4 deletions contracts/p1/mixins/RewardableLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,12 @@ library RewardableLibP1 {
using Address for address;
using SafeERC20 for IERC20;

event Complete();

// === Used by Traders + RToken ===

/// Claim all rewards
/// @custom:interaction mostly CEI but see comments
// actions:
// do asset.claimRewards() for asset in assets
// try erc20.claimRewards() for erc20 in erc20s
function claimRewards(IAssetRegistry reg) internal {
Registry memory registry = reg.getRegistry();
for (uint256 i = 0; i < registry.erc20s.length; ++i) {
Expand All @@ -36,7 +34,7 @@ library RewardableLibP1 {
/// Claim rewards for a single ERC20
/// @custom:interaction mostly CEI but see comments
// actions:
// do asset.claimRewards()
// try erc20.claimRewards()
function claimRewardsSingle(IAsset asset) internal {
// empty try/catch because not every erc20 will be wrapped & have a claimRewards func
// solhint-disable-next-line
Expand Down
2 changes: 1 addition & 1 deletion docs/recollateralization.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Recollateralization Trading Algorithm

Recollateralization takes place in the central loop of [`BackingManager.manageTokens()`](../contracts/p1/BackingManager). Since the BackingManager can only have open 1 trade at a time, it needs to know which tokens to try to trade and how much. This algorithm should not be gameable and should not result in unnecessary losses.
Recollateralization takes place in the central loop of [`BackingManager.rebalance()`](../contracts/p1/BackingManager). Since the BackingManager can only have open 1 trade at a time, it needs to know which tokens to try to trade and how much. This algorithm should not be gameable and should not result in unnecessary losses.

```solidity
(bool doTrade, TradeRequest memory req) = RecollateralizationLibP1.prepareRecollateralizationTrade(...);
Expand Down
5 changes: 4 additions & 1 deletion docs/solidity-style.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,15 @@ For each `external` or `public` function, one of these tags MUST be in the corre

- stRSR.stake()
- stRSR.unstake()
- stRSR.cancelUnstaking()
- stRSR.withdraw()
- rToken.issue()
- rToken.redeem()
- {rsrTrader,rTokenTrader,backingManager}.claimRewards()
- {rsrTrader,rTokenTrader,backingManager}.settleTrade()
- backingManager.grantRTokenAllowances()
- backingManager.manageTokens\*()
- backingManager.rebalance\*()
- backingManager.forwardRevenue\*()
- {rsrTrader,rTokenTrader}.manageToken()

### `@custom:governance`
Expand Down
Loading

0 comments on commit 0c11e56

Please sign in to comment.