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

Issuance Premium v2 #1175

Merged
merged 26 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6d64f03
add optional (on-by-default) issuance premium (#1173)
tbrent Jul 18, 2024
1616b12
Merge branch 'master' into 4.0.0
tbrent Jul 18, 2024
ab69c4c
nit: move timestamp check into helper + document
tbrent Jul 18, 2024
0ca8f32
impl
tbrent Jul 22, 2024
e601bbf
update tests
tbrent Jul 22, 2024
94010fb
CHANGELOG
tbrent Jul 22, 2024
fba4044
get under contract limit
tbrent Jul 22, 2024
c7a9dd6
fix tests
tbrent Jul 22, 2024
1ac93e5
Merge branch '4.0.0' into toxic-issuance-2
tbrent Jul 22, 2024
427cbba
CHANGELOG
tbrent Jul 22, 2024
06dbbfd
fix comment
tbrent Jul 24, 2024
e299521
fix unit comment
tbrent Jul 24, 2024
ebf1414
document RTokenAsset.tryPrice() limitations
tbrent Jul 24, 2024
841256f
push target amount normalization for reweightable RTokens outside the…
tbrent Jul 24, 2024
376907d
add wrapper functions to price()/quote() to not do a breaking change
tbrent Jul 24, 2024
09ae245
skipIssuancePremium() -> enableIssuancePremium(), for clarity; opt-in…
tbrent Jul 24, 2024
2a9dae8
do not revert if savedPegPrice() is missing from collateral
tbrent Jul 24, 2024
f7ec9a1
document governors should use a spell to call forceSetPrimeBasket() f…
tbrent Jul 24, 2024
6d102fe
update tests
tbrent Jul 24, 2024
9fbdf6d
CHANGELOG
tbrent Jul 24, 2024
7a5e02e
nits
tbrent Jul 25, 2024
2190916
minimize facade/facet diff
tbrent Jul 25, 2024
1dbb2e1
fix test
tbrent Jul 25, 2024
21d1c78
add back qty == 0 continue
tbrent Jul 25, 2024
9868f32
fix comment
tbrent Jul 25, 2024
e27c884
CHANGELOG
tbrent Jul 25, 2024
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
33 changes: 26 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,40 @@ This release prepares the core protocol for veRSR through the introduction of 3

The release also expands collateral decimal support from 18 to 21, with some caveats about minimum token value. See [docs/solidity-style.md](./docs/solidity-style.md#Collateral-decimals) for more details.

Finally, it adds resistance to toxic issuance by charging more when the collateral is under peg.

## Upgrade Steps

TODO
Upgrade to 4.0.0 is expected to occur by spell. This section is still TODO, but some important notes for steps that should be hit:

Make sure distributor table sums to >10000.
- Distributor table must sum to >=10000
- Opt RTokens into the issuance premium by default
- Upgrade all collateral plugins and RTokenAsset
- ...

## Core Protocol Contracts

All components: make Main the only component that can call `upgradeTo()`

- `AssetRegistry`
- Prevent registering assets that are not in the `AssetPluginRegistry`
- Add `validateCurrentAssets() view`
- `BackingManager`
- Switch from sizing trades using the low price to the high price
- `Broker`
- Make setters only callable by `Main`
- `BasketHandler`
- Add `issuancePremium() view returns (uint192)`
- Add `setIssuancePremiumEnabled(bool)`, callable by governance. Begins disabled by default for upgraded RTokens
- Add `quote(uint192 amount, bool applyIssuancePremium, RoundingMode rounding)`
- Modify `quote(uint192 amount, RoundingMode rounding)` to include the issuance premium
- Add `price(bool applyIssuancePremium)`
- Modify `price()` to include the issuance premium
- Remove `lotPrice()`
- Minor changes to require error strings
- `Deployer`
- Add `enableIssuancePremium` parameter to `IDeployer.DeploymentParams`
- `Distributor`
- Add `setDistributions()` function to parallel `setDistribution()`
- Take DAO fee out account in `distribute()` and `totals()`
- Take DAO fee into account in `distribute()` and `totals()`
- Add new revenue share table invariant: must sum to >=10000 (for precision reasons)
- `Main`
- Add `versionRegistry()`/`assetPluginRegistry()`/`daoFeeRegistry()` getters
Expand All @@ -37,9 +53,12 @@ Make sure distributor table sums to >10000.

### Assets

No functional change. FLOOR rounding added explicitly to `shiftl_toFix`.
- Support expanded from 18 to 21 decimals, with minimum collateral token value requirement of `$0.001` at-peg.
- FLOOR rounding added explicitly to `shiftl_toFix` everywhere

#### Collateral

Support expanded from 18 to 21 decimals, with minimum collateral token value requirement of `$0.001` at-peg.
Add `savedPegPrice` to `ICollateral` interface

### Trading

Expand Down
1 change: 1 addition & 0 deletions common/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,7 @@ export interface IConfig {
withdrawalLeak: BigNumber
warmupPeriod: BigNumber
reweightable: boolean
enableIssuancePremium: boolean
tradingDelay: BigNumber
batchAuctionLength: BigNumber
dutchAuctionLength: BigNumber
Expand Down
6 changes: 4 additions & 2 deletions contracts/facade/facets/MaxIssuableFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../../interfaces/IBasketHandler.sol";
import "../../interfaces/IRToken.sol";
import "../../libraries/Fixed.sol";
import "../../p1/BasketHandler.sol";

/**
* @title MaxIssuableFacet
Expand All @@ -21,7 +22,8 @@ contract MaxIssuableFacet {
/// @return {qRTok} How many RToken `account` can issue given current holdings
/// @custom:static-call
function maxIssuable(IRToken rToken, address account) external returns (uint256) {
(address[] memory erc20s, ) = rToken.main().basketHandler().quote(FIX_ONE, FLOOR);
BasketHandlerP1 bh = BasketHandlerP1(address(rToken.main().basketHandler()));
(address[] memory erc20s, ) = bh.quote(FIX_ONE, FLOOR);
uint256[] memory balances = new uint256[](erc20s.length);
for (uint256 i = 0; i < erc20s.length; ++i) {
balances[i] = IERC20(erc20s[i]).balanceOf(account);
Expand All @@ -45,7 +47,7 @@ contract MaxIssuableFacet {
main.assetRegistry().refresh();

// Get basket ERC20s
IBasketHandler bh = main.basketHandler();
BasketHandlerP1 bh = BasketHandlerP1(address(main.basketHandler()));
(address[] memory erc20s, uint256[] memory quantities) = bh.quote(FIX_ONE, CEIL);

// Compute how many baskets we can mint with the collateral amounts
Expand Down
18 changes: 10 additions & 8 deletions contracts/facade/facets/ReadFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ contract ReadFacet {

// Cache components
IRToken rTok = rToken;
IBasketHandler bh = main.basketHandler();
BasketHandlerP1 bh = BasketHandlerP1(address(main.basketHandler()));
IAssetRegistry reg = main.assetRegistry();

// Poke Main
Expand Down Expand Up @@ -91,7 +91,7 @@ contract ReadFacet {

// Cache Components
IRToken rTok = rToken;
IBasketHandler bh = main.basketHandler();
BasketHandlerP1 bh = BasketHandlerP1(address(main.basketHandler()));

// Poke Main
main.assetRegistry().refresh();
Expand Down Expand Up @@ -173,7 +173,7 @@ contract ReadFacet {
{
uint256[] memory deposits;
IAssetRegistry assetRegistry = rToken.main().assetRegistry();
IBasketHandler basketHandler = rToken.main().basketHandler();
BasketHandlerP1 basketHandler = BasketHandlerP1(address(rToken.main().basketHandler()));

// solhint-disable-next-line no-empty-blocks
try rToken.main().furnace().melt() {} catch {} // <3.1.0 RTokens may revert while frozen
Expand Down Expand Up @@ -299,7 +299,8 @@ contract ReadFacet {

/// @return tokens The ERC20s backing the RToken
function basketTokens(IRToken rToken) external view returns (address[] memory tokens) {
(tokens, ) = rToken.main().basketHandler().quote(FIX_ONE, RoundingMode.FLOOR);
BasketHandlerP1 bh = BasketHandlerP1(address(rToken.main().basketHandler()));
(tokens, ) = bh.quote(FIX_ONE, RoundingMode.FLOOR);
}

/// Returns the backup configuration for a given targetName
Expand Down Expand Up @@ -335,10 +336,11 @@ contract ReadFacet {
uint192 uoaNeeded; // {UoA}
uint192 uoaHeldInBaskets; // {UoA}
{
(address[] memory basketERC20s, uint256[] memory quantities) = rToken
.main()
.basketHandler()
.quote(basketsNeeded, FLOOR);
BasketHandlerP1 bh = BasketHandlerP1(address(rToken.main().basketHandler()));
(address[] memory basketERC20s, uint256[] memory quantities) = bh.quote(
basketsNeeded,
FLOOR
);

IAssetRegistry reg = rToken.main().assetRegistry();
IBackingManager bm = rToken.main().backingManager();
Expand Down
12 changes: 4 additions & 8 deletions contracts/interfaces/IAsset.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,6 @@ 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);

Expand Down Expand Up @@ -114,7 +107,7 @@ interface ICollateral is IAsset {

/// @dev refresh()
/// Refresh exchange rates and update default status.
/// VERY IMPORTANT: In any valid implemntation, status() MUST become DISABLED in refresh() if
/// VERY IMPORTANT: In any valid implementation, status() MUST become DISABLED in refresh() if
/// refPerTok() has ever decreased since last call.

/// @return The canonical name of this collateral's target unit.
Expand All @@ -130,6 +123,9 @@ interface ICollateral is IAsset {

/// @return {target/ref} Quantity of whole target units per whole reference unit in the peg
function targetPerRef() external view returns (uint192);

/// @return {target/ref} The peg price of the token during the last update
function savedPegPrice() external view returns (uint192);
}

// Used only in Testing. Strictly speaking a Collateral does not need to adhere to this interface
Expand Down
46 changes: 27 additions & 19 deletions contracts/interfaces/IBasketHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ interface IBasketHandler is IComponent {
/// @param newVal The new warmup period
event WarmupPeriodSet(uint48 oldVal, uint48 newVal);

/// Emitted when the issuance premium logic is changed
/// @param oldVal The old value of enableIssuancePremium
/// @param newVal The new value of enableIssuancePremium
event EnableIssuancePremiumSet(bool oldVal, bool newVal);

/// Emitted when the status of a basket has changed
/// @param oldStatus The previous basket status
/// @param newStatus The new basket status
Expand All @@ -57,19 +62,20 @@ interface IBasketHandler is IComponent {
function init(
IMain main_,
uint48 warmupPeriod_,
bool reweightable_
bool reweightable_,
bool enableIssuancePremium_
) external;

/// Set the prime basket
/// For an index RToken (reweightable = true), use forceSetPrimeBasket to skip normalization
/// Set the prime basket, checking target amounts are constant
/// @param erc20s The collateral tokens for the new prime basket
/// @param targetAmts The target amounts (in) {target/BU} for the new prime basket
/// required range: 1e9 values; absolute range irrelevant.
/// @custom:governance
function setPrimeBasket(IERC20[] calldata erc20s, uint192[] calldata targetAmts) external;

/// Set the prime basket without normalizing targetAmts by the UoA of the current basket
/// Works the same as setPrimeBasket for non-index RTokens (reweightable = false)
/// Set the prime basket, skipping any constant target amount checks if RToken is reweightable
/// Warning: Reweightable RTokens SHOULD use a spell to execute this function to avoid
/// accidentally changing the UoA value of the RToken.
/// @param erc20s The collateral tokens for the new prime basket
/// @param targetAmts The target amounts (in) {target/BU} for the new prime basket
/// required range: 1e9 values; absolute range irrelevant.
Expand Down Expand Up @@ -110,29 +116,33 @@ interface IBasketHandler is IComponent {
/// @return If the basket is ready to issue and trade
function isReady() external view returns (bool);

/// Returns basket quantity rounded up, wihout any issuance premium
/// @param erc20 The ERC20 token contract for the asset
/// @return {tok/BU} The whole token quantity of token in the reference basket
/// @return {tok/BU} The redemption quantity of token in the reference basket, rounded up
/// Returns 0 if erc20 is not registered or not in the basket
/// Returns FIX_MAX (in lieu of +infinity) if Collateral.refPerTok() is 0.
/// Otherwise, returns (token's basket.refAmts / token's Collateral.refPerTok())
function quantity(IERC20 erc20) external view returns (uint192);

/// Returns basket quantity rounded up, wihout any issuance premium
/// Like quantity(), but unsafe because it DOES NOT CONFIRM THAT THE ASSET IS CORRECT
/// @param erc20 The ERC20 token contract for the asset
/// @param asset The registered asset plugin contract for the erc20
/// @return {tok/BU} The whole token quantity of token in the reference basket
/// @return {tok/BU} The redemption quantity of token in the reference basket, rounded up
/// Returns 0 if erc20 is not registered or not in the basket
/// Returns FIX_MAX (in lieu of +infinity) if Collateral.refPerTok() is 0.
/// Otherwise, returns (token's basket.refAmts / token's Collateral.refPerTok())
function quantityUnsafe(IERC20 erc20, IAsset asset) external view returns (uint192);

/// @param amount {BU}
/// @param applyIssuancePremium Whether to apply the issuance premium
/// @return erc20s The addresses of the ERC20 tokens in the reference basket
/// @return quantities {qTok} The quantity of each ERC20 token to issue `amount` baskets
function quote(uint192 amount, RoundingMode rounding)
external
view
returns (address[] memory erc20s, uint256[] memory quantities);
function quote(
uint192 amount,
bool applyIssuancePremium,
RoundingMode rounding
) external view returns (address[] memory erc20s, uint256[] memory quantities);

/// Return the redemption value of `amount` BUs for a linear combination of historical baskets
/// @param basketNonces An array of basket nonces to do redemption from
Expand All @@ -152,16 +162,10 @@ interface IBasketHandler is IComponent {

/// Should not revert
/// low should be nonzero when BUs are worth selling
/// @param applyIssuancePremium Whether to apply the issuance premium to the high 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() 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);
function price(bool applyIssuancePremium) external view returns (uint192 low, uint192 high);

/// @return timestamp The timestamp at which the basket was last set
function timestamp() external view returns (uint48);
Expand Down Expand Up @@ -190,4 +194,8 @@ interface TestIBasketHandler is IBasketHandler {
function warmupPeriod() external view returns (uint48);

function setWarmupPeriod(uint48 val) external;

function enableIssuancePremium() external view returns (bool);

function setIssuancePremiumEnabled(bool val) external;
}
1 change: 1 addition & 0 deletions contracts/interfaces/IDeployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ struct DeploymentParams {
// === BasketHandler ===
uint48 warmupPeriod; // {s} how long to wait until issuance/trading after regaining SOUND
bool reweightable; // whether the target amounts in the prime basket can change
bool enableIssuancePremium; // whether to enable the issuance premium
//
// === BackingManager ===
uint48 tradingDelay; // {s} how long to wait until starting auctions after switching basket
Expand Down
Loading
Loading