Hooks everywhere: the age of dynamic contracts
Devcon 8 · 4 Nov 2026 · 24:23
OpenZeppelin Solidity engineering manager Hadrien Croubois maps how hooks inject custom logic into immutable smart contracts, from Uniswap v4 pools to modular smart accounts under ERC-7579 and ERC-6900 and token transfer callbacks. The talk compares trust, gas and composability tradeoffs, the security pitfalls of reentrancy, malicious hooks and unbounded gas, and how to choose an approach for a protocol.
Speakers
Topics
From Devcon 8, hosted by Ethereum Foundation.
Transcript
[music] [music] Can you hear me? Yeah. Hello everyone. Thank you. My name is Hrien. I'm a Sidity engineer at Open Zeppelin. And today I want to make a brief presentation of of hooks and their usage in the context of solidity smart contracts. So if you know me, you know that I've worked for Open Zeppelin. we are providing smart contracts for everybody to use for developers and so on and we are now releasing I mean we have our version 5.7 that is live that you can use and 5.8 it is coming very soon I guess. Uh so the basic idea is that uh we have this property that we
all I think love is that onchain code is immutable and that's so far been true of like everything maybe egota will change that but so far so good the contracts are immutable and that's also true of what we call upgradeable contract when we use upgradeable contract we tend to see them as dynamic object but it's just a proxy that is immutable that through storage reconfigures itself to point to a new piece of of logic. So we have workar around to do upgradability but overall the logic remains that the code is is really static on chain and that gives good properties but sometimes you want to build something that is dynamic in how you interact with it. And so the idea today is I want to
go through how we make contracts more dynamic. what are the different approaches not in chronological order but more in the different ways it can be done from like the more static approach to really the the more dynamic ones. So the first class of hooks I think we all all have seen in this space are what I would call notification hooks like they were presented in in tutorial by by the foundation in 2015 and they have been put in in many ERC's and and token classes in them and the logic of a of a notification hook is just that when you have a transfer like this uh there is a version that is called a safe transfer form that after the transfer does a call uh check on ERC720 21 receive and that's
just like calling the the two address. So in the case of 721 we're just going to call the two the recipient telling him hey you received this token do you want to do something with this information and that's great for composibility for like building flows where you send a token and the receiver reacts to that and maybe say oh I don't want the token revert everything or you can you can do more logic um and that's present in most uh token standards with the exception of ERC20 but there have been extension that try to add that to ERC20 and sometimes it can be a bit dangerous 777 has caused some issues because people were not used to this paradigm and it created free entrancey but overall it's it's very very useful. Uh
but it remains something very basic in the sense that only the sender and sometimes only the receiver and sometimes the senders are notified and they can cancel a transfer but you cannot use that to like inform a compliance third party to check that compliance is met and so on. Uh so then there is what comes a bit later in terms of it's a bit more dynamic even though like it's it's compile time it's what I would call compile time hooks and that's a very different class of hooks and that's something that was introduced in open zeppelin and that's more something that the developer would configure uh when they build their contract there is a function that is like the transfer function in the case of FRC20 and before the transfer happen we call this small function before transfer token transfer
and after we have the after token transfer And those functions by default are empty. They don't do anything. But the developer has the ability to put logic in there. And that's how a developer can reconfigure their contract and the compiler will resolve all that. And you get a more d a more powerful contract built. Uh but that's just at compile time. Once it's compiled, everything is frozen. And so that's how we used to build ERC20 pausible by just overriding this and checking that we're not posed. It's also how we build ERC20 snapshots. This is an early version of what we currently do with ERC20 votes where based on what happens in the transfer before the transfer happens we just do some accounting say like hey
this person has this amount at this specific time and we use that to track voting power and it's not specific to open zeppelin so this is the very latest version of so and so has these hooks as well this before and after token transfer so this is something quite common in libraries uh but it's just compile time extensions And then we've seen something bit more creative with what I would call configuration who can configuration hook can be more static or more dynamic. So again not in chronological order. We're going from the more static to the more dynamic. And I think a very good example is unis swap v4. Uh I don't know who remembers that but when unis swap v4 was released it was really a very big moment for all the solidity developers because
it's completely shifted the paradigm from what we were used to with amm isolating pools and different contracts. It used to have before you have a singleton that holds all the token and that's way more efficient because no token transfer between pools. Uh I mean token transfer between pools are internal accounting as opposed to external transfers. And you create your pools and you want to isolate your pools. uh and you do that like I mean they do that with with with this system of of of keys and ids and each pool has as has its own ID and the ID includes an address and and that address is it's part of the key and whenever something happens like a donation before the donation we look at the hook that is inside the key and we
could do a before donate and there is an after donate and there is an equivalent thing for providing liquidity or doing a swap or whatever and so the idea is that when this is called like we'll check whether the the the hook was set up to do this this thing basically the whether or not a hook should be called this is encoded as part of the address of the hook some of you may have seen that I think it's extremely smart from unis swap and it allows pools that live on the same singleton to have custom logic in how they process different operation by delegating this to to this hook contract so that's very smart it's static that mean it's basically the hook address is part of the key. So once a pool has been created, you cannot change
the address of the hook that provides guarantees to to a user. Uh still the hook itself could be an upgradeable contract. So you have to be careful that it's not upgradeable otherwise the the the logic that is applied could be changed later on. But if someone wants to build it completely closed and static, they can. And I think that's what we recommend. And by the way, if you're planning on building on unis swap v4 and building your own hooks, uh we have a libraries for that. some of our engineers worked on on this recently. So I encourage you to have a look. uh it's really like the the place you want to start if you're looking into how to build unis swap hooks or if you want to explore this paradigm to apply it to your own application and then there are things that are a bit
more dynamic but are still somehow similar and I think a good example a good first example here is safe no safe and it started doing that in 2021 so quite early and basically what they do is they have a guard and the guard is very similar to the hook system by uni swap except it's can be modified because each safe has its own guard and there is no reason to prevent a safe from updating their guard. And so that's what's happening like every time you execute a transaction on the safe. uh they are going to look whether the guard is set and if it's set they're going to call the guour telling him like hey this is being executed by the by the safe and you can either like track that if you
want emit some events or you can even like completely revert if you think that transaction should not go through and because this is in storage and not like a key like in the in unis swap this is completely dynamic so it can be updated by the safe at any point here are a few example or where this hook systems is used. You have something called safe lockdowns that has a lockdown dossole. This is pretty recent code and this is a project that tries to uh lock provide an ability to lock a safe in case of emergency like in case of kidnapping or things like that if if the chrome is compromised and basically they have this hook system like implemented in their contract and it's going to check the state of the of the noisy safe that is
performing the operation and if the state is normal then we do some low-level checks but that's fine but if the state is in lockdown then we revert So that's how this system if you enable it, if you configure your safe to use that that system will then have the ability to log down your safe under specific circumstances. Another example is optimism time lock guard. Uh same hook, same system but completely different logic. This is just a way that optimism has built to turn a safe into a time lock. They did not want to have a time log that is owned by a safe because then like the assets have to live on the time lock and they rather have this asset live on the safe for clarity and auditability. So they just say like hey let's take our self and use
this hook system to turn it into a time lock and and that's what they did. So it's also an interesting example. Uh and then when you continue in that direction uh you can go way further. Uh there the hooks are no basically everywhere. They are in accounts they are in more and more token and they are in defi basically. Uh in the case of accounts uh we at open zeppelin have built solutions for for you to build your open source account using like standard interfaces. We really believe 7579 is probably the best standard for for modular smart account out there. And 7579 comes mostly with like the validator and executor mechanisms. But there is also an optional hook extension. And this
optional hook extension what it does is it adds a modifier to your to your account a hook modifier that will just call the hook before something happens get some data some context back from the hook and then call the hook after. I mean if the hook is zero nothing happens but you can set the hook at any point. And then when something like installing a module or executing an operation or just doing a fallback happens uh there is this re hook that is called and that means that this pre-check function on the hook will be called regardless of what happened. So it's not like in the case of unis swap where the hook had to implement separate logic for before donation after donation before swap after swap. Here all the all the data is dumped into one endpoint in the hook and
then the hook has to parse that. Uh in the case of 7579 there is only one single hook per account but like if you wanted you could put a multiplexer here and and use that to combine more hooks mechanisms. And here again like this is this is currently something like the hooks is being that is being called can revert. So you can also use that to to check some operation like if a module was to execute something on your account the hook could could verify that. So technically you could use that to uh to build something like the the lockdown that we saw for safe. Uh we would just have to to put the right logic in the implementation of the hook. Uh there is also ERC 6900 that is another approach
to modular smart accounts. Uh in the case of 6T900 they support multiple hooks uh by default but it creates other issues with ordering of the execution and so on. So we think that this is possibly the simplest and best approach and if you want multip hooks build your own multiplexer instead of relying on something that is uh not well standardized. Uh another example on on dynamic hooks uh is is 1 in uh I think this one presented in the THCC7 about 18 months ago and this is very different from what we saw before because I think this is the first case of uh best effort hooks.
Uh basically what they said is okay in most tokens you can notify like the receiver sometimes the sender well what if we were able to notify any other address obviously I'm not going to be able to say that I want to be notified for your transfers but I can say I want to be notified for my transfer and if the transfer is from me to you or for you to me then whoever I want notified will be notified and that's quite interesting from one in I'm not sure how it's been used in production or if it was uh but the system is quite interesting and it's what's important about it. It's it's a best effort system like uh I don't think we see it on the code here but if you go look at at one in code what's happened is that it's a best effort system the gas is limited it
only gets a certain amount and and any revert from the hook are in ignored so you cannot use that to basically prevent a transfer from happening but you can use that to say like hey uh in that context as a token owner I want to be notified or I want this system to be notified whenever a token is received on on my beh whenever I receive or I send a token and that's kind of interesting and yeah it it's very different from 721 hooks because you cannot revert here but it's it's another approach to hooks and I think like you start understanding that there is a very big diversity in what people build and they all named that hooks and it's all different and another example is ascent as a D5 protocol that we worked with a few
months ago uh and basically it's like a big uh contract that handles loans and and collateral and all of that. And the question was like how do we put compliance into that in a modular way? How do we make sure that this system that has to be compliant with regulation can keep their contract keep their asset? We don't want to change it every time, but we want to be able to update how compliance checks are going to happen. And we've did that with something that we wanted to be generic. And so we have this with hook modifier that is very similar to what you saw in 7579.
uh except that the hook that is being called is not one. There may be multiple hooks. Uh and the hooks are not the same for all the contract. They are by selector. So you can put this modifier on all your functions if you want and for more most functions selectors no hooks would be registered and so nothing would really happen and you will be just fine. But if you register a hook then you will get a list of hooks uh for for that signature and that list of hook will all be called with pre-check and they will pass context and then after the execution will do the project check and those hooks are not like the one we saw before with one in those are not
best efforts those can prevent the operation from happening and this is where like you could have one hook that does KYC compliance another hook that does I don't know if you want to restrict transfers to a new big days between 9 to 5. You could do that in a hook. Uh and you could like layer these hooks to add all the compliance you want to your DeFi application. Obviously, this creates a very important governance issue that whoever is able to put these hooks here uh control the mechanism, but this is a compliant finance protocol.
So, it's kind of expected. Uh what we've seen so far is that hooks apart from the one-inch version were all able to revert uh to prevent something from happening. Um and and and that's okay. Uh but it start this model uh is limited when we moved on to more confidential approach like any uh FH powered uh confidential kernel like Zama or or or INC or Fenix. Uh and we worked with with Zama on building confidential contract and obviously they want to have hooks in their confidential contract to be able to build in building like compliance and things like that. And the issue with confidential values is that
you cannot really inspect them at runtime. You can do fat operations but this is an asynchronous process that happens basically of chain after the fact and you are only working with handles that that are pointers to the value but you don't have the value in real time. In particular what you cannot do in confidential contract is revert based on is my value bigger than 10 or or lower than 11 or something like that. And so it's impossible to branch here and it's possible to revert. So how do we do hooks in that context? Well, in that context, what we do is that when there is an update happening in the in the token that uh that is confidential token with hooks. So what we're going to
do is we're going to run the pre-transfer hook and the pre-transfer hook. So again there are multiples here. So it's it's an array for each one of them. We are going to just provide them the ability to decode the amount. So we say like hey we want to transfer this amount from this person to this person. So here you have the ability to process this encrypted value and then we are just going to do an FH end. So this is an end operation between compliant compliant being uh true by default. Where is it? Yeah, it's true by default and and end with the prehook. So basically we are calling this module and we are getting an encrypted boolean back. Instead of getting nothing and
maybe reverting, the hook here is telling us yes or no in an encrypted manner. And then we can use that to do FH operation, combine that and do a select where this boolean will either select whether we transfer everything or we transfer zero. So the transfer is happening regardless. It's just the amount that is transferred is an encrypted amount that is selected using FHE and combined with the hooks. So it's it's more in the complex logic. If you're not familiar with confidential contract, that may be a bit difficult. I encourage you to look at our confidential contract to get a better grasp of how how we can use FH to build confidential tokens today. But this shows you that uh even if we were to build like a generic hook system that
allows reverts, it would not fit this system here. We need read return values and those return values are encrypted boolean. So I guess you you come to the same conclusion as me that like hooks first of all is a one single keyword that represents a very very big diversity of of of things and it's very difficult to understand what's when someone tells you like hey we have a hook system in our contracts what do they really mean it's very difficult to know often so so so when ideally we would like to have a taxonomy on on what kind of hooks exist I try to start it today maybe people will will continue that work. Uh and the idea is that uh in this spectrum there
is no good or bad option. It's not like static hooks are bad and dynamic goods are hooked. Like in the case of unis swap a more static system is really what they needed. So that's very good. Uh on the other end like dynamic can be potentially dangerous and creates governance issues. So you may want to use that but you also have to understand the drawbacks. It also creates issues with auditability of your code. What if you audit a code piece of code that it's safe, but then hooks can be modified dynamically during the life cycle of the contract? Uh this is also part of your of your of your attack surface. So you need to to be very clear about that both for you as a protocol and for your user in terms of like what could you do to harm them. Uh and and the idea to me
that to me the bottom line is that people have often asked me like hey should we standardize hook? Should we make it so that like everybody uses the same approach to hooks and there is one interface, one ERC and that's how people do hooks. And I very often say that I don't think that's a good idea and I think this presentation explains why is that there are many realities. Many people want to build different things and I don't think it falls under one ERC. So maybe we want to to standardize some of that but we would not be standardizing it under one ERC. I think we did at least three, four, maybe five to to focus on the to to be able to focus on the different aspects of how hooking works.
So that's all for today. Thank you very much and I think we have some time for questions. [music] All right, that was a great session as expected from Hrien. We have a couple of questions. So first one, modular hooks are great on paper but force integrators to understand arbitrary modules. How can we reduce this complexity while keeping extensibility? >> Uh I'm not sure we can. Uh no, it's like the yes the the dynamic hooks are modular hooks are are really making the contracts more alive. So the complexity is alive. The the the vulnerabilities change when hook changes. So I think it comes with good practices as a
developer. uh uh I'm not sure we can reduce complexity. We can maybe provide some security to users by [snorts] uh time gating when the hooks can be modified. Like modifying a hook is a very critical operation. It's like upgrading a contract. Basically, you you should see it basically the same way as a contract. It's a bit less powerful, but it's the same attack scenario for for a user. So having a time lock be in charge of modifying the hook that could be a good approach to make sure that users have an exit strategy in case a hook is malicious like in case a malicious admin installs a a hook that is dangerous.
>> Perfect. Thank you so much. Uh we can take a couple of more questions. What are the top two drawbacks of using hooks? Uh uh so to me the first one is is like you you you lose sight of what your security model is. Uh you have to be very careful uh about about the security of all of that. And so that would be my number one. Number two it's because there are many drawbacks. Um uh I mean number one I would also I would put re-entrancey issues in number one. It's like part of your security you have to consider the hook could be a vector for re-entry. And the number two is in terms of gas consumption. Uh
depending on what hooks are installed, you don't necessarily control what logic is going to be executed there. And if it's on your critical path, you could break your contract by having to go through a path that spends too much gas and you can and you cannot execute. I think it's variation of the same issue. But yeah, >> perfect. Thank you so I can take one more I guess. Uh this is an interesting one. So are there any language features in solidity itself that you would like to have to make writing hooks easier? >> Um yeah so there is one thing in solidity that I don't like in classic solidity is that uh I think the try catch mechanism is is really an interesting approach.
And uh sometimes in hooks you want to to be able to to have the hook revert without uh affecting your execution. We saw that in the case of 1 in the try catch mechanism by solidity is nice sugar syntax but under the hood it does a lot of things that sometimes you don't control well in terms of um how it handles return bombs in terms of how it force a check on whether there is code or not at the address before even trying to do the call. Uh so whenever we want to have like fine control of how a call is being done and we sometimes want that with with hooks uh if we don't want to break our contract in like a best effort manner like one in then we have to
resolve to using inline assembly uh where you have more control about how the cool data is processed on the way back. So, so to me like yeah it's uh uh I made a talk a few years ago. I don't remember when. I think it was at the solidity summit about the features I hoped I would be able to see in the solidity language in the classic solidity language at least. Uh I think some of them apply to hooks in the sense that solidity limits your ability to do certain things very finely and and yeah when you want to be in >> [music]