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

feat(ct): add ProxyAdmin #43

Merged
merged 2 commits into from
Aug 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/green-dryers-raise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@chugsplash/contracts': minor
---

Adds the ProxyAdmin, which owns the proxies for a project.
22 changes: 7 additions & 15 deletions packages/contracts/contracts/ChugSplashManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,6 @@ import { MerkleTree } from "./libraries/MerkleTree.sol";
* @title ChugSplashManager
*/
contract ChugSplashManager is Owned {
/**
* @notice The storage slot that holds the address of an EIP-1967 implementation. To be used
* as the implementation key for standard proxies.
* bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)
*/
bytes32 internal constant EIP1967_IMPLEMENTATION_KEY =
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

/**
* @notice Enum representing possible ChugSplash action types.
*/
Expand Down Expand Up @@ -154,7 +146,7 @@ contract ChugSplashManager is Owned {
* @notice Mapping of target names to proxy addresses. If a target is using the default
* proxy, then its value in this mapping is the zero-address.
*/
mapping(string => address) public proxies;
mapping(string => address payable) public proxies;

/**
* @notice Mapping of target names to proxy types. If a target is using the default proxy,
Expand All @@ -166,7 +158,7 @@ contract ChugSplashManager is Owned {
* @param _registry Address of the ChugSplashRegistry.
* @param _name Name of the project this contract is managing.
* @param _owner Initial owner of this contract.
* @param _proxyUpdater Address of the ProxyUpdater for this project.
* @param _proxyUpdater Address of the ProxyUpdater.
*/
constructor(
ChugSplashRegistry _registry,
Expand All @@ -177,7 +169,7 @@ contract ChugSplashManager is Owned {
registry = _registry;
proxyUpdater = _proxyUpdater;
name = _name;
proxyAdmin = new ProxyAdmin{ salt: bytes32(0) }();
proxyAdmin = new ProxyAdmin{ salt: bytes32(0) }(_registry, _proxyUpdater);
}

/**
Expand Down Expand Up @@ -297,7 +289,7 @@ contract ChugSplashManager is Owned {

// Get the proxy to use for this target. The proxy can either be the default proxy used by
// ChugSplash or a non-standard proxy that has previously been set by the project owner.
address proxy;
address payable proxy;
if (proxyType == bytes32(0)) {
// Use a default proxy if this target has no proxy type assigned to it.

Expand Down Expand Up @@ -363,7 +355,7 @@ contract ChugSplashManager is Owned {
*/
function setProxyToTarget(
string memory _name,
address _proxy,
address payable _proxy,
bytes32 _proxyType
) external onlyOwner {
proxies[_name] = _proxy;
Expand All @@ -381,11 +373,11 @@ contract ChugSplashManager is Owned {
*
* @return Address of the proxy for the given name.
*/
function getProxyByName(string memory _name) public view returns (address) {
function getProxyByName(string memory _name) public view returns (address payable) {
return (
payable(
Create2.compute(address(this), keccak256(bytes(_name)), type(Proxy).creationCode)
)
);
}
}
}
4 changes: 3 additions & 1 deletion packages/contracts/contracts/ChugSplashRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,10 @@ contract ChugSplashRegistry {
adapters[_proxyType] == address(0),
"ChugSplashRegistry: proxy type has an existing adapter"
);
// TODO: We might want to add a check here that the adapter supports the correct interface
// (e.g. using ERC165Checker) to avoid incorrectly inputted adapter addresses.
adapters[_proxyType] = _adapter;

emit ProxyTypeAdded(_proxyType, _adapter);
}
}
}
16 changes: 16 additions & 0 deletions packages/contracts/contracts/IProxyAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

interface IProxyAdapter {
function getProxyImplementation() external returns (address implementation);

function upgradeProxyTo(address _proxy, address _implementation) external;

function setProxyCode(address _proxy, bytes memory _code) external;

function setProxyStorage(
address _proxy,
bytes32 _key,
bytes32 _value
) external;
}
130 changes: 116 additions & 14 deletions packages/contracts/contracts/ProxyAdmin.sol
Original file line number Diff line number Diff line change
@@ -1,28 +1,130 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import { ChugSplashRegistry } from "./ChugSplashRegistry.sol";
import { Proxy } from "@eth-optimism/contracts-bedrock/contracts/universal/Proxy.sol";
import { ProxyUpdater } from "./ProxyUpdater.sol";
import { IProxyAdapter } from "./IProxyAdapter.sol";

/**
* @title ProxyAdmin
* @notice The ProxyAdmin is a contract associated with each ChugSplashManager that owns the various
* proxies for a given project. The ProxyAdmin delegatecalls into various ProxyAdapter
* contracts that correspond to different proxy types. Using this pattern, the ProxyAdmin
* can universally handle all different proxy types as long as the ProxyAdmin is considered
* the owning address of each proxy.
*/
contract ProxyAdmin {
function getProxyImplementation(address _proxy, bytes32 _proxyType)
public
returns (address implementation)
{}
/**
* @notice Address of the ChugSplashRegistry.
*/
ChugSplashRegistry public immutable registry;

function upgrade(
address _proxy,
bytes32 _proxyType,
address _implementation
) public {}
/**
* @notice Address of the ProxyUpdater.
*/
address public immutable proxyUpdater;

/**
* @param _registry Address of the ChugSplashRegistry.
* @param _proxyUpdater Address of the ProxyUpdater.
*/
constructor(ChugSplashRegistry _registry, address _proxyUpdater) {
registry = _registry;
proxyUpdater = _proxyUpdater;
}

/**
* @notice Sets new code for the proxy contract's implementation.
*
* @param _proxy Address of the proxy to upgrade.
* @param _proxyType The proxy's type. This is the zero-address for default proxies.
* @param _code Creation bytecode to be deployed.
*/
function setProxyCode(
address _proxy,
address payable _proxy,
bytes32 _proxyType,
bytes memory _data
) public {}
bytes memory _code
) public {
// Get the adapter that corresponds to this proxy type.
address adapter = registry.adapters(_proxyType);
require(adapter != address(0), "ProxyAdmin: proxy type has no adapter");

// Delegatecall the adapter to upgrade the proxy's implementation to be the
// ProxyUpdater, which has the `setCode` function.
_upgradeUsingAdapter(_proxy, adapter, proxyUpdater);

// Delegatecall the adapter, which in turn will call the proxy to trigger a `setCode`
// action.
(bool success, ) = adapter.delegatecall(
abi.encodeCall(IProxyAdapter.setProxyCode, (_proxy, _code))
);
require(success, "ProxyAdmin: delegatecall to set proxy code failed");
}

/**
* @notice Modifies a storage slot within the proxy contract.
*
* @param _proxy Address of the proxy to upgrade.
* @param _proxyType The proxy's type. This is the zero-address for default proxies.
* @param _key Storage key to modify.
* @param _value New value for the storage key.
*/
function setProxyStorage(
address _proxy,
address payable _proxy,
bytes32 _proxyType,
bytes32 _key,
bytes32 _value
) public {}
) public {
// Get the adapter that corresponds to this proxy type.
address adapter = registry.adapters(_proxyType);
require(adapter != address(0), "ProxyAdmin: proxy type has no adapter");

// Get the address of the current implementation for the proxy. The ProxyAdmin will set
// the proxy's implementation back to this address after setting it to be the
// ProxyUpdater and calling `setStorage`.
(bool success, bytes memory implementationBytes) = adapter.delegatecall(
abi.encodeCall(IProxyAdapter.getProxyImplementation, ())
);
require(success, "ProxyAdmin: delegatecall to get proxy implementation failed");

// Delegatecall the adapter to upgrade the proxy's implementation to be the
// ProxyUpdater, which has the `setStorage` function.
_upgradeUsingAdapter(_proxy, adapter, proxyUpdater);

// Delegatecall the adapter, which in turn will call the proxy to trigger a `setStorage`
// action.
(bool setStorageSuccess, ) = adapter.delegatecall(
abi.encodeCall(IProxyAdapter.setProxyStorage, (_proxy, _key, _value))
);
require(setStorageSuccess, "ProxyAdmin: delegatecall to set proxy storage failed");

// Convert the implementation's type from bytes to address.
address implementation;
assembly {
implementation := mload(add(implementationBytes, 32))
}

// Delegatecall the adapter to set the proxy's implementation back to its original
// address.
_upgradeUsingAdapter(_proxy, adapter, implementation);
}

/**
* @notice Delegatecalls an adapter to upgrade a proxy's implementation contract.
*
* @param _proxy Address of the proxy to upgrade.
* @param _adapter Address of the adapter to use for the proxy.
* @param _implementation Address to set as the proxy's new implementation contract.
*/
function _upgradeUsingAdapter(
address _proxy,
address _adapter,
address _implementation
) internal {
(bool success, ) = _adapter.delegatecall(
abi.encodeCall(IProxyAdapter.upgradeProxyTo, (_proxy, _implementation))
);
require(success, "ProxyAdmin: delegatecall to upgrade proxy failed");
}
}