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

Loading player…

How to predict the impact of EVM upgrades | Tony Valentine, Dedaub | ETHTaipei 2024

ETHTaipeiWed, Oct 9, 2024, 12:00 AM

How to predict the impact of EVM upgrades | Tony Valentine, Dedaub | ETHTaipei 2024

Transcript

hello can everyone hear me okay so um I already got a really nice introduction from Anton uh so yeah I'm Tony I'm representing uh dor we're a security company and yeah the title of today's presentation is how to predict the impact of uh evm upgrades so what we're going to be covering today is uh two spe uh two studies that we did for the ethereum foundation last May uh the first study is about uh self-destruct removal so that's EIP 4758 and EIP 6780 so they're two kind of competing proposals for how self-destruct was going to be removed uh I'll give you kind of like a quick rundown of what self-destruct is and what it does or now what it did uh we'll then understand why it kind of needed to go uh we'll understand how it was being used and then I'll give you kind of the verdict for that initial study the second kind of thing that I'll get into is the transaction and receipt route serialization changes uh so that's kind of related to how now we have a difference between consensus layer and execution layer uh consensus layer uses this new ssz format execution layer uses an older serialization format known as rlp recursive length prefix and so there's some changes there with how uh kind of U MPT routs are constructed uh so yeah we'll get into um like what exactly is changing you'll get a little bit more of understanding there why it needed to change uh we'll also then cover how we found contracts which were doing rlp decoding because that's kind of like crucial to understand what the impact would be uh we'll find rlp blobs in uh call data and I'll give you the verdict for the second study so as I mentioned the first study it's EIP 4758 and 6780 are basically in short removing self-destruct from the evm uh so a tldr of self-destruct basically self the struct it clears up a contract so it resets the nons it clears the storage it uh clears the uh code and you can also send all of the eth in the address to an address of you're choosing um there were also at one point like gas refunds associated with self-destruct and kind of the purpose of introducing self-destruct was to kind of promote State cleanup inside of the uh evm so I don't know if anyone here like runs a full node but they're kind of expensive to run so if we can get people to clean up things when they no longer need them then we could hopefully have reduced the impact of like running a full node and we saw like other things uh for example you had storage slot gas refunds which were also kind of to promote State cleanup if you didn't need a storage slot and you reset it to zero you would have got a refund um this kind of mechanism did actually sadly end up getting abused through gas token I don't know does anyone here remember gas token yeah so the point of gas token was basically you could uh create a storage slot well sorry you could fill in a storage slot or you could create a contract when gas was low and then when gas was high you would destroy the smart contract or reset the storage slot and you would get the gas back and you could have like offset higher gas costs so that was one abuse we saw over the years it wasn't really the reason it needed to be removed because that got mitigated in EIP 3529 which basically slashed these refunds another kind of abuse we see with self-destruct is mutable code otherwise referred to as metamorphic smart contracts um so there's kind of various ways they're done uh it's kind of a combination of create two and self-destruct I'll get into that a little bit more later uh there there was a proposal that came up whilst we were doing this study 6 913 which is set code which kind of aims to like make this a part of the evm but I won't really get into that I just thought I'd mentioned it since it did come up whilst we were um doing the study so to kind of break down uh EIP 4758 and 678 I'll summarize 4758 as deactivating self-destruct so what I mean by deactivating self-destruct is we're going to rename the op code from self-destruct to send all we're not going to touch the code we're not going to touch the state we're not going to touch the nons and the kind of the actual reason we wanted to go about this change is because of a movement called uh The Verge so I don't know if you keep up with the road map that metallic publishes but there's numerous kind of like phases going on in parallel in ethereum one of them is called The Verge The Verge is related to uh originally veral trees but now just verification in general uh the kind of the veral tree component is basically you take a Merkel tree and you replace its like hash proofs with what's called uh Vector commitments and the really nice property you get there is that you can do proofs over these vertical trees with reduced data sizes and uh smaller proofs because basically if you look at the tree there you can actually prove a path through the tree without needing to send these sibling data so you're able to actually kind of do these like parent child proofs and then you can like combine them and compress them into really condensed proofs the problem that self-destruct causes with this is you can kind of have an unbounded number of State updates so think about it over numerous transactions you could fill up lots and lots and lots and lots of storage slots you self-destruct all of those storage slots get cleared they get set to zero you have a really large uh payload update and the reason payload update is like a problem at least the size of it is because in the future we want to have these stateless light clients so basically the like client wouldn't store any state they would need to have all the states sent to them and these large unbounded unpredictable State updates would cause a problem for implementing this as well as you know like implementation complexity in the nodes for like kind of keeping this state around for a number of you know blocks or epochs um and that's kind of why um we wanted to remove it so uh 6780 I can kind of summarize as restricting self-destruct and basically it's the exact same proposal except for the fact that if you self-destruct within a single transaction it acts as it originally did and the reason that this is okay is because you still have uh bounded State updates and you're not really like committing to the actual uh State Route um the reason this was proposed is because some people were kind of using this pattern it doesn't have the same negative consequences and we needed to evaluate like if this was actually better than uh 4758 um so the way we went about doing this study um there's this like really nice tracing interface that's provided by uh gu or we use Aragon internally uh because it's like you know a bit more performant uh but basically you can hook into the evm during its execution the things that we care about here are uh capture start and capture end as well as capture enter and capture exit these basically are what's have what's triggered whenever you have a call frame um there used to be capture self-destruct I don't remember if that was an Aragon specific thing but now self-destructs are actually um kind of like modeled using uh call frames which is kind of interesting since they don't actually initiate call frames so they're like pseudo call frames in terms of tracing through the execution um and I'll like touch on that a little bit more later uh so yeah we recorded um all contract creations and self-destructs we specifically looked at ranges 15 million to 177.23 million for our study since that was like the most recent data we cared about what people are actually like actively doing um and we saw like 9.5 million Creations split into a million creates 8.5 million create twos as well as like 420,000 self-destructs so the the first thing that we saw was metamorphic smart contract so mutable code on chain so most people are kind of under the assumption that uh smart contract code is immutable and it's kind of not so um yeah the the reason that this works is because uh using uh create 2 uh or even creates you're able to kind of manipulate I'll show the patterns a little bit later but to give you kind of some numbers about this um during like I think we saw 170,000 is redeploys we saw one contract if you look at the table on the which left for you guys uh we saw one contract that was redeployed to the same address 848 times we saw one contract that was redeployed with 33 different bite codes and uh the main kind of use case we saw for this was uh me bots so if you have an me bot on chain you've built up lots of balances allowances permits Etc on chain it's very kind of you know expensive to transition all of that state to a new address maybe some of it you can't transition um so if you can upgrade your mbb Bot in place because you've made it more efficient you've had new strategy um it makes your life like a lot lot easier um so here's a couple of the ways people are able to do metamorphic contracts uh the first pattern is like what's called create three essentially what you do is uh you have a deployer contract that has a constant code because it has constant code you're can to always deploy it to the same address so you create to it to an address you then call a function inside this contract Loy which basically is just a light wrapper around create it then creates your metamorphic contract and the kind of the key here is that create depends on the message sender and the nons and uh since we can reset the nons using self-destruct what we can do is we can self-destruct the deployer we can redeploy it in place uh because it was deployed using Create 2 and then we can create with an arbitrary payload to the same address and that's how we have our metamorphic contract the other pattern is what's known as the metamorphic Factory pattern and uh basically the way that works you have a factory contract on chain you send it the code that you want to deploy it deploys your contract to kind of like an arbitrary address it then deploys your metamorphic contract with a constant init code so you're using Create two and all the init code of the metamorphic contract does is it just takes the address or wherever it was deployed does a code copy and sets that as the return data and then it set it it code is whatever you wanted it to be and then to kind of reiterate because it has constant in it code and it's created with create 2 you can self-destruct it and recreate it in place um so yeah the other thing we saw is uh sud sudo core frame so it's kind of like a interesting behavior of self-destruct but self-destruct doesn't actually initiate a core frame which allows you to circumvent fullback functions so whenever you execute a self-destruct what it does internally in the node it just takes the address it subtracts your balance and adds your balance onto the address uh that you're sending it to and it doesn't actually initiate a call frame so usually when you send eth you have to actually like send some amount of gaps with that since code can actually execute when you're sending eth um and uh you can have some like quirky Behavior with this so if we look at the we contract you can see the fullback function is payable and it calls deposit you should kind of know what deposit does it just increments your weth balance and emits the positive ENT if you look at the total Supply function for w the total Supply function relies on querying the E balance of the contract so it doesn't actually do any Accounting in the deposit or the withdrawal um functions the reason this is like problematic it is uh because basically you can increase the eth balance of the we contract without actually increasing the amount of we that's distributed to users and you do this by sending eth to we using self-destruct and if you actually sum up all the kind of we allocations and you actually take a look at the eth balance of we it's off by about like 1,925 eth it doesn't really pose any issues to users because you can still like liquidate your eth you can convert your sorry your weth you can convert your we into eth and eth into W it's just kind of like an interesting side effect of the fact that it doesn't initiate a call frame um we also saw people using self-destruct just to do arbitrary transfers so if you look at uh the line of this transaction Trace specifically like the one originating from the arrow you can see that someone creates a uh smart contract they passed some value 0.0125 whatever e um and then that contract just instantly self-destructs and sends all of its value to the specified address and if I take the init code and pass it through uh the decompiler that we developed at DOR it's like one of our fundamental Technologies um the decompilation is on the bottom right all it does is it uh takes the address from the end of its own initialization code and just sets that as the address for the self-destruct and for example this was being used by uh 3F Mutual to uh to send funds so uh to kind of break down the everything that we saw whilst we were doing this study we saw about 10 protocols that were deemed affected by EIP 4758 of which about three were considered high impact but then if you look at EIP 6780 uh we didn't really deem any protocols to be uh affected by this uh both the proposals fix the metamorphic contract issue that I mentioned earlier and uh e 6780 was actually included in the denune hard Fork that you probably heard about from other speakers uh today so that was happened last week um nothing's collapsed so everything went uh went well I hope um getting into the second study it's uh migrating the root serialization from rlp to ssz so uh basically you might have heard of this like thing of E2 essentially we have a separate consensus layer and a separate execution layer uh E2 uses this new like serialization format known as simple serialize ssz over the original serialization format that we used rlp recursive length prefix so uh SSE has numerous advantages over rlp uh the ones we care about the most are that it's Merkle first and that it's indexable and the reason that like this property matters is because of this thing that I mentioned about The Verge where we want to increase like the provability powers for like clients and stuff like that uh so the two eips are EIP 64 for which basically transitions the transactions route from being a merco Patricia tree Constructor using RP encoding into a ssz route and this is being done on execution layer because the transactions route on consensus layer was already an ssz route as for the receipts route it's also migrating from uh mer Patricia Tre to ssz and this is being done for both execution and for consensus so the important kind of thing we need to understand in terms of who's going to be affected by this change is that we're looking for people who are using transactions routes and people who are using receipts routs and the way that we figure this out is that they need to be in some way manipulating rlp data inside of their contract to be performing proofs over these routs so we need to look for rlp like decoding or encoding so let's kind of understand what rlp is so there's five kind of cases for rlp you've got single bite a b long string a list of items and a long list of items and they're kind of specified by prefix ranges so depending on what your prefix range is there's a way to get the actual data out and figure out the length of the data I can actually show you what this looks like in pseudo code and you can see the function like the code RP we've got a nice if if else chain and so this is actually the pattern that we want to detect on chain how would you go about finding smart contracts that look kind of like this or have a fun that looks like this and the important thing to note is these constants so we have these constants that are actually used in comparison operations and uh so we want to look for contracts that have these constants and the reason that you have to have these contracts uh constant with rlp data is because rlp data is dynamically sized it's non- indexable remember I mentioned index ability with as an advantage to ssz you have to have these constants to do RP deod so uh if you look at the top left I have a uh disassembled view of uh B codes and you can see that they are pushing one of the rlp constants 0xc0 to the stack and then a few kind of operations later we're seeing a comparison operation and then we're seeing a jump so if you just naively look over uh like the bike code and you look for contracts that have the necessary rlp constants you get about 25,000 white code results so that Maps even more contracts it's pretty infeasible to go through 25,000 in a twoe study so we needed to be a bit more clever now you could say well really we care about these RP constants being used in comparison operations and we can see that there is a comparison operation you know two operations later but the problem with doing it on the bik code itself is that you can have numerous uh stack manipulation operations or random operations in between the less than operation or the greater than operation being uh used uh so really what we need to do is we need to lift this I already kind of mentioned we have a decompiler and that's actually what we see in the middle uh kind of like code block so that's the decompiled version of this and if you see the line of code that originates from the arrow we can just see uh less than variable rlp constant so the 0xc0 and so we can see straight away that okay it is actually being used in a comparison operation so we index all the code on chain we've decompiled it all we have it all in our database and we can run this really nice like SQL query over it where basically we just look for a variable being assigned to the result of one variable being compared with an rlp constant the actual query is like a horrible rex that you can look at at the end of the slides I'll link it um but you need to account for for example plus and minus one to account for the difference between greater than or greater than or equal to and uh whoops go back there um and yeah so when you run that career over the data you go from 26,000 to 556 so it's an order of magnitude less it's actually kind of doable within two weeks we wanted to take it a step further and be a little bit more precise and we ran something known as a data flow analysis and so what the data flow analysis does is basically we see if you look here the less than operation flow flows into the green variable the result of the green variable flows through an iszero operation into the purple variable and then the purple variables used as the conditional operation of your jump eye and what this means is that the result of the comparison operation is actually dictating the control flow of the program and that's what an if statement is an if statement has a like a Bo Boolean operation which you know determines the control flow of the rest of the program and when you run a data flow analysis and you combine it with transaction data you get down to a result set of about under a 100 contracts which is very feasible to actually go through manually in like two weeks so some of our team got started on the manual investigation and uh the rest of our team we kind of wanted to have like a backup method like a second you know a second stab at trying to find um like contracts doing R decoding and we wanted to try doing it through call data and so if we're going to like look at data we need to understand uh solidity ABI encoding uh so there's a few cases here you've got fixed size type Dynamic size type arrays we don't need to get into any of them too much in detail except for the dynamic size types because these like roots and these RP data are going to be passed as bytes objects so let's see how byes objects look like in uh call data so if I want to encode ABCDEF as byes the first thing you do for dynamic data is is you Preen the length of padded to 32 bytes so ABCDE EF it's six hex characters so that's three bytes so we put three at the beginning and then you just slap on the data at the end and so if you recall we are trying to detect rlp data recursive length prefix data and we have a length field from the ABI and rlp has a length field so is there any way that we can kind of like use the synchronicity of these length fields to detect rlp data and the answer is yes so if we look at X that is like the first word that is 217 and then Y is the beginning part of an rlp payload and so we look at F9 that's uh we can decode we subtract F7 to get two so it's a long list so two is what's known as the length the length field and that is the 214 in blue because that's two bytes so we know through the rlp data is telling us from that point onwards the length of this payload is 214 bytes so if we add on the length of the payload the length of the length length field and the length of the prefix we have 214 + 2 + 1 is equal to 217 so these two lengths match up and therefore we can say kind of with some certainty that uh this is in fact rlp data because the chance of this happening with any two words inside the call data at random is extremely low so using these two approaches we were able to kind of like get a data set of contracts and of transactions we started filtering through them in about like a week and a half and uh we came up with kind of like a list of of who we thought was affected and kind of to summarize what we found we found that most Bridge applications on ethereum weren't actually affected and this was because in fact the people doing rlp proofs over ethereum blocks would actually be on chains as opposed to on ethereum itself with regards to ethereum there were actually Three Bridges which were affected that was layer zero uh ZK Bridge which Builds on layer zero as far as I'm aware but feel free to correct me and telepathy uh we also noticed that EIP 6466 the receipts rout change actually had a larger impact than the transactions route change we don't really see many people doing uh transaction root proofs on if eum blocks inside of ethereum itself and so that's kind of all I've got time for today if you scan this QR code you can get a link to my slides you'll also get um a page with some resources about there's like some stuff on the verge there's some stuff on rlp serialization uh as well as links to our studies and to the eips I don't know how I'm doing on time uh I don't have a time for questions yeah there are time for question for sure anyone if no I can I can ask a question okay go ahead so yeah you said uh you said you uh kind of filter it down to 100ish trans um contract after you're applying the uh why I forgot which technique uh data flow analysis okay so like did you guys check what was filtered out like what are those contracts doing because they also had the constants right uh yes but you can be you can be comparing to the like the constant but doing something completely different it may not be dictating uh the control flow we did a sampling of them and we found the sample that were rejected weren't actually doing RP data oh um I'm just curious like did you maybe find out some contract that are verified and then found out they're doing something particularly interesting that similar to R RP but not I from what I recall there wasn't anything um super interesting in terms of like contracts that were doing something similar um there there is kind of this like uh interesting thing that like if you try to do it just on source code like searching for rlp and source code you actually get like lots of false positives because even though it's in your source code it doesn't have to be in your compiled binary which is why we did this entire study from binaries to begin with okay yeah okay that makes sense any more questions all right well thank you for having me thank you everyone for paying attention I hope I didn't boy you [Applause]

Automatic transcript — names and jargon may be misspelled.