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

Chain exit: add support for upgrades and ownership transfer to the minimal proxy implementation #25

Merged
merged 4 commits into from
Dec 9, 2024
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
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
Loading