Skip to content

Commit 85e659f

Browse files
Merge pull request #12 from StartaleLabs/fix/quantstamp-remediations
merging as the report is finalised after remediation review.
2 parents be5beb1 + 262e02f commit 85e659f

36 files changed

+1204
-659
lines changed

commitlint.config.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
1-
module.exports = { extends: ['@commitlint/config-conventional'] };
1+
module.exports = {
2+
extends: ['@commitlint/config-conventional'],
3+
rules: {
4+
'subject-case': [0, 'never']
5+
}
6+
};

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"scripts": {
1313
"build": "forge build",
1414
"build:optimized": "FOUNDRY_PROFILE=optimized forge build",
15-
"coverage": "forge coverage --report summary --report lcov --match-path 'test/unit/*'",
15+
"coverage": "forge coverage --report summary --ir-minimum --report lcov --match-path 'test/foundry/unit/*'",
1616
"deploy:mainnet": "bash -c 'source .env && forge script Deploy --rpc-url $MAINNET_RPC --account $MAINNET_DEPLOYER_NAME --broadcast --verify --chain mainnet -vvvvv'",
1717
"deploy:sepolia": "bash -c 'source .env && forge script Deploy --rpc-url $SEPOLIA_RPC --account $SEPOLIA_DEPLOYER_NAME --broadcast --verify --chain sepolia -vvvvv'",
1818
"lint:check": "yarn lint:sol && forge fmt --check",

src/StartaleSmartAccount.sol

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@
22
pragma solidity ^0.8.29;
33

44
import {BaseAccount} from './core/BaseAccount.sol';
5-
65
import {ERC7779Adapter} from './core/ERC7779Adapter.sol';
76
import {ExecutionHelper} from './core/ExecutionHelper.sol';
87
import {ModuleManager} from './core/ModuleManager.sol';
8+
import {IERC7579Account} from './interfaces/IERC7579Account.sol';
99
import {IValidator} from './interfaces/IERC7579Module.sol';
1010
import {IStartaleSmartAccount} from './interfaces/IStartaleSmartAccount.sol';
11+
import {IAccountConfig} from './interfaces/core/IAccountConfig.sol';
1112
import {ExecutionLib} from './lib/ExecutionLib.sol';
12-
import {ACCOUNT_STORAGE_LOCATION} from './types/Constants.sol';
13-
1413
import {Initializable} from './lib/Initializable.sol';
1514
import {
1615
CALLTYPE_BATCH,
@@ -24,6 +23,7 @@ import {
2423
ModeLib
2524
} from './lib/ModeLib.sol';
2625
import {NonceLib} from './lib/NonceLib.sol';
26+
import {ACCOUNT_STORAGE_LOCATION} from './types/Constants.sol';
2727
import {
2828
MODULE_TYPE_EXECUTOR,
2929
MODULE_TYPE_FALLBACK,
@@ -36,9 +36,12 @@ import {
3636
VALIDATION_FAILED,
3737
VALIDATION_SUCCESS
3838
} from './types/Constants.sol';
39-
4039
import {EmergencyUninstall} from './types/Structs.sol';
4140
import {PackedUserOperation} from '@account-abstraction/interfaces/PackedUserOperation.sol';
41+
import {IERC1271} from '@openzeppelin/contracts/interfaces/IERC1271.sol';
42+
import {IERC1155Receiver} from '@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol';
43+
import {IERC721Receiver} from '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol';
44+
import {IERC165} from '@openzeppelin/contracts/utils/introspection/IERC165.sol';
4245
import {SENTINEL, SentinelListLib, ZERO_ADDRESS} from 'sentinellist/SentinelList.sol';
4346
import {UUPSUpgradeable} from 'solady/utils/UUPSUpgradeable.sol';
4447

@@ -49,6 +52,7 @@ import {UUPSUpgradeable} from 'solady/utils/UUPSUpgradeable.sol';
4952
/// Special thanks to the Biconomy team for https://github.com/bcnmy/nexus/ on which this implementation is highly based on.
5053
contract StartaleSmartAccount is
5154
IStartaleSmartAccount,
55+
IERC165,
5256
BaseAccount,
5357
ExecutionHelper,
5458
ModuleManager,
@@ -105,12 +109,7 @@ contract StartaleSmartAccount is
105109
address validator;
106110
PackedUserOperation memory userOp = op;
107111

108-
if (op.nonce.isValidateMode()) {
109-
// do nothing special. This is introduced
110-
// to quickly identify the most commonly used
111-
// mode which is validate mode
112-
// and avoid checking two above conditions
113-
} else if (op.nonce.isModuleEnableMode()) {
112+
if (op.nonce.isModuleEnableMode()) {
114113
// if it is module enable mode, we need to enable the module first
115114
// and get the cleaned signature
116115
userOp.signature = _enableMode(userOpHash, op.signature);
@@ -120,7 +119,7 @@ contract StartaleSmartAccount is
120119
validationData = IValidator(validator).validateUserOp(userOp, userOpHash);
121120
}
122121

123-
/// @notice Executes transactions in single or batch modes as specified by the execution mode.
122+
/// @notice Executes transactions in single or batch modes as specified g by the execution mode.
124123
/// @param mode The execution mode detailing how transactions should be handled (single, batch, default, try/catch).
125124
/// @param executionCalldata The encoded transaction data to execute.
126125
/// @dev This function handles transaction execution flexibility and is protected by the `onlyEntryPoint` modifier.
@@ -166,7 +165,7 @@ contract StartaleSmartAccount is
166165
/// @dev Only callable by the EntryPoint. Decodes the user operation calldata, skipping the first four bytes, and executes the inner call.
167166
function executeUserOp(PackedUserOperation calldata userOp, bytes32) external payable virtual onlyEntryPoint withHook {
168167
bytes calldata callData = userOp.callData[4:];
169-
(bool success, bytes memory innerCallRet) = address(this).delegatecall(callData);
168+
(bool success,) = address(this).delegatecall(callData);
170169
if (!success) {
171170
revert ExecutionFailed();
172171
}
@@ -193,6 +192,27 @@ contract StartaleSmartAccount is
193192
emit ModuleInstalled(moduleTypeId, module);
194193
}
195194

195+
/// @notice Installs an interface to the smart account.
196+
/// @param interfaceId The id of the interface to install.
197+
/// @dev This function can only be called by the EntryPoint or the account itself for security reasons.
198+
function installInterface(bytes4 interfaceId) external payable onlyEntryPointOrSelf {
199+
_installInterface(interfaceId);
200+
}
201+
202+
/// @notice Installs multiple interfaces to the smart account.
203+
/// @param interfaceIds The ids of the interfaces to install.
204+
/// @dev This function can only be called by the EntryPoint or the account itself for security reasons.
205+
function installInterfaces(bytes4[] calldata interfaceIds) external payable onlyEntryPointOrSelf {
206+
_installInterfaces(interfaceIds);
207+
}
208+
209+
/// @notice Uninstalls an interface from the smart account.
210+
/// @param interfaceId The id of the interface to uninstall.
211+
/// @dev This function can only be called by the EntryPoint or the account itself for security reasons.
212+
function uninstallInterface(bytes4 interfaceId) external payable onlyEntryPointOrSelf {
213+
_uninstallInterface(interfaceId);
214+
}
215+
196216
/// @notice Uninstalls a module from the smart account.
197217
/// @param moduleTypeId The type ID of the module to be uninstalled, matching the installation type:
198218
/// - 1 for Validator
@@ -305,18 +325,21 @@ contract StartaleSmartAccount is
305325
}
306326
}
307327

308-
/// @dev Uninstalls all validators, executors, hooks, and pre-validation hooks.
309-
/// Review: _onRedelegation
328+
/// @dev Uninstalls all validators, executors, hooks, fallbacks, and pre-validation hooks.
329+
/// @notice It is worth noting that, onRedelegation() does not obligate the account to completely wipe out it’s storage.
330+
/// @notice It is an optional action for the account where it could uninitialize the storage variables as much as it can to provide clean storage for new wallet.
310331
function _onRedelegation() internal override {
311-
_tryUninstallValidators();
312-
_tryUninstallExecutors();
313-
_tryUninstallHook(_getHook());
314-
_tryUninstallPreValidationHook(
332+
_uninstallAllValidators();
333+
_uninstallAllExecutors();
334+
_uninstallHook(_getHook());
335+
_uninstallPreValidationHook(
315336
_getPreValidationHook(MODULE_TYPE_PREVALIDATION_HOOK_ERC1271), MODULE_TYPE_PREVALIDATION_HOOK_ERC1271
316337
);
317-
_tryUninstallPreValidationHook(
338+
_uninstallPreValidationHook(
318339
_getPreValidationHook(MODULE_TYPE_PREVALIDATION_HOOK_ERC4337), MODULE_TYPE_PREVALIDATION_HOOK_ERC4337
319340
);
341+
_uninstallAllFallbacks();
342+
_uninstallAllInterfaces();
320343
_initSentinelLists();
321344
}
322345

@@ -330,6 +353,7 @@ contract StartaleSmartAccount is
330353
// Handle potential ERC7739 support detection request
331354
if (signature.length == 0) {
332355
// Forces the compiler to optimize for smaller bytecode size.
356+
// checking if the hash equals to 0x7739773977397739773977397739773977397739773977397739773977397739
333357
if (uint256(hash) == (~signature.length / 0xffff) * 0x7739) {
334358
return checkERC7739Support(hash, signature);
335359
}
@@ -381,6 +405,19 @@ contract StartaleSmartAccount is
381405
&& (execType == EXECTYPE_DEFAULT || execType == EXECTYPE_TRY);
382406
}
383407

408+
function supportsInterface(bytes4 interfaceId) external view virtual override returns (bool) {
409+
AccountStorage storage ds = _getAccountStorage();
410+
if (
411+
interfaceId == type(IERC721Receiver).interfaceId || interfaceId == type(IERC1155Receiver).interfaceId
412+
|| interfaceId == type(IERC165).interfaceId || interfaceId == type(IERC1271).interfaceId
413+
|| interfaceId == type(IStartaleSmartAccount).interfaceId || interfaceId == type(IERC7579Account).interfaceId
414+
|| interfaceId == type(IAccountConfig).interfaceId
415+
) {
416+
return true;
417+
}
418+
return ds.supportedIfaces[interfaceId];
419+
}
420+
384421
/// @notice Determines whether a module is installed on the smart account.
385422
/// @param moduleTypeId The ID corresponding to the type of module (Validator, Executor, Fallback, Hook).
386423
/// @param module The address of the module to check.

src/core/BaseAccount.sol

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ contract BaseAccount is IBaseAccount {
4444
assembly {
4545
if missingAccountFunds {
4646
// Ignore failure (it's EntryPoint's job to verify, not the account's).
47-
pop(call(gas(), caller(), missingAccountFunds, codesize(), 0x00, codesize(), 0x00))
47+
pop(call(gas(), caller(), missingAccountFunds, 0, 0, 0, 0))
4848
}
4949
}
5050
}
@@ -68,14 +68,19 @@ contract BaseAccount is IBaseAccount {
6868
address entryPointAddress = _ENTRYPOINT;
6969
assembly {
7070
let freeMemPtr := mload(0x40) // Store the free memory pointer.
71-
mstore(0x14, to) // Store the `to` argument.
72-
mstore(0x34, amount) // Store the `amount` argument.
73-
mstore(0x00, 0x205c2878000000000000000000000000) // `withdrawTo(address,uint256)`.
74-
if iszero(call(gas(), entryPointAddress, 0, 0x10, 0x44, codesize(), 0x00)) {
75-
returndatacopy(freeMemPtr, 0x00, returndatasize())
76-
revert(freeMemPtr, returndatasize())
71+
72+
mstore(freeMemPtr, shl(224, 0x205c2878)) // `withdrawTo(address,uint256)` selector
73+
mstore(add(freeMemPtr, 0x04), to) // Store the `to` argument.
74+
mstore(add(freeMemPtr, 0x24), amount) // Store the `amount` argument.
75+
76+
if iszero(call(gas(), entryPointAddress, 0, freeMemPtr, 0x44, 0, 0)) {
77+
let rdsize := returndatasize()
78+
returndatacopy(freeMemPtr, 0, rdsize)
79+
revert(freeMemPtr, rdsize)
7780
}
78-
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
81+
82+
// Update the free memory pointer (ptr + 0x44)
83+
mstore(0x40, add(freeMemPtr, 0x44))
7984
}
8085
}
8186

src/core/ERC7779Adapter.sol

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ abstract contract ERC7779Adapter is IERC7779 {
4343
assembly {
4444
$.slot := ERC7779_STORAGE_BASE
4545
}
46+
// Check if the storageBase already exists to avoid duplication
47+
for (uint256 i = 0; i < $.storageBases.length; i++) {
48+
if ($.storageBases[i] == storageBase) {
49+
return; // Exit if the storageBase is already present
50+
}
51+
}
4652
$.storageBases.push(storageBase);
4753
}
4854

src/core/ExecutionHelper.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ abstract contract ExecutionHelper is IExecutionHelper {
114114
exec = executions[i];
115115
bool success;
116116
(success, result[i]) = _tryExecute(exec.target, exec.value, exec.callData);
117-
if (!success) emit TryExecuteUnsuccessful(exec.callData, result[i]);
117+
if (!success) emit TryExecuteUnsuccessful(exec.target, exec.callData, result[i]);
118118
}
119119
}
120120

@@ -183,7 +183,7 @@ abstract contract ExecutionHelper is IExecutionHelper {
183183
_executeNoReturndata(target, value, callData);
184184
} else if (execType == EXECTYPE_TRY) {
185185
(bool success, bytes memory result) = _tryExecute(target, value, callData);
186-
if (!success) emit TryExecuteUnsuccessful(callData, result);
186+
if (!success) emit TryExecuteUnsuccessful(target, callData, result);
187187
} else {
188188
revert UnsupportedExecType(execType);
189189
}
@@ -230,7 +230,7 @@ abstract contract ExecutionHelper is IExecutionHelper {
230230
returnData[0] = _execute(target, value, callData);
231231
} else if (execType == EXECTYPE_TRY) {
232232
(success, returnData[0]) = _tryExecute(target, value, callData);
233-
if (!success) emit TryExecuteUnsuccessful(callData, returnData[0]);
233+
if (!success) emit TryExecuteUnsuccessful(target, callData, returnData[0]);
234234
} else {
235235
revert UnsupportedExecType(execType);
236236
}

0 commit comments

Comments
 (0)