@@ -4,7 +4,7 @@ pragma solidity ^0.8.23;
44import {Proxy} from "openzeppelin-contracts/contracts/proxy/Proxy.sol " ;
55import {ERC1967Utils } from "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol " ;
66import {ECDSA} from "openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol " ;
7- import {Address } from "openzeppelin-contracts/contracts/utils/Address .sol " ;
7+ import {StorageSlot } from "openzeppelin-contracts/contracts/utils/StorageSlot .sol " ;
88
99/// @title EIP7702Proxy
1010/// @notice Proxy contract designed for EIP-7702 smart accounts
@@ -24,6 +24,12 @@ contract EIP7702Proxy is Proxy {
2424 /// @notice Function selector on the implementation that is guarded from direct calls
2525 bytes4 immutable guardedInitializer;
2626
27+ /// @dev Storage slot with the initialized flag, conforms to ERC-7201
28+ bytes32 internal constant INITIALIZED_SLOT =
29+ keccak256 (
30+ abi.encode (uint256 (keccak256 ("EIP7702Proxy.initialized " )) - 1 )
31+ ) & ~ bytes32 (uint256 (0xff ));
32+
2733 /// @notice Emitted when the implementation is upgraded
2834 event Upgraded (address indexed implementation );
2935
@@ -33,8 +39,8 @@ contract EIP7702Proxy is Proxy {
3339 /// @notice Emitted when the `guardedInitializer` is called
3440 error InvalidInitializer ();
3541
36- /// @notice Emitted when initialization is attempted on a non-initial implementation
37- error InvalidImplementation ();
42+ /// @notice Emitted when trying to delegate before initialization
43+ error ProxyNotInitialized ();
3844
3945 /// @notice Emitted when constructor arguments are zero
4046 error ZeroValueConstructorArguments ();
@@ -52,6 +58,11 @@ contract EIP7702Proxy is Proxy {
5258 guardedInitializer = initializer;
5359 }
5460
61+ /// @dev Checks if proxy has been initialized by checking the initialized flag
62+ function _isInitialized () internal view returns (bool ) {
63+ return StorageSlot.getBooleanSlot (INITIALIZED_SLOT).value;
64+ }
65+
5566 /// @notice Initializes the proxy and implementation with a signed payload
5667 ///
5768 /// @dev Signature must be from this contract's address
@@ -62,17 +73,20 @@ contract EIP7702Proxy is Proxy {
6273 bytes calldata args ,
6374 bytes calldata signature
6475 ) external {
65- // construct hash incompatible with wallet RPCs to avoid phishing
76+ // Construct hash without Ethereum signed message prefix to prevent phishing via standard wallet signing.
77+ // Since this proxy is designed for EIP-7702 (where the proxy address is an EOA),
78+ // using a raw hash ensures that initialization signatures cannot be obtained through normal
79+ // wallet "Sign Message" prompts. This prevents malicious dapps from tricking users into
80+ // initializing their account via standard wallet signing flows.
81+ // Wallets must implement custom signing logic at a lower level to support initialization.
6682 bytes32 hash = keccak256 (abi.encode (proxy, args));
6783 address recovered = ECDSA.recover (hash, signature);
6884 if (recovered != address (this )) revert InvalidSignature ();
6985
70- // enforce initialization only on initial implementation
71- address implementation = _implementation ();
72- if (implementation != initialImplementation)
73- revert InvalidImplementation ();
86+ // Set initialized flag before upgrading
87+ StorageSlot.getBooleanSlot (INITIALIZED_SLOT).value = true ;
7488
75- // Set the ERC-1967 implementation slot, emit Upgraded event, call the initializer on the initial implementation
89+ // Set the ERC-1967 implementation slot, emit Upgraded event, call the initializer
7690 ERC1967Utils .upgradeToAndCall (
7791 initialImplementation,
7892 abi.encodePacked (guardedInitializer, args)
@@ -91,6 +105,9 @@ contract EIP7702Proxy is Proxy {
91105 bytes32 hash ,
92106 bytes calldata signature
93107 ) external returns (bytes4 ) {
108+ // Check initialization status first
109+ if (! _isInitialized ()) revert ProxyNotInitialized ();
110+
94111 // First try delegatecall to implementation
95112 (bool success , bytes memory result ) = _implementation ().delegatecall (
96113 msg .data
@@ -117,22 +134,19 @@ contract EIP7702Proxy is Proxy {
117134 return ERC1271_FAIL_VALUE ;
118135 }
119136
120- /// @inheritdoc Proxy
121- function _implementation () internal view override returns (address ) {
122- address implementation = ERC1967Utils .getImplementation ();
123- return
124- implementation != address (0 )
125- ? implementation
126- : initialImplementation;
127- }
128-
129137 /// @inheritdoc Proxy
130138 /// @dev Handles ERC-1271 signature validation by enforcing an ecrecover check if signatures fail `isValidSignature` check
131139 /// @dev Guards a specified initializer function from being called directly
132140 function _fallback () internal override {
141+ if (! _isInitialized ()) revert ProxyNotInitialized ();
142+
133143 // block guarded initializer from being called
134144 if (msg .sig == guardedInitializer) revert InvalidInitializer ();
135145
136146 _delegate (_implementation ());
137147 }
148+
149+ function _implementation () internal view override returns (address ) {
150+ return ERC1967Utils .getImplementation ();
151+ }
138152}
0 commit comments