Skip to content

Commit 6592d3c

Browse files
committed
Improved documentation of Design for Composition
1 parent 4417d48 commit 6592d3c

File tree

2 files changed

+147
-34
lines changed

2 files changed

+147
-34
lines changed

website/docs/design/design-for-composition.mdx

Lines changed: 116 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ title: Design for Composition
44
description: How to design Compose facets and libraries for composition.
55
---
66

7-
Here are guidelines and rules to create composable facets.
7+
Here are the guidelines and rules for creating composable facets.
88

99
Compose replaces source-code inheritance with on-chain composition. Facets are the building blocks; diamonds wire them together.
1010

1111
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.
1212

1313
## Writing Facets
1414

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.
1616
2. Each facet is a self-contained, conceptual unit.
1717
3. A facet is designed for all of its functions to be added, not just some of them.
1818
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
3030

3131
## Facets & Libraries
3232

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`.
3434

3535

3636
## Extending Facets
3737

3838

3939
1. Every extension of a standard or facet should be implemented as a new facet.
4040
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.
4749

4850
:::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.
5252
:::
5353

5454

@@ -61,6 +61,96 @@ There may be reasonable exceptions to these rules. If you believe one applies, p
6161

6262
For example, `ERC721EnumerableFacet` does not extend `ERC721Facet` because enumeration requires re-implementing transfer and mint/burn logic, making it incompatible with `ERC721Facet`.
6363

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`:
78+
79+
```solidity
80+
/// @notice Storage struct for ERC20.
81+
/// @custom:storage-location erc8042:compose.erc20
82+
struct ERC20Storage {
83+
mapping(address owner => uint256 balance) balanceOf;
84+
uint256 totalSupply;
85+
mapping(address owner => mapping(address spender => uint256 allowance)) allowances;
86+
uint8 decimals;
87+
string name;
88+
string symbol;
89+
}
90+
```
91+
92+
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).
102+
bytes32 constant ERC20_STORAGE_POSITION = keccak256("compose.erc20");
103+
104+
/// @notice Storage struct for ERC20 but with `symbol` removed.
105+
/// @dev Reused struct definition with unused variables at the end removed
106+
/// @custom:storage-location erc8042:compose.erc20
107+
struct ERC20Storage {
108+
mapping(address owner => uint256 balance) balanceOf;
109+
uint256 totalSupply;
110+
mapping(address owner => mapping(address spender => uint256 allowance)) allowances;
111+
uint8 decimals;
112+
string name;
113+
}
114+
115+
/// @notice Returns the storage for ERC20.
116+
/// @return s The ERC20 storage struct.
117+
function getERC20Storage() internal pure returns (ERC20Storage storage s) {
118+
bytes32 position = ERC20_STORAGE_POSITION;
119+
assembly {
120+
s.slot := position
121+
}
122+
}
123+
124+
/// @notice Storage slot identifier for Permit functionality.
125+
bytes32 constant STORAGE_POSITION = keccak256("compose.erc20.permit");
126+
127+
/// @notice Storage struct for ERC20Permit.
128+
/// @custom:storage-location erc8042:compose.erc20.permit
129+
struct ERC20PermitStorage {
130+
mapping(address owner => uint256) nonces;
131+
}
132+
133+
/// @notice Returns the storage for ERC20Permit.
134+
/// @return s The ERC20Permit storage struct.
135+
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+
64154
### Example: Extending ERC20Facet with Staking Functionality
65155

66156
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 {
98188
/// @notice Storage slot identifier for ERC20 (reused to access token data).
99189
bytes32 constant ERC20_STORAGE_POSITION = keccak256("compose.erc20");
100190
101-
/// @notice Storage struct for ERC20 (reused struct definition with unused variables removed).
102-
/// @dev Must match the struct definition in ERC20Facet, but we remove `nonces`
103-
/// since this facet doesn't need permit functionality. The `nonces` mapping
104-
/// is at the end of the original struct, so it can be safely removed.
105-
/// Note: The order of variables must match the original struct.
191+
/// @notice Storage struct for ERC20
192+
/// @dev This struct is from ERC20Facet.
193+
/// `balanceOf` is the only variable used in this struct.
194+
/// All variables after it are removed.
106195
/// @custom:storage-location erc8042:compose.erc20
107196
struct ERC20Storage {
108-
string name;
109-
string symbol;
110-
uint8 decimals;
111-
uint256 totalSupply;
112-
mapping(address owner => uint256 balance) balanceOf;
113-
mapping(address owner => mapping(address spender => uint256 allowance)) allowances;
114-
// Note: nonces mapping removed - not needed for staking functionality
115-
}
197+
mapping(address owner => uint256 balance) balanceOf;
198+
}
116199
117200
/// @notice Returns the storage for ERC20.
118201
/// @return s The ERC20 storage struct.
@@ -210,23 +293,23 @@ contract ERC20StakingFacet {
210293
}
211294
```
212295

213-
### Summary: How This Example Follows the Guide
296+
#### Summary: How This Example Follows the Guide
214297

215298
This example demonstrates proper facet extension by:
216299

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.
218301

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.
220303

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.
222305

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.
224307

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 diamond storage struct.
226309

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.
228311

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.
230313

231314
***
232315

0 commit comments

Comments
 (0)