 We may as well just dive in because there's a lot to get through here. So today, we're going to be talking about the dark arts of contract runtime immutability. More specifically, defending against them. I'm Zero Age. Thank you guys for being here. Alright, so here's the gist of it. Deploy contract code is not immutable. Here's the link to the repo that's got all of the code that we're going to go through today. So you can kind of review async or follow along. So we won't get too bogged down in working through it, flying by flying. That's a lot to digest, but this will have everything that we're going to go through today. Alright, so here's what we're going to talk about today. First, contracts can be destroyed and then redeployed. Zombie contracts is another term for this. Secondly, depending on the way that you deploy them, these contracts can actually be redeployed with a brand new runtime code. Third, we're going to discuss some techniques that you can use to protect yourself against these immutable contracts. Finally, we'll explore a few ways that you can build cool new stuff doing this if you're willing to sort of trailblaze on that front. Okay, so just so we're all on the same page, go over some of the important, basic things to understand here. First off, contact creation code is the byte code that you feed into when you're creating the contract, whereas the runtime code is what gets returned from the creation code and will actually run when you call the contract. Secondly, we've got contract storage, which is S store, S load, standard storage. But then you also got runtime code from before, which in addition to running logic and all of that is where you're going to hold the actual constants in your contracts in that contract. Okay, so the OG original method here is create for actually deploying contracts. That's what happens when you're actually deploying a contract via a transaction to a null address, uses the employer's address and the nonce to determine the address. The new kid on the block create too, instead of using the nonce of the employer, it's basing the address and the deploy is too often with the deployment address, the salt, and the initialization of the contract creation code. Okay, self-destruct. I'm sure you guys are all familiar with this. It actually featured in like the original Ethereum Foundation Hello World app. This was like, seen as you doing your civic duty on the blockchain and cleaning up after yourself. And then of course, the parody multi-sig hack and a whole bunch of very sharp edge cases have led most developers to just saying, avoid this like the play. I'm not most developers. There are a couple of cool things you can do with it if you're willing to take a lot of wild side, but always tread with caution when you're using self-destruct. And one of those things that self-destruct can enable or exploit is redeployments. So the gist of a contract redeployment, let's go over how this sort of comes down to be. If I try to deploy to an account that's already got a contract, this is a scenario that in a create-only world isn't going to happen without a hash collision because the nonce is incremented every time. But if you create two world, if you try to feed in those exact same arguments, it's just going to fail, right? There's already a contract there, whether or not there's code, this is invalid. But when you self-destruct a contract, what's actually happening? It's more than the runtime code getting cleared out. The entire thing is wiped. You've got no nonce, no residual balance, no state, everything is gone. As far as the blockchain is concerned, this contract is never adjusted. So then once it's already been wiped, what happens when I deploy to it again? Well, in that case, fine. The contract can be deployed, no problem. But if I was relying on the state or the nonce or whatever property of that contract before this got wiped and then put back, well, now you're in trouble, right? So let's look at an example of how this might sort of rear its head. And bear in mind that the code won't actually even be changing. This is just an example of greatly simplified, like a more realistic use case would be like a DEX or a swap contract or something like that. But this is a contract that we approve to transfer our shitcoins, okay? And initially it's in testing mode, so don't approve when it's in testing mode because anyone can move your funds around. But then we turn the testing mode off, but then the contract can be destroyed or tired. And once you redeploy it, it's back in testing mode and you can get your funds stolen, okay? So this is a problem and the really low hanging fruit advice here is be careful when you're giving him ERC-20 approvals out, you guys already know that. But let's look at then the remediation to this, which here's our same shitcoin mover contract and we've stripped out the testing mode. So you're good, you can approve this contract and you're going to be the only one that's able to call the transfer from that bid. We're all set, we've fixed this problem, everyone can rest easy. Except you can't because if it was deployed via some of these tricky methods, then you're back in the same problem where they could deploy a contract that can move your funds again. And the phenomenon here that the phrasing I started using for this particular type of redeployment where you've got brand new code coming in is metamorphism. This also goes by malleable contracts, there's a few different ways of describing this. A metamorphic contract is one that has non-deterministic creation code. So you feed in the same fixed creation code that you're sourcing how you're going to derive the runtime code from something other than that creation code payload. So here's the basic rules of how this happens. First off, you have to destroy the existing contract, you have to call self-destruct. Second, you have to use create2 and the interesting thing about this is it doesn't have to be deployed directly from create2 because if anywhere along the chain some contract uses create2 and then all those intermediate contracts get destroyed, then you can redeploy all the way up. Third, the deployment address, the creation code and the salt can't change because otherwise you're going to have a new address. Lastly, once those three rules are adhered to anything is fair game and is good. So let's talk about some of the main ways that this will work. First off, a straightforward way to do this is to clone another contract. So what I can do is I can store the address that I want to clone in contract storage and then my creation code is going to go get that address and clone the contract. Now this has some limitations, you can use a constructor sort of like with common upgrade ability patterns, you have to call it to an initializer. Another way to do it is using what I call a transient deploy. And a transient deployer is going to use this trick where it's going to, rather than getting an address, it's going to go get the actual creation code that it wants to deploy, then it's going to deploy its own contract via create using that creation code. And create doesn't have the same limitations on being you can change the creation code all you want. But then if the transient contract immediately destroys itself after it's done. So then if you want to go back and redeploy, remember the nonce of that contract is reset. So create is going to hit the same address again. This has the limitation of any time you want to do this, there's two contract deploys. So there's some overhead, but it's a way to do it in a more flexible fashion and support constructors. Finally, my personal favorite pattern for this, I call the metamorphic delegator pattern. And what you do here is you actually take your target creation code and you put it into the runtime code of another contract. Then when you deploy this metamorphic contract, it's going to delegate call into that creation code and runtime code contract. And it'll set up all of the initialization stuff in the constructor and then return your target code, which it then writes to runtime. So first let's look at what a cloner might look like. This is a standard non-metamorphic cloner where the creation code changes. And this is just going to take our target and put it on the stack and then copy the code. And this is what a factory that spits out that clone might look like. As you can see, our creation code is going to change. So if we try to clone a new address, it's not going to work. So here's a metamorphic version of that cloner. And what this is going to do is we store the address you want to clone on the factory and then this calls into the fallback on the contract that deploys it, does a static call and gets that address and then uses that to copy the code from the other contract. And this is what that might look like. You've got a simple fallback that is just going to grab the target address from storage and give it back. So then you can see here, your creation code never changes. But every time we call this, as long as we can destroy the existing clone, we can keep adding, upgrading to new cloned contracts. Let's look at the transient deployer then. This is a little more involved. But what we're basically going to do here is we're going to call into the fallback, same thing. But we're not getting an address, we're getting the creation code. Then we take that, we don't know the size of it ahead of time. So we're going to use return data size or return data copy to put it into memory. And then we create, now if it reverts, we can go and grab the reverberation and return it. Otherwise, we're going to self-destruct this contract so that we can do it again. And then all we've got to do is self-destruct this extra hop contract and we can redeploy it. So this one, it looks pretty similar to the last example of a factory. Rather than storing an address, we're storing a binary. And the other step of this that's important is we have to, we know the nonce is going to be one on the transient deployer, so we can use the RLP encoding to figure out what the address is that we're going to. All right, and then lastly, this metamorphic delegator. This one is basically two steps. Step one is that you need to insert that creation code into the runtime code of another contract. This is what I call prelude or a block of code you can insert in front of whatever arbitrary runtime you want to be deploying and it will deploy it for you. You can also use libraries and the cool thing about this is you can do this ahead of time. You can even reuse the same creation code in runtime code blocks. Then once you've got that contract deployed, then here's a metamorphic delegator payload here. We're just going to put a bunch of zeroes on the stack to use later. Then we go and get the address from fallback, the creation and runtime address. Then we're going to delegate call into that address and either bubble up the revert reason or return the runtime code and because we're in the constructor, by returning that code, we write it to storage or to runtimes. Here's the factory for this. It's basically going to take our creation code and deploy a creation code in runtime storage contract. Once again, this is something you could do ahead of time and reuse if you wanted to really juice the efficiency out of it. Then we're going to take it and deploy our fixed non-deterministic byte code that goes and grabs that address and delegate calls it. This obviously, if you want to go down this rabbit hole, there's a lot to it. These are kind of minimal examples where you can supply your own salts, you can do all kinds of tricks. I put together a utility contract called Homework that uses this metamorphic delegator pattern. What it does is it mints you NFTs, but you park addresses ahead of time and then deploy or redeploy contracts to those addresses and also makes them fun to. You're welcome to check it out. I'm also happy to answer any questions about it. Now, all of this stuff might make you a bit squeamish. I know most of the people I explain this to, it really goes against the standard immutable principles of what we're doing. Let's talk about how you can protect against this because the truth is it's possible. There's three big things. First off, the best protection you can provide is to make sure that a contract cannot be destroyed. It's not quite as simple as no self-destruct doc codes because you can also, if there's delegate calls or call codes, those could trigger a self-destruct in the context of your contract. In this world where it's upgradable everything and proxy everything, this is actually a harder guarantee to provide in many cases than many of us would like. What you can do, though, if you can provide this guarantee, if you give your contracts to this registry, indestructible.e, it'll scan them and stamp them as like, this is indestructible, you're good. That's one way you can protect. Another thing you can do is you can ensure that the contracts are being deployed immutably. It's not just quite as simple as create, it has to create all the way down to something that has never touched create2 because you can bubble them all up or it's created by something that is itself indestructible. Finally, you can go through immutable create2 factories. There is an example of one that you can use in immutable create2factory.e and other contracts that do similar stuff and then they basically just track once you've used a particular assault in an info or whatever and don't let you read it. The third thing here is that you can check the run time, the Xcode hash into the contracts that you want to interact with and make sure they haven't changed in the interim. Now this isn't going to protect against standard redeploys, the zombie deploys, it's only going to protect against metamorphic contracts so you still have to be careful about a state that might get wiped in between if something is destructible and you can register ahead of time and use a registry like this one, code hash cash is an example. Now if you combine two and three, you've covered most of your bases. If one, it is not available to you. The biggest thing here is educate people about this. This should be a checklist during an audit to make sure that just like you can't, you have the right trust assumptions for dealing with ownership, you have the right trust assumptions when you're dealing with deployment methods if they're susceptible to this. All right, all you white hats can take a deep breath. For the rest of us, let's talk about some of the cool things that you can do with this technology. So first off, contract deployments, it's actually really cool to be able to park an address ahead of time and find an address that's efficient without needing to tie that into the creation code. So if you use an address that has lots of zero bytes, you can save quite a bit of gas in certain scenarios. Also, we can know the deployment address of any of our contracts ahead of time. So you can deploy a whole family of contracts that refer to each other as constants and are interrelated and save gas that way or just have coordinated plays without having to fuss and track all of the nonsense of your deployment addresses in a really granular way. Finally, if this makes it easy to deploy different contracts that are slightly different to the same addresses on local test nets, main nets, other chains, as long as you're coming out of the same factory that's been deployed across chains. Second, runtime storage. So there's a lot of talk with the new Istanbul art form that the price of SLO is going to quadruple. So you can use runtime storage in certain read heavy applications to make storage a lot cheaper and also more accessible. So usually when you go through contract storage, you have to go through a ghetto, right? And the contract is dictating how that's going to be read back to you. But if you're using runtime storage, you just copy whatever portion or section of the data that's relevant to you. And so you can access storage on your own trains. It's like true public storage in that regard and it's also cheap to do. You can also, by transferring ownership over the accounts that you can deploy to, you can give ownership of a region of storage in a way that's really future proof, right? It's not tied into any particular implementation on your contract. And finally, it's economical as we were sort of discussing. You can save a lot on not only reads, which is like big savings, but also on initial storage rates, especially for larger payloads, because in effect you pay 20,000 gas a word if you're storing on contract storage, whereas you're only paying 6,400 a word for every runtime storage word plus the overhead of the contract. And you can still do updates by using some kind of metamorphic system. Third, smart wallets. So Ethereum obviously doesn't have native transactions script support. It's just calling into contracts. But if you use one of these parked addresses as your smart wallet, you can deploy contracts that will do, throw in an arbitrary bytecode and then blow up, and then you can do that at Nozzy to sort of have a transaction script behavior. It's also really secure because there's no code to hack. In between these transactions, it's empty code. So there's not the same risk at rest for your funds. And this leads to being able to support really user friendly stuff where you can do these complex actions in a way that includes conditionals and atomic operations and all of that. And it's only one transaction from the perspective of the user. Fourth, you can tokenize accounts this way. So in effect, as long as I own the rights to deploy to a particular address and I give that to you, I can reassign control of this stuff and you're not tied in to any particular smart wallet implementation or whatever. You can put whatever code you want there once you own it. It's your account as long as it can be destroyed. So this way you can create portfolios, baskets of assets that go beyond just like a particular ERC 20, ERC 7.1, whatever. You can make a ownership, reputation, all of that because the account is the ultimate abstraction for doing that. And it's like a reliable way to do this in a future-proof manner so that if you do need to make changes down-line, you can do that for whatever your tokenized account is. Yeah, this is out there. Like this sort of new use cases for this, they're weird. But if we weren't willing to get weird, like, what are we doing here? This is Ethereum anyway, right? So I'll leave you with a quote on the dark arts and maybe a slight modification that I think is more applicable to us here. And this concludes the talk. Thank you guys very much. I'll put up the link one more time if you want to go through it. Is that just being properly priced or is it using... So when you're accessing contract storage, there is an extra... You have to go not only to the contract, but then you have to send it into that Patricia tree as well. So there is an overhead on the machine level. Is it doing it that way? Now, does it make sense as a preferred storage method? I don't know. I think that it might be an indication that there's more work that needs to be done to help level that out and maybe a gas correction. And I think that's also important to bear in mind if you decide to use one exotic storage method over another that you should basically anticipate that things might change in terms of gas pricing down the line and you might be stuck with something that's really esoteric and out there. But as long as you couple that with good upgradeability then it's maybe not so much to preserve. Yeah? Do you have an idea of what the gas out there is for creating a contract and then seeing something like a token transfer and then just running the contract which is doing the... To use like this transaction script type functionality. So you're right out of the gates. You're dealing with the 32,000 gas overhead for a deployment. Then you can deduct out the 24,000 rebate. It starts to make a lot more sense as you get into more complex operations and especially stuff that like you couldn't go through another helper or something that would do that matching for you. But the big thing is most like batch transaction type things where you do multiple calls. They don't really have a strong ability to hook into conditionals of like well based on this I'm going to go do this other thing I want to roll this other back. This way you can like the sky's the limit. And there's also the security consideration of nothing to hack which is nice. Yeah? I'm still trying to catch up with the method of multiple calls. The address from create2 is determined by the code hash itself. Yes. How do you change the code without changing that? So you don't change the deployer, the salt, or the creation code. But the creation code is non-deterministic meaning it's fixed creation code but during that creation step rather than a standard like solidity deployment that's going to basically have the runtime code and then just copy that over you're going to say instead I'm going to go ask this other contract for their code that they have right now and I'm going to return that for some variations. Okay, last question. Is the balance of the previous contract destroyed? Is it also destroyed or you have to kind of move it out? Yes. So whenever you're self-destructing contracts you have to be very careful with balances. These token balances are good because they live on the tokens, right? That's not so much of a concern. But if you have ether at the contract when it's destroyed that your ether is burned and not like sent to the null address, it's annihilated and your money isn't gone. So obviously self-destruct, you specify a recipient and it will immediately forward the funds to them. But if any funds make their way back within that same transaction the contract hasn't been deleted until the end of the transaction at which point. It's only been scheduled for deletion. So another limitation to this is that you're limited to one redeploy per transaction. That's a pretty extreme case that you would be doing multiple redeploys in a single transaction but that's something to be aware of so there's like a slight additional delay in the finishes. Okay, I'm happy to chat more and take some questions after this but thank you guys for being here and go teach people about this.