New Ethereum talks, every Monday. The week's conference uploads by event, in your inbox.

Loading player…

Secrets of Uniswap V4: A Deep Dive into Hooks Security - Damian Rusinek | Composable Security

ETH Belgrade CommunityMon, Oct 7, 2024, 12:00 AM

Speaker

Damian Rusinek

Transcript

again thanks thanks for waiting um well my my name is Daman I run composable security we are security Boutique and we actually have created the first security standard for smart contracts and uh We've like secured multiple multiple projects both like dexus and Texas uh so offchain and and onchain and today I'd like to show you our experience um that we got from the research security research for UNIS swap uh V4 and I'd like to show you the architecture of V4 and also of course this is security talk so it will be about security threats uh in V4 so if you if you plan to build any hook uh because this the keyword right uh you might find it very very useful so let's start with this simple equation who knows the equation yeah everybody right so that's Unis swap V too right very successful very simple project but it had a one huge downside right that was the inefficient liquidity so whenever you put liquidity into V2 it's put on the whole range right it's it's very inefficient especially for stable coins uh sorry and that's why uh some other projects uh fill this Gap and for example curve by changing the the Curve uh basically but so then there was an idea to do the new version that will um solve this problem so that's how V3 was born and the the biggest change in V3 was this concentrate concentrated liquidity so like to put it simple you basically add liquidity to a price range right so you you don't put it on the whole curve you you define when you pull liquidity when you add liquidity you define what's the price range you want uh your liquidity to be used on however there was also a downside so now you cannot really easily uh represent the liquidity so you cannot use uh erc20 anymore to represent LP token uh and that was uh that's the change so now the nft was your liquidity position token so let's see what's the uh can you see that yeah so looks good uh let's see let's look at the architecture of V3 so if you have like multiple pools all of them were uh separate contracts so you had you have a factory and you can create a new pool you simply call create pool function and it deploys a new contract then you can do some functions directly on the pools but the most common functions that you do are swapping and and liquidity management right so you cannot do it directly on the contract you have to use some helper like for example to add some liquidity you would use a unable position manager which is official contract by Unis Swap and it's your like forwarder uh to interact with Unis V3 pools so you as user uh add liquidity through this contract the contract calls um Min uh function as an example and the the pool calls back uh with Unis swab V3 mint callback function and then the non uh non-fungible position manager does all the logic takes your tokens put it there and gives you the nft uh that is created when you add liquidity and also like when you do a swap it's very similar so there's also this callback mechanism uh so we also have to use a helper contract which is called swap router and that's why like usually when we talk about contracts that uh call Unis swap pools we usually call them routers right however after now it's like a three years uh there is still an issue because like 90% of uh pools that are uh used by like anyone any project and basically the community uh are Unis swap V2 pools this is a bit outdated but I checked it um this morning and like it's still the same like 90% % is still Unis swap V2 and not Unis swap V3 so the idea came to to create V4 and like this is one of the goals of V4 like to migrate uh liquidity providers to uh V2 to V4 from V2 uh but also it's even more gas optimized and it's customizable so like you can create a Unis swab V2 uh pool on Unis swab V4 with the same fun fun ality same token um like same ec20 uh liquidity token so you can basically create Unis swap V2 on V4 but you can also do a lot of more and there are some changes like the first one is transion storage and that's why Unis swap had to wait to the denong uh upgrade denong hardw Fork however they still are they they still haven't released the the project so it's being audited now uh and it will be probably released soon uh and all the other changes I'm going to show you on on the difference to the V3 so first of all there is this Singleton pattern that means that there is one contract it's called pool manager contract and so all pools are within this contract so when you create a new pool you basically initialize a new pool within pool manager contract then you have this lock mechanism so it's a bit similar to call mechanism but uh there are a bit uh there are some differences the first difference is that you still have you still have a need a helper contract like we call it pool operator we don't call it router we don't call it liquidity manager and I I'll show you why in a second so the the the pool operator calls the unlock function on the pool manager and when you unlock pool manager you can do multiple operations like minting burning liquidity swapping like doing all of that like taking a flesh loan um but in the end your balances have to be settled so you have to like you you can think of it like a flesh loan so if you take a flesh loan you take a lot of money and then you have to pay back possibly Plus premium right so it works similar you unlock the uh pool manager do some operations but in the end you have to be settled like all your balances have to be uh settled so the pool operator can be your router can be your liquidity pool manager but it can also be uh totally uh customizable contract because the next big thing that comes here are those hooks so hook is a contract hook contract is a contract that is called by the pool manager uh in multiple different situations so there are like uh there are like uh operations like like initialization so the first time you create um a pool uh adding liquidity removing liquidity swapping and donating and before and after each of these operation you can call a hook contract so there is a function for each of that operation that can be called on the hook contract by the pool manager but the the hook can also be a pool pool operator right so you can like start with a pool operator let's say it's your router then it calls a pool manager on your behalf and then calls back the same contract so you you can like keep the state uh of this whole uh business flow so the the logic behind the hooking hook functions is very simple like each pool has uh some Flags defined and you define them at the beginning like when you initialize the the pool you define those flags and each flag says that should I call before swap should I call after Swap and if I should by I I mean pool manager then it will call the hook contract specific function like in this example it's before swap function so let's move on to the mid security threats so we we have identified on our diagram we have identified some security threats but I'm going to focus on six of them like and we'll start with a very simple one which is our lack of access control on hook contract here's an example of a contract as you can see can you see the code from the back cool so I'll I'll describe briefly what it does this is the before initialized function so this function is called on the hook contract right before you create a new pool right and what it does it simply so it's a full rench hook so this is actually the V2 pool on V4 uh un on unit V4 what it does it simply says that it will always add liquidity to the full range range but it also creates um erc20 token that tracks your uh liquidity uh that you've added to the pool so you can think of it like it's V2 however there is no like the the before initialized function have to be it has to be external because it's called by the pool manager so it's called by different contract right so it's external but there are no modifiers there is no access control so why can't you simply call it directly and what would happen if you call it directly so here's the here's the code you call the um here's the code you call the before initialized function directly not through the pool pool manager and you specify like any any parameters it doesn't really matter because what matters is that it will create cre a new pool token that uh is this erc20 that tracks your liquidity and simply override it like in this pool info uh mapping it will override the uh the token so now when somebody tries to remove liquidity from this token from this pool sorry uh this this token should be burned right and if you added liquidity this token was minted for you so when you remove liquidity it's burned so it should work fine but if you call before initialize again there is a new erc20 token right so there are no balances so like everyone has zero balance so if you try to burn anything it will simply revert so the the result is that all those tokens uh all the liquidity that people have put into this uh pool is locked now right nobody can withdraw it so what's the solution very simple you have a modifier uh I called it pool manager only but it's quite common name for for this kind of uh modifier that allows this function to be called only by the pool manager pool manager is one contract it's like you can hard code this address on on particular chain right uh so it's very easy to implement and if you have that modifier you simply cannot call it uh directly anymore another thing very similar so it's also an unauthorized call but on the other side of the flow so this is how unlocking works you as the user will use some kind of pool operator and you will execute some like business function let's say withdraw function and then pool operator calls unlock on pool manager and pool manager calls unlock call back and from now on pool operator can do whatever they want uh as long as in the end the balances are uh settled so the one idea like one way to pass the information from user to the pool manager is to use this data in unlock um function this data is sent back in unlock callback so like you specify the data that will be managed by pool man pool manager in this unlock callback so why don't you try like why can't you simply call this unlock callback function which is also external because it has to be called by pool manager but now it's not calling the hook it's calling back the pool pool operator right why why can't you try to call it directly of course you will you would have to unlock pool manager by your own but that's very easy and then you call unlock call back on pool operator and you simply change the parameters right so and this is like the solution here is very similar so like actually it's the same so you simply check whether the sender uh that calls unlock callback is the pool manager so let's move on to something more specific for UNIS swap V before here's an example of dynamic fee hook so because this is also one of things that V4 introduces and again we have this before initialized function and but as you can see it's already protected the is that pool manager only modifier so we cannot call it directly right and uh what it does it simply takes the pool key uh of the pool being initialized and gets the uh last uh block number and last price so then when you do uh when you mint some liquidity so you simply add some liquidity through a liquidity manager because the hook is also your pool operator so now you call OD liquidity on the pool operator which is hook it calls pool manager and pool manager calls back the same contract which is both H hook pool operator and hook so it takes this pool key right this is the data that was saved during the initial initialization right so it tries to modify position for a specific pool but like what happens if you try to create another Pool and pass the same hook contract to the new pool so this is the code that does that you simply call um initialize on pool manager and specify that you want to initialize a uh a pool uh with some data and one of the data is the hooks contract so you're going to use the same uh hook contract that was on the previous example and what happens then oh sorry uh the as you can see on the left the pool key uh storage variable is overridden right so now the pool key is different so the result is very similar to the first case because now all the liquidity is locked because when you try to modify your position the pool operator will try to do it on a different pool right because the pool key has been changed in this pool operator which is also a hook so what's the solution for that well you should either um make sure that your hook can be used only by one pool so we can like simply have some kind of initialized um initialized storage variable that you set to True at on the first initialization and then you revert on when you try to initialize a new pool or simply you can use mapping so that you can store data uh for each pool uh in a separate struct the next one is cender impersonation and I also have a uh example here from from some hackaton and here you can see there is a function that the hook uh function is after swap so when you do the swap when you finish the swap the pool operator may call this function and one of the parameters is the center right so you can think like this is the user that makes a call right so the idea behind this Hook was that they didn't want one person like one address uh to keep too many tokens so they checked whether the the balance of that um address was above some threshold and if it was they simply reverted right however they took this sender parameter so let's let's see what it is actually so here is the flow of execution so you are the user you are called the swap function on pool operator P pool operator calls unlock and then swap on pool manager and pool manager calls after swap um on hook with and it specifies the sender parameter which is message sender so this is actually the pool operator and not the user right so the sender is not the user that initiated the operation but it's the pool operator that uh calls the pool manager on behalf of the user right so how to solve that because they still want to to achieve that uh um limiting so there is another uh parameter called hook data so when user calls pool operator the pool operator can take users address from message sender and uh save it in a hook data uh parameter then when pool operator calls pool manager it passes the data right so now the hook can have access to the real user that executed this operation uh and it's even more interesting where when the the hook and pool operator is the same contract right but there was no impersonation yet so here it comes like why can't you use some malicious pool operator and make it call pool manager um with some um like malicious data right so we simply change the sender now we want to steal some tokens you want to steal some liquidity from the victim so we specify that victim is the initiator of the operation and what would pool manager do pool manager has this hook address hardore coded I'm not hardcoded but stored in the storage so it won't call back the malicious pool operator but it will call the legitimate hook right so how can what can go wrong you can simply try to um persuade the hook that we are uh withdrawing uh tokens from some victim and if the victim has approved uh hook then you can like simply steal the data uh the liquidity tokens and number five theft via Dynamic fee so Dynamic fee is a new feature uh so you can like specify let's say in before swap you can uh calculate the fee based on the volume based on the user based on whatever you have access to and then you update the fee so you call this update Dynamic swap fee on pool manager actually uh there is one catch the pool has to have this Dynamic Fe flag set during the initialization so if the pool doesn't have this flag set it will revert because you cannot update Dynamic fee because that particular pool has static fee so there is one catch but still if you have a um Dynamic fee pool uh then you can update it so what are the balances well there is a limit on in unisp before that the fee cannot be over 100% right so the hook can simply say that okay you want to swap uh 10,000 USD to if no worries but the fee is 10,000 USD so we have no if in return and that's that's how it works like that's how it can work so you have to be careful what Hooks you use and of course simulating transactions is very important but on the other side uh there is um so what happens if the fee is actually more than 100% it will simply revert so if your calculation function can end up with returning over 100% your hook will be blocked like it will be dust okay because if you end up in a situation when your hook cannot do any swap so it cannot update all those parameters that are used to calculate fee and the fee the current fee is over 100 % you're stuck right so this is another issue that you should check so like best way to do that is to I guess F your calculations or like also write it down in MAF formulas just to make sure that you do the correct calculations and then check whether the limits are there uh and last but not least upgradability right so this is quite like easy to understand for some upgrade ability is a vulnerability for others it's a feature that helps you protect from being hacked right and there is no like simple answer whether it's a vulnerability or or a feature but if you see a hook can is upgradable uh it means that it can change its uh logic like in any way there are some limits again there are those uh Flags so if there is a flag for uh specific hooks hook functions you cannot call another hook functions through the pool manager but still like let's say before swap after swap these are quite powerful uh functions so you as user should be careful of that and also if you integrate with some hooks you you should also keep that in mind and yeah and actually that's that's the six I've chosen for this presentation uh here's the list that what you could should do like the the basics the the first things that you uh should do and also uh we've got a bunch of Articles uh from from our research about unap before security so you can check out that uh on our blog some examples of malicious hooks so examples of what you should avoid uh hopefully not build uh and yeah and you can like find me there if you have any questions regarding Unis swab before in terms of security or Security in general um I'm here till the end so yeah that's that's me thank [Applause] you are there any questions a little one I I can I can repeat the question yeah problems uh okay so the question was that V4 is looks more complex which is correct and do I see it like more issues with integration well actually that's the question I'm asking myself and and many people because like I see like there are more attack vectors so that's the cost of C customization right customizability right if you if you make a project that is very customizable there are also a lot of different ways to hack it right so um I believe that what's important now is that you have those good best practices uh lists for integration so the checklist that you have to go through and check and actually that's that's one of our outcomes from the research in our uh standard that's csvs you can find this category for UNIS swap V4 uh hooks so if you're building a hook what what you should look for uh what you should avoid and that's very well that's not exactly how to integrate safely securely but that's another category for for the standard I guess so like somebody has to do the research on that and create that list and we've done that for for hooks um so I guess this is the most important thing now so you you create um read easy to read uh materials for for Developers all right uh like we cannot have any more questions now so again if you have more questions I'm I'm going to be somewhere here thank you

Automatic transcript — names and jargon may be misspelled.