diff --git a/src/DeployChain.sol b/src/DeployChain.sol index 16fd7bd..6460dfd 100644 --- a/src/DeployChain.sol +++ b/src/DeployChain.sol @@ -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"; @@ -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) }); } @@ -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) }); } @@ -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) - } - } } diff --git a/src/ResolvingProxy.sol b/src/ResolvingProxy.sol new file mode 100644 index 0000000..bce6775 --- /dev/null +++ b/src/ResolvingProxy.sol @@ -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; + } +} diff --git a/src/ResolvingProxyFactory.sol b/src/ResolvingProxyFactory.sol new file mode 100644 index 0000000..a27b796 --- /dev/null +++ b/src/ResolvingProxyFactory.sol @@ -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))); + } +} diff --git a/test/ResolvingProxyFactory.t.sol b/test/ResolvingProxyFactory.t.sol new file mode 100644 index 0000000..aa7c4f9 --- /dev/null +++ b/test/ResolvingProxyFactory.t.sol @@ -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"); + } +}