Skip to content

Commit

Permalink
feat(ct): Support opt-in manager upgrades (sphinx-labs#602)
Browse files Browse the repository at this point in the history
  • Loading branch information
RPate97 authored Apr 11, 2023
1 parent 2ce0e42 commit 69dcfba
Show file tree
Hide file tree
Showing 14 changed files with 282 additions and 90 deletions.
6 changes: 6 additions & 0 deletions .changeset/new-pandas-unite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@chugsplash/contracts': patch
'@chugsplash/core': patch
---

Add support for opt-in manager upgrades
28 changes: 21 additions & 7 deletions packages/contracts/contracts/ChugSplashManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -296,14 +296,20 @@ contract ChugSplashManager is OwnableUpgradeable, ReentrancyGuardUpgradeable {
}

/**
* @param _organizationID ID of the organization this contract is managing.
* @param _owner Initial owner of this contract.
* @param _data Arbitrary initialization data, allows for future manager versions to use the
* same interface.
* In this version, we expect the following data:
* - address _owner: Address of the owner of this contract.
* - bytes32 _organizationID: ID of the organization this contract is managing.
* - bool _allowManagedProposals: Whether or not to allow upgrade proposals from
* the ChugSplash managed service.
*/
function initialize(
address _owner,
bytes32 _organizationID,
bool _allowManagedProposals
) public initializer {
function initialize(bytes memory _data) public initializer {
(address _owner, bytes32 _organizationID, bool _allowManagedProposals) = abi.decode(
_data,
(address, bytes32, bool)
);

organizationID = _organizationID;
allowManagedProposals = _allowManagedProposals;

Expand Down Expand Up @@ -1057,4 +1063,12 @@ contract ChugSplashManager is OwnableUpgradeable, ReentrancyGuardUpgradeable {
);
require(success, "ChugSplashManager: delegatecall to set storage failed");
}

/**
* @notice Returns whether or not a bundle is currently being executed.
* Used to determine if the manager implementation can safely be upgraded.
*/
function isExecuting() external view returns (bool) {
return activeBundleId != bytes32(0);
}
}
71 changes: 71 additions & 0 deletions packages/contracts/contracts/ChugSplashManagerProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import { Proxy } from "./libraries/Proxy.sol";
import { ChugSplashRegistry } from "./ChugSplashRegistry.sol";
import { IChugSplashManager } from "./interfaces/IChugSplashManager.sol";
import { IChugSplashRegistry } from "./interfaces/IChugSplashRegistry.sol";

/**
* @title ChugSplashManagerProxy
* @notice Designed to be upgradable only by the end user and to allow upgrades only to
* new manager versions that whitelisted by the ChugSplashRegistry.
*/
contract ChugSplashManagerProxy is Proxy {
/**
* @notice Address of the ChugSplashRegistry.
*/
ChugSplashRegistry public immutable registry;

/**
* @param _registry The ChugSplashRegistry's address.
* @param _admin Owner of this contract.
*/
constructor(ChugSplashRegistry _registry, address _admin) payable Proxy(_admin) {
registry = _registry;
}

modifier isNotExecuting() {
require(
_getImplementation() == address(0) ||
IChugSplashManager(_getImplementation()).isExecuting() == false,
"ChugSplashManagerProxy: execution in progress"
);
_;
}

modifier isApprovedImplementation(address _implementation) {
require(
registry.versions(_implementation) == true,
"ChugSplashManagerProxy: unapproved manager"
);
_;
}

/**
* @inheritdoc Proxy
*/
function upgradeTo(
address _implementation
) public override proxyCallIfNotAdmin isNotExecuting isApprovedImplementation(_implementation) {
super.upgradeTo(_implementation);
}

/**
* @inheritdoc Proxy
*/
function upgradeToAndCall(
address _implementation,
bytes calldata _data
)
public
payable
override
proxyCallIfNotAdmin
isNotExecuting
isApprovedImplementation(_implementation)
returns (bytes memory)
{
return super.upgradeToAndCall(_implementation, _data);
}
}
91 changes: 44 additions & 47 deletions packages/contracts/contracts/ChugSplashRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
pragma solidity ^0.8.9;

import { ChugSplashManager } from "./ChugSplashManager.sol";
import { ChugSplashManagerProxy } from "./ChugSplashManagerProxy.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import { IChugSplashManager } from "./interfaces/IChugSplashManager.sol";

/**
* @title ChugSplashRegistry
Expand Down Expand Up @@ -113,81 +115,72 @@ contract ChugSplashRegistry is Ownable, Initializable {
mapping(address => bool) public protocolPaymentRecipients;

/**
* @notice Amount that must be deposited in the ChugSplashManager in order to execute a bundle.
* @notice Mapping of valid manager implementations
*/
uint256 public immutable ownerBondAmount;
mapping(address => bool) public versions;

/**
* @notice Amount of time for an executor to completely execute a bundle after claiming it.
* @param _owner Address of the owner of the registry.
*/
uint256 public immutable executionLockTime;

/**
* @notice Amount that executors are paid, denominated as a percentage of the cost of execution.
*/
uint256 public immutable executorPaymentPercentage;

uint256 public immutable protocolPaymentPercentage;

/**
* @param _ownerBondAmount Amount that must be deposited in the ChugSplashManager in
* order to execute a bundle.
* @param _executionLockTime Amount of time for an executor to completely execute a
* bundle after claiming it.
* @param _executorPaymentPercentage Amount that an executor will earn from completing a bundle,
* denominated as a percentage.
*/
constructor(
address _owner,
uint256 _ownerBondAmount,
uint256 _executionLockTime,
uint256 _executorPaymentPercentage,
uint256 _protocolPaymentPercentage
) {
ownerBondAmount = _ownerBondAmount;
executionLockTime = _executionLockTime;
executorPaymentPercentage = _executorPaymentPercentage;
protocolPaymentPercentage = _protocolPaymentPercentage;

constructor(address _owner) {
_transferOwnership(_owner);
}

/**
* @param _executors Array of executors to add.
* @param _executors Array of executors to add.
* @param _initialManagerVersion Initial manager version used for new projects before
* upgrading to the requested version.
*/
function initialize(address[] memory _executors) public initializer {
function initialize(
address _initialManagerVersion,
address[] memory _executors
) public initializer {
for (uint i = 0; i < _executors.length; i++) {
executors[_executors[i]] = true;
}

versions[_initialManagerVersion] = true;
}

/**
* @notice Claims a new project.
*
* @param _organizationID ID of the new ChugSplash project.
* @param _owner Initial owner for the new project.
* @param _organizationID ID of the new ChugSplash organization.
* @param _owner Initial owner for the new organization.
* @param _version Manager version for the new organization.
* @param _data Any data to pass to the ChugSplashManager initalizer.
*/
function claim(address _owner, bytes32 _organizationID, bool _allowManagedProposals) public {
function claim(
bytes32 _organizationID,
address _owner,
address _version,
bytes memory _data
) public {
require(
address(projects[msg.sender][_organizationID]) == address(0),
"ChugSplashRegistry: organization ID already claimed by the caller"
);

// Deploy the ChugSplashManager.
require(versions[_version] == true, "ChugSplashRegistry: invalid manager version");

// Deploy the ChugSplashManager proxy and set the implementation to the requested version
bytes32 salt = keccak256(abi.encode(msg.sender, _organizationID));
ChugSplashManager manager = new ChugSplashManager{ salt: salt }(
ChugSplashManagerProxy managerProxy = new ChugSplashManagerProxy{ salt: salt }(
this,
executionLockTime,
ownerBondAmount,
executorPaymentPercentage,
protocolPaymentPercentage
address(this)
);
manager.initialize(_owner, _organizationID, _allowManagedProposals);
managerProxy.upgradeToAndCall(
_version,
abi.encodeCall(IChugSplashManager.initialize, _data)
);

// Change manager proxy admin to the Org owner
managerProxy.changeAdmin(_owner);

projects[msg.sender][_organizationID] = ChugSplashManager(payable(address(manager)));
managers[ChugSplashManager(payable(address(manager)))] = true;
projects[msg.sender][_organizationID] = ChugSplashManager(payable(address(managerProxy)));
managers[ChugSplashManager(payable(address(managerProxy)))] = true;

emit ChugSplashProjectClaimed(_organizationID, msg.sender, address(manager), _owner);
emit ChugSplashProjectClaimed(_organizationID, msg.sender, address(managerProxy), _owner);
}

/**
Expand Down Expand Up @@ -292,4 +285,8 @@ contract ChugSplashRegistry is Ownable, Initializable {
protocolPaymentRecipients[_recipient] = false;
emit ProtocolPaymentRecipientRemoved(_recipient);
}

function setVersion(address _version, bool _isVersion) external onlyOwner {
versions[_version] = _isVersion;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ interface IChugSplashManager {
function activeBundleId() external returns (bytes32);

function proposers(address) external returns (bool);

function isExecuting() external view returns (bool);

function initialize(bytes memory) external;
}
4 changes: 2 additions & 2 deletions packages/contracts/contracts/libraries/Proxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ contract Proxy {
*
* @param _implementation Address of the implementation contract.
*/
function upgradeTo(address _implementation) external proxyCallIfNotAdmin {
function upgradeTo(address _implementation) public virtual proxyCallIfNotAdmin {
_setImplementation(_implementation);
}

Expand All @@ -98,7 +98,7 @@ contract Proxy {
function upgradeToAndCall(
address _implementation,
bytes calldata _data
) external payable proxyCallIfNotAdmin returns (bytes memory) {
) public payable virtual proxyCallIfNotAdmin returns (bytes memory) {
_setImplementation(_implementation);
(bool success, bytes memory returndata) = _implementation.delegatecall(_data);
require(success, "Proxy: delegatecall to new implementation contract failed");
Expand Down
1 change: 1 addition & 0 deletions packages/contracts/src/ifaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import path from 'path'
/* eslint-disable @typescript-eslint/no-var-requires */
export const ChugSplashRegistryArtifact = require('../artifacts/contracts/ChugSplashRegistry.sol/ChugSplashRegistry.json')
export const ChugSplashManagerArtifact = require('../artifacts/contracts/ChugSplashManager.sol/ChugSplashManager.json')
export const ChugSplashManagerProxyArtifact = require('../artifacts/contracts/ChugSplashManagerProxy.sol/ChugSplashManagerProxy.json')
export const ProxyArtifact = require('../artifacts/contracts/libraries/Proxy.sol/Proxy.json')
export const DefaultUpdaterArtifact = require('../artifacts/contracts/updaters/DefaultUpdater.sol/DefaultUpdater.json')
export const OZUUPSUpdaterArtifact = require('../artifacts/contracts/updaters/OZUUPSUpdater.sol/OZUUPSUpdater.json')
Expand Down
50 changes: 33 additions & 17 deletions packages/core/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
OZ_UUPS_UPDATER_ADDRESS,
OWNER_MULTISIG_ADDRESS,
ChugSplashRegistryABI,
ChugSplashManagerABI,
} from '@chugsplash/contracts'
import { utils, constants } from 'ethers'
import { CustomChain } from '@nomiclabs/hardhat-etherscan/dist/src/types'
Expand Down Expand Up @@ -47,13 +48,7 @@ const defaultUpdaterSourceName = DefaultUpdaterArtifact.sourceName
const OZUUPSUpdaterSourceName = OZUUPSUpdaterArtifact.sourceName
const OZTransparentAdapterSourceName = OZTransparentAdapterArtifact.sourceName

export const registryConstructorValues = [
OWNER_MULTISIG_ADDRESS,
OWNER_BOND_AMOUNT,
EXECUTION_LOCK_TIME,
EXECUTOR_PAYMENT_PERCENTAGE,
PROTOCOL_PAYMENT_PERCENTAGE,
]
export const registryConstructorValues = [OWNER_MULTISIG_ADDRESS]

const [registryConstructorFragment] = ChugSplashRegistryABI.filter(
(fragment) => fragment.type === 'constructor'
Expand All @@ -77,23 +72,44 @@ export const CHUGSPLASH_REGISTRY_ADDRESS = utils.getCreate2Address(
)
)

export const chugsplashManagerConstructorArgs = {
_registry: CHUGSPLASH_REGISTRY_ADDRESS,
_executionLockTime: EXECUTION_LOCK_TIME,
_ownerBondAmount: OWNER_BOND_AMOUNT.toString(),
_executorPaymentPercentage: EXECUTOR_PAYMENT_PERCENTAGE,
_protocolPaymentPercentage: PROTOCOL_PAYMENT_PERCENTAGE,
}
export const managerConstructorValues = [
CHUGSPLASH_REGISTRY_ADDRESS,
EXECUTION_LOCK_TIME,
OWNER_BOND_AMOUNT.toString(),
EXECUTOR_PAYMENT_PERCENTAGE,
PROTOCOL_PAYMENT_PERCENTAGE,
]

const [managerConstructorFragment] = ChugSplashManagerABI.filter(
(fragment) => fragment.type === 'constructor'
)
const managerConstructorArgTypes = managerConstructorFragment.inputs.map(
(input) => input.type
)

export const CHUGSPLASH_MANAGER_V1_ADDRESS = utils.getCreate2Address(
DETERMINISTIC_DEPLOYMENT_PROXY_ADDRESS,
constants.HashZero,
utils.solidityKeccak256(
['bytes', 'bytes'],
[
ChugSplashManagerArtifact.bytecode,
utils.defaultAbiCoder.encode(
managerConstructorArgTypes,
managerConstructorValues
),
]
)
)

export const CHUGSPLASH_CONSTRUCTOR_ARGS = {}
CHUGSPLASH_CONSTRUCTOR_ARGS[chugsplashRegistrySourceName] = [
OWNER_BOND_AMOUNT,
EXECUTION_LOCK_TIME,
EXECUTOR_PAYMENT_PERCENTAGE,
]
CHUGSPLASH_CONSTRUCTOR_ARGS[chugsplashManagerSourceName] = Object.values(
chugsplashManagerConstructorArgs
)
CHUGSPLASH_CONSTRUCTOR_ARGS[chugsplashManagerSourceName] =
managerConstructorValues
CHUGSPLASH_CONSTRUCTOR_ARGS[defaultAdapterSourceName] = [
DEFAULT_UPDATER_ADDRESS,
]
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/etherscan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,15 @@ import {
OZUUPSAccessControlAdapterArtifact,
OZ_UUPS_OWNABLE_ADAPTER_ADDRESS,
OZ_UUPS_ACCESS_CONTROL_ADAPTER_ADDRESS,
ChugSplashManagerArtifact,
} from '@chugsplash/contracts'
import { request } from 'undici'
import { CompilerInput } from 'hardhat/types'

import {
CHUGSPLASH_CONSTRUCTOR_ARGS,
CHUGSPLASH_REGISTRY_ADDRESS,
CHUGSPLASH_MANAGER_V1_ADDRESS,
customChains,
} from './constants'
import { CanonicalChugSplashConfig } from './config/types'
Expand Down Expand Up @@ -159,6 +161,10 @@ export const verifyChugSplash = async (
)

const contracts = [
{
artifact: ChugSplashManagerArtifact,
address: CHUGSPLASH_MANAGER_V1_ADDRESS,
},
{ artifact: DefaultUpdaterArtifact, address: DEFAULT_UPDATER_ADDRESS },
{ artifact: DefaultAdapterArtifact, address: DEFAULT_ADAPTER_ADDRESS },
{
Expand Down
Loading

0 comments on commit 69dcfba

Please sign in to comment.