@@ -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,10 @@ 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
28+ bytes32 internal constant INITIALIZED_SLOT =
29+ bytes32 (uint256 (keccak256 ("EIP7702Proxy.initialized " )) - 1 );
30+
2731 /// @notice Emitted when the implementation is upgraded
2832 event Upgraded (address indexed implementation );
2933
@@ -33,8 +37,8 @@ contract EIP7702Proxy is Proxy {
3337 /// @notice Emitted when the `guardedInitializer` is called
3438 error InvalidInitializer ();
3539
36- /// @notice Emitted when initialization is attempted on a non-initial implementation
37- error InvalidImplementation ();
40+ /// @notice Emitted when trying to delegate before initialization
41+ error ProxyNotInitialized ();
3842
3943 /// @notice Emitted when constructor arguments are zero
4044 error ZeroValueConstructorArguments ();
@@ -52,6 +56,11 @@ contract EIP7702Proxy is Proxy {
5256 guardedInitializer = initializer;
5357 }
5458
59+ /// @dev Checks if proxy has been initialized by checking the initialized flag
60+ function _isInitialized () internal view returns (bool ) {
61+ return StorageSlot.getBooleanSlot (INITIALIZED_SLOT).value;
62+ }
63+
5564 /// @notice Initializes the proxy and implementation with a signed payload
5665 ///
5766 /// @dev Signature must be from this contract's address
@@ -62,17 +71,20 @@ contract EIP7702Proxy is Proxy {
6271 bytes calldata args ,
6372 bytes calldata signature
6473 ) external {
65- // construct hash incompatible with wallet RPCs to avoid phishing
74+ // Construct hash without Ethereum signed message prefix to prevent phishing via standard wallet signing.
75+ // Since this proxy is designed for EIP-7702 (where the proxy address is an EOA),
76+ // using a raw hash ensures that initialization signatures cannot be obtained through normal
77+ // wallet "Sign Message" prompts. This prevents malicious dapps from tricking users into
78+ // initializing their account via standard wallet signing flows.
79+ // Wallets must implement custom signing logic at a lower level to support initialization.
6680 bytes32 hash = keccak256 (abi.encode (proxy, args));
6781 address recovered = ECDSA.recover (hash, signature);
6882 if (recovered != address (this )) revert InvalidSignature ();
6983
70- // enforce initialization only on initial implementation
71- address implementation = _implementation ();
72- if (implementation != initialImplementation)
73- revert InvalidImplementation ();
84+ // Set initialized flag before upgrading
85+ StorageSlot.getBooleanSlot (INITIALIZED_SLOT).value = true ;
7486
75- // Set the ERC-1967 implementation slot, emit Upgraded event, call the initializer on the initial implementation
87+ // Set the ERC-1967 implementation slot, emit Upgraded event, call the initializer
7688 ERC1967Utils .upgradeToAndCall (
7789 initialImplementation,
7890 abi.encodePacked (guardedInitializer, args)
@@ -91,6 +103,9 @@ contract EIP7702Proxy is Proxy {
91103 bytes32 hash ,
92104 bytes calldata signature
93105 ) external returns (bytes4 ) {
106+ // Check initialization status first
107+ if (! _isInitialized ()) revert ProxyNotInitialized ();
108+
94109 // First try delegatecall to implementation
95110 (bool success , bytes memory result ) = _implementation ().delegatecall (
96111 msg .data
@@ -117,22 +132,19 @@ contract EIP7702Proxy is Proxy {
117132 return ERC1271_FAIL_VALUE ;
118133 }
119134
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-
129135 /// @inheritdoc Proxy
130136 /// @dev Handles ERC-1271 signature validation by enforcing an ecrecover check if signatures fail `isValidSignature` check
131137 /// @dev Guards a specified initializer function from being called directly
132138 function _fallback () internal override {
139+ if (! _isInitialized ()) revert ProxyNotInitialized ();
140+
133141 // block guarded initializer from being called
134142 if (msg .sig == guardedInitializer) revert InvalidInitializer ();
135143
136144 _delegate (_implementation ());
137145 }
146+
147+ function _implementation () internal view override returns (address ) {
148+ return ERC1967Utils .getImplementation ();
149+ }
138150}
0 commit comments