Skip to content

Commit

Permalink
Chain exit: add support for upgrades and ownership transfer to the mi…
Browse files Browse the repository at this point in the history
…nimal proxy implementation (#25)

* Add upgrade and ownership transfer support to minimal proxy

* Fix upgradeToAndCall return value

* Add some comments

* forge fmt
  • Loading branch information
mdehoog authored Dec 9, 2024
1 parent c131655 commit 3c20634
Show file tree
Hide file tree
Showing 4 changed files with 336 additions and 49 deletions.
64 changes: 15 additions & 49 deletions src/DeployChain.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import {ResolvingProxyFactory} from "./ResolvingProxyFactory.sol";
import {Portal} from "./Portal.sol";
import {OutputOracle} from "./OutputOracle.sol";
import {SystemConfigOwnable} from "./SystemConfigOwnable.sol";
Expand Down Expand Up @@ -101,13 +102,13 @@ contract DeployChain {
function deployAddresses(uint256 chainID) external view returns (DeployAddresses memory) {
bytes32 salt = keccak256(abi.encodePacked(chainID));
return DeployAddresses({
l2OutputOracle: proxyAddress(l2OutputOracle, salt),
systemConfig: proxyAddress(systemConfig, salt),
optimismPortal: proxyAddress(optimismPortal, salt),
l1CrossDomainMessenger: proxyAddress(l1CrossDomainMessenger, salt),
l1StandardBridge: proxyAddress(l1StandardBridge, salt),
l1ERC721Bridge: proxyAddress(l1ERC721Bridge, salt),
optimismMintableERC20Factory: proxyAddress(optimismMintableERC20Factory, salt)
l2OutputOracle: ResolvingProxyFactory.proxyAddress(l2OutputOracle, proxyAdmin, salt),
systemConfig: ResolvingProxyFactory.proxyAddress(systemConfig, proxyAdmin, salt),
optimismPortal: ResolvingProxyFactory.proxyAddress(optimismPortal, proxyAdmin, salt),
l1CrossDomainMessenger: ResolvingProxyFactory.proxyAddress(l1CrossDomainMessenger, proxyAdmin, salt),
l1StandardBridge: ResolvingProxyFactory.proxyAddress(l1StandardBridge, proxyAdmin, salt),
l1ERC721Bridge: ResolvingProxyFactory.proxyAddress(l1ERC721Bridge, proxyAdmin, salt),
optimismMintableERC20Factory: ResolvingProxyFactory.proxyAddress(optimismMintableERC20Factory, proxyAdmin, salt)
});
}

Expand Down Expand Up @@ -149,13 +150,13 @@ contract DeployChain {
function setupProxies(uint256 chainID) internal returns (DeployAddresses memory) {
bytes32 salt = keccak256(abi.encodePacked(chainID));
return DeployAddresses({
l2OutputOracle: setupProxy(l2OutputOracle, salt),
systemConfig: setupProxy(systemConfig, salt),
optimismPortal: setupProxy(optimismPortal, salt),
l1CrossDomainMessenger: setupProxy(l1CrossDomainMessenger, salt),
l1StandardBridge: setupProxy(l1StandardBridge, salt),
l1ERC721Bridge: setupProxy(l1ERC721Bridge, salt),
optimismMintableERC20Factory: setupProxy(optimismMintableERC20Factory, salt)
l2OutputOracle: ResolvingProxyFactory.setupProxy(l2OutputOracle, proxyAdmin, salt),
systemConfig: ResolvingProxyFactory.setupProxy(systemConfig, proxyAdmin, salt),
optimismPortal: ResolvingProxyFactory.setupProxy(optimismPortal, proxyAdmin, salt),
l1CrossDomainMessenger: ResolvingProxyFactory.setupProxy(l1CrossDomainMessenger, proxyAdmin, salt),
l1StandardBridge: ResolvingProxyFactory.setupProxy(l1StandardBridge, proxyAdmin, salt),
l1ERC721Bridge: ResolvingProxyFactory.setupProxy(l1ERC721Bridge, proxyAdmin, salt),
optimismMintableERC20Factory: ResolvingProxyFactory.setupProxy(optimismMintableERC20Factory, proxyAdmin, salt)
});
}

Expand Down Expand Up @@ -264,39 +265,4 @@ contract DeployChain {
gasPayingToken: gasToken
});
}

function setupProxy(address proxy, bytes32 salt) internal returns (address instance) {
address _proxyAdmin = proxyAdmin;
/// @solidity memory-safe-assembly
assembly {
let ptr := mload(0x40)
mstore(ptr, 0x60678060095f395ff363204e1c7a60e01b5f5273000000000000000000000000)
mstore(add(ptr, 0x14), shl(0x60, proxy))
mstore(add(ptr, 0x28), 0x6004525f5f60245f730000000000000000000000000000000000000000000000)
mstore(add(ptr, 0x31), shl(0x60, _proxyAdmin))
mstore(add(ptr, 0x45), 0x5afa3d5f5f3e3d60201416604d573d5ffd5b5f5f365f5f51365f5f375af43d5f)
mstore(add(ptr, 0x65), 0x5f3e5f3d91606557fd5bf3000000000000000000000000000000000000000000)
instance := create2(0, ptr, 0x70, salt)
}
require(instance != address(0), "Proxy: create2 failed");
}

function proxyAddress(address proxy, bytes32 salt) internal view returns (address predicted) {
address _proxyAdmin = proxyAdmin;
address deployer = address(this);
/// @solidity memory-safe-assembly
assembly {
let ptr := mload(0x40)
mstore(ptr, 0x60678060095f395ff363204e1c7a60e01b5f5273000000000000000000000000)
mstore(add(ptr, 0x14), shl(0x60, proxy))
mstore(add(ptr, 0x28), 0x6004525f5f60245f730000000000000000000000000000000000000000000000)
mstore(add(ptr, 0x31), shl(0x60, _proxyAdmin))
mstore(add(ptr, 0x45), 0x5afa3d5f5f3e3d60201416604d573d5ffd5b5f5f365f5f51365f5f375af43d5f)
mstore(add(ptr, 0x65), 0x5f3e5f3d91606557fd5bf3ff0000000000000000000000000000000000000000)
mstore(add(ptr, 0x71), shl(0x60, deployer))
mstore(add(ptr, 0x85), salt)
mstore(add(ptr, 0xa5), keccak256(ptr, 0x70))
predicted := keccak256(add(ptr, 0x70), 0x55)
}
}
}
155 changes: 155 additions & 0 deletions src/ResolvingProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

// The ProxyAdmin contract conforms to this interface.
interface IResolver {
function getProxyImplementation(address _proxy) external view returns (address);
}

/// @notice ResolvingProxy is a modification of the op-stack Proxy contract that allows for
/// proxying a proxy. This is useful to have a central upgradable proxy that this
/// contract can point to, but also support detaching this proxy so it can have its
/// own implementation.
/// @dev Only proxies that are owned by a ProxyAdmin can be proxied by this contract,
/// because it calls getProxyImplementation() on the ProxyAdmin to retrieve the
/// implementation address.
/// @dev This contract is based on the EIP-1967 transparent proxy standard. It is slightly
/// simplified in that it doesn't emit logs for implementation and admin changes. This
/// is to simplify the assembly implementation provided in ResolvingProxyFactory.
contract ResolvingProxy {
/// @notice The storage slot that holds the address of a proxy implementation.
/// @dev `bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)`
bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

/// @notice The storage slot that holds the address of the owner.
/// @dev `bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)`
bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;

/// @notice A modifier that reverts if not called by the owner or by address(0) to allow
/// eth_call to interact with this proxy without needing to use low-level storage
/// inspection. We assume that nobody is able to trigger calls from address(0) during
/// normal EVM execution.
modifier proxyCallIfNotAdmin() {
if (msg.sender == _getAdmin() || msg.sender == address(0)) {
_;
} else {
// This WILL halt the call frame on completion.
_doProxyCall();
}
}

/// @notice Sets the initial admin during contract deployment. Admin address is stored at the
/// EIP-1967 admin storage slot so that accidental storage collision with the
/// implementation is not possible.
/// @param _admin Address of the initial contract admin. Admin has the ability to access the
/// transparent proxy interface.
constructor(address _implementation, address _admin) {
_setImplementation(_implementation);
_setAdmin(_admin);
}

receive() external payable {
// Proxy call by default.
_doProxyCall();
}

fallback() external payable {
// Proxy call by default.
_doProxyCall();
}

/// @notice Gets the owner of the proxy contract.
/// @return Owner address.
function admin() external virtual proxyCallIfNotAdmin returns (address) {
return _getAdmin();
}

/// @notice Changes the owner of the proxy contract. Only callable by the owner.
/// @param _admin New owner of the proxy contract.
function changeAdmin(address _admin) external virtual proxyCallIfNotAdmin {
_setAdmin(_admin);
}

//// @notice Queries the implementation address.
/// @return Implementation address.
function implementation() external virtual proxyCallIfNotAdmin returns (address) {
return _getImplementation();
}

/// @notice Set the implementation contract address. The code at the given address will execute
/// when this contract is called.
/// @param _implementation Address of the implementation contract.
function upgradeTo(address _implementation) external virtual proxyCallIfNotAdmin {
_setImplementation(_implementation);
}

/// @notice Set the implementation and call a function in a single transaction. Useful to ensure
/// atomic execution of initialization-based upgrades.
/// @param _implementation Address of the implementation contract.
/// @param _data Calldata to delegatecall the new implementation with.
function upgradeToAndCall(address _implementation, bytes calldata _data)
external
payable
proxyCallIfNotAdmin
returns (bytes memory)
{
_setImplementation(_implementation);
_implementation = _resolveImplementation();
(bool success, bytes memory returndata) = _implementation.delegatecall(_data);
require(success, "Proxy: delegatecall to new implementation contract failed");
return returndata;
}

function _getImplementation() internal view returns (address) {
address impl;
bytes32 proxyImplementation = IMPLEMENTATION_SLOT;
assembly {
impl := sload(proxyImplementation)
}
return impl;
}

function _setImplementation(address _implementation) internal {
bytes32 proxyImplementation = IMPLEMENTATION_SLOT;
assembly {
sstore(proxyImplementation, _implementation)
}
}

function _getAdmin() internal view returns (address) {
address owner;
bytes32 proxyOwner = ADMIN_SLOT;
assembly {
owner := sload(proxyOwner)
}
return owner;
}

function _setAdmin(address _admin) internal {
bytes32 proxyOwner = ADMIN_SLOT;
assembly {
sstore(proxyOwner, _admin)
}
}

function _doProxyCall() internal {
address impl = _resolveImplementation();
assembly {
calldatacopy(0x0, 0x0, calldatasize())
let success := delegatecall(gas(), impl, 0x0, calldatasize(), 0x0, 0x0)
returndatacopy(0x0, 0x0, returndatasize())
if iszero(success) { revert(0x0, returndatasize()) }
return(0x0, returndatasize())
}
}

function _resolveImplementation() internal view returns (address) {
address proxy = _getImplementation();
bytes memory data = abi.encodeCall(IResolver.getProxyImplementation, (proxy));
(bool success, bytes memory returndata) = _getAdmin().staticcall(data);
if (success && returndata.length == 0x20) {
return abi.decode(returndata, (address));
}
return proxy;
}
}
74 changes: 74 additions & 0 deletions src/ResolvingProxyFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

import {ResolvingProxy} from "./ResolvingProxy.sol";

/// @title ResolvingProxyFactory
/// @notice ResolvingProxyFactory is a factory contract that creates ResolvingProxy instances.
/// @dev The setupProxy / proxyAddress functions provide a smaller assembly-based ResolvingProxy
/// implementation that is more gas efficient to deploy and operate than the solidity
/// ResolvingProxy implementation.
library ResolvingProxyFactory {
function setupProxy(address proxy, address admin, bytes32 salt) internal returns (address instance) {
/// @solidity memory-safe-assembly
assembly {
let ptr := mload(0x40)
mstore(ptr, shl(0xC0, 0x600661011c565b73))
mstore(add(ptr, 0x8), shl(0x60, proxy))
mstore(add(ptr, 0x1c), shl(0xE8, 0x905573))
mstore(add(ptr, 0x1f), shl(0x60, admin))
mstore(add(ptr, 0x33), 0x905561012580603f5f395ff35f365f600860dd565b8054909180548033143315)
mstore(add(ptr, 0x53), 0x171560545760045f5f375f5160e01c8063f851a4401460a25780635c60da1b14)
mstore(add(ptr, 0x73), 0x609f5780638f2839701460af5780633659cfe61460ac57634f1ef2861460aa57)
mstore(add(ptr, 0x93), 0x5b63204e1c7a60e01b5f52826004525f5f60245f845afa3d5f5f3e3d60201416)
mstore(add(ptr, 0xb3), 0x805f510290158402015f875f89895f375f935af43d5f893d60205260205f523e)
mstore(add(ptr, 0xd3), 0x5f3d890191609d57fd5bf35b50505b505f5260205ff35b5f5b93915b50506020)
mstore(add(ptr, 0xf3), 0x60045f375f518091559160d957903333602060445f375f519560649550506040)
mstore(add(ptr, 0x113), 0x96506054565b5f5ff35b7f360894a13ba1a3210667c828492db98dca3e2076cc)
mstore(add(ptr, 0x133), 0x3735a920a3ca505d382bbc7fb53127684a568b3173ae13b9f8a6016e243e63b6)
mstore(add(ptr, 0x153), shl(0x90, 0xe8ee1178d6a717850b5d61039156))
instance := create2(0, ptr, 0x161, salt)
}
require(instance != address(0), "Proxy: create2 failed");
}

function proxyAddress(address proxy, address admin, bytes32 salt) internal view returns (address predicted) {
address deployer = address(this);
/// @solidity memory-safe-assembly
assembly {
let ptr := mload(0x40)
mstore(ptr, shl(0xC0, 0x600661011c565b73))
mstore(add(ptr, 0x8), shl(0x60, proxy))
mstore(add(ptr, 0x1c), shl(0xE8, 0x905573))
mstore(add(ptr, 0x1f), shl(0x60, admin))
mstore(add(ptr, 0x33), 0x905561012580603f5f395ff35f365f600860dd565b8054909180548033143315)
mstore(add(ptr, 0x53), 0x171560545760045f5f375f5160e01c8063f851a4401460a25780635c60da1b14)
mstore(add(ptr, 0x73), 0x609f5780638f2839701460af5780633659cfe61460ac57634f1ef2861460aa57)
mstore(add(ptr, 0x93), 0x5b63204e1c7a60e01b5f52826004525f5f60245f845afa3d5f5f3e3d60201416)
mstore(add(ptr, 0xb3), 0x805f510290158402015f875f89895f375f935af43d5f893d60205260205f523e)
mstore(add(ptr, 0xd3), 0x5f3d890191609d57fd5bf35b50505b505f5260205ff35b5f5b93915b50506020)
mstore(add(ptr, 0xf3), 0x60045f375f518091559160d957903333602060445f375f519560649550506040)
mstore(add(ptr, 0x113), 0x96506054565b5f5ff35b7f360894a13ba1a3210667c828492db98dca3e2076cc)
mstore(add(ptr, 0x133), 0x3735a920a3ca505d382bbc7fb53127684a568b3173ae13b9f8a6016e243e63b6)
mstore(add(ptr, 0x153), shl(0x88, 0xe8ee1178d6a717850b5d61039156ff))
mstore(add(ptr, 0x162), shl(0x60, deployer))
mstore(add(ptr, 0x176), salt)
mstore(add(ptr, 0x196), keccak256(ptr, 0x161))
predicted := keccak256(add(ptr, 0x161), 0x55)
}
}

function setupExpensiveProxy(address proxy, address admin, bytes32 salt) internal returns (address instance) {
return address(new ResolvingProxy{salt: salt}(proxy, admin));
}

function expensiveProxyAddress(address proxy, address admin, bytes32 salt)
internal
view
returns (address predicted)
{
bytes memory bytecode = abi.encodePacked(type(ResolvingProxy).creationCode, abi.encode(proxy, admin));
bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, keccak256(bytecode)));
return address(uint160(uint256(hash)));
}
}
Loading

0 comments on commit 3c20634

Please sign in to comment.