-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add ability for the minimal proxy implementation to switch admins and…
… upgrade implementations, for future chain exit purposes
- Loading branch information
Showing
4 changed files
with
276 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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))); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | ||
} | ||
} |