Handles private accounting and executes operations
The Handler executes operations on behalf of the Teller contract. It inherits from the BalanceManager and CommitmentTreeManager contracts. Combined, the Handler is responsible for maintaining the note commitment tree, gathering funds before executing operations, executing operations, handling refunds after action execution, and compensating the bundler.
Maintaining the Note Commitment Tree
The Handler maintains the protocol's note commitment tree. It inserts new note commitments upon deposits and refunds (received/remaining funds after execution). The tree state is accessed to verify that joinsplits are valid and that notes have not already been spent.
Gathering Funds for Operations
Before executing operations, the Handler contract gathers the required funds from the associated Teller contract, ensuring that necessary assets are available for the operation. For each joinsplit, it checks for valid merkle roots and nullifiers before gathering the assets associated with that joinsplit.
Executing Operation Actions
The Handler contract is responsible for executing the actions specified in operations. If the operation has been submitted with atomic set to true, the revert of any action in the operation will result in the whole operation reverting.
During operation execution, the Handler may receive new assets from the protocols it interacts with. Once an operation is completed, the Handler will look at each of the outstanding assets it has, insert new note commitments into the commitment tree for accounting, then send the underlying assets back to the Teller.
Note on Bundler Compensation
During the "gathering funds" step, the Handler computes the amount of gas the operation is expected to use. This calculation is defined as the sum of the following:
Action execution (op.executionGasLimit)
Joinsplit proof verification (depends number of proofs batch verified in bundle)
Joinsplit nullifier set stores (2 nullifiers per joinsplit, 20k per nullifier store)
Joinsplit new note commitment queue insertions (2 new note commitments per joinsplit, ~20k gas per queue insertion)
Refund note queue insertions (number of refunds depends on operation/actions, ~20k per insertion)
Subtree update cost per insertion (16 insertions per batch, ~320k for subtree update, ~20k per insertion)
Miscellaneous costs such as internal token transfers (between Teller, Handler, and bundler), calldata costs, event data costs, etc
The Handler withholds calculatedBundlerCompensation * operation.gasPrice tokens of the specified operation.encodedGasAsset when unwrapping funds from JoinSplits, assuming the JoinSplits cumulatively have enough for the gas assets. If the operation does not have enough gas tokens, the operation reverts. In essence, the amount of funds actually unwrapped for action execution is the sum of the operation's JoinSplit public spends minus the withheld gas tokens.
After action execution completes but before refunds are handled, the Handler will withdraw the originally withheld gas tokens from the Teller. It will calculate the actual amount of gas spent by the bundler (which is inferred via gas metering done throughout execution of the operation) and send the more exact amount back to the bundler. Any gas tokens remaining in the Handler not sent to the bundler are refunded to the user.
Note on Whitelisted Protocols
It is worth noting that the Handler will not support arbitrary contract calls at the onset. In order to ensure the Deposit Manager controls the inflow of funds, the set of protocols/tokens the Handler can call will currently be determined by an in-contract whitelist of contract addresses. Restricting what protocols can be called avoids the case where a hacker can deploy its own contract with stolen funds and simply have the Handler call that contract to receive all the funds at once (bypassing deposit filtering and rate limits).
It's also worth noting that we ensure only assets on this whitelist may be deposited or received by the Handler. This avoids the case where a user deposits or receives an unsupported asset which is effectively stuck in the protocol until whitelisted.
Lastly, it is also worth noting that the Deposit Manager contract has its own whitelist which is meant to match the Handler's whitelist. In the case the Deposit Manager mistakenly whitelists additional token addresses that are not whitelisted in the Handler, deposits of that asset will be able to be instantiated but not completed because the Handler will reject them at completion time.