2

I have been absolutely racking my brain trying to understand this line of code from OpenZeppelin's ERC721.sol file. It is inside the function _checkOnERC721Received. I've already dove into a bunch of documentation and I just can't find anything that makes this line truly make sense

assembly { revert(add(32, reason), mload(reason)) }

taken from

function _checkOnERC721Received(
        address from,
        address to,
        uint256 tokenId,
        bytes memory data
    ) private returns (bool) {
        if (to.isContract()) {
            try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
                return retval == IERC721Receiver.onERC721Received.selector;
            } catch (bytes memory reason) {
                if (reason.length == 0) {
                    revert("ERC721: transfer to non ERC721Receiver implementer");
                } else {
                    /// @solidity memory-safe-assembly
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        } else {
            return true;
        }
    }

Why 32 is added to reason and the starting point of revert? Is revert using 32 as bits of bytes? From my understanding it would be the difference between a fresh 256 bit block(32*8) and the 32 bits that the reason variable takes up. reason is a "bytes memory" type, which is a size of 32 bits. reason is equivalent to the error variable in a try{} catch{}. With bytes type being big endian the 32 addition, in bits, would completely skip over the reason variable into the bit immediately after reason. Either way both of these options skip over reason completely, which, from my understanding, is what revert is supposed to return. How can it return anything if revert is of the general format revert(start, end). The only way I see this working out is if revert is of the general format revert(end, start).

2 Answers 2

1

So, in Yul revert(p, s) means returning the memory interval [p, p+s), i.e. from byte p and with length s.

When there's a variable in memory, for example bytes memory reason, doing mload(reason) will read the first 32 bytes of reason. Indeed, in Yul reason becomes a pointer in memory.

Since reason is of dynamic type bytes, we have to interpret it using its ABI encoding form. So the first 32 bytes are the length in bytes, followed by the actual data.

Putting all these infos together:

  • the length of the revert message (s before) needs to be reason.length, so mload(reason) since the length is the first 32 bytes.
  • the start of the message (p) is read from reason skipping the length, so it's add(reason, 32)

It helps writing down what reason looks like in memory, for example this is "Hello, world!" saved in memory from byte 0x100.

SLOT   -  MLOAD(SLOT)
0x100  -  0x000000000000000000000000000000000000000000000000000000000000000d  // 13: length in bytes of data
0x120  -  0x48656c6c6f2c20776f726c642100000000000000000000000000000000000000  // data
0

catch block will catch this bytes memory reason.

The term bytes in Solidity represents a dynamic array of bytes. It’s a shorthand for byte[].

the lenght of bytes is 32 bits.

mload(reason) will return the length of the reason

in add(32, reason), "reason" is the starting memory address of "reason" data from the data.

this revert(add(32, reason), mload(reason)) will revert the "reason" from its array in an memory efficient way

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.