Upgrade safe libraries

I've been doing some research around using external libraries and I still have a couple of questions which would be great if someone could help.

I have come across this paragraph:

In the meantime you can deploy upgradeable contracts linked to external libraries by setting the unsafeAllowLinkedLibraries flag to true in the deployProxy or upgradeProxy calls, or including 'external-library-linking' in the unsafeAllow array. Keep in mind the plugins will not verify that the linked libraries are upgrade safe. This has to be done manually for now until the full support for external libraries is implemented.

The confusing part here is that it suggests we need to do manual verification to see if the linked library is upgrade safe.

The is documentation about when a contract is upgrade safe

However, I'm not quite sure how does this translate into the context of libraries. When a library is upgrade safe?

Moreover, I came across this sentence from the link above:

As such, it is not allowed to use either selfdestruct or delegatecall in your contracts.

Isn't a call to an external library a delegatecall? If so does it mean we cannot/should not use external libraries whatosver?

The reason external libraries are not allowed by default is precisely that they are delegatecall. But the only reason delegatecall is dangerous is that it can result in triggering a selfdestruct. If you can guarantee that your linked external library will never trigger selfdestruct, that's what would be considered upgrade safe in this scenario.

If you're passing storage pointers to structs you also need to make sure they expect the same struct layout.

1 Like

Hey @frangio,

Thanks for getting back so quickly. Ok now the delegate part makes absolute sense. In my case I'm using an external library that is deployed by me so it should be ok with.

I haven't really understood this part though:

If you're passing storage pointers to structs you also need to make sure they expect the same struct layout.

Image the following use case

library LibA {
  struct State {
    uint256 propA;
    uint256 propB;
    uint256 propC;
  }

  function funcA(Data storage self) public {}
  function funcB(Data storage self) public {} 
}

and the contract that is delegating the calls to LibA

contract ContractA {
  using LibA for LibA.State;

  LibA.State private state;

  function funcA() public {
    state.funcA();
  }

  function funcB() public {
    state.funcB();
  }
}

Is this considered a safe usage of the proxy pattern and does it make the contract and lib upgrade safe?

That's ok. The issue is if you change State in the future. You need to change it in a compatible way, that doesn't break the library, unless you also redeploy the library, in which case it would be ok.

1 Like

You need to change it in a compatible way, that doesn't break the library

I guess you mean I cannot change it to this (i.e add a field to the beggining)

  struct State {
    uint256 newProp;  // new prop added
    uint256 propA;
    uint256 propB;
    uint256 propC;
  }

but instead do this

  struct State {
    uint256 propA;
    uint256 propB;
    uint256 propC;
    uint256 newProp;  // new prop added
  }

unless you also redeploy the library, in which case it would be ok.

I'm not sure how this will work though. Event if we deploy a new logic contract i.e. ContractA and link it to a newly deployed LibA library, the state layout still cannot change, right? The state is always stored on the Proxy not the lib or the logic contract. Am I missing something here?

Yes all of that is true! I think you understand this quite well! :grinning_face_with_smiling_eyes:

2 Likes