Skip to content

Commit

Permalink
feat(ct): add ProxyAdmin (sphinx-labs#43)
Browse files Browse the repository at this point in the history
changeset

add `abi.encodeCall` to proxy admin

removed default proxy logic in Admin

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
sam-goldman and mergify[bot] authored Aug 21, 2022
1 parent 4c73fc1 commit e7ee72d
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 30 deletions.
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");
}
}

0 comments on commit e7ee72d

Please sign in to comment.