Skip to content

Commit 3c20634

Browse files
authored
Chain exit: add support for upgrades and ownership transfer to the minimal proxy implementation (#25)
* Add upgrade and ownership transfer support to minimal proxy * Fix upgradeToAndCall return value * Add some comments * forge fmt
1 parent c131655 commit 3c20634

File tree

4 files changed

+336
-49
lines changed

4 files changed

+336
-49
lines changed

src/DeployChain.sol

Lines changed: 15 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity 0.8.15;
33

4+
import {ResolvingProxyFactory} from "./ResolvingProxyFactory.sol";
45
import {Portal} from "./Portal.sol";
56
import {OutputOracle} from "./OutputOracle.sol";
67
import {SystemConfigOwnable} from "./SystemConfigOwnable.sol";
@@ -101,13 +102,13 @@ contract DeployChain {
101102
function deployAddresses(uint256 chainID) external view returns (DeployAddresses memory) {
102103
bytes32 salt = keccak256(abi.encodePacked(chainID));
103104
return DeployAddresses({
104-
l2OutputOracle: proxyAddress(l2OutputOracle, salt),
105-
systemConfig: proxyAddress(systemConfig, salt),
106-
optimismPortal: proxyAddress(optimismPortal, salt),
107-
l1CrossDomainMessenger: proxyAddress(l1CrossDomainMessenger, salt),
108-
l1StandardBridge: proxyAddress(l1StandardBridge, salt),
109-
l1ERC721Bridge: proxyAddress(l1ERC721Bridge, salt),
110-
optimismMintableERC20Factory: proxyAddress(optimismMintableERC20Factory, salt)
105+
l2OutputOracle: ResolvingProxyFactory.proxyAddress(l2OutputOracle, proxyAdmin, salt),
106+
systemConfig: ResolvingProxyFactory.proxyAddress(systemConfig, proxyAdmin, salt),
107+
optimismPortal: ResolvingProxyFactory.proxyAddress(optimismPortal, proxyAdmin, salt),
108+
l1CrossDomainMessenger: ResolvingProxyFactory.proxyAddress(l1CrossDomainMessenger, proxyAdmin, salt),
109+
l1StandardBridge: ResolvingProxyFactory.proxyAddress(l1StandardBridge, proxyAdmin, salt),
110+
l1ERC721Bridge: ResolvingProxyFactory.proxyAddress(l1ERC721Bridge, proxyAdmin, salt),
111+
optimismMintableERC20Factory: ResolvingProxyFactory.proxyAddress(optimismMintableERC20Factory, proxyAdmin, salt)
111112
});
112113
}
113114

@@ -149,13 +150,13 @@ contract DeployChain {
149150
function setupProxies(uint256 chainID) internal returns (DeployAddresses memory) {
150151
bytes32 salt = keccak256(abi.encodePacked(chainID));
151152
return DeployAddresses({
152-
l2OutputOracle: setupProxy(l2OutputOracle, salt),
153-
systemConfig: setupProxy(systemConfig, salt),
154-
optimismPortal: setupProxy(optimismPortal, salt),
155-
l1CrossDomainMessenger: setupProxy(l1CrossDomainMessenger, salt),
156-
l1StandardBridge: setupProxy(l1StandardBridge, salt),
157-
l1ERC721Bridge: setupProxy(l1ERC721Bridge, salt),
158-
optimismMintableERC20Factory: setupProxy(optimismMintableERC20Factory, salt)
153+
l2OutputOracle: ResolvingProxyFactory.setupProxy(l2OutputOracle, proxyAdmin, salt),
154+
systemConfig: ResolvingProxyFactory.setupProxy(systemConfig, proxyAdmin, salt),
155+
optimismPortal: ResolvingProxyFactory.setupProxy(optimismPortal, proxyAdmin, salt),
156+
l1CrossDomainMessenger: ResolvingProxyFactory.setupProxy(l1CrossDomainMessenger, proxyAdmin, salt),
157+
l1StandardBridge: ResolvingProxyFactory.setupProxy(l1StandardBridge, proxyAdmin, salt),
158+
l1ERC721Bridge: ResolvingProxyFactory.setupProxy(l1ERC721Bridge, proxyAdmin, salt),
159+
optimismMintableERC20Factory: ResolvingProxyFactory.setupProxy(optimismMintableERC20Factory, proxyAdmin, salt)
159160
});
160161
}
161162

@@ -264,39 +265,4 @@ contract DeployChain {
264265
gasPayingToken: gasToken
265266
});
266267
}
267-
268-
function setupProxy(address proxy, bytes32 salt) internal returns (address instance) {
269-
address _proxyAdmin = proxyAdmin;
270-
/// @solidity memory-safe-assembly
271-
assembly {
272-
let ptr := mload(0x40)
273-
mstore(ptr, 0x60678060095f395ff363204e1c7a60e01b5f5273000000000000000000000000)
274-
mstore(add(ptr, 0x14), shl(0x60, proxy))
275-
mstore(add(ptr, 0x28), 0x6004525f5f60245f730000000000000000000000000000000000000000000000)
276-
mstore(add(ptr, 0x31), shl(0x60, _proxyAdmin))
277-
mstore(add(ptr, 0x45), 0x5afa3d5f5f3e3d60201416604d573d5ffd5b5f5f365f5f51365f5f375af43d5f)
278-
mstore(add(ptr, 0x65), 0x5f3e5f3d91606557fd5bf3000000000000000000000000000000000000000000)
279-
instance := create2(0, ptr, 0x70, salt)
280-
}
281-
require(instance != address(0), "Proxy: create2 failed");
282-
}
283-
284-
function proxyAddress(address proxy, bytes32 salt) internal view returns (address predicted) {
285-
address _proxyAdmin = proxyAdmin;
286-
address deployer = address(this);
287-
/// @solidity memory-safe-assembly
288-
assembly {
289-
let ptr := mload(0x40)
290-
mstore(ptr, 0x60678060095f395ff363204e1c7a60e01b5f5273000000000000000000000000)
291-
mstore(add(ptr, 0x14), shl(0x60, proxy))
292-
mstore(add(ptr, 0x28), 0x6004525f5f60245f730000000000000000000000000000000000000000000000)
293-
mstore(add(ptr, 0x31), shl(0x60, _proxyAdmin))
294-
mstore(add(ptr, 0x45), 0x5afa3d5f5f3e3d60201416604d573d5ffd5b5f5f365f5f51365f5f375af43d5f)
295-
mstore(add(ptr, 0x65), 0x5f3e5f3d91606557fd5bf3ff0000000000000000000000000000000000000000)
296-
mstore(add(ptr, 0x71), shl(0x60, deployer))
297-
mstore(add(ptr, 0x85), salt)
298-
mstore(add(ptr, 0xa5), keccak256(ptr, 0x70))
299-
predicted := keccak256(add(ptr, 0x70), 0x55)
300-
}
301-
}
302268
}

src/ResolvingProxy.sol

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.15;
3+
4+
// The ProxyAdmin contract conforms to this interface.
5+
interface IResolver {
6+
function getProxyImplementation(address _proxy) external view returns (address);
7+
}
8+
9+
/// @notice ResolvingProxy is a modification of the op-stack Proxy contract that allows for
10+
/// proxying a proxy. This is useful to have a central upgradable proxy that this
11+
/// contract can point to, but also support detaching this proxy so it can have its
12+
/// own implementation.
13+
/// @dev Only proxies that are owned by a ProxyAdmin can be proxied by this contract,
14+
/// because it calls getProxyImplementation() on the ProxyAdmin to retrieve the
15+
/// implementation address.
16+
/// @dev This contract is based on the EIP-1967 transparent proxy standard. It is slightly
17+
/// simplified in that it doesn't emit logs for implementation and admin changes. This
18+
/// is to simplify the assembly implementation provided in ResolvingProxyFactory.
19+
contract ResolvingProxy {
20+
/// @notice The storage slot that holds the address of a proxy implementation.
21+
/// @dev `bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)`
22+
bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
23+
24+
/// @notice The storage slot that holds the address of the owner.
25+
/// @dev `bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)`
26+
bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
27+
28+
/// @notice A modifier that reverts if not called by the owner or by address(0) to allow
29+
/// eth_call to interact with this proxy without needing to use low-level storage
30+
/// inspection. We assume that nobody is able to trigger calls from address(0) during
31+
/// normal EVM execution.
32+
modifier proxyCallIfNotAdmin() {
33+
if (msg.sender == _getAdmin() || msg.sender == address(0)) {
34+
_;
35+
} else {
36+
// This WILL halt the call frame on completion.
37+
_doProxyCall();
38+
}
39+
}
40+
41+
/// @notice Sets the initial admin during contract deployment. Admin address is stored at the
42+
/// EIP-1967 admin storage slot so that accidental storage collision with the
43+
/// implementation is not possible.
44+
/// @param _admin Address of the initial contract admin. Admin has the ability to access the
45+
/// transparent proxy interface.
46+
constructor(address _implementation, address _admin) {
47+
_setImplementation(_implementation);
48+
_setAdmin(_admin);
49+
}
50+
51+
receive() external payable {
52+
// Proxy call by default.
53+
_doProxyCall();
54+
}
55+
56+
fallback() external payable {
57+
// Proxy call by default.
58+
_doProxyCall();
59+
}
60+
61+
/// @notice Gets the owner of the proxy contract.
62+
/// @return Owner address.
63+
function admin() external virtual proxyCallIfNotAdmin returns (address) {
64+
return _getAdmin();
65+
}
66+
67+
/// @notice Changes the owner of the proxy contract. Only callable by the owner.
68+
/// @param _admin New owner of the proxy contract.
69+
function changeAdmin(address _admin) external virtual proxyCallIfNotAdmin {
70+
_setAdmin(_admin);
71+
}
72+
73+
//// @notice Queries the implementation address.
74+
/// @return Implementation address.
75+
function implementation() external virtual proxyCallIfNotAdmin returns (address) {
76+
return _getImplementation();
77+
}
78+
79+
/// @notice Set the implementation contract address. The code at the given address will execute
80+
/// when this contract is called.
81+
/// @param _implementation Address of the implementation contract.
82+
function upgradeTo(address _implementation) external virtual proxyCallIfNotAdmin {
83+
_setImplementation(_implementation);
84+
}
85+
86+
/// @notice Set the implementation and call a function in a single transaction. Useful to ensure
87+
/// atomic execution of initialization-based upgrades.
88+
/// @param _implementation Address of the implementation contract.
89+
/// @param _data Calldata to delegatecall the new implementation with.
90+
function upgradeToAndCall(address _implementation, bytes calldata _data)
91+
external
92+
payable
93+
proxyCallIfNotAdmin
94+
returns (bytes memory)
95+
{
96+
_setImplementation(_implementation);
97+
_implementation = _resolveImplementation();
98+
(bool success, bytes memory returndata) = _implementation.delegatecall(_data);
99+
require(success, "Proxy: delegatecall to new implementation contract failed");
100+
return returndata;
101+
}
102+
103+
function _getImplementation() internal view returns (address) {
104+
address impl;
105+
bytes32 proxyImplementation = IMPLEMENTATION_SLOT;
106+
assembly {
107+
impl := sload(proxyImplementation)
108+
}
109+
return impl;
110+
}
111+
112+
function _setImplementation(address _implementation) internal {
113+
bytes32 proxyImplementation = IMPLEMENTATION_SLOT;
114+
assembly {
115+
sstore(proxyImplementation, _implementation)
116+
}
117+
}
118+
119+
function _getAdmin() internal view returns (address) {
120+
address owner;
121+
bytes32 proxyOwner = ADMIN_SLOT;
122+
assembly {
123+
owner := sload(proxyOwner)
124+
}
125+
return owner;
126+
}
127+
128+
function _setAdmin(address _admin) internal {
129+
bytes32 proxyOwner = ADMIN_SLOT;
130+
assembly {
131+
sstore(proxyOwner, _admin)
132+
}
133+
}
134+
135+
function _doProxyCall() internal {
136+
address impl = _resolveImplementation();
137+
assembly {
138+
calldatacopy(0x0, 0x0, calldatasize())
139+
let success := delegatecall(gas(), impl, 0x0, calldatasize(), 0x0, 0x0)
140+
returndatacopy(0x0, 0x0, returndatasize())
141+
if iszero(success) { revert(0x0, returndatasize()) }
142+
return(0x0, returndatasize())
143+
}
144+
}
145+
146+
function _resolveImplementation() internal view returns (address) {
147+
address proxy = _getImplementation();
148+
bytes memory data = abi.encodeCall(IResolver.getProxyImplementation, (proxy));
149+
(bool success, bytes memory returndata) = _getAdmin().staticcall(data);
150+
if (success && returndata.length == 0x20) {
151+
return abi.decode(returndata, (address));
152+
}
153+
return proxy;
154+
}
155+
}

src/ResolvingProxyFactory.sol

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.15;
3+
4+
import {ResolvingProxy} from "./ResolvingProxy.sol";
5+
6+
/// @title ResolvingProxyFactory
7+
/// @notice ResolvingProxyFactory is a factory contract that creates ResolvingProxy instances.
8+
/// @dev The setupProxy / proxyAddress functions provide a smaller assembly-based ResolvingProxy
9+
/// implementation that is more gas efficient to deploy and operate than the solidity
10+
/// ResolvingProxy implementation.
11+
library ResolvingProxyFactory {
12+
function setupProxy(address proxy, address admin, bytes32 salt) internal returns (address instance) {
13+
/// @solidity memory-safe-assembly
14+
assembly {
15+
let ptr := mload(0x40)
16+
mstore(ptr, shl(0xC0, 0x600661011c565b73))
17+
mstore(add(ptr, 0x8), shl(0x60, proxy))
18+
mstore(add(ptr, 0x1c), shl(0xE8, 0x905573))
19+
mstore(add(ptr, 0x1f), shl(0x60, admin))
20+
mstore(add(ptr, 0x33), 0x905561012580603f5f395ff35f365f600860dd565b8054909180548033143315)
21+
mstore(add(ptr, 0x53), 0x171560545760045f5f375f5160e01c8063f851a4401460a25780635c60da1b14)
22+
mstore(add(ptr, 0x73), 0x609f5780638f2839701460af5780633659cfe61460ac57634f1ef2861460aa57)
23+
mstore(add(ptr, 0x93), 0x5b63204e1c7a60e01b5f52826004525f5f60245f845afa3d5f5f3e3d60201416)
24+
mstore(add(ptr, 0xb3), 0x805f510290158402015f875f89895f375f935af43d5f893d60205260205f523e)
25+
mstore(add(ptr, 0xd3), 0x5f3d890191609d57fd5bf35b50505b505f5260205ff35b5f5b93915b50506020)
26+
mstore(add(ptr, 0xf3), 0x60045f375f518091559160d957903333602060445f375f519560649550506040)
27+
mstore(add(ptr, 0x113), 0x96506054565b5f5ff35b7f360894a13ba1a3210667c828492db98dca3e2076cc)
28+
mstore(add(ptr, 0x133), 0x3735a920a3ca505d382bbc7fb53127684a568b3173ae13b9f8a6016e243e63b6)
29+
mstore(add(ptr, 0x153), shl(0x90, 0xe8ee1178d6a717850b5d61039156))
30+
instance := create2(0, ptr, 0x161, salt)
31+
}
32+
require(instance != address(0), "Proxy: create2 failed");
33+
}
34+
35+
function proxyAddress(address proxy, address admin, bytes32 salt) internal view returns (address predicted) {
36+
address deployer = address(this);
37+
/// @solidity memory-safe-assembly
38+
assembly {
39+
let ptr := mload(0x40)
40+
mstore(ptr, shl(0xC0, 0x600661011c565b73))
41+
mstore(add(ptr, 0x8), shl(0x60, proxy))
42+
mstore(add(ptr, 0x1c), shl(0xE8, 0x905573))
43+
mstore(add(ptr, 0x1f), shl(0x60, admin))
44+
mstore(add(ptr, 0x33), 0x905561012580603f5f395ff35f365f600860dd565b8054909180548033143315)
45+
mstore(add(ptr, 0x53), 0x171560545760045f5f375f5160e01c8063f851a4401460a25780635c60da1b14)
46+
mstore(add(ptr, 0x73), 0x609f5780638f2839701460af5780633659cfe61460ac57634f1ef2861460aa57)
47+
mstore(add(ptr, 0x93), 0x5b63204e1c7a60e01b5f52826004525f5f60245f845afa3d5f5f3e3d60201416)
48+
mstore(add(ptr, 0xb3), 0x805f510290158402015f875f89895f375f935af43d5f893d60205260205f523e)
49+
mstore(add(ptr, 0xd3), 0x5f3d890191609d57fd5bf35b50505b505f5260205ff35b5f5b93915b50506020)
50+
mstore(add(ptr, 0xf3), 0x60045f375f518091559160d957903333602060445f375f519560649550506040)
51+
mstore(add(ptr, 0x113), 0x96506054565b5f5ff35b7f360894a13ba1a3210667c828492db98dca3e2076cc)
52+
mstore(add(ptr, 0x133), 0x3735a920a3ca505d382bbc7fb53127684a568b3173ae13b9f8a6016e243e63b6)
53+
mstore(add(ptr, 0x153), shl(0x88, 0xe8ee1178d6a717850b5d61039156ff))
54+
mstore(add(ptr, 0x162), shl(0x60, deployer))
55+
mstore(add(ptr, 0x176), salt)
56+
mstore(add(ptr, 0x196), keccak256(ptr, 0x161))
57+
predicted := keccak256(add(ptr, 0x161), 0x55)
58+
}
59+
}
60+
61+
function setupExpensiveProxy(address proxy, address admin, bytes32 salt) internal returns (address instance) {
62+
return address(new ResolvingProxy{salt: salt}(proxy, admin));
63+
}
64+
65+
function expensiveProxyAddress(address proxy, address admin, bytes32 salt)
66+
internal
67+
view
68+
returns (address predicted)
69+
{
70+
bytes memory bytecode = abi.encodePacked(type(ResolvingProxy).creationCode, abi.encode(proxy, admin));
71+
bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, keccak256(bytecode)));
72+
return address(uint160(uint256(hash)));
73+
}
74+
}

0 commit comments

Comments
 (0)