Security considerations when building Hooks in Uniswap V4

DeFi Security Summit 2024 · 8 Nov 2024 · 15:18

Jota Carpanelli, head of security at OpenZeppelin, covers the basic security considerations for building Uniswap v4 hooks, after explaining the singleton pool manager, transient storage and the unlock flow. With code snippets he shows risks such as one hook serving many pools, liquidity that can no longer be removed, arbitrary external calls, upgradeable hooks, denial of service from reverting calls and oracle manipulation.

Speakers

Topics

From DeFi Security Summit 2024, hosted by DeFi Security Summit.

Transcript

hello um hi everyone I'm kodar banelli from Oben Zeppelin work as head of security for the team um and today I'm here with you to talk about security considerations when building in swap before hooks or at least basic security considerations because many many things can happen when you are implementing hooks um short agenda for today because we only have 10 minutes uh we're gonna talk a little bit about un before what Hooks are and we're going to go through four main but also very very simple security considerations to take into account and we're going to see some uh

code Snippets and some examples in in real code or at least chat GPT generated code that I use to create these slides and then I'm just going to list a couple of other considerations but again uh the list is literally infinite so uh let's get started so to start what unisoft B4 is uh I'm going to assume that most of them know unisoft B3 unisoft B2 know kind of like how the protocol Works um but basically in unisoft B before we have a main contract a Singleton that is a pool manager the pool manager differently to un swap V3 has all the operation logic

so basically in V3 if you wanted to have a contract uh a pool uh you had to deploy it so you would have as many contracts as pools uh you had in this case this is not like that all the pools Le in the pool manager and the pool manager defines all the different functions that we already know from unisoft like uh swap add liquidity remove liquidity minan burn Etc um in this example I also said uh some sort of like a pool operator contract that in unisoft before at the moment could be the unisoft router could be also the position manager but actually can be any contract any contract that you want to build and talk

to the pool manager you just can do it um the flow works as follows the user first calls this uh random contract or router let's just take the example of the unof router um what happens here since um these contracts Implement transient storage and given that we have transient storage we can um uh preserve State throughout uh a transaction from the beginning to the end um we first will call an unlock function the moment we call the unlock function we can basically do whatever we want in the pool manager and interact as much as we want with as many pools as we want so basically if you want to perform a flash

loan taking all the USD from all all the pools that exist in the pool manager you should be able to do it um this then triggers an unlock callback function that uh goes back to the router for example and then the router takes uh care of calling the following function that can be for example that liquidity or uh or or a swap so that's what I call here operation um and again this operation could be many operations could be anything and finally when everything is finished uh the same uh unlock function we'll call the uh lock function so that no one after this specific operation or set of operations can can

be run what is a hook um a hook is a smart contract this smart contract has defined uh in its interface many functions uh a hook is basically as we said or as we saw in ERC 20s back in the day when we had the before trans transer function so basically or after transfer function it's basically code that you can run before and after an operation in this case we don't have transfer because it's not a token we have swap initialize at liquidity uh remove liquidity so we have functions like before swap after swap before initialize after initialize all the ones that you can uh see here um these hooks you set them at

initialization of a pool and it's going to be the same good not be the same but ideally it's going to it's going to be the same for a specific pool throughout the the life of that of that pool um and implements logic uh all these functions you can Implement basically whatever you want um and we're going to see a little bit of of that now so what can hooks do uh in this list I give a couple of examples a hook can be an oracle can basically say a price of a specific token of a specific pool can be another router or another position manager could be Unis swap B2 we can set for example uh um we can kill the range

implementation of B3 and B4 and just have swaps on the whole range of a pool we can Implement lending borrowing let's say that we want to me a u token that represents the position of uh liquidity of uh a user we can give rewards we can uh give access control to a function um for example not everyone should Swap this specific token so kind of like a block list or allow list if we wanted to basically you can do anything you can since you can uh code whatever you want you can Implement whatever you want that's why I'm kind of like here talking about this because I think this is going

to be very interesting um next year a lot of uh protocols will be building on unisoft before Hooks and implementing their protocols there so since it can do anything also means that we can have out of out of date oracles we can take positions uh of users we can uh blog list a specc uh specific uh list of addresses uh we can introduce bugs um we can take tokens from users we can basically introduce any bug and that's why we have to um talk about security considerations to start um multip Pool Management so here we have an example of

actually actually it's not an example is a simplified version of the initialized function in the pool manager that unof before implements it has um three main parts the first one is checking whether you're passing uh a hook contract um then it calls this after I mentioned uh before initialize hook and then it creates the pull and finally uh executes the after initial hook so uh as I said before these hooks are like uh contracts that anyone can Implement anyone can deploy and as we saw in this example what we're sending here in that pool key um parameter initializer has information

of this hook but again this this uh function can can also be called by any one so anyone can initialize a pool and therefore anyone can set for a specific pool uh any hook so something we have to take into account is that a specific pool can have um or sorry a hook can interact with many pools so one thing to take into account in this example of this hook we have the before initialized function and we're setting the pool info which is basically just a an address as anist 721 let's say that in this before initialize what we want to do is just Deploy on your SE 721 that we're going to use later for liquidity providers to

to me them an nft let's say a cat or something like that so as I said since a hook can uh be part of many pools um it's very important for us to do proper Pool Management we cannot expect that multiple pools are not going to interact with a hook so every time we play with the storage of a hook we have to be sure that we that another Pool is not going to override that storage so in this case that address uh pool info this CRC 721 that we deployed if the pool a calls the hook and initialize it then the pool B can also initialize it again and that

would break things um how to fix it very easily uh we have to uh do proper uh Pool Management maybe use a mapping so that we can track uh every single pool in a different storage slot Access Control second one uh in this example also implementing the before initialize function um we're basically doing exactly the same as before uh deploying the erc721 uh as you see this function and all the functions defined in a hook are all public and therefore I mean external and since they're external anyone can call them and that means that if I wanted to just call the before

initialized function again for a specific pool since the all the information that I'm showing there in the parameters I just can set them when I send the call data to to the blockchain uh I can uh without any restrictions uh for example override a specific storage slot of a specific pool um how can we fix this as we saw before here the pool manager is the one that usually calls the hook this is not necessarily always like that and I think that the Unis swap team decided not to add any sort of like modifier in the hooks just to give full customization uh for developers but most of the cases you would want the pool

manager to be one the the one that calls the hook and uh modifies its storage so um very easy way to fix this just add a only pull manager modifier and avoid uh these hooks to be called from other from other contracts logic checks this is more like not that related to unisa before but something that I'm pretty sure we're going to see a lot in odic next year when when we check Hooks and it's basically that since these hooks are going to be um developed by uh anyone um you're going to have bugs and these bugs can be like very silly things such as in this contract we see this before add

liquidity um hook that in the last line is this is basically it um it mints some us20 tokens as a reward let's say for liquidity providers and as we can see in this code every time that a user adds some liquidity will override its uh its rewards easy way to fix this uh just add a plus there this is a very silly issue uh of course and I just want to reinforce that again like logic issues is something that we're going to see in hooks very often and it's important to get this code uh code base uh and hooks audited finally um unexpected reverts uh

this is a very interesting one and it's also uh kind of like uh can happen in in unof before because of the flexibility that it gives um let's say that we have a before remove liquidity function here um that in the last line checks that a specific price is greater or equal than a minimum price and also let's say that for some reason the price orle is always uh lower sorry higher than this uh specific price what will happen is that this before remove liquidity function will always revert uh which can be a problem because if we add liquidity and then we want to remove it um we are not going to be able to do it so uh this sort of interactions in Paris

as in like at liquidity uh remove liquidity we always have to take into account and be aware that if we want to for example interact with the uh contract with the pool and for example we add liquidity we have to make sure that we can remove liquidity um this is again a very uh simple um example that can get your liquidity logged and uh you're not going to be able to collect fees because uh the collection happens in in this oper um but there are other examples that can trigger this and other things some of them um arbitrary calls to external contracts that's something that can

definitely happen again because if you want to do a swap and after or before swap implements an arbitrary call to an aary address someone could uh take all our tokens for example that's a very common issue in the past upgradability uh all these hooks can be upgradable so we have to be aware to interact only with those hooks that we know that if they're going to be upgradeable the process of upgradability uh is set on Place properly and that we are not going to see any or we're not going to have any sort of surprises um Doos is via uh long Loops or external calls that revert very similar to the example that I gave before but let's say that in that

specific example we perform an external call in the remove liquidity uh contract and that function call reverts well we're not going to be able to remove the the liquidity it could be that that contract is also upgradeable and then upgrade they upgraded it and it also revers because of that so also uh we aware of that uh since we can basically build protocols in hooks uh we have to be very careful that uh we don't write INE inefficient code that will uh make all these operations very expensive we can have or Oracle price manipulation spot price uh issues or outdated price issues in uh in our oracles so basically any known

issue in defi can be can be present in your in your hook so as a general recommendation when we build hooks we have to be aware that things can go wrong not only because of how hooks are implemented and some of the examples that that are inherent to um two unit of before but also the fact that Builders are going to be building on top of before through hooks can introduce normal bags that we see every day in dii um and that's it thank you so much [Applause]