1+ // SPDX-License-Identifier: MIT
2+ pragma solidity ^ 0.8.0 ;
3+
4+ interface IInitialized {
5+ function initialized () external view returns (bool );
6+ }
7+
8+ contract AccountConfiguration {
9+ enum OwnerType {
10+ ADDRESS,
11+ SECP256K1,
12+ SECP256R1,
13+ WEBAUTHN,
14+ BLS12381
15+ }
16+
17+ struct Owner {
18+ OwnerType ownerType;
19+ bytes ownerBytes;
20+ }
21+
22+ struct OwnerConfig {
23+ bool added;
24+ bool removed;
25+ }
26+
27+ struct OwnerChange {
28+ bool add;
29+ Owner owner;
30+ uint64 nonceSequence;
31+ }
32+
33+ bytes4 constant ADDRESS_OWNER = bytes4 (keccak256 ("ADDRESS " ));
34+ bytes4 constant SECP256K1_OWNER = bytes4 (keccak256 ("SECP256K1 " ));
35+ bytes4 constant SECP256R1_OWNER = bytes4 (keccak256 ("SECP256R1 " ));
36+ bytes4 constant WEBAUTHN_OWNER = bytes4 (keccak256 ("WEBAUTHN " ));
37+ bytes4 constant BLS12381_OWNER = bytes4 (keccak256 ("BLS12381 " ));
38+
39+ uint196 constant REPLAY_NONCE_KEY = type (uint196 ).max;
40+
41+ /// @dev Inner mappings keyed by account address for ERC-7562 compliance
42+ uint256 counter;
43+ mapping (address account = > uint256 index = > bytes ownerBytes );
44+ mapping (address account = > bytes owner = > bool ) isOwner;
45+
46+ mapping (bytes ownerBytes = > mapping (address account = > OwnerConfig config )) public ownerConfig;
47+ mapping (uint196 key = > mapping (address account = > uint64 sequence )) public nonce;
48+
49+ event AccountCreated (address indexed account , Owner[] owners , bytes32 bytecodeHash );
50+ event OwnerAdded (address indexed account , address ownerId , Owner owner );
51+ event OwnerRemoved (address indexed account , address ownerId );
52+ event NonceIncremented (address indexed account , uint256 indexed nonceKey , uint256 nonceSequence );
53+
54+ error InvalidOwner (Owner owner );
55+ error InvalidOwnerIndex (uint8 index );
56+ error AccountNotInitialized (address account );
57+ error NoOwnerAtIndex (address account , uint8 index );
58+ error NotAccountOwner (address account , Owner owner );
59+
60+ /// @notice Fallback function to delegatecall diamond, functions set by node
61+ function fallback () external {
62+ // delegatecall diamond
63+ }
64+
65+ /// @notice Designed for 7702 accounts to initialize on first use
66+ function initializeAccount () external {}
67+
68+ function createAccount (
69+ Owner[] calldata owners ,
70+ uint256 nonce ,
71+ bytes calldata bytecode ,
72+ bytes calldata initializeCall // most helpful for ERC-1167 proxies
73+ ) external returns (address account ) {
74+ // Early return if account deployed
75+ account = computeAddress (owners, nonce, bytecode);
76+ if (account.code != 0 ) return ;
77+
78+ // Configure intitial owners
79+ for (uint i; i < owners.length ; i++ ) {
80+ if (! isValidOwner (owners[i])) revert InvalidOwner (owners[i]);
81+ ownerAtIndex[i][account] = owners[i];
82+ ownerConfig[computeOwnerId (owners[i])][account] = OwnerConfig ({removed: false , index: i, nextIndex: i + 1 });
83+ emit OwnerUpdated (account, i, owners[i]);
84+ }
85+ nextOwnerIndex[account] = owners.length ;
86+
87+ // Create account
88+ bytes32 salt = computeSalt (owners, nonce);
89+ assembly {
90+ create2 (0 , add (bytecode, 0x20 ), mload (bytecode), salt)
91+ }
92+ emit AccountCreated (account, owners, keccak256 (bytecode));
93+
94+ // Initialize
95+ account.call (initializeCall);
96+
97+ // Assert account is initialized to mitigate undeployed implementations
98+ if (! IInitialized (account).initialized ()) revert AccountNotInitialized (account);
99+ }
100+
101+ function addOwner (Owner owner ) external {
102+ // Validate owner
103+ if (! isValidOwner (owner)) revert InvalidOwner (owner);
104+
105+ // Clear previous owner if exists
106+ Owner memory previousOwner = ownerAtIndex[index][msg .sender ];
107+ if (previousOwner.ownerId.length != 0 ) {
108+ ownerConfig[_hashOwner (previousOwner)][msg .sender ];
109+ }
110+
111+ // Update owner configuration
112+ ownerAtIndex[index][msg .sender ] = owner;
113+ ownerConfig[_hashOwner (owner)][msg .sender ] = index + 1 ;
114+ emit OwnerUpdated (msg .sender , index, owner);
115+ }
116+
117+ function removeOwner (address ownerId ) external {
118+ // Validate index
119+ if (index > 127 ) revert InvalidOwnerIndex (index);
120+
121+ // Check owner exists
122+ Owner memory owner = ownerAtIndex[index][msg .sender ];
123+ if (owner.ownerId.length == 0 ) revert NoOwnerAtIndex (account, index);
124+
125+ // Remove owner configuration
126+ delete ownerAtIndex[index][msg .sender ];
127+ ownerConfig[_hashOwner (owner)][msg .sender ];
128+ emit OwnerRemoved (msg .sender , index)
129+ }
130+
131+ /// @notice Apply signed, replayable owner changes
132+ function applyOwnerChanges (address account , OwnerChange[] calldata ownerChanges ) external {
133+ // validate signature
134+
135+ for (uint i; i < ownerChanges.length ; i++ ) {
136+ if (nonce[REPLAY_NONCE_KEY][account] != ownerChanges[i].nonceSequence) revert ();
137+ uint64 usedSequence = nonce[REPLAY_NONCE_KEY][account]++ ;
138+ emit NonceIncremented (account, REPLAY_NONCE_KEY, usedSequence);
139+ // update account owner configuration
140+ }
141+
142+ }
143+
144+ function isOwner (address account , Owner calldata owner ) external view returns (bool ) {
145+ return indexOfOwner (account, owner) > 0 ;
146+ }
147+
148+ function computeAddress (Owner[] calldata owners , uint256 nonce , bytes calldata bytecode ) public view returns (address ) {
149+ bytes32 salt = computeSalt (owners, nonce);
150+ bytes32 bytecodeHash = keccak256 (bytecode);
151+ bytes32 create2Hash = keccak256 (abi.encodePacked (0xFF , address (this ), salt, bytecodeHash));
152+ return address (uint160 (uint256 (create2Hash)));
153+ }
154+
155+ function computeSalt (Owner[] calldata owners , uint256 nonce ) public pure returns (bytes32 ) {
156+ return keccak256 (abi.encode (owners, nonce));
157+ }
158+
159+ function computeOwnerId (Owner memory owner ) public pure returns (address ownerId ) {
160+ if (owner.ownerType == ADDRESS_OWNER) {
161+ return address (bytes20 (owner.ownerBytes));
162+ }
163+ return address (bytes20 (bytes32 (keccak256 (abi.encode (owner.ownerBytes)))));
164+ }
165+
166+ function isValidOwner (Owner memory owner ) public pure returns (bool valid ) {
167+ uint256 assertLength;
168+ if (owner.ownerType == ADDRESS_OWNER) {
169+ assertLength = 20 ;
170+ } else if (
171+ owner.ownerType == SECP256K1_OWNER ||
172+ owner.ownerType == SECP256R1_OWNER ||
173+ owner.ownerType == WEBAUTHN_OWNER ||
174+ owner.ownerType == BLS12381_OWNER
175+ ) {
176+ assertLength = 64 ;
177+ } else {
178+ return false ;
179+ }
180+
181+ if (owner.ownerId.length != assertLength) return false ;
182+
183+ return true ;
184+ }
185+ }
0 commit comments