EIP-1967: Standard Proxy Storage Slots

in fact, it seems that the following also works:

    function _delegate(address implementation) internal virtual {
        (bool success, bytes memory data) = implementation.delegatecall(msg.data);
        assembly {
            let size := mload(data)
            switch success
            case 0 {
                revert(add(data, 0x20), size)
            }
            default {
                return(add(data, 0x20), size)
            }
        }
    }

perhaps it’s a matter of taste, but it seems arguably more aesthetically appealing. it may be possible to get rid of the inline assembly altogether; i’m not sure.

edit: i can confirm that “my” way leads to slightly larger (~234 bytes) bytecode. not sure exactly about the gas.

Rationale is that memory is cleared across EVM CALLs, so if _delegate is always returning from the current CALL frame, then memory will be cleared anyway, so it’s safe to overwrite.

Yup, my guess is that this implementation would also work.

1 Like

Hi guys, thanks for this EIP!
Is there a way to use it for a proxy with 2 (or more) chained implementations?
This is something I’m contemplating for Super Tokens in Superfluid protocol (see draft PR).
Here, the token contracts are instances of UUPSProxy pointing to the actual token logic.
The goal is to allow adding custom logic for individual token contracts, while sticking to the canonical implementation contract.
It’s my understanding that I can’t reuse the implementation slot for the 2nd hop, because we’re always operating on the storage of the proxy contract, thus that storage slot is already occupied.
The current implementation in this PR uses address keccak256('eip1967.proxy.implementation') - 2 for storing the address for the 2nd hop - the idea being that for every additional hop, I add a decrement of 1 for the slot address.
Assuming that the EIP as is doesn’t support multiple hops (please correct me if I’m wrong here), do you think this would be a sensible extension to EIP-1967, or would you recommend a different approach?

1 Like

That’s correct.

Your proposal sounds good to me. However, I would try to make the standard ERC-1967 slot the “main implementation slot”, because I believe that is the one that Etherscan will pick up for proxy-related features. I imagine that the main one is the “final hop”, so I would try to reverse the approach that you described, so that the final hop is located at ... - 1.

For the use case I have in mind, the first hop would be the “main implementation slot” - in the sense that it contains most of the functionality. The 2nd hop would be an optional extension, unused for most tokens.

A disadvantage of the reverse approach is that it would be less flexible - adding a hop would require shifting pre-existing storage slots.

Explorers without knowledge of such an extension would show only part of the logic mapped onto a proxy contract, functionality covered by consecutive hops would remain invisible.
If the first hop happens to contain most of the logic, that would arguably be the best we can hope for. If the main functionality resides in a later hop (e.g. the final one), that would not be the case.
The main rationale for my proposal is to take advantage of existing EIP-1967 support without messing with it (not redefining anything) while making it as easy as possible to extend support for multiple levels.

Does anyone still review this EIP?