Hey, I wanted to get your feedback on a design decision...
In our company, we mostly use upgradeable contracts, UUPS variant using the ERC1967Proxy. This means, there's no logic in the proxy, all the calls are forwarded to the implementation.
We also use something similar to OZ 5.x AccessManager, a centralized contract that manages the permissions for all the contracts in the protocol.
For our new version, we will use the OZ 5.x AccessManager, and I was thinking of moving the access validation (call to .canCall
) to the proxy. In the proxy, before calling the implementation contract, I will check with the AccessManager if the called is authorized to call that method.
It's a change of paradigm from the public by default current approach of contracts, to permissioned by default.
This way, without adding any logic, ALL the methods in my contracts will be permissioned by default. I won't need to add onlyRole
modifiers to the methods and I will move all the access control rules from code to deployment.
So our contract code, instead of looking like this:
function setCriticalParam(uint256 newValue) external onlyRole(ADMIN) {
criticalParam = newValue;
}
Will be like this
function setCriticalParam(uint256 newValue) external {
criticalParam = newValue;
}
And then on the operation recommendations, I will check the setCriticalParam access is only granted to ADMIN role.
I see some advantages of this approach:
- Full flexibility in the access control to my contracts.
- Less complexity in my contracts' code. We will probably add recommendations like "This method should be public" or "This method should be enabled only to top-level admins". But they will be just comments.
- Closing access to public methods (full or granular pause of the contract) can be implemented without hardwiring a particular logic in the contract.
- We can implement cool rollup processes like "closed beta".
- Fewer things to audit for static code audits, more or less the same amount of things to audit for "deployment audits".
Disadvantages:
- Higher gas costs for the public methods that will require an external call to the AccessManager contract just to check they are public.
- For each contract will need to setup the permissions on the access manager for ALL the methods.
- This also includes the views (in principle, see comment below).
To avoid calling the AccessManager contract for views (assuming the views are public by default), we can implement something to detect if we are in a static call and, in that case, skip the access control validation. It can be calling a method that modifies a trasient variable, for example, an if it fails we assume we are in a static call.