Restricts an external function so that it can only be called by the contract itself.
external functions can normally be called by any address - it is up to contracts to set up access control
checks
e.g. to admin functions. only_self similarly makes it so that the only authorized address is the account
contract, i.e. the function requires reentrancy.
These are different from internal functions in that those are internally called, i.e. they don't result in
contract calls.
Use Cases
This attribute can be applied to both private or public external functions, so there are multiple scenarios to
consider.
Private
Private functions can only be externally called by other private functions. In this case, only_self can be used
to recursively call circuits, achieving proving time performance improvements for low-load scenarios.
For example, consider a token transfer for some amount: the number of notes that need to be read and nullified is
not known at compile time. Selecting a large maximum number of notes to read per transfer would result in a large
circuit with excess capacity in transfers of few notes, while a small maximum would outright prevent many notes
from being used at once.
A better approach is to create a private only_self external function in which notes are read and nullified,
recursively calling itself if the sum of the values does not add up to some target amount. This makes the circuit
adapt itself to the number of notes required, resulting in either few or many recursive invocations and therefore
proving time roughly proportional to the number of notes. The recursive function must be only_self because we
want to prevent any other contract from calling it - it is only an internal mechanism.
Public
Public functions can only be externally called by both private and public functions. Public to public only_self
calls are rare and not very useful (it'd be equivalent to an external Solidity function with require(msg.sender == address(this))). Private to public calls do enable useful design patterns though.
A private function that needs to perform some public check (like some public state assertion) or follow-up public
action (like some public state mutation) can do so by enqueuing a call to an externalonly_self public
function. The only_self attribute will prevent external callers from invoking the function, making it be a
purely
internal mechanism.
A classic example is a private mint function in a token, which would require an enqueued public call in which the
total supply is incremented.
Initialization Checks
only_self functions implicitly skip initialization checks (as if they had noinitcheck). We want
only_self functions to be callable during initialization, so we can't have them check the initialization
nullifier since it would fail.
This is safe because only_self functions can be called only by the contract the function is in, meaning
execution must start with another external function in the same contract. Eventually the call stack reaches the
only_self function, but let's focus on that external entry point:
If it already performed an initialization check, then we are safe.
If it skipped the initialization check (via noinitcheck), then the contract developer is explicitly
choosing to not check for initialization, and so will our only_self function. That's a design choice by
the developer. If we didn't skip the initialization check on only_self, the developer would just add
noinitcheck to it anyway.
If it was the initializer, note that initialization nullifiers are emitted at the end of initialization:
the private initialization nullifier after all private execution, and the public one after all public
execution. So in terms of initialization checking, everything behaves as if the contract hasn't been
initialized yet, and the same two points above still apply.
Restricts an
externalfunction so that it can only be called by the contract itself.externalfunctions can normally be called by any address - it is up to contracts to set up access control checks e.g. to admin functions.only_selfsimilarly makes it so that the only authorized address is the account contract, i.e. the function requires reentrancy.These are different from
internalfunctions in that those are internally called, i.e. they don't result in contract calls.Use Cases
This attribute can be applied to both private or public
externalfunctions, so there are multiple scenarios to consider.Private
Private functions can only be externally called by other private functions. In this case,
only_selfcan be used to recursively call circuits, achieving proving time performance improvements for low-load scenarios.For example, consider a token transfer for some amount: the number of notes that need to be read and nullified is not known at compile time. Selecting a large maximum number of notes to read per transfer would result in a large circuit with excess capacity in transfers of few notes, while a small maximum would outright prevent many notes from being used at once.
A better approach is to create a private
only_selfexternal function in which notes are read and nullified, recursively calling itself if the sum of the values does not add up to some target amount. This makes the circuit adapt itself to the number of notes required, resulting in either few or many recursive invocations and therefore proving time roughly proportional to the number of notes. The recursive function must beonly_selfbecause we want to prevent any other contract from calling it - it is only an internal mechanism.Public
Public functions can only be externally called by both private and public functions. Public to public
only_selfcalls are rare and not very useful (it'd be equivalent to anexternalSolidity function withrequire(msg.sender == address(this))). Private to public calls do enable useful design patterns though.A private function that needs to perform some public check (like some public state assertion) or follow-up public action (like some public state mutation) can do so by enqueuing a call to an
externalonly_selfpublic function. Theonly_selfattribute will prevent external callers from invoking the function, making it be a purely internal mechanism.A classic example is a private mint function in a token, which would require an enqueued public call in which the total supply is incremented.
Initialization Checks
only_selffunctions implicitly skip initialization checks (as if they hadnoinitcheck). We wantonly_selffunctions to be callable during initialization, so we can't have them check the initialization nullifier since it would fail.This is safe because
only_selffunctions can be called only by the contract the function is in, meaning execution must start with another external function in the same contract. Eventually the call stack reaches theonly_selffunction, but let's focus on that external entry point:noinitcheck), then the contract developer is explicitly choosing to not check for initialization, and so will ouronly_selffunction. That's a design choice by the developer. If we didn't skip the initialization check ononly_self, the developer would just addnoinitcheckto it anyway.