@@ -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