Skip to content

Commit 10106b8

Browse files
committed
Merge branch 'amie/block-delegatecalls-until-initialized' into amie/dependencies
2 parents 112c353 + 4a80a1a commit 10106b8

File tree

5 files changed

+139
-1
lines changed

5 files changed

+139
-1
lines changed

lib/solady

test/EIP7702Proxy/coinbaseImplementation.t.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ contract CoinbaseImplementationTest is Test {
167167
uint256 amount
168168
) public {
169169
vm.assume(recipient != address(0));
170+
vm.assume(recipient != address(_eoa));
170171
assumeNotPrecompile(recipient);
171172
assumePayable(recipient);
172173
vm.assume(amount > 0 && amount <= 100 ether);
@@ -193,6 +194,7 @@ contract CoinbaseImplementationTest is Test {
193194
) public {
194195
vm.assume(nonOwner != address(0));
195196
vm.assume(nonOwner != _newOwner); // Ensure caller isn't the actual owner
197+
vm.assume(nonOwner != _eoa); // Ensure caller isn't the EOA address
196198

197199
address newImpl = address(new CoinbaseSmartWallet());
198200

test/EIP7702Proxy/delegate.t.sol

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,60 @@ contract DelegateTest is EIP7702ProxyBase {
6868
"State should not change when write fails"
6969
);
7070
}
71+
72+
function test_reverts_whenCallingBeforeInitialization() public {
73+
// Deploy a fresh proxy without initializing it
74+
address payable uninitProxy = payable(makeAddr("uninitProxy"));
75+
_deployProxy(uninitProxy);
76+
77+
vm.expectRevert(EIP7702Proxy.ProxyNotInitialized.selector);
78+
MockImplementation(payable(uninitProxy)).owner();
79+
}
80+
81+
function test_reverts_whenCallingWithArbitraryDataBeforeInitialization(
82+
bytes memory arbitraryCalldata
83+
) public {
84+
// Deploy a fresh proxy without initializing it
85+
address payable uninitProxy = payable(makeAddr("uninitProxy"));
86+
_deployProxy(uninitProxy);
87+
88+
// Test that it reverts with the correct error
89+
vm.expectRevert(EIP7702Proxy.ProxyNotInitialized.selector);
90+
address(uninitProxy).call(arbitraryCalldata);
91+
92+
// Also verify the low-level call fails
93+
(bool success, ) = address(uninitProxy).call(arbitraryCalldata);
94+
assertFalse(success, "Low-level call should fail");
95+
}
96+
97+
function test_continues_delegating_afterUpgrade() public {
98+
// Setup will have already initialized the proxy with initial implementation and an owner
99+
100+
// Deploy a new implementation
101+
MockImplementation newImplementation = new MockImplementation();
102+
103+
// Upgrade to the new implementation
104+
vm.prank(_newOwner);
105+
MockImplementation(_eoa).upgradeToAndCall(
106+
address(newImplementation),
107+
""
108+
);
109+
110+
// Verify the implementation was changed
111+
assertEq(
112+
_getERC1967Implementation(_eoa),
113+
address(newImplementation),
114+
"Implementation should be updated"
115+
);
116+
117+
// Try to make a call through the proxy
118+
vm.prank(_newOwner);
119+
MockImplementation(_eoa).mockFunction();
120+
121+
// Verify the call succeeded
122+
assertTrue(
123+
MockImplementation(_eoa).mockFunctionCalled(),
124+
"Should be able to call through proxy after upgrade"
125+
);
126+
}
71127
}

test/EIP7702Proxy/initialize.t.sol

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {EIP7702ProxyBase} from "../base/EIP7702ProxyBase.sol";
55
import {EIP7702Proxy} from "../../src/EIP7702Proxy.sol";
66
import {MockImplementation, RevertingInitializerMockImplementation} from "../mocks/MockImplementation.sol";
77
import {ECDSA} from "openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol";
8+
import {ERC1967Utils} from "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol";
89

910
contract InitializeTest is EIP7702ProxyBase {
1011
function test_succeeds_withValidSignatureAndArgs(address newOwner) public {
@@ -157,4 +158,70 @@ contract InitializeTest is EIP7702ProxyBase {
157158
vm.expectRevert(EIP7702Proxy.ZeroValueConstructorArguments.selector);
158159
new EIP7702Proxy(address(_implementation), bytes4(0));
159160
}
161+
162+
function test_succeeds_whenImplementationSlotAlreadySetToDifferentAddress(
163+
address mockPreviousImpl,
164+
address newOwner,
165+
uint128 uninitProxyPk
166+
) public {
167+
vm.assume(mockPreviousImpl != address(0));
168+
vm.assume(mockPreviousImpl != address(_implementation));
169+
vm.assume(mockPreviousImpl != address(_eoa));
170+
vm.assume(newOwner != address(0));
171+
vm.assume(newOwner != mockPreviousImpl);
172+
vm.assume(newOwner != _eoa);
173+
assumeNotPrecompile(mockPreviousImpl);
174+
assumeNotPrecompile(newOwner);
175+
vm.assume(uninitProxyPk != 0);
176+
vm.assume(uninitProxyPk != _EOA_PRIVATE_KEY);
177+
178+
// Derive address for uninitProxy from private key
179+
address payable uninitProxy = payable(vm.addr(uninitProxyPk));
180+
181+
// Deploy proxy template and etch its code at the target address
182+
EIP7702Proxy proxyTemplate = new EIP7702Proxy(
183+
address(_implementation),
184+
_initSelector
185+
);
186+
bytes memory proxyCode = address(proxyTemplate).code;
187+
vm.etch(uninitProxy, proxyCode);
188+
189+
// Set the implementation slot to some other address, simulating a previous implementation
190+
vm.store(
191+
uninitProxy,
192+
ERC1967Utils.IMPLEMENTATION_SLOT,
193+
bytes32(uint256(uint160(mockPreviousImpl)))
194+
);
195+
196+
// Verify implementation slot is set to the previous implementation
197+
assertEq(
198+
_getERC1967Implementation(uninitProxy),
199+
mockPreviousImpl,
200+
"Implementation slot should be set to previous implementation"
201+
);
202+
203+
// Initialize the proxy
204+
bytes memory initArgs = _createInitArgs(_newOwner);
205+
bytes32 initHash = keccak256(
206+
abi.encode(address(proxyTemplate), initArgs)
207+
);
208+
(uint8 v, bytes32 r, bytes32 s) = vm.sign(uninitProxyPk, initHash);
209+
bytes memory signature = abi.encodePacked(r, s, v);
210+
211+
EIP7702Proxy(uninitProxy).initialize(initArgs, signature);
212+
213+
// Verify implementation slot was changed to the correct implementation
214+
assertEq(
215+
_getERC1967Implementation(uninitProxy),
216+
address(_implementation),
217+
"Implementation slot should be set to correct implementation"
218+
);
219+
220+
// Verify we can make calls through the proxy now
221+
assertEq(
222+
MockImplementation(payable(uninitProxy)).owner(),
223+
_newOwner,
224+
"Should be able to call through proxy after initialization"
225+
);
226+
}
160227
}

test/EIP7702Proxy/isValidSignature.t.sol

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,19 @@ contract FailingImplementationTest is IsValidSignatureTestBase {
114114
);
115115
assertEq(result, ERC1271_FAIL_VALUE, "Should reject empty signature");
116116
}
117+
118+
function test_reverts_whenCalledBeforeInitialization() public {
119+
// Deploy a fresh proxy without initializing
120+
address payable uninitProxy = payable(makeAddr("uninitProxy"));
121+
_deployProxy(uninitProxy);
122+
123+
// Try to call isValidSignature
124+
bytes32 hash = keccak256("test message");
125+
bytes memory signature = new bytes(65);
126+
127+
vm.expectRevert(EIP7702Proxy.ProxyNotInitialized.selector);
128+
EIP7702Proxy(uninitProxy).isValidSignature(hash, signature);
129+
}
117130
}
118131

119132
/**

0 commit comments

Comments
 (0)