Skip to content

Commit a8c6456

Browse files
authored
Update smart contracts to v1.1.0 (#117)
1 parent d0491d4 commit a8c6456

File tree

7 files changed

+176
-76
lines changed

7 files changed

+176
-76
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 & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ libs = ["lib"]
66
[profile.deploy]
77
optimizer = true
88
optimizer_runs = 999999
9+
via_ir = true
10+
evm_version = "prague"
11+
solc_version = "0.8.23"
912

1013
[fmt]
1114
sort_imports = true

script/DeployFactory.s.sol

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,22 @@ import {SafeSingletonDeployer} from "safe-singleton-deployer-sol/src/SafeSinglet
77
import {CoinbaseSmartWallet, CoinbaseSmartWalletFactory} from "../src/CoinbaseSmartWalletFactory.sol";
88

99
contract DeployFactoryScript is Script {
10-
address constant EXPECTED_IMPLEMENTATION = 0x000100abaad02f1cfC8Bbe32bD5a564817339E72;
11-
address constant EXPECTED_FACTORY = 0x0BA5ED0c6AA8c49038F819E587E2633c4A9F428a;
10+
address constant EXPECTED_IMPLEMENTATION = 0x00000110dCdEdC9581cb5eCB8467282f2926534d;
11+
address constant EXPECTED_FACTORY = 0xBA5ED110eFDBa3D005bfC882d75358ACBbB85842;
1212

1313
function run() public {
1414
console2.log("Deploying on chain ID", block.chainid);
1515
address implementation = SafeSingletonDeployer.broadcastDeploy({
1616
creationCode: type(CoinbaseSmartWallet).creationCode,
17-
salt: 0x3438ae5ce1ff7750c1e09c4b28e2a04525da412f91561eb5b57729977f591fbb
17+
salt: 0x3771220e68256b8d5aa359fe953bf594dad1a5473239d1251256f0e5e7473b16
1818
});
1919
console2.log("implementation", implementation);
2020
assert(implementation == EXPECTED_IMPLEMENTATION);
21+
2122
address factory = SafeSingletonDeployer.broadcastDeploy({
2223
creationCode: type(CoinbaseSmartWalletFactory).creationCode,
2324
args: abi.encode(EXPECTED_IMPLEMENTATION),
24-
salt: 0x278d06dab87f67bb2d83470a70c8975a2c99872f290058fb43bcc47da5f0390c
25+
salt: 0x0000000000000000000000000000000000000000e8448b6b950698874d6a35bd
2526
});
2627
console2.log("factory", factory);
2728
assert(factory == EXPECTED_FACTORY);

src/CoinbaseSmartWallet.sol

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

70+
/// @notice Thrown when an upgrade is attempted to an implementation that does not exist.
71+
///
72+
/// @param implementation The address of the implementation that has no code.
73+
error InvalidImplementation(address implementation);
74+
7075
/// @notice Reverts if the caller is not the EntryPoint.
7176
modifier onlyEntryPoint() virtual {
7277
if (msg.sender != entryPoint()) {
@@ -160,6 +165,22 @@ contract CoinbaseSmartWallet is ERC1271, IAccount, MultiOwnable, UUPSUpgradeable
160165
if (key != REPLAYABLE_NONCE_KEY) {
161166
revert InvalidNonceKey(key);
162167
}
168+
169+
// Check for upgrade calls in the batch and validate implementation has code
170+
bytes[] memory calls = abi.decode(userOp.callData[4:], (bytes[]));
171+
for (uint256 i; i < calls.length; i++) {
172+
bytes memory callData = calls[i];
173+
bytes4 selector = bytes4(callData);
174+
175+
if (selector == UUPSUpgradeable.upgradeToAndCall.selector) {
176+
address newImplementation;
177+
assembly {
178+
// Skip reading the first 32 bytes (length prefix) + 4 bytes (function selector)
179+
newImplementation := mload(add(callData, 36))
180+
}
181+
if (newImplementation.code.length == 0) revert InvalidImplementation(newImplementation);
182+
}
183+
}
163184
} else {
164185
if (key == REPLAYABLE_NONCE_KEY) {
165186
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+
UserOperation 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+
UserOperation 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
}

test/CoinbaseSmartWalletFactory.t.sol

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,21 @@ contract CoinbaseSmartWalletFactoryTest is Test {
1919
owners.push(abi.encode(address(2)));
2020
}
2121

22+
function test_constructor_revertsIfImplementationIsNotDeployed(address implementation) public {
23+
vm.assume(implementation.code.length == 0);
24+
vm.expectRevert(CoinbaseSmartWalletFactory.ImplementationUndeployed.selector);
25+
new CoinbaseSmartWalletFactory(implementation);
26+
}
27+
2228
function test_constructor_setsImplementation(address implementation) public {
29+
// avoid precompiles in fuzz runs
30+
vm.assume(uint160(implementation) > 100);
31+
32+
// set bytecode if not already set
33+
if (implementation.code.length == 0) {
34+
vm.etch(implementation, address(account).code);
35+
}
36+
2337
factory = new CoinbaseSmartWalletFactory(implementation);
2438
assertEq(factory.implementation(), implementation);
2539
}
@@ -32,6 +46,13 @@ contract CoinbaseSmartWalletFactoryTest is Test {
3246
assert(a.isOwnerAddress(address(2)));
3347
}
3448

49+
function test_createAccount_emitsAccountCreatedEvent(uint256 nonce) public {
50+
address expectedAddress = factory.getAddress(owners, nonce);
51+
vm.expectEmit(true, true, true, true);
52+
emit CoinbaseSmartWalletFactory.AccountCreated(expectedAddress, owners, nonce);
53+
factory.createAccount(owners, nonce);
54+
}
55+
3556
function test_revertsIfNoOwners() public {
3657
owners.pop();
3758
owners.pop();

0 commit comments

Comments
 (0)