You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
description: How to design Compose facets and libraries for composition.
5
5
---
6
6
7
-
Here are guidelines and rules to create composable facets.
7
+
Here are the guidelines and rules for creating composable facets.
8
8
9
9
Compose replaces source-code inheritance with on-chain composition. Facets are the building blocks; diamonds wire them together.
10
10
11
11
We focus on building **small, independent, and easy-to-read facets**. Each facet is deployed once, then reused and combined with other facets to form complete, modular smart contract systems.
12
12
13
13
## Writing Facets
14
14
15
-
1. A facet is set of external functions that represent a single unit of self-contained functionality.
15
+
1. A facet is a set of external functions that represent a single, self-contained unit of functionality.
16
16
2. Each facet is a self-contained, conceptual unit.
17
17
3. A facet is designed for all of its functions to be added, not just some of them.
18
18
4. The facet is our smallest building block.
@@ -30,25 +30,25 @@ We focus on building **small, independent, and easy-to-read facets**. Each facet
30
30
31
31
## Facets & Libraries
32
32
33
-
1. Facets and facet libraries should not contain owner/admin authorization checks unless absolutely required or fundamental to the functionality being implemented. Of it must have permission/authorization checks then it should integrate with `AccessControlFacet`.
33
+
1. Facets and facet libraries should not contain owner/admin authorization checks unless absolutely required or fundamental to the functionality being implemented. If permission or authorization checks are required, the facet should integrate with `AccessControlFacet`.
34
34
35
35
36
36
## Extending Facets
37
37
38
38
39
39
1. Every extension of a standard or facet should be implemented as a new facet.
40
40
2. A facet should only be extended with a new facet that composes with it.
41
-
3. When reusing structs from existing facets or libraries, reuse the original diamond storage locations and structs, but only include the variables that the new facet actually needs if possible. Of course a reused struct must maintain the same order of variables as an originally defined struct. You **cannot** remove a variable from the beginning or middle of a struct.
42
-
4. Reusing a struct is done by copying it and removing unused variables at the end of it.
43
-
5. Storage structs should be designed so that removable variables (unused by some facets) appear at the end of the struct.
44
-
6. Storage structs should also be designed for packed storage (smaller sized variables using the same storage slot).
45
-
7. Removing storage variables is done only from the end of a struct. If a variable cannot be removed from the end of a struct, it must remain to preserve compatibility.
46
-
8. A facet that adds new storage variables must define its own diamond storage struct.
41
+
3. Composition is done by facets sharing the same storage structs and containing complementary external functions.
42
+
4. Two facets do not compose if they both expose one or more of the same external function signatures (function name and parameter types).
43
+
5. When reusing a struct from an existing facet, store it at the original diamond storage location and remove unused variables from the end of it. Variables must never be removed from the beginning or middle, as this would break storage compatibility.
44
+
6. Storage structs should be designed so that removable variables (unused by some facets) appear at the end of the struct.
45
+
7. Storage structs should also be designed for packed storage (smaller sized variables using the same storage slot).
46
+
8. If a variable cannot be removed from the end of a struct, it must remain to preserve compatibility.
47
+
9. A facet that adds new storage variables must define its own diamond storage struct.
48
+
10. Never add new variables to an existing struct.
47
49
48
50
:::info Important
49
-
Maintain the same order of variables in structs when reusing the same struct in different facets/libraries.
50
-
51
-
It is important to maintain the same sequence of variables in structs when reusing the same struct in different facets/libraries. Variables can only be removed from the end of structs if they are not used by a facet or library.
51
+
Maintain the same order of variables in structs when reusing them across facets or libraries. Unused variables may only be removed from the end of a struct.
52
52
:::
53
53
54
54
@@ -61,6 +61,96 @@ There may be reasonable exceptions to these rules. If you believe one applies, p
61
61
62
62
For example, `ERC721EnumerableFacet` does not extend `ERC721Facet` because enumeration requires re-implementing transfer and mint/burn logic, making it incompatible with `ERC721Facet`.
63
63
64
+
***
65
+
66
+
### Example: Implementing Storage in ERC20PermitFacet
67
+
68
+
The `ERC20PermitFacet` extends `ERC20Facet` by adding permit functionality (gasless approvals).
69
+
70
+
To do this, it must:
71
+
72
+
1. Access existing ERC20 data.
73
+
2. Add new storage for permit-specific data.
74
+
75
+
This requires reusing the `ERC20Storage` struct from `ERC20Facet` and defining a separate struct for the `nonces` variable used by the permit function.
76
+
77
+
Here is the full `ERC20Storage` struct from `ERC20Facet`:
When reusing this struct in `ERC20PermitFacet`, the [Extending Facets](#extending-facets) rules require removing unused variables at the end of the struct.
93
+
94
+
`ERC20PermitFacet` only uses the variables `allowances` and `name` from this struct. However, `balanceOf`, `totalSupply`, and `decimals`**cannot** be removed, even though they are unused, because they appear before the `name` variable, which is used. Removing them would shift the storage slot used by the `name` variable which would make it refer to something else.
95
+
96
+
Only variables at the **end** of a struct may be safely removed. In this case, `symbol` is the only trailing variable that is unused by `ERC20PermitFacet`, so it is the only one removed.
97
+
98
+
Here is the final struct storage code for `ERC20PermitFacet`:
99
+
100
+
```solidity
101
+
/// @notice Storage slot identifier for ERC20 (reused to access token data).
function getStorage() internal pure returns (ERC20PermitStorage storage s) {
136
+
bytes32 position = STORAGE_POSITION;
137
+
assembly {
138
+
s.slot := position
139
+
}
140
+
}
141
+
```
142
+
#### Summary: How This Example Follows the Guide
143
+
144
+
-**Reusing storage struct**: The `ERC20Storage` struct is copied from `ERC20Facet` and reused at the same location in storage `keccak256("compose.erc20")`, ensuring both facets access the same ERC20 token data. This demonstrates how facets can share storage through diamond storage patterns.
145
+
146
+
-**Maintaining variable order**: All variables in the reused `ERC20Storage` struct maintain the same order as the original struct.
147
+
148
+
-**Removing unused variables**: The `symbol` variable is removed from the end of the struct since permit functionality doesn't use that variable. This follows the rule that unused storage variables at the end of a struct should be removed.
149
+
150
+
-**Adding custom storage**: A new `ERC20PermitStorage` struct is defined with its own storage slot `keccak256("compose.erc20.permit")` for the `nonces` variable. This follows the principle that a facet adding new storage variables must define its own diamond storage struct.
151
+
152
+
***
153
+
64
154
### Example: Extending ERC20Facet with Staking Functionality
65
155
66
156
Here's a complete example showing how to correctly extend `ERC20Facet` by creating a new `ERC20StakingFacet` that adds staking functionality:
@@ -98,21 +188,14 @@ contract ERC20StakingFacet {
98
188
/// @notice Storage slot identifier for ERC20 (reused to access token data).
This example demonstrates proper facet extension by:
216
299
217
-
-**Extending as a new facet**: `ERC20StakingFacet` is a separate, self-contained facet that composes with `ERC20Facet` rather than modifying it. This follows the principle that every extension should be implemented as a new facet.
300
+
-**Extending as a new facet**: `ERC20StakingFacet` is a separate, self-contained facet that composes with `ERC20Facet`. This follows the principle that every extension should be implemented as a new facet.
218
301
219
-
-**Reusing storage struct**: The `ERC20Storage` struct is copied from `ERC20Facet` and reused with the same storage slot (`keccak256("compose.erc20")`), ensuring both facets access the same token data. This demonstrates how facets can share storage through diamond storage patterns.
302
+
-**Reusing storage struct**: The `ERC20Storage` struct is copied from `ERC20Facet` and reused at the same storage location `keccak256("compose.erc20")`, ensuring both facets access the same token data. This demonstrates how facets can share storage through diamond storage patterns.
220
303
221
-
-**Maintaining variable order**: All variables in the reused `ERC20Storage` struct maintain the same order as the original struct. Only the `nonces` mapping at the end is removed, following the rule that variables can only be removed from the end of a struct.
304
+
-**Maintaining variable order**: The `balanceOf` variable remains in the first position of the reused `ERC20Storage` struct, exactly as it appears in the original struct, preserving the order of storage variables.
222
305
223
-
-**Removing unused variables**: The `nonces` mapping (used for permit functionality) is removed from the end of the struct since staking doesn't require permit functionality. This follows the rule that storage structs should be designed so removable variables appear at the end, and removal is only done from the end of a struct.
306
+
-**Removing unused variables**: All variables except `balanceOf` are removed from the reused `ERC20Storage`struct since they are unused by `ERC20StakingFacet`. This follows the rule that storage structs should be designed so removable variables appear at the end, and removal is only done from the end of a struct.
224
307
225
-
-**Adding custom storage**: A new `ERC20StakingStorage` struct is defined with its own storage slot (`keccak256("compose.erc20.staking")`) for staking-specific data. This follows the principle that a facet adding new storage variables must define its own diamond-storage struct.
308
+
-**Adding custom storage**: A new `ERC20StakingStorage` struct is defined with its own storage slot `keccak256("compose.erc20.staking")` for staking-specific data. This follows the principle that a facet adding new storage variables must define its own diamondstorage struct.
226
309
227
-
-**Self-contained design**: The facet contains all necessary code (events, errors, storage definitions, and functions) without imports, making it fully self-contained. This adheres to the rule that facets should only contain code they actually use.
310
+
-**Self-contained design**: The facet contains all necessary code (events, errors, storage definitions, and functions) without imports, making it fully self-contained.
228
311
229
-
-**Composable functionality**: This facet can be deployed once and added to any diamond that includes `ERC20Facet`, demonstrating true onchain composition where facets work together without inheritance.
312
+
-**Composable functionality**: This facet can be deployed once and added to any diamond that includes `ERC20Facet`, demonstrating true on-chain composition where facets work together without inheritance.
0 commit comments