Skip to content

Commit 13a9075

Browse files
committed
Merge branch 'entrypoint-0.7' into entrykit
2 parents f7452bf + a59dddf commit 13a9075

File tree

7 files changed

+186
-103
lines changed

7 files changed

+186
-103
lines changed

.gas-snapshot

Lines changed: 76 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,76 @@
1-
AddOwnerAddressTest:testEmitsAddOwner() (gas: 92424)
2-
AddOwnerAddressTest:testIncreasesOwnerIndex() (gas: 90698)
3-
AddOwnerAddressTest:testRevertsIfAlreadyOwner() (gas: 93235)
4-
AddOwnerAddressTest:testRevertsIfCalledByNonOwner() (gas: 11786)
5-
AddOwnerAddressTest:testSetsIsOwner() (gas: 90492)
6-
AddOwnerAddressTest:testSetsOwnerAtIndex() (gas: 98992)
7-
AddOwnerPublicKeyTest:testEmitsAddOwner() (gas: 115559)
8-
AddOwnerPublicKeyTest:testFuzzIsOwnerPublicKey(bytes32,bytes32) (runs: 257, μ: 115372, ~: 115372)
9-
AddOwnerPublicKeyTest:testRevertsIfAlreadyOwner() (gas: 116399)
10-
AddOwnerPublicKeyTest:testRevertsIfCalledByNonOwner() (gas: 11776)
11-
AddOwnerPublicKeyTest:testSetsIsOwner() (gas: 113813)
12-
AddOwnerPublicKeyTest:testSetsOwnerAtIndex() (gas: 128638)
13-
CoinbaseSmartWallet1271InputGeneratorTest:testGetReplaySafeHashForDeployedAccount() (gas: 308479)
14-
CoinbaseSmartWallet1271InputGeneratorTest:testGetReplaySafeHashForUndeployedAccount() (gas: 291508)
15-
CoinbaseSmartWalletFactoryTest:testDeployDeterministicPassValues() (gas: 267283)
16-
CoinbaseSmartWalletFactoryTest:test_CreateAccount_ReturnsPredeterminedAddress_WhenAccountAlreadyExists() (gas: 286935)
17-
CoinbaseSmartWalletFactoryTest:test_RevertsIfLength32ButLargerThanAddress() (gas: 298669)
18-
CoinbaseSmartWalletFactoryTest:test_constructor_setsImplementation(address) (runs: 257, μ: 313355, ~: 313355)
19-
CoinbaseSmartWalletFactoryTest:test_createAccountDeploysToPredeterminedAddress() (gas: 268634)
20-
CoinbaseSmartWalletFactoryTest:test_createAccountSetsOwnersCorrectly() (gas: 277324)
21-
CoinbaseSmartWalletFactoryTest:test_exitIfAccountIsAlreadyInitialized() (gas: 267739)
22-
CoinbaseSmartWalletFactoryTest:test_implementation_returnsExpectedAddress() (gas: 7676)
23-
CoinbaseSmartWalletFactoryTest:test_initCodeHash() (gas: 7912)
24-
CoinbaseSmartWalletFactoryTest:test_revertsIfNoOwners() (gas: 29232)
25-
ERC1271Test:test_returnsExpectedDomainHashWhenProxy() (gas: 29243)
26-
ERC1271Test:test_static() (gas: 3247131)
27-
MultiOwnableInitializeTest:testRevertsIfLength32ButLargerThanAddress() (gas: 81111)
28-
MultiOwnableInitializeTest:testRevertsIfLength32NotAddress() (gas: 81092)
29-
MultiOwnableInitializeTest:testRevertsIfLengthNot32Or64() (gas: 103395)
30-
RemoveLastOwnerTest:test_emitsRemoveOwner() (gas: 50363)
31-
RemoveLastOwnerTest:test_removesOwner() (gas: 49657)
32-
RemoveLastOwnerTest:test_removesOwnerAtIndex() (gas: 49588)
33-
RemoveLastOwnerTest:test_revert_whenCalledByNonOwner(address) (runs: 257, μ: 19094, ~: 19094)
34-
RemoveLastOwnerTest:test_revert_whenNoOwnerAtIndex() (gas: 48407)
35-
RemoveLastOwnerTest:test_revert_whenWrongOwnerAtIndex() (gas: 34181)
36-
RemoveLastOwnerTest:test_reverts_whenNotLastOwner() (gas: 123462)
37-
RemoveOwnerAtIndexTest:test_emitsRemoveOwner() (gas: 55549)
38-
RemoveOwnerAtIndexTest:test_removesOwner() (gas: 54828)
39-
RemoveOwnerAtIndexTest:test_removesOwnerAtIndex() (gas: 54566)
40-
RemoveOwnerAtIndexTest:test_revert_whenCalledByNonOwner(address) (runs: 257, μ: 19145, ~: 19145)
41-
RemoveOwnerAtIndexTest:test_revert_whenNoOwnerAtIndex() (gas: 33406)
42-
RemoveOwnerAtIndexTest:test_revert_whenWrongOwnerAtIndex() (gas: 36728)
43-
RemoveOwnerAtIndexTest:test_reverts_ifIsLastOwner() (gas: 7647492)
44-
TestCanSkipChainIdValidation:test_approvedSelectorsReturnTrue() (gas: 17781)
45-
TestCanSkipChainIdValidation:test_otherSelectorsReturnFalse() (gas: 12579)
46-
TestExecuteWithoutChainIdValidation:testExecute() (gas: 403873)
47-
TestExecuteWithoutChainIdValidation:testExecuteBatch() (gas: 728942)
48-
TestExecuteWithoutChainIdValidation:testExecuteBatch(uint256) (runs: 257, μ: 3628778, ~: 3584759)
49-
TestExecuteWithoutChainIdValidation:test__codesize() (gas: 50211)
50-
TestExecuteWithoutChainIdValidation:test_revertsWithReservedNonce() (gas: 81941)
51-
TestExecuteWithoutChainIdValidation:test_reverts_whenCallerNotEntryPoint() (gas: 11031)
52-
TestExecuteWithoutChainIdValidation:test_reverts_whenOneCallReverts() (gas: 467783)
53-
TestExecuteWithoutChainIdValidation:test_reverts_whenOneSelectorNotApproved() (gas: 179662)
54-
TestExecuteWithoutChainIdValidation:test_reverts_whenSelectorNotApproved() (gas: 106736)
55-
TestExecuteWithoutChainIdValidation:test_succeeds_whenSelectorAllowed() (gas: 423963)
56-
TestImplementation:testImplementation() (gas: 12558)
57-
TestInitialize:testInitialize() (gas: 21012)
58-
TestInitialize:test_cannotInitImplementation() (gas: 2896342)
59-
TestIsValidSignature:testReturnsInvalidIfPasskeySigButWrongOwnerLength() (gas: 40165)
60-
TestIsValidSignature:testRevertsIfEthereumSignatureButWrongOwnerLength() (gas: 24018)
61-
TestIsValidSignature:testRevertsIfOwnerIsInvalidEthereumAddress() (gas: 21999)
62-
TestIsValidSignature:testSmartWalletSigner() (gas: 3177948)
63-
TestIsValidSignature:testValidateSignatureWithEOASigner() (gas: 24900)
64-
TestIsValidSignature:testValidateSignatureWithEOASignerFailsWithWrongSigner() (gas: 23855)
65-
TestIsValidSignature:testValidateSignatureWithPasskeySigner() (gas: 437729)
66-
TestIsValidSignature:testValidateSignatureWithPasskeySignerFailsBadOwnerIndex() (gas: 35642)
67-
TestIsValidSignature:testValidateSignatureWithPasskeySignerFailsWithWrongBadSignature() (gas: 428169)
68-
TestUpgradeToAndCall:testUpgradeToAndCall() (gas: 25499)
69-
TestValidateUserOp:test_reverts_whenReplayableNonceKeyInvalidForSelector() (gas: 14211)
70-
TestValidateUserOp:test_reverts_whenSelectorInvalidForReplayableNonceKey() (gas: 14476)
71-
TestValidateUserOp:test_succeedsWithEOASigner() (gas: 448010)
72-
TestValidateUserOp:test_succeedsWithPasskeySigner() (gas: 785198)
1+
AddOwnerAddressTest:testEmitsAddOwner() (gas: 91954)
2+
AddOwnerAddressTest:testIncreasesOwnerIndex() (gas: 90492)
3+
AddOwnerAddressTest:testRevertsIfAlreadyOwner() (gas: 92327)
4+
AddOwnerAddressTest:testRevertsIfCalledByNonOwner() (gas: 11831)
5+
AddOwnerAddressTest:testSetsIsOwner() (gas: 90125)
6+
AddOwnerAddressTest:testSetsOwnerAtIndex() (gas: 99961)
7+
AddOwnerPublicKeyTest:testEmitsAddOwner() (gas: 115024)
8+
AddOwnerPublicKeyTest:testFuzzIsOwnerPublicKey(bytes32,bytes32) (runs: 256, μ: 114454, ~: 114454)
9+
AddOwnerPublicKeyTest:testRevertsIfAlreadyOwner() (gas: 115392)
10+
AddOwnerPublicKeyTest:testRevertsIfCalledByNonOwner() (gas: 11895)
11+
AddOwnerPublicKeyTest:testSetsIsOwner() (gas: 113193)
12+
AddOwnerPublicKeyTest:testSetsOwnerAtIndex() (gas: 130925)
13+
CoinbaseSmartWallet1271InputGeneratorTest:testGetReplaySafeHashForDeployedAccount() (gas: 311701)
14+
CoinbaseSmartWallet1271InputGeneratorTest:testGetReplaySafeHashForUndeployedAccount() (gas: 293976)
15+
CoinbaseSmartWalletFactoryTest:testDeployDeterministicPassValues() (gas: 270581)
16+
CoinbaseSmartWalletFactoryTest:test_CreateAccount_ReturnsPredeterminedAddress_WhenAccountAlreadyExists() (gas: 289811)
17+
CoinbaseSmartWalletFactoryTest:test_RevertsIfLength32ButLargerThanAddress() (gas: 303514)
18+
CoinbaseSmartWalletFactoryTest:test_constructor_revertsIfImplementationIsNotDeployed(address) (runs: 256, μ: 39338, ~: 39348)
19+
CoinbaseSmartWalletFactoryTest:test_constructor_setsImplementation(address) (runs: 256, μ: 455736, ~: 455736)
20+
CoinbaseSmartWalletFactoryTest:test_createAccountDeploysToPredeterminedAddress() (gas: 271825)
21+
CoinbaseSmartWalletFactoryTest:test_createAccountSetsOwnersCorrectly() (gas: 281476)
22+
CoinbaseSmartWalletFactoryTest:test_createAccount_emitsAccountCreatedEvent(uint256) (runs: 256, μ: 273358, ~: 273358)
23+
CoinbaseSmartWalletFactoryTest:test_exitIfAccountIsAlreadyInitialized() (gas: 271307)
24+
CoinbaseSmartWalletFactoryTest:test_implementation_returnsExpectedAddress() (gas: 7698)
25+
CoinbaseSmartWalletFactoryTest:test_initCodeHash() (gas: 7913)
26+
CoinbaseSmartWalletFactoryTest:test_revertsIfNoOwners() (gas: 29256)
27+
ERC1271Test:test_returnsExpectedDomainHashWhenProxy() (gas: 31630)
28+
ERC1271Test:test_static() (gas: 4046108)
29+
MultiOwnableInitializeTest:testRevertsIfLength32ButLargerThanAddress() (gas: 80861)
30+
MultiOwnableInitializeTest:testRevertsIfLength32NotAddress() (gas: 81027)
31+
MultiOwnableInitializeTest:testRevertsIfLengthNot32Or64() (gas: 103534)
32+
RemoveLastOwnerTest:test_emitsRemoveOwner() (gas: 50105)
33+
RemoveLastOwnerTest:test_removesOwner() (gas: 48993)
34+
RemoveLastOwnerTest:test_removesOwnerAtIndex() (gas: 49127)
35+
RemoveLastOwnerTest:test_revert_whenCalledByNonOwner(address) (runs: 256, μ: 19210, ~: 19210)
36+
RemoveLastOwnerTest:test_revert_whenNoOwnerAtIndex() (gas: 48103)
37+
RemoveLastOwnerTest:test_revert_whenWrongOwnerAtIndex() (gas: 34023)
38+
RemoveLastOwnerTest:test_reverts_whenNotLastOwner() (gas: 123054)
39+
RemoveOwnerAtIndexTest:test_emitsRemoveOwner() (gas: 55370)
40+
RemoveOwnerAtIndexTest:test_removesOwner() (gas: 54324)
41+
RemoveOwnerAtIndexTest:test_removesOwnerAtIndex() (gas: 54222)
42+
RemoveOwnerAtIndexTest:test_revert_whenCalledByNonOwner(address) (runs: 256, μ: 19232, ~: 19232)
43+
RemoveOwnerAtIndexTest:test_revert_whenNoOwnerAtIndex() (gas: 33098)
44+
RemoveOwnerAtIndexTest:test_revert_whenWrongOwnerAtIndex() (gas: 36598)
45+
RemoveOwnerAtIndexTest:test_reverts_ifIsLastOwner() (gas: 7632833)
46+
TestCanSkipChainIdValidation:test_approvedSelectorsReturnTrue() (gas: 17685)
47+
TestCanSkipChainIdValidation:test_otherSelectorsReturnFalse() (gas: 12561)
48+
TestExecuteWithoutChainIdValidation:testExecute() (gas: 485146)
49+
TestExecuteWithoutChainIdValidation:testExecuteBatch() (gas: 889868)
50+
TestExecuteWithoutChainIdValidation:testExecuteBatch(uint256) (runs: 256, μ: 4544535, ~: 4457018)
51+
TestExecuteWithoutChainIdValidation:test__codesize() (gas: 61710)
52+
TestExecuteWithoutChainIdValidation:test_revertsWithReservedNonce() (gas: 81700)
53+
TestExecuteWithoutChainIdValidation:test_reverts_whenCallerNotEntryPoint() (gas: 11148)
54+
TestExecuteWithoutChainIdValidation:test_reverts_whenOneCallReverts() (gas: 467006)
55+
TestExecuteWithoutChainIdValidation:test_reverts_whenOneSelectorNotApproved() (gas: 179933)
56+
TestExecuteWithoutChainIdValidation:test_reverts_whenSelectorNotApproved() (gas: 106565)
57+
TestExecuteWithoutChainIdValidation:test_succeeds_whenSelectorAllowed() (gas: 424623)
58+
TestImplementation:testImplementation() (gas: 12677)
59+
TestInitialize:testInitialize() (gas: 21146)
60+
TestInitialize:test_cannotInitImplementation() (gas: 3676943)
61+
TestIsValidSignature:testReturnsInvalidIfPasskeySigButWrongOwnerLength() (gas: 40556)
62+
TestIsValidSignature:testRevertsIfEthereumSignatureButWrongOwnerLength() (gas: 24272)
63+
TestIsValidSignature:testRevertsIfOwnerIsInvalidEthereumAddress() (gas: 22001)
64+
TestIsValidSignature:testSmartWalletSigner() (gas: 3967929)
65+
TestIsValidSignature:testValidateSignatureWithEOASigner() (gas: 25053)
66+
TestIsValidSignature:testValidateSignatureWithEOASignerFailsWithWrongSigner() (gas: 23780)
67+
TestIsValidSignature:testValidateSignatureWithPasskeySigner() (gas: 354806)
68+
TestIsValidSignature:testValidateSignatureWithPasskeySignerFailsBadOwnerIndex() (gas: 35960)
69+
TestIsValidSignature:testValidateSignatureWithPasskeySignerFailsWithWrongBadSignature() (gas: 345393)
70+
TestUpgradeToAndCall:testUpgradeToAndCall() (gas: 25322)
71+
TestValidateUserOp:test_reverts_whenReplayableNonceKeyInvalidForSelector() (gas: 14609)
72+
TestValidateUserOp:test_reverts_whenSelectorInvalidForReplayableNonceKey() (gas: 14469)
73+
TestValidateUserOp:test_reverts_whenUpgradeToImplementationWithNoCode(address) (runs: 256, μ: 23208, ~: 23228)
74+
TestValidateUserOp:test_succeedsWithEOASigner() (gas: 456302)
75+
TestValidateUserOp:test_succeedsWithPasskeySigner() (gas: 711409)
76+
TestValidateUserOp:test_succeeds_whenUpgradeToImplementationWithCode() (gas: 3714211)

foundry.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ out = "out"
44
libs = ["lib"]
55

66
[profile.deploy]
7-
solc_version = '0.8.23'
8-
evm_version = "paris"
97
optimizer = true
108
optimizer_runs = 999999
9+
via_ir = true
10+
evm_version = "prague"
11+
solc_version = "0.8.23"
1112

1213
[fmt]
1314
sort_imports = true

script/DeployFactory.s.sol

Lines changed: 15 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,39 +8,25 @@ import {CoinbaseSmartWallet, CoinbaseSmartWalletFactory} from "../src/CoinbaseSm
88

99
contract DeployFactoryScript is Script {
1010
address constant EXPECTED_IMPLEMENTATION =
11-
0x09655Fb46D2673302AEea47a496C7F4408f5c192;
11+
0x1d8a44516C3B3Dc1306b140106EA1b73f4FEAC2a;
1212
address constant EXPECTED_FACTORY =
13-
0x356336adA1619BeC1Ae4E6D94Dd9c0490DA414a8;
14-
15-
function isDeployed(address addr) public view returns (bool) {
16-
// Check if there is a contract at the address
17-
return addr.code.length > 0;
18-
}
13+
0x08e62719a7da36830c50a2903517c2B13d4c8204;
1914

2015
function run() public {
2116
console2.log("Deploying on chain ID", block.chainid);
17+
address implementation = SafeSingletonDeployer.broadcastDeploy({
18+
creationCode: type(CoinbaseSmartWallet).creationCode,
19+
salt: 0x3771220e68256b8d5aa359fe953bf594dad1a5473239d1251256f0e5e7473b16
20+
});
21+
console2.log("implementation", implementation);
22+
assert(implementation == EXPECTED_IMPLEMENTATION);
2223

23-
if (isDeployed(EXPECTED_IMPLEMENTATION)) {
24-
console2.log("implementation already deployed");
25-
} else {
26-
address implementation = SafeSingletonDeployer.broadcastDeploy({
27-
creationCode: type(CoinbaseSmartWallet).creationCode,
28-
salt: 0x3438ae5ce1ff7750c1e09c4b28e2a04525da412f91561eb5b57729977f591fbb
29-
});
30-
console2.log("implementation", implementation);
31-
assert(implementation == EXPECTED_IMPLEMENTATION);
32-
}
33-
34-
if (isDeployed(EXPECTED_FACTORY)) {
35-
console2.log("factory already deployed");
36-
} else {
37-
address factory = SafeSingletonDeployer.broadcastDeploy({
38-
creationCode: type(CoinbaseSmartWalletFactory).creationCode,
39-
args: abi.encode(EXPECTED_IMPLEMENTATION),
40-
salt: 0x278d06dab87f67bb2d83470a70c8975a2c99872f290058fb43bcc47da5f0390c
41-
});
42-
console2.log("factory", factory);
43-
assert(factory == EXPECTED_FACTORY);
44-
}
24+
address factory = SafeSingletonDeployer.broadcastDeploy({
25+
creationCode: type(CoinbaseSmartWalletFactory).creationCode,
26+
args: abi.encode(EXPECTED_IMPLEMENTATION),
27+
salt: 0x0000000000000000000000000000000000000000e8448b6b950698874d6a35bd
28+
});
29+
console2.log("factory", factory);
30+
assert(factory == EXPECTED_FACTORY);
4531
}
4632
}

src/CoinbaseSmartWallet.sol

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ contract CoinbaseSmartWallet is ERC1271, IAccount, MultiOwnable, UUPSUpgradeable
6969
/// @param key The invalid `PackedUserOperation.nonce` key.
7070
error InvalidNonceKey(uint256 key);
7171

72+
/// @notice Thrown when an upgrade is attempted to an implementation that does not exist.
73+
///
74+
/// @param implementation The address of the implementation that has no code.
75+
error InvalidImplementation(address implementation);
76+
7277
/// @notice Reverts if the caller is not the EntryPoint.
7378
modifier onlyEntryPoint() virtual {
7479
if (msg.sender != entryPoint()) {
@@ -163,6 +168,22 @@ contract CoinbaseSmartWallet is ERC1271, IAccount, MultiOwnable, UUPSUpgradeable
163168
if (key != REPLAYABLE_NONCE_KEY) {
164169
revert InvalidNonceKey(key);
165170
}
171+
172+
// Check for upgrade calls in the batch and validate implementation has code
173+
bytes[] memory calls = abi.decode(userOp.callData[4:], (bytes[]));
174+
for (uint256 i; i < calls.length; i++) {
175+
bytes memory callData = calls[i];
176+
bytes4 selector = bytes4(callData);
177+
178+
if (selector == UUPSUpgradeable.upgradeToAndCall.selector) {
179+
address newImplementation;
180+
assembly {
181+
// Skip reading the first 32 bytes (length prefix) + 4 bytes (function selector)
182+
newImplementation := mload(add(callData, 36))
183+
}
184+
if (newImplementation.code.length == 0) revert InvalidImplementation(newImplementation);
185+
}
186+
}
166187
} else {
167188
if (key == REPLAYABLE_NONCE_KEY) {
168189
revert InvalidNonceKey(key);

src/CoinbaseSmartWalletFactory.sol

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ contract CoinbaseSmartWalletFactory {
1414
/// @notice Address of the ERC-4337 implementation used as implementation for new accounts.
1515
address public immutable implementation;
1616

17+
/// @notice Emitted when a new account is created.
18+
///
19+
/// @param account The address of the created account.
20+
/// @param owners Array of initial owners.
21+
/// @param nonce The nonce of the created account.
22+
event AccountCreated(address indexed account, bytes[] owners, uint256 nonce);
23+
24+
/// @notice Thrown when trying to construct with an implementation that is not deployed.
25+
error ImplementationUndeployed();
26+
1727
/// @notice Thrown when trying to create a new `CoinbaseSmartWallet` account without any owner.
1828
error OwnerRequired();
1929

@@ -22,6 +32,7 @@ contract CoinbaseSmartWalletFactory {
2232
///
2333
/// @param implementation_ The address of the CoinbaseSmartWallet implementation which new accounts will proxy to.
2434
constructor(address implementation_) payable {
35+
if (implementation_.code.length == 0) revert ImplementationUndeployed();
2536
implementation = implementation_;
2637
}
2738

@@ -52,6 +63,7 @@ contract CoinbaseSmartWalletFactory {
5263
account = CoinbaseSmartWallet(payable(accountAddress));
5364

5465
if (!alreadyDeployed) {
66+
emit AccountCreated(address(account), owners, nonce);
5567
account.initialize(owners);
5668
}
5769
}

test/CoinbaseSmartWallet/ValidateUserOp.t.sol

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,42 @@ contract TestValidateUserOp is SmartWalletTestBase {
9797
);
9898
account.validateUserOp(userOp, "", 0);
9999
}
100+
101+
function test_reverts_whenUpgradeToImplementationWithNoCode(address emptyImplementation) public {
102+
vm.assume(emptyImplementation.code.length == 0);
103+
104+
// Create a UserOperation that calls executeWithoutChainIdValidation with an upgrade call
105+
bytes[] memory calls = new bytes[](1);
106+
calls[0] = abi.encodeWithSelector(UUPSUpgradeable.upgradeToAndCall.selector, emptyImplementation, "");
107+
108+
PackedUserOperation memory userOp;
109+
userOp.nonce = account.REPLAYABLE_NONCE_KEY() << 64;
110+
userOp.callData = abi.encodeWithSelector(CoinbaseSmartWallet.executeWithoutChainIdValidation.selector, calls);
111+
userOp.signature =
112+
abi.encode(CoinbaseSmartWallet.SignatureWrapper(0, abi.encodePacked(bytes32(0), bytes32(0), uint8(27))));
113+
114+
vm.startPrank(account.entryPoint());
115+
vm.expectRevert(abi.encodeWithSelector(CoinbaseSmartWallet.InvalidImplementation.selector, emptyImplementation));
116+
account.validateUserOp(userOp, "", 0);
117+
}
118+
119+
function test_succeeds_whenUpgradeToImplementationWithCode() public {
120+
// Deploy a mock implementation that has code
121+
MockCoinbaseSmartWallet mockImpl = new MockCoinbaseSmartWallet();
122+
address validImplementation = address(mockImpl);
123+
124+
// Create a UserOperation that calls executeWithoutChainIdValidation with an upgrade call
125+
bytes[] memory calls = new bytes[](1);
126+
calls[0] = abi.encodeWithSelector(UUPSUpgradeable.upgradeToAndCall.selector, validImplementation, "");
127+
128+
PackedUserOperation memory userOp;
129+
userOp.nonce = account.REPLAYABLE_NONCE_KEY() << 64;
130+
userOp.callData = abi.encodeWithSelector(CoinbaseSmartWallet.executeWithoutChainIdValidation.selector, calls);
131+
userOp.signature =
132+
abi.encode(CoinbaseSmartWallet.SignatureWrapper(0, abi.encodePacked(bytes32(0), bytes32(0), uint8(27))));
133+
134+
vm.startPrank(account.entryPoint());
135+
// Should revert with signature error (1) rather than InvalidImplementation
136+
assertEq(account.validateUserOp(userOp, "", 0), 1);
137+
}
100138
}

0 commit comments

Comments
 (0)