EIP-1822: Universal Upgradeable Proxy Standard (UUPS)

I think the addition of “re-initialization” code is a great suggestion. I wouldn’t go as far as say it’s a reset, but definitely it’s a process that might be needed. Although there is already space for it happen, I agree that it’s not the best user experience not having it in a single tx if possible.

I will think a bit and suggest an implementation for it.

I think state reset is needed for 2 reason:

  • I might want to upgrade from mutlisig1 to multisig2 or from multisig2 to multisig1. If the 2 were dot designed to be compatible, I one will end up assuming fiels are null when the previous delegate set them. This can be keys or anything else.

  • When moving from multisig1 to multisig2 then back to multisig1 I will assume no traces of are left that would break the assumptions of the second usage of multisig1.

That is why I believe some data should formalized by ERC1836 to stay as an invariant — nonce / nonceless replay protection / identity generic data (from ERC725) for example — and everything else should be cleaned up.

We could however see the cleanup as a layer on top of the standard, with the basic updateDelegate / updateCodeAddress not performing the cleanup and an added cleanup function that would clean then call the upgrade mechanism. That way you have the choice to cleanup before upgrading or not (in a single tx)

A reset is something potentially impossible, or at the very least cost prohibitive.
If you are upgrading from multisig1 to multisig2, you are not just randomly pointing to another implementation but rather something more like fromMultisig1ToMultisig2 implementation/logic contract.

Also, I remember from a conversation with Fabian where his was proposing (when discussing identity) those contract whose address are beacons (not meant to be changed, such as the 725) be managed by some other contract. This came up when talking how, in this particular case, a 725 is often managed by a single owner but in the future might be managed by a multsig. In his view, when it happens you change the owner to a 734 contract. I think that’s the best option for when changes are quite drastic ( although I do see how you could potentially make it happen with a “simpler” upgrade).

I believe multisig1 and multisig2 cloud be ERC1077 (universal login), gnosis safe, uport, … things that you may want to move from and to in no particular succession.

My issue with having an ERC734 owning an ERC725 is that:

  • By calling the ERC725 proxy you would not be able to access info from the multisig (like ERC1271 interfaces) … this can be solved by a fallback in the proxy, which i proposed as a PR to ERC725
  • The multisig owns no asset, and therefore cannot easily refund relayer for meta transaction (or the multisig has to be ERC725 specific … which I think isn’t a good idea)
  • You end up with a LOT of multisig (one per proxy) which is expensive to deploy and fill the blockchain memory … keep in mind that they will be disposable
1 Like
  • You end up with a LOT of multisig (one per proxy) which is expensive to deploy and fill the blockchain memory … keep in mind that they will be disposable

You missed the point of UUPS hahaha. You wouldn’t really deploy the whole 734 every time, just deploy an UPPS and point to it.

The multisig owns no asset

But it manages a contract that has assets. It is able to implement 1077 with no trouble and ask the 725 to issue the repayment. All this while the other systems are completely unaware of what is really going on with the setup (734->725).

By calling the ERC725 proxy you would not be able to access info from the multisig.

As of now, it’s not part of the ERC725, and for that, I agree that it is not straightforward. But you could have an implementation of 725 that is aware of outside management, meaning another contract.

All this to say, that you really don’t need to resort to “state” resets.

Once a user deploys a contract with a vendor, they will be very limited on migrating it to another vendor’s implementation. Therefore, it makes sense that vendors will keep track (as they have), of their implementations, and when updates/upgrades are available they will have to check for compatibility. We can only try to make this less of a “locked with a vendor” kind of situation. And I think decoupling where possible, as 725 being simple but managed externally is one of those measures.

1 Like

Ok so instead of having potentially millions of abandonned multisigs, you have millions of abandonned ERC1822 … sure it’s better, but still sounds bad to me

But you have 2 different version of the 1077, one for when it’s a standalone, and one for when it’s behind an ERC725

In the end it’s a matter of personal preferences. I see the point of your organisation. It’s less likely to break but is more expensive. I see mine as being more elegant and cheap, but also more complex for SCs developers

1 Like

millions of abandonned ERC1822

Next iteration will have a solution for it.

Glad we could talk.

1 Like

To improve compatibility of this proxy with existing and future solutions, I think it would be much better to use the slot defined in EIP-1967: Standard Proxy Storage Slots. @pi0neerpat Can you explain the reasons why this was explicitly decided against? We’re trying to move said EIP to Final state and it would be good to have UUPS on board.

We really do like this model and want to provide an implementation of it in OpenZeppelin Contracts but the choice of an incompatible storage slot sticks out as a problem for us. For inclusion in OpenZeppelin it would also be necessary to move this EIP to Final.

1 Like

We are on board too with this new 1967 storage slot naming. Working on it here and will report back here once we are finished https://github.com/ethereum/EIPs/pull/2750

1 Like

Currently the purpose of proxiableUUID is not well explained.

It seems to me it would be made more useful, if it would be about differentiating different types of logic contract as opposes to the slot of the implementation:

abstract contract Proxiable {

...
   /**
     * @dev Proxiable UUID marker function.
     *      This would help to avoid wrong logic contract to be used for upgrading.
     */
    function proxiableUUID() public pure virtual returns (bytes32);

    /**
     * @dev Update code address function.
     *      It is internal, so the derived contract could setup its own permission logic.
     */
    function _updateCodeAddress(address newAddress) internal {
        require(
            proxiableUUID() == Proxiable(newAddress).proxiableUUID(),
            "Proxiable: NOT_COMPATIBLE"
        );
        ProxyUtils.setImplementation(newAddress);
...

While in actual implementation of a specific contract:

    function proxiableUUID() public pure override returns (bytes32) {
        return keccak256("org.rdai.contracts.RToken.implementation");
    }

Yes the language here is vague about what the uuid could be intentionally. Would adding some more examples here be helpful? Or would you want to see the section changed to explain more about how uuid should be used?

The code example from the proposal is:

    function updateCodeAddress(address newAddress) internal {
        require(
            bytes32(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7) == Proxiable(newAddress).proxiableUUID(),
            "Not compatible"
        );

And that is suggesting proxiableUUID is basically the slot ID, which I find un-useful. If I wanted to distinguish different type of logic contract, I would rather use the UUID scheme I proposed in the previous reply. Is that close to what you have had in mind, or I have deviated from the original idea?

Hi All,

Is this thread still going on ? I would love to ask some questions that I found very interesting and confusing by following this EIP …

2 Likes

Definitely post whatever you have to ask here, Discourse puts updated threads on top on the Forum home page.

We have been using it (with some extensions) at Superfluid.finance, would love to push this forward more.

@pi0neerpat what do you think?

A wonderful overview with superb Remix demo of Universal Upgradable Proxy Standard by @pi0neerpat & @gbarros on Peep An EIP. Recording available to follow

1 Like

Hello,
While experimenting with proxy patterns, and trying to come up with an implementation that would be nice and modular, we realize there is an issue with the proxiableUUID as a security mechanism.

proxiableUUID() returns a specific value that is supposed to help distinguish between a UUPS compatible logic, and any other smart contract. The point being that is we update a proxy to point to a non-UUPS compatible contract, this would not include/expose the necessary upgrate mechanisms, and this would break further upgradeability.

However, there is an entier class of contract that do expose the proxiableUUID logic but do not contain the corresponding logic: UUPS proxies.

A UUPS proxy will forward any call to the implementation, and since the implementation is requiered to implement the UUID mechanism, the proxy will actually mimic that. However, upgrading your proxy to use another proxy as your logic will instancty and irrevocably break your proxy.

Thus, we feel like the security mechanism described in ERC-1822 is not good, and should either be removed or modified.

2 Likes

@pi0neerpat How do you feel about removing the proxiableUUID mechanism from the EIP? The resulting EIP would only document the UUPS pattern and not impose any restrictions on the ABI of implementation contracts. I’m not sure if this is compatible with the original goals of the EIP.

2 Likes

A couple of issues with the restricting logic functions sections:

contract Owned is Proxiable {
    // ensures no one can manipulate this contract once it is deployed
    address public owner = address(1);

   // DOS vulnerability
    function constructor1() public{
        // ensures this can be called only once per *proxy* contract deployed
      // *typo* require should check address(1) below
        require(owner == address(0));
        owner = msg.sender;
    }

    function updateCode(address newCode) onlyOwner public {
        updateCodeAddress(newCode);
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "x");
        _;
    }
}

The quasi constructor can be DOS’ed by malicious miners, so it would be far preferable to pass in the owner address immediately. Minor, the require check has a typo.

1 Like

The reference link is broken…

References

Results in:

Error

410

The author deleted this Medium story.