Hooks
In Overload we implement Uniswap V4-esque hooks. In the documentation and specification we refer them to as hooks.
Uniswap V4 Hooks are essentially contract callbacks, but implemented differently where permissions are embedded inside the contract address (CA) and where the bytes4
selector is returned when a call succeeds. For Overload, we build upon this and have instead chosen to remove the permissions complexity, for slightly increased gas. We explain our rationale in the next subsection.
Hooks with ERC-165
ERC-165 is the "Standard Interface Detection" EIP. Rather than embedding the permissions inside the CA, we instead choose to implement permissions through the ERC-165 supportsInterface
method. This aligns with best practises in writing production Solidity code and ensures composability, when needed.
The gas will be increased from an external call to the supportsInterface
method, but we deem it negligible on L2s, as gas price has been lowered by an order of magnitude with EIP-4844. As such, complexity goes down by a great amount due to specialized deployment scripts not being needed anymore.
When Overload makes a call to supportsInterface
method, it will also limit the amount of gas used, as this is a vector for out-of-gas
attacks. If the method reverts, then the hook would instead be deemed as inactive and the call would continue (v.s. reverting the whole transaction).
Extend state with Hooks
When writing a consensus contract, there are cases where additional state is needed besides what exists inside of the Overload.sol
core contract. This can be amended easily by implementing hooks into your contract.
For every method in Overload.sol
, there will be _before
and _after
internal methods that will be called. With each hook call, all the parameters are passed to the hook and the consensus contract can then derive and save their desired state, in their contract storage.
Preventing attacks on hooks
In a situation where a user restakes to a malicious contract, we want to prevent the case where the user cannot undelegate their restaked assets to the malicious consensus contract.
There are two types of attack possible to lock delegations (hence, also locking the underlying assets):
revert()
attacksout-of-gas
attacks
To prevent revert()
attacks, we introduce the bool strict
parameter. The strict
parameter makes is possible to not revert the whole transaction, if the hook reverts. This makes it possible to undelegate
even in cases where hooks maliciously revert. We expect consensus contracts to check strict = true
for delegate
calls (to ensure state changes are transitioned correctly for the transaction), and for strict = false
for when undelegating (to prevent locked assets).
To prevent out-of-gas
attacks, we set a hookGas
variable inside for Overload.sol
that is a fixed amount of gas that is sent to the hook every time.
hookGas := 1_000_000
address(consensus).call{gas: hookGas}(...);
This effectively sovles the out-of-gas
attack. If a malicious consensus contract tries to revert any transactions by consuming all gas, now the call will instead revert. The user can then undelegate by setting the gas limit to be higher then 1_000_000 + execution gas
to ensure the transaction goes through.
For the developer writing consensus contracts, this means they have a maximum of 1_000_000
gas to work with, at most. It is crucial to ensure good margin of gas consumption to not go over this, as logic in consensus would otherwise fail and would lead to errors and divergence of state.
In other words, hooks that consume more than 1_000_000
in gas is deemed as undefined behaviour in the consensus, and should not happen under any circumstance.