3

I have read through some standards regarding upgradeable contracts like EIP-1538, EIP-1822 and EIP-2535, and it seems most of them rely on the proxy pattern where a proxy/storage contract with a fallback method uses delegatecall on a "logic contract" to run its code. However I'm not sure how the proxy may call the view/pure functions in the "logic contract" this way? It seems in order to perform a delegatecall, you can't use it in a view function, and you must send a transaction for it.

After read through a lot of docs, I still cannot find anything regarding the handling of view/pure functions in a supposedly upgradeable contract. I wonder if I have missed something? Anyone can explain how these proxy contract based on delegatecall handled view/pure functions? Thanks.

1 Answer 1

1
+150

The concept of a proxy pattern is to have two contracts:

The proxy contract, the one that has the state variables, and the one that delegates the calls to the logic contract.

The logic contract, a contract for code only to run with the context of the proxy contract. (when I say context I mean the state variables of the proxy contract).

Let's say the proxy contract variables are

unit public x;
unit public y;

and the variables on the logic contract are the exact same (they need to be the same so you can access them).

now lets look on the function on the logic contract:

function getY() view returns (uint256) {
     return y;
}

When you call the function getY on the proxy contract, it calls delegate call to the logic contract with that function. now when the code run, what happens is the code sees the variable y, realizes it's on the second storage slot of the logic contract, and then tries to access it.

But since the context is the proxy contract context, it actually accesses the second storage slot on the proxy contract and not the logic contract and finally returns the variable y of the proxy contract.

EDIT

The proxy contract does not suppose to have an actual logic for itself. It holds the state variables you want to have in the logic but no logic function.

The code in the proxy contract should only have functions that are important for the proxy implementation, the view/pure function it has will not be delegated to the logic contract.

Examples for view/pure functions in the proxy contract are:

function _admin() internal view virtual returns (address)
function _implementation() internal view virtual override returns (address impl)

and more, but those will not be delegated to the logic (And solidity doesn't allow it anyways).

All the custom implementations you want are placed in the logic contract and as I explained earlier, you can have view/pure function there.

EDIT 2

Let's say you want to call a view function foo() on the proxy contract P. Once you call P.foo() the fallback() function is activated since P doesn't have the function foo() (instead the logic contract Q has this function).

From the fallback function, it delegates the call to Q.foo() and it works since the fallback is not a view function.

It knows what function to delegate to using assembly opcodes, even though the fallback function doesn't receive any parameters.

You can look at the proxy contract here, and the assembly opcodes are in the _delegate function: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/Proxy.sol

1
  • Comments are not for extended discussion; this conversation has been moved to chat.
    – Ismael
    Nov 17, 2021 at 14:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.