How the new ProxyAdmin will work

Together with @jcarpanelli we are starting work on a new feature to overcome some of the usability challenges that come up around the usage of transparent proxies, and we wanted to open the discussion on how this new feature would fit within the ZeppelinOS client and libraries.

The story so far

But first, a quick reminder on the two different modes we have for running a ZeppelinOS project.

Current default mode

By default, the ZeppelinOS CLI keeps track of all the implementation contracts that are deployed when running zos push in per-network json files. This way, when you create a proxy, the CLI just looks up the address for the implementation on the json file, and creates a proxy that is backed by it. Upgrades are issued directly to the proxy by the sender account.

zos add MyContract 
// Adds the contract to the project

zos push --network mainnet 
// Deploys MyContract to mainnet
// Updates zos.mainnet.json with its address

zos create MyContract --network mainnet
// Looks up the address of MyContract in zos.mainnet.json
// Creates a new proxy backed by that address
// Saves the address of the new proxy on zos.mainnet.json

In this mode, the proxies are created and owned by the sender account, which is problematic when the same accounts wants to interact with the created instance.

MyContract.deployed().myMethod({ from: accounts[0] })
// Fails since the default account is the owner of the proxy

Published mode

Alternatively, a ZeppelinOS project can be published via the zos publish command. This deploys a set of contracts (App, Package, and Directory) that keep the structure of your project on-chain. The Directory keeps track by name of all the implementation contracts deployed on the current version, the Package keeps one Directory per version, and the App keeps track of all the Packages used (you’ll have more than one if you linked an EVM package as a dependency).

App 1--n Package 1--n Directory 1--n Implementation

Publishing your project is only mandatory if you want to share it as an EVM package. Otherwise, ZeppelinOS works simply on the default mode.

In this mode, the App contract not only keeps track of the Packages that comprise the project, but also acts as the owner of all proxies created. It provides methods to create a new proxy and upgrade an existing one.

zos add MyContract 
// Adds the contract to the project

zos push --network mainnet --publish
// Creates App, Package, and Directory contracts
// Deploys MyContract to mainnet
// Registers MyContract in Directory
// Updates zos.mainnet.json with all deployed addresses

zos create MyContract --network mainnet
// Requests the App contract to create a new "MyContract"
// Saves the address of the new proxy on zos.mainnet.json

Note that, since the App is the admin of the proxies, the user’s default account can safely interact with the created proxies. Whenever the user needs to manage the proxies, they do it via the App.

zos update MyContract --network mainnet
// Looks up the address of MyContract proxy in zos.mainnet.json
// Sends a request to App to update that address to the latest "MyContract"

Summing up

  • By default, the user’s account is the admin of all proxies created, and manages them directly.
  • When published, the App contract is the admin of the proxies, and the user’s account is the owner of the App.

SimpleProject and AppProject

As a side note, both these modes are accessible via the programmatic interface as well. The default mode can be replicated in javascript using the SimpleProject class.

const project = new SimpleProject('MyProject', { from: accounts[0] });
// Setup new project

await project.setImplementation(MyContract);
// Deploy an instance of MyContract implementation contract and track its address

const proxy = await project.createProxy(MyContract);
// Creates a new proxy backed by the deployed MyContract instance
// The proxy is owned by the sender address, account[0]

While the published mode is handled by the AppProject class:

const project = AppProject.deploy('MyProject', '0.1', { from: accounts[0] });
// Deploys new App, Package, and Provider contracts

await project.setImplementation(MyContract);
// Deploy an instance of MyContract implementation contract
// Registers it in the Provider contract

const proxy = await project.createProxy(MyContract);
// Sends a tx to App.create('MyContract'), to create a new proxy
// The proxy is owned by the App contract

The new ProxyAdmin

The idea of the new feature we are working on is to have a small contract ProxyAdmin that takes over the responsibility of owning proxies from the user’s account and from the App contract. This contract would only have a method to upgrade a proxy given its address, and would be owned by the user’s account.

This way, the user goes through the ProxyAdmin contract for managing their proxies, instead of interacting directly with them. This allows the user account to interact directly with the proxies, since it is not its admin.

Question: Do we like the name for the ProxyAdmin?

Default and published modes revisited

This new component would now be included on both the default and the published mode:

  • In default mode, the user account no longer is the admin of the proxies. The user account is now the owner of the ProxyAdmin, who in turn is the admin of the proxies
  • In published mode, the App no longer is the admin of the proxies. The user account is now the owner of the ProxyAdmin, who in turn is the admin of the proxies. This allows us to remove the upgrade methods from the App.

This means that there is no way from the CLI to manage the proxies directly, since a ProxyAdmin will always be deployed.

Question: are we fine with this? Do we want to keep a non-default super-lightweight mode in which the users can still manage the proxies directly, and deal themselves with using different accounts for proxy management and interaction? I’d say no, to avoid cluttering the CLI with too many different modes.


For low-level programmatic usage, the SimpleProject would keep behaving exactly the same way as before. The AppProject, on the other hand, would incorporate the new ProxyAdmin for being the admin the proxies, instead of having the App contract in that role.

We would also introduce a new ProxyAdminProject that has the same behaviour as the new CLI default mode: it keeps track of all implementation contracts off-chain, but delegates the admin management of the proxies to a ProxyAdmin instance.

This would allow users of the programmatic interface to manage proxies themselves via the SimpleProject, and also to be able to achieve the same behaviour as in the CLI via the new ProxyAdminProject. The rationale for this decision is that users that rely on the programmatic interface have more knowledge of the system, and can easier manage the quirks of using two different accounts for interacting with their proxies.

Question: Are we fine with keeping 3 different projects, even if one is not used from the CLI? Do we like the new name for the new project?


The new ProxyAdmin implies a breaking change with respect to the old architecture, both for default and published mode. This means that we’d need to migrate the old projects to the new format, which could be easily detected via the zosversion entry on the json files.

The migration would entail the same for both kind of projects: deploying a new ProxyAdmin, and transferring the administration of them from the user account or the App (depending on the project kind) to it.

Instead of adding a new command for the migration, whenever the user attempts to perform a network-related operation (such as create or upgrade), he’s notified by the CLI that the structure is incompatible and needs to migrate. If the user accepts, the migration proceeds; if not, the command execution is aborted.

$ zos create MyContract --network mainnet
> An old version of the ZeppelinOS project is detected on mainnet, and needs to be migrated to the latest one.
> Do you want to proceed? [y/n/h]

// if yes
> Migrating...
> Deploying new ProxyAdmin
> Transferring ownership of 0x123 to proxy admin at 0x456
> Transferring ownership of 0x124 to proxy admin at 0x456
> Migration successful
> Proceeding with original command
> Creating instance of MyContract
> Instance of MyContract created at 0x125

// if no
> Migration cancelled. Aborting.

// if help
> The new version uses a thin ProxyAdmin contract to manage all upgradeable contract instances. This avoids issues with using the same account for managing the upgradeable contracts and interacting with them. All proxies currently managed by your account 0x1234 [or your App 0x12345] will now be managed by the ProxyAdmin, which will be owned only by you.
> Do you want to proceed? [y/n/h]

Question: Are we fine with forcing users to migrate? An alternative would be to maintain support for the old modes, but this means much more complexity to maintain in the project. As for the method for enforcing the migration, are we fine with it?

Wrapping up

The restriction to avoid using the same account for managing a proxy and interacting with it has been the source of much confusion so far. The ProxyAdmin acts as a very small forwarding agent to prevent this issue, by taking over the responsibility of being the admin in all scenarios. We’d appreciate your feedback on this new feature!


I think this is great :slight_smile:!

I’m fine with having these three different projects for now, but we should somehow track if anybody is using SimpleProject and if it’s necessary to keep it in the long term. Regarding the new project class name, we could think about a more descriptive name rather than ProxyAdminProject, but OTOH it’s pretty consistent with the actual naming we have:
AppProject - App and PackageProject - Package, where App and Package are the JS/TS interfaces for the App and the Package contracts.

I agree this might be the friendliest way to suggest a migration. Also, having a migrate command could be somewhat bizarre, as it can easily be confused with truffle’s concept of migration.

It will also be a good opportunity to introduce interactive commands :slightly_smiling_face:.

I would prefer to force them to migrate rather than leaving them the choice, as advanced users will be able to manage their proxies without the ProxyAdmin or the App contracts using SimpleProject.

1 Like

YES! It’s literal and fully explains what the contract does. @elopio thoughts?

As I told you in an offline conversation, I’m slightly in favor of having 1 ProxyAdmin per proxy. Depends on which implementation is easier, as the DX wouldn’t change, if I understand correctly. Not strongly against having the ProxyAdmin manage multiple proxies, but in that case maybe the name should be ProxiesAdmin? (yuck)

CLI should always have the easiest user experience. I wouldn’t worry about hardcore users in the CLI, they can always use zos-lib directly and manage proxies manually.

I didn’t quite get why we need a new project type. What function would this contract provide?

Can’t we make the new scheme backwards-compatible? i.e: that the calls to the ProxyAdmin contract have the same names as the calls to Proxy, so that when users upgrade to the new CLI, everything still works without the ProxyAdmin? I know this is hacky but if it works I’m ok with it (it’s only for the few users we have today).
Otherwise, migration plan looks good. (I wouldn’t support the old scheme, it’s ok to force migration I think – maybe just to be sure we can ask a few users if they’d be ok?).

1 Like

Both are straightforward to implement. I’d say that a singleton admin is easier, as it is more similar to the App we have today. And besides, it implies greater gas savings, which is something we want to keep in mind for our proxies.

The idea is to have the following projects:

  • AppProject: Involves ProxyAdmin, App, Package, Directory, Proxies
  • ProxyAdminProject: Involves ProxyAdmin, Proxies
  • SimpleProject: Involves just Proxies

Not sure it can be done without having some code in the CLI to explicitly manage this case. It wouldn’t be much, but it implies keeping support for old behaviours. I’ll see if I can find a way around this.

1 Like

why would someone use that? I still don’t understand. Aren’t programmatic use-cases all covered by SimpleProject or AppProject?

1 Like

Not if you want the Project instance to set up ProxyAdmin(s) on your behalf. You’d need to set them up manually when using a SimpleProject.


Back in the day I argued that maybe we shouldn’t hide everything from users, because the create command was not clear to me. I suggested renaming it to create-proxy. I still think this is one of the causes of confusion when trying to call the created contracts. Maybe, with this change and a nice message printed after create is executed, things will be clearer for users.

:+1: thanks for the exploration and the explanation @spalladino.

1 Like

Thanks folks! Any feedback on naming for ProxyAdminProject? It would be the facade on zos-lib for managing a project that creates proxies managed by an instance of ProxyAdmin. We thought about ManagedProject, but I’m still not sold.

1 Like

I prefer ProxyAdminProject over ManagedProject. In here managed would hide the fact that there’s a proxy, and doesn’t add a lot of signification.

On a separate note, if all of them are projects, it comes to mind the idea of a hierarchy. Like saying that ProxyAdminProject is an specialization of the SimpleProject. Is this the case?

1 Like

I also prefer ProxyManagedProject but I’m not convinced code changes would deter the confusion in question. I understood the issue with the Proxy deployment the second time I went through the tutorial. It may simply require more attention, perhaps an alert box around it, in the documentation.


Kind-of. Both of them share a base class, but it’s not that one extends from the other. We will definitely want to add a section to explain the differences between the three of them.

I agree that this does not solve the confusion, and we still need to do more work on the docs to make it clear. That said, it does change the happy-path so you don’t hit this issue immediately as you start using zOS, which was a big deterrent for many users.