Using delegatecall within view functions, to implement proxies #14577
Labels
closed due inactivity
The issue/PR was automatically closed due to inactivity.
feature
stale
The issue/PR was marked as stale because it has been open for too long.
Abstract
With some types of proxy contracts, functions invoke
delegatecall
to execute the code stored in a separate implementation contract. However, these functions cannot be declared asview
s becausedelegatecall
cannot be used within view functions. This means that the exported ABIs for these functions will be (ie)nonpayable
, resulting in contracts invoking these functions withcall
instead ofstaticcall
, and causing these functions to be incorrectly categorised by smart contract wallets, block explorers, etc.Motivation
There are several work-arounds to this issue, most of which are discussed in these StackOverflow answers by @k06a : thread 1 and thread 2. Another hack involves post-processing the compiled ABI. Unfortunately, these work-arounds all involve a run-time cost, and/or don't actually mark the functions as views in the ABI output by the compiler itself.
The "best" work-around I have found is described in the first StackOverflow thread above. In this approach, the view function
staticcall
s toaddress(this)
, which is then handled by a special non-view function thatdelegatecall
s the implementation. This results in the functions havingview
state mutability in the output ABI, at the expense of some extra run-time gas usage. (Note thatmsg.sender
is lost, but this typically does not matter for view functions.)This approach works because if a caller
staticcall
s a contract that invokesdelegatecall
, it will succeed as long as the called contract, and all contracts it invokes, do not actually attempt any state modifications. This demonstrates that in principle we could address this issue purely by making a change to the solidity compiler (and no EVM changes are needed).Specification
I did a few small experiments to feel out the possibilities for how this could be addressed.
Option A
Patch / test
This trivial patch adds a
delegatecallToView
function toaddress
. This works exactly the same asdelegatecall
except that it is considered to haveview
state mutability instead ofnonpayable
:This solves all the issues mentioned above, but has the down-side of preventing the compiler from statically verifying that no state modifications will be attempted when invoking
view
functions. In the broad sense, I don't see this as a problem because the actual decision about whether to interpret the function as a view is done by the caller by choosing to invoke it withstaticcall
or not.That said, there may very well be scenarios where the static verification is important, and I would like to learn more about these if so. The only case I can really think of is if you are interacting with a contract that has been verified on etherscan and its ABI says
view
, you can be sure that state modifications will not happen even if you usecall
instead ofstaticcall
for some reason.Option B
Patch / test
This patch is slightly more complicated but still pretty minimal. It adds support for a special
viewable
modifier. This is not a state mutability specifier. All it does is override the function'sstateMutability
field to beview
in the ABI output:The above function is compiled with default state mutability (ie nonpayable), which means that
delegatecall
is allowed. However, at ABI export-time itsstateMutability
is overridden to beview
. The modifier is calledviewable
to indicate that these functions are intended to be invoked withstaticcall
, even though the compiler has not been able to statically verify that it won't attempt state modifications.Typically you would use this function with no state mutability specifier and then perform a
delegatecall
within the function. If you do not do adelegatecall
(or some state changing operation) then you will receive a warning that this function can be made a view instead.One drawback of the patch in its current form is that when importing full contracts into a compilation unit, the external ABI is not used, so
viewable
functions will be invoked withcall
instead ofstaticcall
. This can be worked around by instead using aninterface
where the functions are labeled as views.This patch needs a bit more polish before I'd consider it ready. Testing, obviously, but also things like throwing errors when combining
viewable
withview
/pure
, specifyingviewable
multiple times, etc.Option C
I did not try implementing this, but
viewable
could itself become a state modification specifier, somewhere "in-between"nonpayable
andview
.This would probably solve the "compilation unit" issue mentioned in the previous section, but I think if we went this route then we may as well make function capabilities more general/granular, for example as described here. Personally I would selfishly prefer a focused solution for this specific issue.
Backwards Compatibility
Option A would change the static-verification guarantee implied by
view
, which may or may not cause problems (I'd like to learn more about this!).Option B could break contracts that define their own modifiers named
viewable
.I haven't thought enough about Option C to say one way or the other.
The text was updated successfully, but these errors were encountered: