EthCC 2: Andrej Bencic
Ethereum Paris·Wed, Oct 9, 2019, 12:00 AM
EthCC 2 EthCC, the Ethereum Community Conference is created by the community, for the community. It brings together great minds and forethinkers, and allows the community to grow. Follow us : @AssethFR https://ethcc.io
Transcript
so hello everyone my name is Andre and today we're going to be talking about invalid opcode and what can we learn from it to understand the EVM a little bit better so usually when you develop your contracts and they fail most most likely you've come up to something like this just invalid up code an indication that the contract failed and you don't know where really to start looking so wouldn't it be better to go from something like this to something more detail something you can see in other languages so full stack trace of where your transaction went to when it failed and the exact line of solid is the source code where it failed so you can easily fix it in this session we're gonna try just that we're going to split it into two parts at the first part being going from the position in byte code where the transaction failed which we can get from the EVM to the exact line of solidity source code which we can then use to debug and the second part is build on top of it and get even more information by getting the whole call stack of the failure so let's start the first part is source mapping and what source mapping really is in the first place well it's a method of going from a low-level byte code location which is not easily debuggable to something that is too something that is solidity source code line so how would that process look like in the overview so we would take the byte code and we would we would transform it into something more readable so we will just form it with instructions and from there we would we will find the source mapping for the instruction that we found and use that use it source mapping to get to the final solidity source code that's the let's the overall process but how we do it exactly let's get started we start with bytecode and we know that bytecode is just a sequence of instructions that is deployed that is generated by the sylheti compiler and used to be deployed on the Tyrael network to be executed and when the transaction fails we can get from the EVM the exact line position in the bytecode mark here in red where the failure happened so if we wanted to know which instruction caused the failure because bytecode is pretty unreadable we would first have to transform the bytecode into instructions that process of transformation bytecode into instruction is called disassembling there are a couple of tools online which you can use to disassemble solidity lower even bytecode into a set of instructions but here we're going to do it night we're gonna start by splitting the byte code into sequence bytes and we're going to mark the index of each byte in the byte code to the left in green if we wanted to convert this sequence of bytes into a sequence of instructions what we would want to do ideally is group sequences of one or more bytes into groups which form instructions and how would we do it well we know that each instruction starts with an opcode an opcode is a unique value that identifies each instruction and with with this mapping we know if we need to take any additional bytes to form the entire instruction so we can use the the bytecode and mapping of up codes to instructions to go to the right here and see how we can sort of make out which instruction starts at which position in byte code we then we don't want to group the all the bytes that belong to a single instruction and kind of make out the the final form here the thing you'll notice here is we have a second index on the right which is essentially a position of the instruction in the instruction sequence so the difference between the to the left and the right is because the left one is the position in the byte code and the right one is the index of the instruction and those two aren't the same because one instruction can have one or more bytes that is that's created for it so the final form is we have a sequence of instructions that we derived from the byte code and two indexes which we use to look up the instructions in various ways to the left we have the byte code index and the right we have the instruction number so as an example to what we can do with this information if you have if you have the byte code and a transaction field we get location where the transaction failed and we get the exact instruction which caused the failure but we still have that index on the right why do we need why do we need that index in the first place well turns out that when so relative generates so it generates evm instructions from source code it doesn't really care about it doesn't really care about the positions in byte codes for each instruction it just cares about which instruction was generated by which line in solidity it leaves the it leaves the byte code stuff to the trivia so as an additional initial output of the suity compiler what we get it means aside from the byte code is a source map so a source map is a special format of a file which solidity uses to create to specify which instruction belongs to which line in source code so we can see the dis format here is not that clear what it does so let's get into it a bit better so what we know that we get one line for one instruction of one EVM instruction we can see that we can shorten the each mapping by one or more fields if that field is the same as the previous one and in case where one line generates more instructions so the same line generates for example five instructions and we can omit the entire the entire mapping and we know that it is the same for the for the set of instructions so what is what is contained in each source mapping well we know where is the where the line that generated the AVM was located so an offset in the file example okay yes so the offset the offset of the line in source code we know the length of the line that generated it and the index of the file which generated the the instruction and the fourth indicator is the jump indicator and we'll get to it in a while so as an example how you would read a mapping like this is you would first take the file index and get to the file the generated instruction you would then find the offset where the line starts and finally you're going to get the length of the line and get to the final source code the generated instruction so we made it we now have all the information we basically need to recreate the lining in solidity which failed the transaction let's see it in an example so we start with byte code and we see that the transaction fails somewhere and we use that position in byte code which is the only information we have to get to the instruction that caused the failure we now use the index to the right to find the source mapping for the instruction because as we talked earlier instructions and source mappings are one-to-one so index in index for an instruction is the same for the source mapping and finally we know how to take the source mapping and read it and get to the final get to the final source code which caused the failure so that's the that's the final step of our first part and we effectively already have a lot of information we can use to debug our contracts but what about if we wanted to give even more information to the developer to - easier to quickly find even more obscure bugs so why exactly would we use a call stack in the first place well imagine you have a payment processing contract and you have a single dedicated method that does the transfers from one account to the other and you use that method in a lot of places and if for any reason a transaction fails in that part of the code you don't really know from where it happen where the failure came from so you maybe have 10 10 calls to the Tuda method and for some reason it fails you don't really know where to start looking and you would like more visibility so as an example here let's say you have a method you have a method that failed what you would want to get is the color of it and go all the way up to the initial transaction so you have the whole path and you know exactly which route your program took or when failed so ideally if we had something like this to start with what we would want is get you know get all the other locations of function calls where the transaction where the transaction failed but we can't have that that easily we can use an EVM tracer an EVM tracer plugs directly into the EVM and listens for calls like jumps calls and returns in order to build our virtual virtual stack and keep track of it during the execution of the contract and in case of failure return it to the user at the point of failure but that is pretty involved and we're not going to deal with it right now what we're going to be doing is using a shortcut we have a couple of tools at our disposal that can be that can shorten our journey quite a bit so the first one is how do we know where the program went as it executed well we can call a function here on the on the node on the RPC API of the of the node and get the get a JSON representation of the whole execution of the transaction so that means that means we know which instruction generated and in which order until the failure so if we have if we have the the PC here represents the position in the in the bytecode if we wanted to enhance this information we already know how to get the source mappings from the from the bytecode positions so we would do just that but what do we what do we know with what we know with this that we didn't know with with the bytecode well remember the fourth element of the source mapping and that's the jump indicator we can use it to recreate the stack after the execution to get the to get the same results essentially so we would go and run through each of the each of the lines each of the lines we got from the endpoint and if we see an eye that means the instruction went into a function and we would add it to a call stack if we see a - that means no changes have been made and we can just discard it and if we see an O that means the instruction went out of the function and we can we can remove it because if we go into something and then leave it it doesn't really affect our path of failure so we can skip and then go through the through the rest of the instructions adding and removing entries until we get to the final failing transaction finally instruction added to the call stack and that's what we need so if we want to see a more detailed example of this we see that that we go into a function that function does some work it's not important what work it does and it returns so we can remove so we can discard we can discard the elements that were associated with that with that function because we don't really care about it we care about only the functions that go inside and then fail and we go through the rest like enter enter another function under a function here I mean from which really right away so we discard the the one we called didn't actually fail and once we once we entered the final one let's say we fail here so this is the stack we would get by using the pushing and popping map method so how what what do these source mappings represent well if we want to use the method of converting the source map to a position in source code we would see that each line is essentially the entry point into a function which finally caused the failure so this should be our stack trace and if we convert it into more more readable format we get something like this so that's how you go to the complete stack trace and have way easier time debugging your contracts thank you my name was Andre I come from tenderly we do it just this do we have any questions can anyone hear me so your talk presented this wonderful project as if it was entirely and exclusively aimed at developers however with smart contracts I believe that looking at what happened during execution in the source code is interesting not only for developers but actually to users as well because if I submit a transaction and something happens that I did not expect which is not even necessarily an invalid of code so it's not even necessarily a failure it's just that I didn't get the money or something else happened that one I expected as a user I would like to see what happened like step by step so are the tools that you're building suitable not right now but in the future suitable for that kind of use case yeah so if the question is is this the developer only tool well no at tenderly we use this method I mean this method is like full developer only what you what you need is what you get to get the things you need but at tenderly we are building we are building it all the all the human readable stuff on top of it so we take this knowledge and convert it into something more readable so definitely you can do that I mean we you can use the low-level information here to get more things than just when something failed you know anyone else where do you get the artifacts from so as far as I understand you're writing the common line tool and it's a proxy and you cannot travel to that so as far as I know truffle artifacts don't have exactly enough information to do that because source maps map back to file indices and file and this is you need the list of files which travel artifacts don't have so you kind of need to recompile all the contracts well we use we use just the truffle build artifacts because I believe in the in the truffle configuration you only have one file so it's concatenated the source should be at least concatenated for each contract that you deploy the artifact for that contract contains the source the source maps and everything you need so you just use that source with that source map and it should have everything you need to we don't we don't need to recompile anything so well the proxy yeah you you get to the proxies is a tool which you can put in front of your node so you're doing local development in case of anybody else doesn't know you do local development you put tenderly proxy in front of it and each time a transaction fails for example in truffle test instead of invalid opcode you would get something like the last slide here we got so you just what we do is listen to all connections as if as if we are a node for everything we're not interested in we just pass to know directly and return it back and we check if the if the transaction failed in case of send transaction and to do our work from there no I believe call failures not at the moment but it can be virtual fitted inside yeah this is the question was is this only for solidity yes yeah so what what you can use is right now is the CLI tool but if that's more involved then you would potentially like we are building a dashboard which you can add your contracts there and we can automatically list out all the failures that that you are that you had on the contract and when we released it for the conference so you can check it out anyone else I think that's it [Applause]
Automatic transcript — names and jargon may be misspelled.