 of NoviGlabs where we build this tool called Bidler. It's a smart contact development task runner and we recently launched Bidler EVM and it's a test development network, I mean a development network which has this very distinct feature that whenever your transactions are all phased it generates a soliti stack trace. And this presentation is going to be about how we do that. But going back to the beginning, let's talk a little bit about how Soliti Divine has evolved. For me it started in 2017. I was a Bitcoin maximalist or recovery maximalist back then and one of my friends Manu convinced me to join them to build their first app and I knew nothing about Ethereum but I began reading about it got super excited again about crypto and started building this tab. And I wasn't in charge of the smart contacts so I knew nothing about Soliti just doing some frontend stuff and the day came where I finally had the smart contacts to integrate them. And the first thing I tried to do was the most basic thing just send it to the contract from my UI and I was super happy about that but all I got was in Bali Doko. And what the fuck was going on? In Bali Doko, I was sure that I was using the right compiler. How can I have it in Bali Doko? I wasn't doing anything with Doko so I wasn't modifying the contract just combine, execute in Bali Doko. No sense. So I must have spent at least one day trying to figure out what was going on and then my friend Manu again came and told me oh, in Bali Doko, there's a new keyboard now. It's payable. If you don't have payable into your smart contacts, you can't send it to it. And he was like, man, I check every single line of the documentation, every tutorial, everything. And he was like, okay, man, it's the linear, it's the linear of technology. That's why they call it like that, but it will eventually get better. And ever since then, this is how I felt whenever I tried to touch Soliti. I mean, with that, I developed my own techniques for debugging, like this one, certainly console log between every single line of JavaScript and at least like that I could figure out which transaction was failing. Or this one, just commenting random chunks of Soliti and rerun everything until I eventually figure out what was going on. But I keep thinking about what Manu told me that it will eventually get better and it was a little better. We got real reasons that are super useful, but I don't know, we weren't comfortable enough with the speed of improvement so we started working on our development tool. And last week, we launched what we call integrated JavaScript and Soliti exceptions. These are just native JavaScript exceptions that combine both your JavaScript stack trace up until the moment that you call it into your contract and on top of that, you get the entire Soliti stack trace. So in a single trace, you get the entire picture of why your test is failing. You get like up until which point in your test it failed and then you get where in your contract it failed. And also it recognizes a bunch of common errors in Soliti like calling a function that doesn't exist and it gives you a precise error message on those cases. Just revert in without reason, it takes you, you are trying to call a function that doesn't exist. So how did we do that? Before going into that, let's talk a little bit about how stack traces work on a normal environment. So let's say that you are running a C++ program, it's just a binary. Whenever you have a data structure that everyone knows the stack is called stack, just stack. And whenever you call into a function, a new stack frame is created that is just its local variables with a bunch of metadata. And whenever you return from a function, a stack frame is popped from the stack. This program needs to generate a new program extension. It will just start the process that it's called stack and rolling. It's popping up stack frames one by one until it gets into a narrow handle. And I know that Super Easy, it's the same program that is responsible for doing the stack frame, so some CC. But the thing is that in Soliti, in the AVM in general, we don't have a code stack. Why? I have no idea, but it's that way. There are some people proposing an EIP to have an actual code stack in the AVM. I don't know who they are, and I don't know the state of the EIP, but I think it's a super valuable effort. I hope that keeps them there. And the other problem is gas. So in C++ or for whichever language, you can generate extra code or extra metadata just to do things like stack traces. But in Ethereum, you pay every single local that you execute. So Soliti is kind of forced to generate as small a vital as possible. So things like metadata for stack trace doesn't seem to belong there. So that forced us to generate the stack trace externally. You just, your only hope is to be an external observer of the AVM and somehow map the AVM execution into the Soliti sources. And what does this mean? That you trace, you execute your contacts, trace the AVM. Trace in this case is just get a list of upcodes that you execute. And then you have to do lots and lots of things to reconstruct the Soliti semantics. So our first challenge was how to get from upcodes to Soliti. When you compile a smart contact, you get a ton of output. Between those things, there are bite codes. So bite codes are just a list of instructions, not much. And luckily for each bite code, you also have a source map. And a source map is an option that takes you a little bit about the bite code. It's just got an offset length of 5 that maps into a chunk of a 5 that generated that output. And then it has this runtime thing that we'll talk about that later. But okay, this is super useful and without this we wouldn't have done nothing. But the problem is that it's super low level. We need to have a more semantic model of the thing. So from a bite code we want to be able to get which contract it belongs to, all of their functions, if they are payable, external, internal, their selectors, everything. Which source were they defined on? I don't know, I think this doesn't look like hard. But this was probably one of the most challenging things. You get the output of the compiler and the Solidity input. And then you have to recreate lots of logic from the compiler just to mimic what it does to make sure that you are actually creating a model that maps what Solidity uses. So for example, there is a selector of a function, some CC. But when you start to take into consideration all the different features from Solidity, I assure you that computing selectors is super hard. But okay, so if we have this model, the next thing is to reconstruct the cost tag. And this is the easiest part. You don't have a cost tag in the EVM, but you have an EVM and you can create your own cost tag. It's just a stack. So when you execute a smart contact, you save the list of code that you are executing. That's an EVM phrase. And you inspect by byte by byte iterating. And whenever you find a sham, here you pay attention to that sham type thing that the source map gives you. And what's in there is whether if the sham was a sham into a function, a return, or an internal sham like an EVM. So it's easy. If your sham is calling into a function, you push that function into your stack. If your sham is a return, you pop a function. And that's it. I mean, some CC, there's some complications because there's things like modifiers that are in actual functions, but still we wanted to show them on our stack traces. So we have some special cases for things like that just to, I don't know, make the life of developers easier. Because, I don't know, if you just go into a function that is only owner, you may end up just showing only owner and not which function was called. But apart from that, creating a code stack is pretty easy. So next challenge, external calls. What I just said about the code stack is, in fact, a super simplification because the EVM is a very weird machine. Whenever you have to do an external call that's called DelayCode or all of those, the semantics of that is that you have to create another EVM, an empty EVM, and you execute there the other contract. So, in fact, the price of the EVM is a reclusive thing. So you get a list of outcomes that were executed up until an external call and then you get another price. And then some outcomes continue. And of course, these are the price. These are smart content. You can also have an external call. So it's a reclusive thing by nature. So yes, creating the code stack for each of these traces individually is very easy. But then you have, then you need some logic to combine them because they are just different executions with different stack traces. And how do you combine them? Well, it depends. It depends on what's going to the smart contract do with the external call phase. So most of them, especially if you are doing anything low level, we just forward the error and eventually reverse the transaction. But you have to detect that because some calls just don't forward errors and ignore them. And in the case, and if the error is forward, that means that you have to combine the stack traces like merging them somehow and keep going because maybe you are still on an external call and you may have to merge the thing multiple times. But that's fine. I mean, we had lots of logic to detect those phases, but once it's done, we got a step closer. Another challenge, and this is also a big one, is recognizing which contracts are you running. Because up until now I was assuming that I knew which smart contract I was looking at, but that's not the case when you are running lots of tests with multiple smart contracts. You only have an EVM that executes chunks of bytecode and that bytecode belongs to a smart contract, but figuring out which one is not that easy. For each smart contract there are two different bytecodes generated, one for deploying it and the other one for running it on runtime. I mean, it's the deployed bytecode, and recognizing the deployed bytecode can be very easy, especially if you don't use libraries. It's just an exact match, you just search for it, and that's it. But the problem is that when you use libraries, when you link libraries, you are actually modifying the bytecode. You just have to add an address somewhere in the bytecode. And also when you are deploying a smart contract, you are also modifying the bytecode, because you are appending its parents at the end of it. So the exact match search thing doesn't work. What we did is to create a modified write to search for these bytecodes. It's a kind of tricky thing, but in general terms we created a canonical representation of each bytecode by zeroing out the places where the addresses are going to be placed, and then used that canonical representation to save the bytecode model that we talked about before. And then when you are executing a bytecode in the ABA and you don't know which model it belongs to, you just search it on the drive. If you go to the match, you're done, that's fine. But if you don't, there are two possibilities. It might have got stuck, because the next byte is the beginning of the parents of a constructor, and if that's the case, you can check if that node has a constructor associated and use that. But if not, you are probably stuck because of a library that got linked and you have the normalized version of it associated to the drive. So what we do there is to check all the descendants from that node, figure out which of them used libraries, and try to normalize our bytecode as if it were one of those, and rerun the search, rerun the search. And that works. It can't sum issues about how to implement it, and you can't use things like normal drives, using records, things like that. But after some work, we got this working, and we were happy because we felt that this was the last missing piece to get stuck traces. So we started doing some integration tests, and everything worked great for some time, because then we started doing crazy things with our slide contacts, and we figured out that most of them failed on things that are just not mapped by Solidity. So I know I told you that for every bytecode, for every old code, you get this source map object, and that's true, but some of them mean nothing. They are just, you know, they are just there to keep the indexes but they just have a five minus one, that means this didn't come from Solidity. This was also generated. So we could stop there, because you kind of have a stuck trace, and if up to some point you have a stuck trace, and if your users were actually using reverb, require, and things like that, they have the entire thing. But, given a great developer experience, it means going direct to my to get that better error message or to get that info to your users, just to make things better. So we really compile lots and lots of contracts with weird error conditions, and just look at their bytecode for weeks. I mean, it's a very stressful thing to do, especially at the beginning, but then you get used to it, and you start recognizing the different error patterns that Solidity generates, and once you are pretty certain that you can recognize them by chance, you can codify that in Shavascript and just repeat the same thing and detect them. The thing is that you may overfit a little bit too much on how you recognize the patterns, so you have to create some loosely heuristics. You can't just look at the actual code that Solidity generates, because that may change from version to version, and you have to do things a little bit more flexible. So our heuristics are things like, okay, this smart contract failed in an opcode that's not mapped, and okay, so let's look at the latest map source code, opcode, and things like that. I don't know, maybe each of them work like that, with super loose heuristics, but that also means that they can't go wrong. So what we did was create a whole lot of unit testing for, especially for this part of the thing, actually got it working correctly. So our final challenge, how do we show this info? Because so far it's just a Shavascript data stack chart, or a JSON, and we need to show it to the user. So one of the ideas we have was just to create a mocha reporter. Reporters are just the UI that happens to be shown when your test fails, and that was a nice idea, but it failed a little bit to convert it to mocha, because VDL AVM doesn't just work on mocha, but it just runs everywhere, no branch. And also we don't want to tie VDL to mocha. For example, some guys from changing came to say hi, and thought that they are using, or trying to use VDL AVM with chess, so they need to mocha, would fail like a failure. And the other thing is that we really wanted to show these Solidity stack traces in the context of Shavascript, because before your transaction failed, you probably executed a whole lot of other things, mostly on Shavascript, to set up the smart contact in a particular state that makes it fail. So we wanted to take that into account. Using some not so well known, but very well documented and supported V8 APIs that let you create stack traces as you wish. So we get the stack trace up until the point where you started executing the AVM, you started executing your contract, and combine that with the Solidity stack trace. So that's how we got this result that I showed at the beginning. This is just a Shavascript extension, with a few Shavascript frames, the whole Solidity stack trace, an automatically generated error that was recognized with one of those characteristics. So, how can you use this? You just have to install it there. It has plugins for almost everything, so if you're using Trophy 4, Trophy 5, Waffle, you just install your plugins, create a super simple configuration file, re-run your tests, and you have stack traces. So that's it. Thank you to the people who supported this effort, some people at the EF, and mostly to the few Shiges team, Sina and Holger, who have been super helpful, and to my friends who support me when I was crazy doing this and asking lots of questions.