Skip to content

Commit bbdaadd

Browse files
committed
packedSelectors improvements
1 parent 61cdb30 commit bbdaadd

File tree

1 file changed

+84
-48
lines changed

1 file changed

+84
-48
lines changed

src/diamond/DiamondUpgradeFacet.sol

Lines changed: 84 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -186,88 +186,124 @@ contract DiamondUpgradeFacet {
186186

187187
function packedSelectors(address _facet) internal view returns (bytes memory selectors) {
188188
assembly ("memory-safe") {
189+
if iszero(extcodesize(_facet)) {
190+
/**
191+
* error NoBytecodeAtAddress(address)
192+
*/
193+
mstore(0x00, 0xd94e3bbf00000000000000000000000000000000000000000000000000000000)
194+
mstore(0x04, _facet)
195+
revert(0x00, 0x24)
196+
}
189197
/**
190-
* 1. Point to the current Free Memory Pointer (0x40).
191-
* We use this as the start of our "Static Buffer" workspace.
198+
* 1. Initialize Pointer.
199+
* Load the Free Memory Pointer (0x40). This points to the start of currently
200+
* unallocated memory. We will use this space to build our 'selectors' array.
192201
*/
193202
let ptr := mload(0x40)
194203
/**
195-
* 2. Prepare calldata for packedSelectors()
196-
* "0x3e62267c" is the function selector for packedSelectors()
197-
* "0x3e62267c" is bytes4(keccak256("packedSelectors()"))
198-
* We store it at ptr to reuse that memory immediately.
204+
* 2. Prepare Calldata.
205+
* We reuse the 'ptr' memory temporarily to store the function selector for the call.
206+
* 0x3e62267c = bytes4(keccak256("packedSelectors()"))
207+
* Layout at 'ptr': [0x3e62267c... (padded to 32 bytes)]
199208
*/
200209
mstore(ptr, 0x3e62267c00000000000000000000000000000000000000000000000000000000)
201210
/**
202-
* 3. Perform the staticcall.
203-
* We pass 0 for out and outSize to keep the return data in the
204-
* "Free Waiting Room" (Return Data Buffer) until we verify it.
211+
* 3. Perform Staticcall.
212+
* out/outSize are 0 because we handle the output dynamically using returndatacopy.
213+
* The return data remains in the contract's "Return Data Buffer" for now.
205214
*/
206215
let success :=
207216
staticcall(
208217
gas(), // pass all available gas
209218
_facet, // target address
210219
ptr, // pointer to start of input
211220
0x4, // input length (4 bytes for selector)
212-
0, // output pointer, not used
213-
0 // output length, not used
221+
0, // out pointer, not used
222+
0 // outSize, not used
214223
)
215224
/**
216225
* 4. Basic Safety Check.
217-
* Ensure the call succeeded and returned at least 68 bytes
226+
* We verify two things:
227+
* a) The call succeeded.
228+
* b) The return data is at least 68 bytes (Standard ABI Encoded Bytes).
229+
* 68 bytes = 32 (Offset) + 32 (Length) + 4 (Minimum 1 selector).
218230
*/
219-
if iszero(success) {
231+
if or(iszero(success), lt(returndatasize(), 68)) {
220232
/**
221-
* error FunctionSelectorsCallFailed(address) selector: 0x30319baa
233+
* Handle Failure.
234+
* If success is false, we revert with FunctionSelectorsCallFailed(address).
235+
* If size < 68, we revert with NoSelectorsForFacet(address).
222236
*/
223-
mstore(0x00, 0x30319baa00000000000000000000000000000000000000000000000000000000)
237+
if iszero(success) {
238+
/**
239+
* error FunctionSelectorsCallFailed(address)
240+
*/
241+
mstore(0x00, 0x30319baa00000000000000000000000000000000000000000000000000000000)
242+
mstore(0x04, _facet)
243+
revert(0x00, 0x24)
244+
}
245+
// error NoSelectorsForFacet(address)
246+
mstore(0x00, 0x9c23886b00000000000000000000000000000000000000000000000000000000)
224247
mstore(0x04, _facet)
225248
revert(0x00, 0x24)
226249
}
250+
227251
/**
228-
* Minimum return data size is 68 bytes:
229-
* 32 bytes offset + 32 bytes array length + 4 bytes (at least one selector).
230-
* If facet address has no bytecode then return size will be 0.
252+
* 5. Initialize the Array & "Peek" Length.
253+
* We copy the Length word from the Return Data Buffer directly to 'ptr'.
254+
* - Source Offset: 0x20 (We skip the first 32 bytes, which is the ABI Offset).
255+
* - Length: 0x20 (We copy exactly 32 bytes).
256+
* Result: ptr now holds the declared length of the bytes array.
231257
*/
232-
if lt(returndatasize(), 68) {
233-
/**
234-
* error NoSelectorsForFacet(address) selector: 0x9c23886b
235-
*/
236-
mstore(0x00, 0x9c23886b00000000000000000000000000000000000000000000000000000000)
237-
mstore(0x04, _facet)
238-
revert(0x00, 0x24)
258+
returndatacopy(ptr, 0x20, 0x20)
259+
let declaredLength := mload(ptr)
260+
/**
261+
* 6. Bounds Check.
262+
* Verify the Return Data Buffer actually contains the data declared by 'declaredLength'.
263+
* Formula: 32 (Offset) + 32 (Length Word) + declaredLength <= returndatasize()
264+
*/
265+
if lt(returndatasize(), add(declaredLength, 0x40)) {
266+
revert(0, 0)
239267
}
240268
/**
241-
* 5. Extraction.
242-
* Move the data from the hidden Return Data Buffer.
243-
* This overwrites the 4-byte selector and facet address we stored earlier.
244-
*
245-
* ABI encoding for 'bytes' includes a 32-byte offset word at the start.
246-
* We point 'selectors' to ptr + 0x20 to skip the offset and point
247-
* directly to the Length word, making it a valid Solidity bytes array.
269+
* 7. Domain Validation (4-Byte Alignment).
270+
* Function selectors are strictly 4 bytes. We ensure the length is a multiple of 4.
271+
* Logic: (x % 4 == 0) is equivalent to (x & 3 == 0).
248272
*/
249-
let size := sub(returndatasize(), 0x20) // Adjust size to account for the 32-byte offset word
250-
returndatacopy(ptr, 0x20, size)
251-
273+
if and(declaredLength, 3) {
274+
revert(0, 0)
275+
}
252276
/**
253-
* 6. Integrity check
254-
* @param selectors
255-
* @param index
277+
* 8. Calculate Memory Size.
278+
* Solidity requires arrays to be padded to 32-byte boundaries in memory.
279+
* Formula: RoundUp32(x) = (x + 31) & ~31
256280
*/
281+
let paddedLength := and(add(declaredLength, 0x1f), not(0x1f))
257282

283+
/**
284+
* 9. Extraction & Auto-Padding.
285+
* We copy the data payload from the Return Data Buffer to memory.
286+
* - Dest: ptr + 0x20 (After the Length word we set in Step 5).
287+
* - Source: 0x40 (Skip the 32-byte ABI Offset + 32-byte Length Word).
288+
* - Size: paddedLength.
289+
*
290+
* MAGIC: If returndatasize is smaller than (64 + paddedLength), returndatacopy
291+
* automatically fills the remaining bytes with 0x00. This ensures the
292+
* memory is clean and perfectly padded without manual masking.
293+
*/
294+
returndatacopy(add(ptr, 0x20), 0x40, paddedLength)
295+
/**
296+
* 10. Finalize Pointer.
297+
* Set the return variable 'selectors' to point to our new array in memory.
298+
*/
258299
selectors := ptr
259300
/**
260-
* 6. Update Free Memory Pointer
261-
* New Free Memory Pointer is after the copied selectors.
262-
* Solidity requires the Free Memory Pointer to be aligned to 32 bytes.
263-
* 1. add(ptr, size) - end of copied selectors
264-
* 2. add(..., 0x1f) - round up to next 32-byte boundary (0x1f is 31)
265-
* 3. and(..., not(0x1f)) - clear lower 5 bits to align to 32 bytes
301+
* 11. Update Free Memory Pointer.
302+
* We advance the Free Memory Pointer to protect the data we just allocated.
303+
* New 0x40 = Start(ptr) + LengthWord(32) + Data(paddedLength).
304+
* No rounding is needed here because 'paddedLength' is already 32-byte aligned.
266305
*/
267-
mstore(
268-
0x40,
269-
and(add(add(ptr, size), 0x1f), 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0)
270-
)
306+
mstore(0x40, add(ptr, add(0x20, paddedLength)))
271307
}
272308
}
273309

0 commit comments

Comments
 (0)