Skip to content

Commit 4c29657

Browse files
authored
Merge pull request #67 from rhinestonewtf/audit/fix
Fixing Audit Findings
2 parents fe0735f + aaa9ce1 commit 4c29657

25 files changed

+891
-720
lines changed

.solhint.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"extends": "solhint:recommended",
33
"rules": {
44
"avoid-low-level-calls": "off",
5-
"code-complexity": ["error", 9],
5+
"code-complexity": ["error", 11],
66
"compiler-version": ["error", ">=0.8.0"],
77
"contract-name-camelcase": "off",
88
"const-name-snakecase": "off",

package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,14 @@
2525
"spellcheck": "cspell '**'"
2626
},
2727
"dependencies": {
28-
"@openzeppelin/contracts": "5.0.1"
28+
"@openzeppelin/contracts": "5.0.1",
29+
"solady": "github:vectorized/solady#9deb9ed36a27261a8745db5b7cd7f4cdc3b1cd4e",
30+
"forge-std": "github:foundry-rs/forge-std#v1.7.6"
2931
},
3032
"devDependencies": {
3133
"@defi-wonderland/natspec-smells": "^1.1.1",
3234
"cspell": "^8.6.0",
3335
"ds-test": "github:dapphub/ds-test#e282159d5170298eb2455a6c05280ab5a73a4ef0",
34-
"forge-std": "github:foundry-rs/forge-std#v1.7.6",
35-
"solady": "github:vectorized/solady#9deb9ed36a27261a8745db5b7cd7f4cdc3b1cd4e",
3636
"solhint": "^4.5.2",
3737
"solmate": "github:transmissions11/solmate#c892309933b25c03d32b1b0d674df7ae292ba925",
3838
"@rhinestone/erc4337-validation": "github:rhinestonewtf/erc4337-validation"

pnpm-lock.yaml

+477-579
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

remappings.txt

+4
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,7 @@ solmate/=node_modules/solmate/src/
33
solady/=node_modules/solady/src/
44
forge-std/=node_modules/forge-std/src/
55
ds-test/=node_modules/ds-test/src/
6+
erc4337-validation/=node_modules/@rhinestone/erc4337-validation/src/
7+
account-abstraction/=node_modules/account-abstraction/contracts/
8+
account-abstraction-v0.6/=node_modules/account-abstraction-v0.6/contracts/
9+
@openzeppelin/=node_modules/@openzeppelin/

src/DataTypes.sol

+5-5
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ struct TrustedAttesterRecord {
4141
uint8 attesterCount; // number of attesters in the linked list
4242
uint8 threshold; // minimum number of attesters required
4343
address attester; // first attester in linked list. (packed to save gas)
44-
mapping(address attester => address linkedAttester) linkedAttesters; // linked list
44+
mapping(address attester => mapping(address account => address linkedAttester)) linkedAttesters;
4545
}
4646

4747
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
@@ -75,12 +75,12 @@ type SchemaUID is bytes32;
7575
using { schemaEq as == } for SchemaUID global;
7676
using { schemaNotEq as != } for SchemaUID global;
7777

78-
function schemaEq(SchemaUID uid1, SchemaUID uid) pure returns (bool) {
79-
return SchemaUID.unwrap(uid1) == SchemaUID.unwrap(uid);
78+
function schemaEq(SchemaUID uid1, SchemaUID uid2) pure returns (bool) {
79+
return SchemaUID.unwrap(uid1) == SchemaUID.unwrap(uid2);
8080
}
8181

82-
function schemaNotEq(SchemaUID uid1, SchemaUID uid) pure returns (bool) {
83-
return SchemaUID.unwrap(uid1) != SchemaUID.unwrap(uid);
82+
function schemaNotEq(SchemaUID uid1, SchemaUID uid2) pure returns (bool) {
83+
return SchemaUID.unwrap(uid1) != SchemaUID.unwrap(uid2);
8484
}
8585

8686
//--------------------- ResolverUID -----------------------------|

src/IRegistry.sol

+18-39
Original file line numberDiff line numberDiff line change
@@ -13,43 +13,9 @@ import {
1313
SchemaUID,
1414
SchemaRecord
1515
} from "./DataTypes.sol";
16-
1716
import { IExternalSchemaValidator } from "./external/IExternalSchemaValidator.sol";
1817
import { IExternalResolver } from "./external/IExternalResolver.sol";
19-
20-
interface IERC7484 {
21-
event NewTrustedAttesters();
22-
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
23-
/* Check with Registry internal attesters */
24-
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
25-
26-
function check(address module) external view;
27-
28-
function checkForAccount(address smartAccount, address module) external view;
29-
30-
function check(address module, ModuleType moduleType) external view;
31-
32-
function checkForAccount(address smartAccount, address module, ModuleType moduleType) external view;
33-
34-
/**
35-
* Allows Smart Accounts - the end users of the registry - to appoint
36-
* one or many attesters as trusted.
37-
* @dev this function reverts, if address(0), or duplicates are provided in attesters[]
38-
*
39-
* @param threshold The minimum number of attestations required for a module
40-
* to be considered secure.
41-
* @param attesters The addresses of the attesters to be trusted.
42-
*/
43-
function trustAttesters(uint8 threshold, address[] calldata attesters) external;
44-
45-
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
46-
/* Check with external attester(s) */
47-
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
48-
49-
function check(address module, address[] calldata attesters, uint256 threshold) external view;
50-
51-
function check(address module, ModuleType moduleType, address[] calldata attesters, uint256 threshold) external view;
52-
}
18+
import { IERC7484 } from "./interfaces/IERC7484.sol";
5319

5420
/**
5521
* Interface definition of all features of the registry:
@@ -91,6 +57,7 @@ interface IRegistry is IERC7484 {
9157
event Attested(address indexed moduleAddr, address indexed attester, SchemaUID schemaUID, AttestationDataRef indexed sstore2Pointer);
9258

9359
error AlreadyRevoked();
60+
error AlreadyAttested();
9461
error ModuleNotFoundInRegistry(address module);
9562
error AccessDenied();
9663
error InvalidAttestation();
@@ -236,12 +203,14 @@ interface IRegistry is IERC7484 {
236203
* @param metadata The metadata to be stored on the registry.
237204
* This field is optional, and might be used by the module developer to store additional
238205
* information about the module or facilitate business logic with the Resolver stub
206+
* @param resolverContext bytes that will be passed to the resolver contract
239207
*/
240208
function deployModule(
241209
bytes32 salt,
242210
ResolverUID resolverUID,
243211
bytes calldata initCode,
244-
bytes calldata metadata
212+
bytes calldata metadata,
213+
bytes calldata resolverContext
245214
)
246215
external
247216
payable
@@ -250,7 +219,9 @@ interface IRegistry is IERC7484 {
250219
/**
251220
* In order to make the integration into existing business logics possible,
252221
* the Registry is able to utilize external factories that can be utilized to deploy the modules.
253-
* @dev Registry can use other factories to deploy the module
222+
* @dev Registry can use other factories to deploy the module.
223+
* @dev Note that this function will call the external factory via the FactoryTrampoline contract.
224+
* Factory MUST not assume that msg.sender == registry
254225
* @dev This function is used to deploy and register a module using a factory contract.
255226
* Since one of the parameters of this function is a unique resolverUID and any
256227
* registered module address can only be registered once,
@@ -260,7 +231,8 @@ interface IRegistry is IERC7484 {
260231
address factory,
261232
bytes calldata callOnFactory,
262233
bytes calldata metadata,
263-
ResolverUID resolverUID
234+
ResolverUID resolverUID,
235+
bytes calldata resolverContext
264236
)
265237
external
266238
payable
@@ -278,8 +250,15 @@ interface IRegistry is IERC7484 {
278250
* @param metadata The metadata to be stored on the registry.
279251
* This field is optional, and might be used by the module developer to store additional
280252
* information about the module or facilitate business logic with the Resolver stub
253+
* @param resolverContext bytes that will be passed to the resolver contract
281254
*/
282-
function registerModule(ResolverUID resolverUID, address moduleAddress, bytes calldata metadata) external;
255+
function registerModule(
256+
ResolverUID resolverUID,
257+
address moduleAddress,
258+
bytes calldata metadata,
259+
bytes calldata resolverContext
260+
)
261+
external;
283262

284263
/**
285264
* in conjunction with the deployModule() function, this function let's you

src/core/AttestationManager.sol

+5
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ abstract contract AttestationManager is IRegistry, ModuleManager, SchemaManager,
122122
}
123123
// caching module address.
124124
address module = request.moduleAddr;
125+
AttestationRecord storage $record = $getAttestation({ module: module, attester: attester });
126+
// If the attestation was already made for module, but not revoked, revert.
127+
if ($record.time != ZERO_TIMESTAMP && $record.revocationTime == ZERO_TIMESTAMP) {
128+
revert AlreadyAttested();
129+
}
125130
// SLOAD the resolverUID from the moduleRecord
126131
resolverUID = $moduleAddrToRecords[module].resolverUID;
127132
// Ensure that attestation is for module that was registered.

src/core/ModuleManager.sol

+64-13
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,32 @@ import { ResolverRecord, ModuleRecord, ResolverUID } from "../DataTypes.sol";
99
import { ResolverManager } from "./ResolverManager.sol";
1010
import { IRegistry } from "../IRegistry.sol";
1111

12+
/**
13+
* In order to separate msg.sender context from registry,
14+
* interactions with external Factories are done with this Trampoline contract.
15+
*/
16+
contract FactoryTrampoline {
17+
error FactoryCallFailed(address factory);
18+
19+
/**
20+
* @param factory the address of the factory to call
21+
* @param callOnFactory the call data to send to the factory
22+
* @return moduleAddress the moduleAddress that was returned by the
23+
*/
24+
function deployViaFactory(address factory, bytes memory callOnFactory) external payable returns (address moduleAddress) {
25+
// call external factory to deploy module
26+
bool success;
27+
/* solhint-disable no-inline-assembly */
28+
assembly ("memory-safe") {
29+
success := call(gas(), factory, callvalue(), add(callOnFactory, 0x20), mload(callOnFactory), 0, 32)
30+
moduleAddress := mload(0)
31+
}
32+
if (!success) {
33+
revert FactoryCallFailed(factory);
34+
}
35+
}
36+
}
37+
1238
/**
1339
* In order for Attesters to be able to make statements about a Module, the Module first needs to be registered on the Registry.
1440
* This can be done as part of or after Module deployment. On registration, every module is tied to a
@@ -34,29 +60,40 @@ abstract contract ModuleManager is IRegistry, ResolverManager {
3460

3561
mapping(address moduleAddress => ModuleRecord moduleRecord) internal $moduleAddrToRecords;
3662

63+
FactoryTrampoline private immutable FACTORY_TRAMPOLINE;
64+
65+
constructor() {
66+
FACTORY_TRAMPOLINE = new FactoryTrampoline();
67+
}
68+
3769
/**
3870
* @inheritdoc IRegistry
3971
*/
4072
function deployModule(
4173
bytes32 salt,
4274
ResolverUID resolverUID,
4375
bytes calldata initCode,
44-
bytes calldata metadata
76+
bytes calldata metadata,
77+
bytes calldata resolverContext
4578
)
4679
external
4780
payable
4881
returns (address moduleAddress)
4982
{
5083
ResolverRecord storage $resolver = $resolvers[resolverUID];
51-
if ($resolver.resolverOwner == ZERO_ADDRESS) revert InvalidResolver($resolver.resolver);
84+
if ($resolver.resolverOwner == ZERO_ADDRESS) revert InvalidResolverUID(resolverUID);
5285

5386
moduleAddress = initCode.deploy(salt);
5487
// _storeModuleRecord() will check if module is already registered,
5588
// which should prevent reentry to any deploy function
5689
ModuleRecord memory record =
5790
_storeModuleRecord({ moduleAddress: moduleAddress, sender: msg.sender, resolverUID: resolverUID, metadata: metadata });
5891

59-
record.requireExternalResolverOnModuleRegistration({ moduleAddress: moduleAddress, $resolver: $resolver });
92+
record.requireExternalResolverOnModuleRegistration({
93+
moduleAddress: moduleAddress,
94+
$resolver: $resolver,
95+
resolverContext: resolverContext
96+
});
6097
}
6198

6299
/**
@@ -69,11 +106,18 @@ abstract contract ModuleManager is IRegistry, ResolverManager {
69106
/**
70107
* @inheritdoc IRegistry
71108
*/
72-
function registerModule(ResolverUID resolverUID, address moduleAddress, bytes calldata metadata) external {
109+
function registerModule(
110+
ResolverUID resolverUID,
111+
address moduleAddress,
112+
bytes calldata metadata,
113+
bytes calldata resolverContext
114+
)
115+
external
116+
{
73117
ResolverRecord storage $resolver = $resolvers[resolverUID];
74118

75119
// ensure that non-zero resolverUID was provided
76-
if ($resolver.resolverOwner == ZERO_ADDRESS) revert InvalidResolver($resolver.resolver);
120+
if ($resolver.resolverOwner == ZERO_ADDRESS) revert InvalidResolverUID(resolverUID);
77121

78122
ModuleRecord memory record = _storeModuleRecord({
79123
moduleAddress: moduleAddress,
@@ -83,7 +127,11 @@ abstract contract ModuleManager is IRegistry, ResolverManager {
83127
});
84128

85129
// resolve module registration
86-
record.requireExternalResolverOnModuleRegistration({ moduleAddress: moduleAddress, $resolver: $resolver });
130+
record.requireExternalResolverOnModuleRegistration({
131+
moduleAddress: moduleAddress,
132+
$resolver: $resolver,
133+
resolverContext: resolverContext
134+
});
87135
}
88136

89137
/**
@@ -93,7 +141,8 @@ abstract contract ModuleManager is IRegistry, ResolverManager {
93141
address factory,
94142
bytes calldata callOnFactory,
95143
bytes calldata metadata,
96-
ResolverUID resolverUID
144+
ResolverUID resolverUID,
145+
bytes calldata resolverContext
97146
)
98147
external
99148
payable
@@ -105,11 +154,9 @@ abstract contract ModuleManager is IRegistry, ResolverManager {
105154
// prevent someone from calling a registry function pretending its a factory
106155
if (factory == address(this)) revert FactoryCallFailed(factory);
107156

108-
// call external factory to deploy module
109-
(bool ok, bytes memory returnData) = factory.call{ value: msg.value }(callOnFactory);
110-
if (!ok) revert FactoryCallFailed(factory);
111-
112-
moduleAddress = abi.decode(returnData, (address));
157+
// Call the factory via the trampoline contract. This will make sure that there is msg.sender separation
158+
// Making "raw" calls to user supplied addresses could create security issues.
159+
moduleAddress = FACTORY_TRAMPOLINE.deployViaFactory{ value: msg.value }({ factory: factory, callOnFactory: callOnFactory });
113160

114161
ModuleRecord memory record = _storeModuleRecord({
115162
moduleAddress: moduleAddress,
@@ -118,7 +165,11 @@ abstract contract ModuleManager is IRegistry, ResolverManager {
118165
metadata: metadata
119166
});
120167

121-
record.requireExternalResolverOnModuleRegistration({ moduleAddress: moduleAddress, $resolver: $resolver });
168+
record.requireExternalResolverOnModuleRegistration({
169+
moduleAddress: moduleAddress,
170+
$resolver: $resolver,
171+
resolverContext: resolverContext
172+
});
122173
}
123174

124175
/**

src/core/SchemaManager.sol

+10-10
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@ abstract contract SchemaManager is IRegistry {
2323
// The global mapping between schema records and their IDs.
2424
mapping(SchemaUID uid => SchemaRecord schemaRecord) internal schemas;
2525

26+
/**
27+
* If a validator is not address(0), we check if it supports the IExternalSchemaValidator interface
28+
*/
29+
modifier onlySchemaValidator(IExternalSchemaValidator validator) {
30+
if (address(validator) != address(0) && !validator.supportsInterface(type(IExternalSchemaValidator).interfaceId)) {
31+
revert InvalidSchemaValidator(validator);
32+
}
33+
_;
34+
}
35+
2636
/**
2737
* @inheritdoc IRegistry
2838
*/
@@ -49,16 +59,6 @@ abstract contract SchemaManager is IRegistry {
4959
emit SchemaRegistered(uid, msg.sender);
5060
}
5161

52-
/**
53-
* If a validator is not address(0), we check if it supports the IExternalSchemaValidator interface
54-
*/
55-
modifier onlySchemaValidator(IExternalSchemaValidator validator) {
56-
if (address(validator) != address(0) && !validator.supportsInterface(type(IExternalSchemaValidator).interfaceId)) {
57-
revert InvalidSchemaValidator(validator);
58-
}
59-
_;
60-
}
61-
6262
/**
6363
* @inheritdoc IRegistry
6464
*/

0 commit comments

Comments
 (0)