all files / facade/ FacadeWrite.sol

100% Statements 61/61
100% Branches 32/32
100% Functions 3/3
100% Lines 68/68
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206                                  67× 66×                 35× 34×     33× 23×       32× 38×                 30×                     30× 30× 30×     30× 30×       29× 29×     29× 47× 46× 46×       28× 28×       28× 28× 18×       18× 18× 18× 18× 18×     18×                 28× 32×             28×     28×     28×                             22×   22× 21×     20×   20×                                       16× 15×       18×           18×       18× 12×       18× 18× 18× 18× 18× 18× 18× 18×      
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.17;
 
import "../interfaces/IFacadeWrite.sol";
import "./lib/FacadeWriteLib.sol";
 
/**
 * @title FacadeWrite
 * @notice A UX-friendly layer to interact with the protocol
 * @dev Under the hood, uses two external libs to deal with blocksize limits.
 */
contract FacadeWrite is IFacadeWrite {
    using FacadeWriteLib for address;
 
    IDeployer public immutable deployer;
 
    constructor(IDeployer deployer_) {
        require(address(deployer_) != address(0), "invalid address");
        deployer = deployer_;
    }
 
    /// Step 1
    function deployRToken(ConfigurationParams calldata config, SetupParams calldata setup)
        external
        returns (address)
    {
        // Perform validations
        require(setup.primaryBasket.length > 0, "no collateral");
        require(setup.primaryBasket.length == setup.weights.length, "invalid length");
 
        // Validate backups
        for (uint256 i = 0; i < setup.backups.length; ++i) {
            require(setup.backups[i].backupCollateral.length > 0, "no backup collateral");
        }
 
        // Validate beneficiaries
        for (uint256 i = 0; i < setup.beneficiaries.length; ++i) {
            require(
                setup.beneficiaries[i].beneficiary != address(0) &&
                    (setup.beneficiaries[i].revShare.rTokenDist > 0 ||
                        setup.beneficiaries[i].revShare.rsrDist > 0),
                "beneficiary revShare mismatch"
            );
        }
 
        // Deploy contracts
        IRToken rToken = IRToken(
            deployer.deploy(
                config.name,
                config.symbol,
                config.mandate,
                address(this), // set as owner
                config.params
            )
        );
 
        // Get Main
        IMain main = rToken.main();
        IAssetRegistry assetRegistry = main.assetRegistry();
        IBasketHandler basketHandler = main.basketHandler();
 
        // Register assets
        for (uint256 i = 0; i < setup.assets.length; ++i) {
            require(assetRegistry.register(setup.assets[i]), "duplicate asset");
        }
 
        // Setup basket
        {
            IERC20[] memory basketERC20s = new IERC20[](setup.primaryBasket.length);
 
            // Register collateral
            for (uint256 i = 0; i < setup.primaryBasket.length; ++i) {
                require(assetRegistry.register(setup.primaryBasket[i]), "duplicate collateral");
                IERC20 erc20 = setup.primaryBasket[i].erc20();
                basketERC20s[i] = erc20;
            }
 
            // Set basket
            basketHandler.setPrimeBasket(basketERC20s, setup.weights);
            basketHandler.refreshBasket();
        }
 
        // Setup backup config
        {
            for (uint256 i = 0; i < setup.backups.length; ++i) {
                IERC20[] memory backupERC20s = new IERC20[](
                    setup.backups[i].backupCollateral.length
                );
 
                for (uint256 j = 0; j < setup.backups[i].backupCollateral.length; ++j) {
                    ICollateral backupColl = setup.backups[i].backupCollateral[j];
                    assetRegistry.register(backupColl); // do not require the asset is new
                    IERC20 erc20 = backupColl.erc20();
                    backupERC20s[j] = erc20;
                }
 
                basketHandler.setBackupConfig(
                    setup.backups[i].backupUnit,
                    setup.backups[i].diversityFactor,
                    backupERC20s
                );
            }
        }
 
        // Setup revshare beneficiaries
        for (uint256 i = 0; i < setup.beneficiaries.length; ++i) {
            main.distributor().setDistribution(
                setup.beneficiaries[i].beneficiary,
                setup.beneficiaries[i].revShare
            );
        }
 
        // Pause until setupGovernance
        main.pause();
 
        // Setup deployer as owner to complete next step - do not renounce roles yet
        main.grantRole(OWNER, msg.sender);
 
        // Return rToken address
        return address(rToken);
    }
 
    /// Step 2
    /// @return newOwner The address of the new owner
    function setupGovernance(
        IRToken rToken,
        bool deployGovernance,
        bool unpause,
        GovernanceParams calldata govParams,
        address owner,
        address guardian,
        address pauser
    ) external returns (address newOwner) {
        // Get Main
        IMain main = rToken.main();
 
        require(main.hasRole(OWNER, address(this)), "ownership already transferred");
        require(main.hasRole(OWNER, msg.sender), "not initial deployer");
 
        // Remove ownership to sender
        main.revokeRole(OWNER, msg.sender);
 
        if (deployGovernance) {
            require(owner == address(0), "owner should be empty");
 
            TimelockController timelock = new TimelockController(
                govParams.timelockDelay,
                new address[](0),
                new address[](0)
            );
 
            // Deploy Governance contract
            address governance = FacadeWriteLib.deployGovernance(
                IStRSRVotes(address(main.stRSR())),
                timelock,
                govParams.votingDelay,
                govParams.votingPeriod,
                govParams.proposalThresholdAsMicroPercent,
                govParams.quorumPercent
            );
            emit GovernanceCreated(rToken, governance, address(timelock));
 
            // Setup Roles
            timelock.grantRole(timelock.PROPOSER_ROLE(), governance); // Gov only proposer
            timelock.grantRole(timelock.CANCELLER_ROLE(), guardian); // Guardian as canceller
            timelock.grantRole(timelock.EXECUTOR_ROLE(), address(0)); // Anyone as executor
            timelock.revokeRole(timelock.TIMELOCK_ADMIN_ROLE(), address(this)); // Revoke admin role
 
            // Set new owner to timelock
            newOwner = address(timelock);
        } else {
            require(owner != address(0), "owner not defined");
            newOwner = owner;
        }
 
        // Setup guardian as freeze starter / extender + pauser
        if (guardian != address(0)) {
            // As a further decentralization step it is suggested to further differentiate between
            // these two roles. But this is what will make sense for simple system setup.
            main.grantRole(SHORT_FREEZER, guardian);
            main.grantRole(LONG_FREEZER, guardian);
            main.grantRole(PAUSER, guardian);
        }
 
        // Setup Pauser
        if (pauser != address(0)) {
            main.grantRole(PAUSER, pauser);
        }
 
        // Unpause if required
        if (unpause) {
            main.unpause();
        }
 
        // Transfer Ownership and renounce roles
        main.grantRole(OWNER, newOwner);
        main.grantRole(SHORT_FREEZER, newOwner);
        main.grantRole(LONG_FREEZER, newOwner);
        main.grantRole(PAUSER, newOwner);
        main.renounceRole(OWNER, address(this));
        main.renounceRole(SHORT_FREEZER, address(this));
        main.renounceRole(LONG_FREEZER, address(this));
        main.renounceRole(PAUSER, address(this));
    }
}