Skip to content

Commit

Permalink
Add ability for the minimal proxy implementation to switch admins and…
Browse files Browse the repository at this point in the history
… upgrade implementations, for future chain exit purposes
  • Loading branch information
mdehoog committed Dec 6, 2024
1 parent cee5db1 commit f92054e
Show file tree
Hide file tree
Showing 4 changed files with 276 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)
}
}
}
163 changes: 163 additions & 0 deletions src/ResolvingProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

interface IResolver {
function getProxyImplementation(address _proxy) external view returns (address);
}

/// @notice Proxy is a transparent proxy that passes through the call if the caller is the owner or
/// if the caller is address(0), meaning that the call originated from an off-chain
/// simulation.
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() public 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) public virtual proxyCallIfNotAdmin {
_setAdmin(_admin);
}

//// @notice Queries the implementation address.
/// @return Implementation address.
function implementation() public 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) public 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)
public
payable
virtual
proxyCallIfNotAdmin
returns (bytes memory)
{
_setImplementation(_implementation);
address impl = _resolveImplementation();
assembly {
calldatacopy(0x0, _data.offset, _data.length)
let success := delegatecall(gas(), impl, 0x0, _data.length, 0x0, 0x0)
returndatacopy(0x0, 0x0, returndatasize())
if iszero(success) { revert(0x0, returndatasize()) }
return(0x0, returndatasize())
}
}

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();
address admin_ = _getAdmin();

bytes memory data = abi.encodeCall(IResolver.getProxyImplementation, (proxy));
address impl;
assembly {
let success := staticcall(gas(), admin_, add(data, 0x20), mload(data), 0x0, 0x0)
if success {
if eq(returndatasize(), 0x20) {
returndatacopy(0x0, 0x0, 0x20)
impl := mload(0x0)
}
}
}
if (impl != address(0)) {
return impl;
}
return proxy;
}
}
64 changes: 64 additions & 0 deletions src/ResolvingProxyFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

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

library ResolvingProxyFactory {
function setupProxy(address proxy, address admin, bytes32 salt) internal returns (address instance) {
/// @solidity memory-safe-assembly
//600661010d565b73bebebebebebebebebebebebebebebebebebebebe905573cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd905561011380603f5f395ff3365f600760ce565b8054909180548033143315171560535760045f5f375f5160e01c8063f851a4401460975780635c60da1b1460945780638f2839701460a45780633659cfe61460a157634f1ef28614609f575b63204e1c7a60e01b5f52826004525f5f60245f845afa3d5f5f3e3d60201416805f510290158402015f875f89895f375f935af43d5f5f3e5f3d91609257fd5bf35b50505b505f5260205ff35b5f5b93915b5050602060045f375f518091559160ca57903333602060445f375f519560649550506053565b5f5ff35b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61039156
assembly {
let ptr := mload(0x40)
mstore(ptr, 0x600661010d565b73000000000000000000000000000000000000000000000000)
mstore(add(ptr, 0x8), shl(0x60, proxy))
mstore(add(ptr, 0x1c), 0x9055730000000000000000000000000000000000000000000000000000000000)
mstore(add(ptr, 0x1f), shl(0x60, admin))
mstore(add(ptr, 0x33), 0x905561011380603f5f395ff3365f600760ce565b805490918054803314331517)
mstore(add(ptr, 0x53), 0x1560535760045f5f375f5160e01c8063f851a4401460975780635c60da1b1460)
mstore(add(ptr, 0x73), 0x945780638f2839701460a45780633659cfe61460a157634f1ef28614609f575b)
mstore(add(ptr, 0x93), 0x63204e1c7a60e01b5f52826004525f5f60245f845afa3d5f5f3e3d6020141680)
mstore(add(ptr, 0xb3), 0x5f510290158402015f875f89895f375f935af43d5f5f3e5f3d91609257fd5bf3)
mstore(add(ptr, 0xd3), 0x5b50505b505f5260205ff35b5f5b93915b5050602060045f375f518091559160)
mstore(add(ptr, 0xf3), 0xca57903333602060445f375f519560649550506053565b5f5ff35b7f360894a1)
mstore(add(ptr, 0x113), 0x3ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc7fb53127)
mstore(add(ptr, 0x133), 0x684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103915600)
instance := create2(0, ptr, 0x152, 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, 0x600661010d565b73000000000000000000000000000000000000000000000000)
mstore(add(ptr, 0x8), shl(0x60, proxy))
mstore(add(ptr, 0x1c), 0x9055730000000000000000000000000000000000000000000000000000000000)
mstore(add(ptr, 0x1f), shl(0x60, admin))
mstore(add(ptr, 0x33), 0x905561011380603f5f395ff3365f600760ce565b805490918054803314331517)
mstore(add(ptr, 0x53), 0x1560535760045f5f375f5160e01c8063f851a4401460975780635c60da1b1460)
mstore(add(ptr, 0x73), 0x945780638f2839701460a45780633659cfe61460a157634f1ef28614609f575b)
mstore(add(ptr, 0x93), 0x63204e1c7a60e01b5f52826004525f5f60245f845afa3d5f5f3e3d6020141680)
mstore(add(ptr, 0xb3), 0x5f510290158402015f875f89895f375f935af43d5f5f3e5f3d91609257fd5bf3)
mstore(add(ptr, 0xd3), 0x5b50505b505f5260205ff35b5f5b93915b5050602060045f375f518091559160)
mstore(add(ptr, 0xf3), 0xca57903333602060445f375f519560649550506053565b5f5ff35b7f360894a1)
mstore(add(ptr, 0x113), 0x3ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc7fb53127)
mstore(add(ptr, 0x133), 0x684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103915600)
mstore(add(ptr, 0x152), shl(0x60, deployer))
mstore(add(ptr, 0x166), salt)
mstore(add(ptr, 0x186), keccak256(ptr, 0x152))
predicted := keccak256(add(ptr, 0x152), 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)));
}
}
34 changes: 34 additions & 0 deletions test/ResolvingProxyFactory.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

import {Vm} from "forge-std/Vm.sol";
import {Test, console} from "forge-std/Test.sol";
import {ResolvingProxyFactory} from "../src/ResolvingProxyFactory.sol";
import {ProxyAdmin} from "@eth-optimism-bedrock/src/universal/ProxyAdmin.sol";
import {Proxy} from "@eth-optimism-bedrock/src/universal/Proxy.sol";

contract Implementation {
function foo() public pure returns (string memory) {
return "bar";
}
}

contract ResolvingProxyFactoryTest is Test {
Implementation public implementation;
ProxyAdmin public admin;
Proxy public proxy;
address public resolvingProxy;

function setUp() public {
implementation = new Implementation();
admin = new ProxyAdmin(address(this));
proxy = new Proxy(address(admin));
admin.upgrade(payable(address(proxy)), address(implementation));
resolvingProxy = ResolvingProxyFactory.setupExpensiveProxy(address(proxy), address(admin), 0x00);
}

function test_setupProxy() public view {
string memory foo = Implementation(resolvingProxy).foo();
assertEq(foo, "bar");
}
}

0 comments on commit f92054e

Please sign in to comment.