Skip to content

Commit 90f7d7d

Browse files
authored
Merge pull request #1 from fomoweth/erc-1967
Erc 1967
2 parents 9f23524 + ecd6272 commit 90f7d7d

14 files changed

Lines changed: 798 additions & 57 deletions

script/Counter.s.sol

Lines changed: 0 additions & 19 deletions
This file was deleted.

src/Counter.sol

Lines changed: 0 additions & 14 deletions
This file was deleted.

src/ERC1967/ERC1967Proxy.sol

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.24;
3+
4+
import {Proxy} from "../Proxy.sol";
5+
import {ERC1967Utils} from "./ERC1967Utils.sol";
6+
7+
/// @title ERC1967Proxy
8+
/// @notice Minimal upgradeable proxy that delegates calls to an implementation stored in the ERC-1967 implementation slot.
9+
/// @author fomoweth
10+
contract ERC1967Proxy is Proxy {
11+
/// @notice Thrown when the proxy is left uninitialized.
12+
error ProxyUninitialized();
13+
14+
/// @notice Initializes the proxy with an implementation and optional initializer calldata.
15+
/// @param implementation The address of the initial implementation contract.
16+
/// @param data ABI-encoded initializer calldata, or empty to skip initialization when permitted.
17+
constructor(address implementation, bytes memory data) payable {
18+
if (!_unsafeAllowUninitialized() && data.length == 0) revert ProxyUninitialized();
19+
ERC1967Utils.upgradeToAndCall(implementation, data);
20+
}
21+
22+
/// @notice Returns the current implementation used for delegation.
23+
function _implementation() internal view virtual override returns (address) {
24+
return ERC1967Utils.getImplementation();
25+
}
26+
27+
/// @notice Returns whether the proxy can be left uninitialized.
28+
function _unsafeAllowUninitialized() internal pure virtual returns (bool) {}
29+
}

src/ERC1967/ERC1967Utils.sol

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.24;
3+
4+
/// @title ERC1967Utils
5+
/// @notice Library for reading and writing ERC-1967 storage slots and emitting corresponding events for upgradeable proxies.
6+
/// @author fomoweth
7+
library ERC1967Utils {
8+
/// @notice Thrown when the provided implementation address is invalid.
9+
error InvalidImplementation();
10+
11+
/// @notice Thrown when the provided admin address is invalid.
12+
error InvalidAdmin();
13+
14+
/// @notice Thrown when the provided beacon address is invalid.
15+
error InvalidBeacon();
16+
17+
/// @notice Thrown when Ether is sent to an upgrade with no initialization call.
18+
error NonPayable();
19+
20+
/// @notice Thrown when the returned UUID does not match expected ERC-1967 slot.
21+
error UnsupportedProxiableUUID(bytes32 slot);
22+
23+
/// @notice Emitted when the ERC-1967 implementation slot is updated.
24+
event Upgraded(address indexed implementation);
25+
26+
/// @notice Emitted when the ERC-1967 admin slot is updated.
27+
event AdminChanged(address previousAdmin, address newAdmin);
28+
29+
/// @notice Emitted when the ERC-1967 beacon slot is updated.
30+
event BeaconUpgraded(address indexed beacon);
31+
32+
/// @notice Precomputed event topic for {Upgraded}.
33+
/// @dev keccak256(bytes("Upgraded(address)"))
34+
bytes32 internal constant UPGRADED_EVENT_SIGNATURE =
35+
0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b;
36+
37+
/// @notice Precomputed event topic for {AdminChanged}.
38+
/// @dev keccak256(bytes("AdminChanged(address,address)"))
39+
bytes32 internal constant ADMIN_CHANGED_EVENT_SIGNATURE =
40+
0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f;
41+
42+
/// @notice Precomputed event topic for {BeaconUpgraded}.
43+
/// @dev keccak256(bytes("BeaconUpgraded(address)"))
44+
bytes32 internal constant BEACON_UPGRADED_EVENT_SIGNATURE =
45+
0x1cf3b03a6cf19fa2baba4df148e9dcabedea7f8a5c07840e207e5c089be95d3e;
46+
47+
/// @notice ERC-1967 storage slot for the implementation address.
48+
/// @dev bytes32(uint256(keccak256(bytes("eip1967.proxy.implementation"))) - 1)
49+
bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
50+
51+
/// @notice ERC-1967 storage slot for the admin address.
52+
/// @dev bytes32(uint256(keccak256(bytes("eip1967.proxy.admin"))) - 1)
53+
bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
54+
55+
/// @notice ERC-1967 storage slot for the beacon address.
56+
/// @dev bytes32(uint256(keccak256(bytes("eip1967.proxy.beacon"))) - 1)
57+
bytes32 internal constant BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
58+
59+
/// @notice Returns the current implementation stored in the ERC-1967 implementation slot.
60+
/// @return implementation The address of the implementation contract.
61+
function getImplementation() internal view returns (address implementation) {
62+
assembly ("memory-safe") {
63+
implementation := sload(IMPLEMENTATION_SLOT)
64+
}
65+
}
66+
67+
/// @notice Upgrades the proxy implementation and optionally executes an initialization call.
68+
/// @dev Reverts with {InvalidImplementation} if `implementation` has no deployed code.
69+
/// Emits {Upgraded} with `implementation`.
70+
/// @param implementation The address of the new implementation contract.
71+
/// @param data ABI-encoded initializer calldata, or empty to skip the execution.
72+
function upgradeToAndCall(address implementation, bytes memory data) internal {
73+
assembly ("memory-safe") {
74+
implementation := shr(0x60, shl(0x60, implementation))
75+
76+
if iszero(extcodesize(implementation)) {
77+
mstore(0x00, 0x68155f9a) // InvalidImplementation()
78+
revert(0x1c, 0x04)
79+
}
80+
81+
sstore(IMPLEMENTATION_SLOT, implementation)
82+
log2(codesize(), 0x00, UPGRADED_EVENT_SIGNATURE, implementation)
83+
}
84+
85+
_executeInitialization(implementation, data);
86+
}
87+
88+
/// @notice Upgrades the proxy implementation via the UUPS pattern with proxiable UUID validation.
89+
/// @dev Reverts with {InvalidImplementation} if `proxiableUUID()` call fails or does not return 32 bytes.
90+
/// Reverts with {UnsupportedProxiableUUID} if returned UUID is not {IMPLEMENTATION_SLOT}.
91+
/// Emits {Upgraded} with `implementation`.
92+
/// @param implementation The address of the new UUPS-compliant implementation contract.
93+
/// @param data ABI-encoded initializer calldata, or empty to skip the execution.
94+
function upgradeToAndCallUUPS(address implementation, bytes memory data) internal {
95+
assembly ("memory-safe") {
96+
implementation := shr(0x60, shl(0x60, implementation))
97+
98+
mstore(0x00, 0x52d1902d) // proxiableUUID()
99+
100+
if iszero(and(eq(returndatasize(), 0x20), staticcall(gas(), implementation, 0x1c, 0x04, 0x20, 0x20))) {
101+
mstore(0x00, 0x68155f9a) // InvalidImplementation()
102+
revert(0x1c, 0x04)
103+
}
104+
105+
if iszero(eq(mload(0x20), IMPLEMENTATION_SLOT)) {
106+
mstore(0x00, 0x3878d626) // UnsupportedProxiableUUID(bytes32)
107+
revert(0x1c, 0x24)
108+
}
109+
110+
sstore(IMPLEMENTATION_SLOT, implementation)
111+
log2(codesize(), 0x00, UPGRADED_EVENT_SIGNATURE, implementation)
112+
}
113+
114+
_executeInitialization(implementation, data);
115+
}
116+
117+
/// @notice Returns the current admin stored in the ERC-1967 admin slot.
118+
/// @return admin The address of the proxy admin.
119+
function getAdmin() internal view returns (address admin) {
120+
assembly ("memory-safe") {
121+
admin := sload(ADMIN_SLOT)
122+
}
123+
}
124+
125+
/// @notice Updates the proxy admin to a new address.
126+
/// @dev Reverts with {InvalidAdmin} if `admin` is the zero address.
127+
/// Emits {AdminChanged} with previous admin and new admin.
128+
/// @param admin The address of the new proxy admin.
129+
function changeAdmin(address admin) internal {
130+
assembly ("memory-safe") {
131+
if iszero(shl(0x60, admin)) {
132+
mstore(0x00, 0xb5eba9f0) // InvalidAdmin()
133+
revert(0x1c, 0x04)
134+
}
135+
136+
admin := shr(0x60, shl(0x60, admin))
137+
138+
mstore(0x00, sload(ADMIN_SLOT))
139+
mstore(0x20, admin)
140+
sstore(ADMIN_SLOT, admin)
141+
log1(0x00, 0x40, ADMIN_CHANGED_EVENT_SIGNATURE)
142+
}
143+
}
144+
145+
/// @notice Returns the current beacon stored in the ERC-1967 beacon slot.
146+
/// @return beacon The address of the beacon contract.
147+
function getBeacon() internal view returns (address beacon) {
148+
assembly ("memory-safe") {
149+
beacon := sload(BEACON_SLOT)
150+
}
151+
}
152+
153+
/// @notice Returns the current implementation resolved by beacon via `implementation()`.
154+
/// @dev Reverts with {InvalidBeacon} if the call fails or does not return 32 bytes.
155+
/// @param beacon The address of the beacon contract to query.
156+
/// @return implementation The address of the implementation returned by the `beacon.implementation()`.
157+
function getBeaconImplementation(address beacon) internal view returns (address implementation) {
158+
assembly ("memory-safe") {
159+
mstore(0x00, 0x5c60da1b) // implementation()
160+
161+
if iszero(and(eq(returndatasize(), 0x20), staticcall(gas(), beacon, 0x1c, 0x04, 0x00, 0x20))) {
162+
mstore(0x00, 0x30740e75) // InvalidBeacon()
163+
revert(0x1c, 0x04)
164+
}
165+
166+
implementation := mload(0x00)
167+
}
168+
}
169+
170+
/// @notice Upgrades the beacon and optionally executes an initialization call on its implementation.
171+
/// @dev Reverts with {InvalidBeacon} if `implementation()` call fails or returned implementation
172+
/// has no deployed code. Emits {BeaconUpgraded} with `beacon`.
173+
/// @param beacon The address of the new beacon contract.
174+
/// @param data ABI-encoded initializer calldata, or empty to skip the execution.
175+
function upgradeBeaconToAndCall(address beacon, bytes memory data) internal {
176+
assembly ("memory-safe") {
177+
beacon := shr(0x60, shl(0x60, beacon))
178+
179+
mstore(0x00, returndatasize())
180+
mstore(0x01, 0x5c60da1b) // implementation()
181+
182+
if iszero(extcodesize(mload(staticcall(gas(), beacon, 0x1d, 0x04, 0x01, 0x20)))) {
183+
mstore(0x01, 0x30740e75) // InvalidBeacon()
184+
revert(0x1d, 0x04)
185+
}
186+
187+
sstore(BEACON_SLOT, beacon)
188+
log2(codesize(), 0x00, BEACON_UPGRADED_EVENT_SIGNATURE, beacon)
189+
}
190+
191+
_executeInitialization(getBeaconImplementation(beacon), data);
192+
}
193+
194+
/// @notice Executes initialization on `implementation` with `data` if provided; otherwise validates that no Ether was sent.
195+
/// @dev Reverts with {NonPayable} if `data` is empty and `msg.value` is nonzero.
196+
/// @param implementation The address of the target for the initialization delegatecall.
197+
/// @param data ABI-encoded initializer calldata, or empty to skip the execution.
198+
function _executeInitialization(address implementation, bytes memory data) private {
199+
assembly ("memory-safe") {
200+
switch mload(data)
201+
case 0x00 {
202+
if callvalue() {
203+
mstore(0x00, 0x6fb1b0e9) // NonPayable()
204+
revert(0x1c, 0x04)
205+
}
206+
}
207+
default {
208+
if iszero(delegatecall(gas(), implementation, add(data, 0x20), mload(data), codesize(), 0x00)) {
209+
let ptr := mload(0x40)
210+
returndatacopy(ptr, 0x00, returndatasize())
211+
revert(ptr, returndatasize())
212+
}
213+
}
214+
}
215+
}
216+
}

src/ERC1967/beacon/BeaconProxy.sol

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.24;
3+
4+
import {ERC1967Utils} from "../ERC1967Utils.sol";
5+
import {Proxy} from "../../Proxy.sol";
6+
7+
/// @title BeaconProxy
8+
contract BeaconProxy is Proxy {
9+
uint256 private immutable _beacon;
10+
11+
constructor(address beacon, bytes memory data) payable {
12+
ERC1967Utils.upgradeBeaconToAndCall(beacon, data);
13+
_beacon = uint256(uint160(beacon));
14+
}
15+
16+
/// @notice Returns the beacon.
17+
function _getBeacon() internal view virtual returns (address) {
18+
return address(uint160(_beacon));
19+
}
20+
21+
/// @notice Returns the current implementation of the associated beacon.
22+
function _implementation() internal view virtual override returns (address) {
23+
return ERC1967Utils.getBeaconImplementation(_getBeacon());
24+
}
25+
}
26+
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.24;
3+
4+
import {Ownable} from "src/utils/Ownable.sol";
5+
6+
/// @title UpgradeableBeacon
7+
contract UpgradeableBeacon is Ownable {
8+
/// @notice Thrown when the provided implementation address for the beacon is invalid.
9+
error InvalidBeaconImplementation();
10+
11+
/// @notice Emitted when the implementation returned by the beacon is updated.
12+
event Upgraded(address indexed implementation);
13+
14+
/// @notice Precomputed event topic for {Upgraded}.
15+
/// @dev keccak256(bytes("Upgraded(address)"))
16+
bytes32 private constant UPGRADED_EVENT_SIGNATURE =
17+
0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b;
18+
19+
/// @notice Storage slot for the implementation address.
20+
/// @dev uint72(bytes9(keccak256("UPGRADEABLE_BEACON_IMPLEMENTATION_SLOT")))
21+
uint256 private constant UPGRADEABLE_BEACON_IMPLEMENTATION_SLOT = 0x7adb300363fbbe04c9;
22+
23+
constructor(address initialImplementation, address initialOwner) {
24+
_setImplementation(initialImplementation);
25+
_initializeOwner(initialOwner);
26+
}
27+
28+
/// @notice Returns the current implementation.
29+
function implementation() public view virtual returns (address logic) {
30+
assembly ("memory-safe") {
31+
logic := sload(UPGRADEABLE_BEACON_IMPLEMENTATION_SLOT)
32+
}
33+
}
34+
35+
/// @notice Upgrades the beacon to a new implementation.
36+
function upgradeTo(address newImplementation) public payable virtual onlyOwner {
37+
_setImplementation(newImplementation);
38+
}
39+
40+
/// @notice Sets the implementation to `newImplementation` for this beacon.
41+
/// @dev Reverts with {InvalidBeaconImplementation} if `newImplementation`
42+
/// is not a deployed contract. Emits {Upgraded} with `newImplementation`.
43+
function _setImplementation(address newImplementation) private {
44+
assembly ("memory-safe") {
45+
newImplementation := shr(0x60, shl(0x60, newImplementation))
46+
47+
if iszero(extcodesize(newImplementation)) {
48+
mstore(0x00, 0x7e5aeae6) // InvalidBeaconImplementation()
49+
revert(0x1c, 0x04)
50+
}
51+
52+
sstore(UPGRADEABLE_BEACON_IMPLEMENTATION_SLOT, newImplementation)
53+
log2(codesize(), 0x00, UPGRADED_EVENT_SIGNATURE, newImplementation)
54+
}
55+
}
56+
}

0 commit comments

Comments
 (0)