Skip to content

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):

  1. revert() attacks
  2. out-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.