Uniswap Hook Incubator — Technical Introduction- #1
Currently I am part of cohort organized by Atrium Academy and Uniswap Foundation wherein we get to learn about Uniswap v4. At the point of me writing this blog, v4 is not deployed on mainnet yet. The main idea behind writing this a self study in the future. Hope it helps you guys out as well.
Singleton Design
You might have heard that v4 follows a Singleton design pattern in contrast to v3. In addition to this v3 introduced the concentrated liquidity which allows to create capital efficient markets since LPs are not forced to provide liquidity across the entire price range like in the case of v2. But a multi hop swap would always be expensive, since the swapped tokens from each single swap needs to be sent to a new pool.
In v4, there exists PoolManager
contract. It stores the mapping of the all the pools deployed. These pools kind of live inside the PoolManager
instead of being their own contracts. This is possible because pools now are implemented as Solidity libraries and calling functions through those libraries rather than deploying new contracts.
Flash Accounting
With Singleton architecture, there is no more need of external calls to transfer the tokens from one pool to another. v4 introduces flash accounting wherein users sends token to the PoolManager
which later performs the single/multi-hop swap and only the final output tokens need to be withdrawn from the PoolManager
.
Locking
To ensure correctness and atomicity in complex operations, v4 uses a locking mechanism. If an user wants to perform swap or change liquidity, they need to unlock the PoolManager
. A periphery(Router) contact calls the unlock
function and checks if the PoolManager
isn't unlocked already. If not, it unlocks it with a callback. Inside the callback the swap/liquidity operations are performed and at the end of it PoolManager
checks if all the balances are settled and then proceeds to lock it.
Transient Storage
EIP-1153 introduces two new OPCODES to the EVM i.e.,TSTORE
and TLOAD
which recently got merged into mainnet. Basically, it only exists in the context of the transaction and does not persist after the transaction is complete and due to this it is really cheap to read/write to.
ERC-6909 Claims
It is a multi-token contract standard. In v4, it to represent claim tokens. Imagine you are a high frequency trader performing regular swaps with your tokens, instead of moving this in and out of the PoolManager
, you can use the claim tokens. For future swaps, based on the output, the old claim tokens are burned and new claim tokens are minted. you might still think its not so gas efficient as there is still transfer of tokens happening via mint
/burn
but the main advantage is the custom logic of popular tokens like USDT/USDC is bypassed making it gas efficient over multiple swaps.
Types Of Hooks
These are the hook function that v4 supports. The names of the functions should be self-descriptive to help understand what they do !
beforeInitialize
afterInitialize
beforeAddLiquidity
afterAddLiquidity
beforeRemoveLiquidity
afterRemoveLiquidity
beforeSwap
afterSwap
beforeDonate
afterDonate
beforeSwapReturnDelta
afterSwapReturnDelta
afterAddLiquidityReturnDelta
afterRemoveLiquidityReturnDelta
The v4 design follows a design pattern wherein whether a specific hook smart contract supports one of the above hooks or not is done via bitmap technique of the deployed smart contract. Therefore, when deploying hooks to an actual network — we need to do a little bit of address mining to find a proper deployment address for our hook which can properly represent the functions we have implemented in our hooks.
Example of how a swap works ?
User calls the Router Contract to initiate a swap, this unlocks the PoolManager
, triggering the manager to call unlockCallback
on the periphery. Inside the callback function, the periphery executes swap
on the manager. The manager checks the hook address attached to the pool. Suppose beforeSwap
is set to true
as part of the bitmap of the deployed contract address. After beforeSwap
hook is run, the actual swap is conducted inside the manager contract. This BalanceDelta
is returned to the periphery contract, which actually settles the balances. Now, the periphery contract is done with the callback and re-enter the execution context of the unlock
function from the manager. The PoolManager
ensures no balances remain unsettled, locks itself again, and the transaction completes
The BalanceDelta
value is simply two int
values and not uint
.A positive value implies a positive balance change for the user i.e. the PoolManager
owes money to the user, and a negative value implies a negative balance change for the user i.e. the user owes money to the PoolManager
.
This brings me to the end of the blog. I hope you enjoyed reading it. The information provided above should give you a good foundation to start exploring on your own.
Thanks for Reading ! Stay Tuned for the next blog !