-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Chain exit: add support for upgrades and ownership transfer to the mi…
…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
Showing
4 changed files
with
336 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,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; | ||
} | ||
} |
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,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))); | ||
} | ||
} |
Oops, something went wrong.