Logical organization of upgradeable contracts

I am trying to write my first upgradeable (UUPS) contract(s) and I'm a little bit puzzled with some logic regarding what goes into the "proxy" and what goes into "implementation" contracts. It would seem logical to me if proxy contract would be very simple one, probably with initialize() and _authorizeUpgrade() functions only, while implementation would hold all the business logic (e.g. minting, burning, whatever else), especially if there's plenty of different functions in it.

However, after reading couple of articles about upgradeable contracts, I see that lots of people are adding business logic in the same contract where initialize() and _authorizeUpgrade() functions are.

Is it really a common pattern to write the very first version of the business logic in the same place where proxy-related functions are or should I keep my proxy as minimal as possible and write everything else in a separate contract?

Generally, you should not write your own proxy contracts. You should deploy the proxy contracts that are in the OpenZeppelin Contracts library. For UUPS, that would be ERC1967Proxy.

You can also use the Upgrades Plugin if you are using Hardhat, which helps with deploying and upgrading proxies.

The design of the UUPS pattern is such that your initialize() and _authorizeUpgrade() functions should be in the implementation contract. See https://wizard.openzeppelin.com/ and select UUPS for an example implementation.

1 Like