Skip to content

Commit

Permalink
Merge pull request #1219 from reserve-protocol/registry-deployer
Browse files Browse the repository at this point in the history
Add Registry Setup & Basket Normalization Spell
  • Loading branch information
akshatmittal authored Oct 29, 2024
2 parents 740f8a1 + 19b0717 commit f9380fd
Show file tree
Hide file tree
Showing 31 changed files with 708 additions and 152 deletions.
16 changes: 8 additions & 8 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- run: yarn install --immutable
- run: yarn devchain &
env:
MAINNET_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/${{ secrets.ALCHEMY_MAINNET_KEY }}
MAINNET_RPC_URL: https://eth-mainnet.g.alchemy.com/v2/${{ secrets.ALCHEMY_MAINNET_KEY }}
FORK_NETWORK: mainnet
- run: yarn deploy:run --network localhost
env:
Expand Down Expand Up @@ -82,7 +82,7 @@ jobs:
env:
NODE_OPTIONS: '--max-old-space-size=32768'
TS_NODE_SKIP_IGNORE: true
MAINNET_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/${{ secrets.ALCHEMY_MAINNET_KEY }}
MAINNET_RPC_URL: https://eth-mainnet.g.alchemy.com/v2/${{ secrets.ALCHEMY_MAINNET_KEY }}
FORK_NETWORK: mainnet
PROTO_IMPL: 1
FORK: 1
Expand All @@ -109,7 +109,7 @@ jobs:
env:
NODE_OPTIONS: '--max-old-space-size=32768'
TS_NODE_SKIP_IGNORE: true
MAINNET_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/${{ secrets.ALCHEMY_MAINNET_KEY }}
MAINNET_RPC_URL: https://eth-mainnet.g.alchemy.com/v2/${{ secrets.ALCHEMY_MAINNET_KEY }}
FORK_NETWORK: mainnet
PROTO_IMPL: 1
FORK: 1
Expand All @@ -136,7 +136,7 @@ jobs:
env:
NODE_OPTIONS: '--max-old-space-size=32768'
TS_NODE_SKIP_IGNORE: true
MAINNET_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/${{ secrets.ALCHEMY_MAINNET_KEY }}
MAINNET_RPC_URL: https://eth-mainnet.g.alchemy.com/v2/${{ secrets.ALCHEMY_MAINNET_KEY }}
FORK_NETWORK: mainnet
PROTO_IMPL: 1
FORK: 1
Expand All @@ -163,7 +163,7 @@ jobs:
env:
NODE_OPTIONS: '--max-old-space-size=32768'
TS_NODE_SKIP_IGNORE: true
BASE_RPC_URL: https://base-mainnet.infura.io/v3/${{ secrets.INFURA_BASE_KEY }}
BASE_RPC_URL: https://base-mainnet.g.alchemy.com/v2/${{ secrets.ALCHEMY_BASE_KEY }}
FORK_NETWORK: base
FORK_BLOCK: 4446300
FORK: 1
Expand Down Expand Up @@ -262,7 +262,7 @@ jobs:
env:
NODE_OPTIONS: '--max-old-space-size=32768'
TS_NODE_SKIP_IGNORE: true
MAINNET_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/${{ secrets.ALCHEMY_MAINNET_KEY }}
MAINNET_RPC_URL: https://eth-mainnet.g.alchemy.com/v2/${{ secrets.ALCHEMY_MAINNET_KEY }}
FORK_NETWORK: mainnet

integration-tests:
Expand All @@ -289,7 +289,7 @@ jobs:
env:
NODE_OPTIONS: '--max-old-space-size=32768'
TS_NODE_SKIP_IGNORE: true
MAINNET_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/${{ secrets.ALCHEMY_MAINNET_KEY }}
MAINNET_RPC_URL: https://eth-mainnet.g.alchemy.com/v2/${{ secrets.ALCHEMY_MAINNET_KEY }}
FORK_NETWORK: mainnet

monitor-tests:
Expand All @@ -314,7 +314,7 @@ jobs:
env:
NODE_OPTIONS: '--max-old-space-size=32768'
TS_NODE_SKIP_IGNORE: true
MAINNET_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/${{ secrets.ALCHEMY_MAINNET_KEY }}
MAINNET_RPC_URL: https://eth-mainnet.g.alchemy.com/v2/${{ secrets.ALCHEMY_MAINNET_KEY }}
FORK_NETWORK: mainnet
FORK: 1
PROTO_IMPL: 1
Expand Down
33 changes: 33 additions & 0 deletions common/registries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
interface IRegistries {
roleRegistry: string
versionRegistry: string
assetPluginRegistry: string
daoFeeRegistry: string
}

interface IRegistryControl {
owner: string
feeRecipient: string
}

export interface RegistryChainRecord {
registries: IRegistries
registryControl: IRegistryControl
}

export const registryConfig: Record<string, RegistryChainRecord> = {
'1': {
registryControl: {
owner: '0x0000000000000000000000000000000000000123',
feeRecipient: '0x0000000000000000000000000000000000000123',
},
registries: {
roleRegistry: '',
versionRegistry: '',
assetPluginRegistry: '',
daoFeeRegistry: '',
},
},
}

registryConfig['31337'] = registryConfig['1']
2 changes: 0 additions & 2 deletions contracts/facade/DeployerRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import "../interfaces/IDeployerRegistry.sol";
* @dev Does not allow overwriting without deregistration
*/
contract DeployerRegistry is IDeployerRegistry, Ownable {
string public constant ENS = "reserveprotocol.eth";

mapping(string => IDeployer) public deployments;

IDeployer public override latestDeployment;
Expand Down
13 changes: 8 additions & 5 deletions contracts/facade/FacadeWrite.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity 0.8.19;

import "../interfaces/IFacadeWrite.sol";
import "../interfaces/IDeployer.sol";
import "./lib/FacadeWriteLib.sol";

/**
Expand All @@ -21,10 +22,11 @@ contract FacadeWrite is IFacadeWrite {
}

/// Step 1
function deployRToken(ConfigurationParams calldata config, SetupParams calldata setup)
external
returns (address)
{
function deployRToken(
ConfigurationParams calldata config,
SetupParams calldata setup,
IDeployer.Registries calldata registries
) external returns (address) {
// Perform validations
require(setup.primaryBasket.length != 0, "no collateral");
require(setup.primaryBasket.length == setup.weights.length, "invalid length");
Expand All @@ -51,7 +53,8 @@ contract FacadeWrite is IFacadeWrite {
config.symbol,
config.mandate,
address(this), // set as owner
config.params
config.params,
registries
)
);

Expand Down
26 changes: 12 additions & 14 deletions contracts/interfaces/IDeployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import "./IStRSR.sol";
import "./ITrade.sol";
import "./IVersioned.sol";

import "../registry/VersionRegistry.sol";
import "../registry/AssetPluginRegistry.sol";
import "../registry/DAOFeeRegistry.sol";

/**
* @title DeploymentParams
* @notice The set of protocol params needed to configure a new system deployment.
Expand Down Expand Up @@ -92,21 +96,27 @@ interface IDeployer is IVersioned {
/// @param rTokenAsset The address of the RTokenAsset
event RTokenAssetCreated(IRToken indexed rToken, IAsset rTokenAsset);

//
struct Registries {
VersionRegistry versionRegistry;
AssetPluginRegistry assetPluginRegistry;
DAOFeeRegistry daoFeeRegistry;
}

/// Deploys an instance of the entire system
/// @param name The name of the RToken to deploy
/// @param symbol The symbol of the RToken to deploy
/// @param mandate An IPFS link or direct string; describes what the RToken _should be_
/// @param owner The address that should own the entire system, hopefully a governance contract
/// @param params Deployment params
/// @param registries Registries list; can be 0 to unset
/// @return The address of the newly deployed Main instance.
function deploy(
string calldata name,
string calldata symbol,
string calldata mandate,
address owner,
DeploymentParams calldata params
DeploymentParams calldata params,
Registries calldata registries
) external returns (address);

/// Deploys a new RTokenAsset instance. Not needed during normal deployment flow
Expand All @@ -115,15 +125,3 @@ interface IDeployer is IVersioned {

function implementations() external view returns (Implementations memory);
}

interface TestIDeployer is IDeployer {
/// A top-level ENS domain that should always point to the latest Deployer instance
// solhint-disable-next-line func-name-mixedcase
function ENS() external view returns (string memory);

function rsr() external view returns (IERC20Metadata);

function rsrAsset() external view returns (IAsset);

function implementations() external view returns (Implementations memory);
}
8 changes: 5 additions & 3 deletions contracts/interfaces/IFacadeWrite.sol
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,11 @@ interface IFacadeWrite {
);

/// Deploys an instance of an RToken
function deployRToken(ConfigurationParams calldata config, SetupParams calldata setup)
external
returns (address);
function deployRToken(
ConfigurationParams calldata config,
SetupParams calldata setup,
IDeployer.Registries calldata registries
) external returns (address);

/// Sets up governance for an RToken
function setupGovernance(
Expand Down
5 changes: 2 additions & 3 deletions contracts/p0/Deployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ import "../mixins/Versioned.sol";
* @notice The factory contract that deploys the entire P0 system.
*/
contract DeployerP0 is IDeployer, Versioned {
string public constant ENS = "reserveprotocol.eth";

IERC20Metadata public immutable rsr;
IGnosis public immutable gnosis;
IAsset public immutable rsrAsset;
Expand Down Expand Up @@ -61,7 +59,8 @@ contract DeployerP0 is IDeployer, Versioned {
string memory symbol,
string calldata mandate,
address owner,
DeploymentParams memory params
DeploymentParams memory params,
Registries calldata // ignored
) external returns (address) {
require(owner != address(0) && owner != address(this), "invalid owner");

Expand Down
17 changes: 14 additions & 3 deletions contracts/p1/Deployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ import "../plugins/trading/GnosisTrade.sol";
contract DeployerP1 is IDeployer, Versioned {
using Clones for address;

string public constant ENS = "reserveprotocol.eth";

IERC20Metadata public immutable rsr;
IAsset public immutable rsrAsset;

Expand Down Expand Up @@ -106,7 +104,8 @@ contract DeployerP1 is IDeployer, Versioned {
string memory symbol,
string calldata mandate,
address owner,
DeploymentParams memory params
DeploymentParams memory params,
Registries calldata registries
) external returns (address) {
require(owner != address(0) && owner != address(this), "invalid owner");

Expand Down Expand Up @@ -251,11 +250,23 @@ contract DeployerP1 is IDeployer, Versioned {
// Init Asset Registry
components.assetRegistry.init(main, assets);

// Assign DAO Registries
if (address(registries.versionRegistry) != address(0)) {
main.setVersionRegistry(registries.versionRegistry);
}
if (address(registries.assetPluginRegistry) != address(0)) {
main.setAssetPluginRegistry(registries.assetPluginRegistry);
}
if (address(registries.daoFeeRegistry) != address(0)) {
main.setDAOFeeRegistry(registries.daoFeeRegistry);
}

// Transfer Ownership
main.grantRole(OWNER, owner);
main.renounceRole(OWNER, address(this));

emit RTokenCreated(main, components.rToken, components.stRSR, owner, version());

return (address(components.rToken));
}

Expand Down
2 changes: 1 addition & 1 deletion contracts/plugins/trading/GnosisTrade.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ contract GnosisTrade is ITrade, Versioned {

// ==== The rest of contract state is all parameters that are immutable after init()
// == Metadata
IGnosis public gnosis_DEPRECATED; // made immutable in 4.0.0; left in for testing compatibility
IGnosis public gnosis_DEPRECATED; // made immutable in 4.0.0; left in for storage compat
uint256 public auctionId; // The Gnosis Auction ID returned by gnosis.initiateAuction()
IBroker public broker; // The Broker that cloned this contract into existence

Expand Down
8 changes: 4 additions & 4 deletions contracts/spells/3_4_0.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
pragma solidity 0.8.19;

import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts/governance/IGovernor.sol";
import "@openzeppelin/contracts/governance/TimelockController.sol";
import "../interfaces/IDeployer.sol";
import "../interfaces/IMain.sol";
import "../p1/Deployer.sol";

// interface avoids needing to know about P1 contracts
interface ICachedComponent {
Expand Down Expand Up @@ -120,7 +120,7 @@ contract Upgrade3_4_0 {

using EnumerableSet for EnumerableSet.Bytes32Set;

TestIDeployer public deployer;
DeployerP1 public deployer;

struct NewGovernance {
IGovernor anastasius;
Expand Down Expand Up @@ -163,7 +163,7 @@ contract Upgrade3_4_0 {
// Setup `assets` array
if (_mainnet) {
// Setup `deployer`
deployer = TestIDeployer(0x2204EC97D31E2C9eE62eaD9e6E2d5F7712D3f1bF);
deployer = DeployerP1(0x2204EC97D31E2C9eE62eaD9e6E2d5F7712D3f1bF);

// Setup `newGovs`
// eUSD
Expand Down Expand Up @@ -244,7 +244,7 @@ contract Upgrade3_4_0 {
}
} else {
// Setup `deployer`
deployer = TestIDeployer(0xFD18bA9B2f9241Ce40CDE14079c1cDA1502A8D0A);
deployer = DeployerP1(0xFD18bA9B2f9241Ce40CDE14079c1cDA1502A8D0A);

// Setup `newGovs`
// hyUSD (base)
Expand Down
51 changes: 51 additions & 0 deletions contracts/spells/SpellBasketNormalizer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;

import "../p1/mixins/BasketLib.sol";
import "../p1/BasketHandler.sol";
import "../p1/Main.sol";
import "../interfaces/IDeployer.sol";
import "../interfaces/IMain.sol";
import "../p1/Deployer.sol";

/**
* This spell is used by reweightable RTokens with rev 4.0.0 or later.
*
* This allows governance to normalize the basket by price at the time of setting it in place,
* effectively allowing for a basket that is continous in USD terms.
*
* Before casting `setNormalizedBasket` function this contract must have `MAIN_OWNER_ROLE` of Main,
* and should also revoke the said role at the end of the transaction.
*
* The spell function should be called by the timelock owning Main. Governance should NOT
* grant this spell ownership without immediately executing the spell function after.
*/
contract SpellBasketNormalizer {
function setNormalizedBasket(
IRToken rToken,
IERC20[] calldata erc20s,
uint192[] calldata targetAmts
) external {
require(erc20s.length == targetAmts.length, "SBN: mismatch");

MainP1 main = MainP1(address(rToken.main()));
IAssetRegistry assetRegistry = main.assetRegistry();
IBasketHandler basketHandler = main.basketHandler();

require(BasketHandlerP1(address(basketHandler)).reweightable(), "SBN: reweightable");

assetRegistry.refresh();
(uint192 low, uint192 high) = basketHandler.price(false);

uint192[] memory newTargetAmts = BasketLibP1.normalizeByPrice(
assetRegistry,
erc20s,
targetAmts,
(low + high + 1) / 2
);

basketHandler.forceSetPrimeBasket(erc20s, newTargetAmts);

main.revokeRole(main.OWNER_ROLE(), address(this));
}
}
15 changes: 15 additions & 0 deletions docs/reweightable-rtokens.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Reweightable RTokens

The protocol includes a flag to enable reweightable baskets for RTokens. This flag can only be set at the time of deployment and enables certain additional capabilities for the RToken while disabling others.

In simple terms, a reweightable RToken can change the target units. For example, if an RToken is configured as 1 ETH + 1 BTC in the basket, only the reweightable RTokens can change it to something like 1 ETH + 1 BTC + 100 USD.

In most cases, a non-reweightable RToken will suffice, we expect that to be 99% of all RTokens that exist. However, there are specific cases where you'd want to have reweightable RTokens such as ETFs.

## Basket Normalization

In reweightable RTokens, it's not a guarantee that during a basket change the USD value of the basket remains continuous at the time of the switch. You can easily see this property when, say, a basket switches from being 1 ETH to 1 ETH + 100 USD. The USD value of the basket will increase in this case, but the protocol doesn't have the extra funds (unless it seizes from the stRSR staking pool).

To enable this functionality and to allow governance to make sure that the set baskets can keep the same price at the time of the switch, a spell `SpellBasketNormalizer` is provided in the spells directory. This spell can be used to set the basket in such a way that the USD value of the basket remains the same at the time of the switch.

In order to use the spell, you must create a governance proposal granting the spell contract the `OWNER` role on the RToken then calling the `setNormalizedBasket` basket on the spell contract with appropriate parameters. See the `BasketNormalization` scenario test in the `test` directory for an example of how to use the spell.
Loading

0 comments on commit f9380fd

Please sign in to comment.