Skip to content

Commit

Permalink
feat(ct): add ProxyAdmin
Browse files Browse the repository at this point in the history
  • Loading branch information
sam-goldman committed Aug 18, 2022
1 parent efccd1a commit eb089ed
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 30 deletions.
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);
}
}
}
169 changes: 155 additions & 14 deletions packages/contracts/contracts/ProxyAdmin.sol
Original file line number Diff line number Diff line change
@@ -1,28 +1,169 @@
// 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";

/**
* @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 The storage slot that holds the address of the implementation contract for default
* proxies.
* bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)
*/
bytes32 internal constant EIP1967_IMPLEMENTATION_KEY =
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

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

/**
* @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, which can be a default proxy or a non-standard proxy.
* @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 {
// If no proxy type has been set, use the default proxy.
if (_proxyType == bytes32(0)) {
// Upgrade the proxy's implementation to be the ProxyUpdater, and call `setCode` on the
// proxy.
Proxy(_proxy).upgradeToAndCall(
proxyUpdater,
abi.encodeWithSignature("setCode(bytes32,bytes)", EIP1967_IMPLEMENTATION_KEY, _code)
);
} else {
// If a proxy type has been specified for this action, then the ProxyAdmin delegatecalls
// the corresponding adapter, which contains the logic that will be used to call the
// proxy from the context of this contract.

// 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.
_upgradeWithAdapter(_proxy, adapter, proxyUpdater);

// Delegatecall the adapter, which in turn will call the proxy to trigger a `setCode`
// action.
(bool success, ) = adapter.delegatecall(
abi.encodeWithSignature("setProxyCode(address,bytes)", _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, which can be a default proxy or a non-standard proxy.
* @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 {
// If no proxy type has been set, use the default proxy.
if (_proxyType == bytes32(0)) {
// Upgrade the proxy's implementation to be the ProxyUpdater, and call `setStorage` on
// the proxy.
Proxy(_proxy).upgradeToAndCall(
proxyUpdater,
abi.encodeWithSignature("setStorage(bytes32,bytes32)", _key, _value)
);
} else {
// If a proxy type has been specified for this action, then the ProxyAdmin delegatecalls
// the corresponding adapter, which contains the logic that will be used to call the
// proxy from the context of this contract.

// 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.encodeWithSignature("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.
_upgradeWithAdapter(_proxy, adapter, proxyUpdater);

// Delegatecall the adapter, which in turn will call the proxy to trigger a `setStorage`
// action.
(bool setStorageSuccess, ) = adapter.delegatecall(
abi.encodeWithSignature(
"setProxyStorage(address,bytes32,bytes32)",
_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.
_upgradeWithAdapter(_proxy, adapter, implementation);
}
}

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

0 comments on commit eb089ed

Please sign in to comment.