 And so today I'm going to present you Source E-Verify, which is a source level formal verification tool for Solidity. I'm actually a PhD student at the Budapest University of Technology and Economics, but this is a work that I've been doing together with some people from SRI International. So coming back to verification, there's a wide selection of verification tools available for Solidity and Ethereum smart contracts, ranging from testing tools and fuzzers, all the way to static analyzers, symbolic executors, and also formal verification tools, like you've seen, Ksolidity, there's also going to be some more talks on this topic later. And these are all pretty good tools, I would say, and they all have their strengths and application areas. And with today's talk, I would like to convince you that our tool, Source E-Verify, also has its place in this landscape, and it can also provide you some useful features that other tools might not. So let me start right away with a quick demo on what you should expect. So here's a really basic contract. It has two state variables, X and Y, which should always be the same. The constructor sets them to a starting value, and then there's an add function, which will just increment both of them with the same value. So basically, when you say that these two values should always be the same, except of course, during some intermediate steps within a transaction, basically by this statement, you are formulating a so-called invariant over the contract. And invariants basically must hold before and after every transaction. So roughly speaking, before and after the execution of every public function. Now with Source E-Verify, you can actually express this as in code annotation. You can say, we use this special notice doc tag. You can say invariant X equals to Y. So then you can just run Source E-Verify on this example, and it will tell you that they found no errors, both the constructor and both add satisfy the specs. But for example, if you mess something up, you forget to initialize value, or you do some wrong arithmetic operations, then we can report that there are errors, your invariant is not satisfied by for example, the constructor or for example, by this add function. So as you've seen, Source E-Verify takes smart contracts annotated with these specification exceptions, for example, invariants, but I will also show you many more in today's talk. And we are using our extended version of the compiler, from which we reuse parsing, reference dissolving, and type checking in order to build an AST. And then we traverse this AST and translate it into a boogie program, which has a formal semantics. And basically by doing this translation, this is how we give a formal meaning to the Solidity language, on which we actually spent a lot of time, as also highlighted by the previous talks, there are many corner cases and interesting stuff, especially going around with the storage and memory, reference types and things like that. I will have some links at the end of my presentation to some of these papers. So then this boogie program can be passed to the boogie verifier, which is a modular program verifier. It was originally built for object-oriented programs, but since smart contracts are somewhat similar to classes, boogie is also a good match for smart contracts. And what boogie does is it checks the program by discharging these verification conditions to logic solvers, SMT solvers, which have a sound mathematical basis, and they support various theories, such as arithmetic, arrays, data types, and so on. And these solvers can return proofs or refutations, so boogie can tell if the boogie program is correct or not. And of course, in order to be useful for the developers, we back annotate these results to the original contract as you've seen it in my demo. So now we say we perform formal verification. It is important to note that we prove functional correctness with respect to the specification that you wrote. So if you don't have any specs or you have some problems in your specs, of course then the results of the formal verification cannot be trusted, but that is how these things usually work in common. And we are also not targeting these common vulnerability patterns besides some examples that I will present you, like overflows and reinterancy. There are many other tools for that. Our focus is really on these functional specifications. And these specifications can be implicit coming from the code, like for example, checking for failing assertions or checking for overflows. But as I presented you, our main goal is to provide explicit annotation possibilities, like for example, invariance and the other ones that I will present you in the moment. And basically, SOSI verified performs modular verification, which means that functions are verified independently from each other to give a better performance and scalability. And basically each function will have a precondition and the post condition, which might come of course from invariance, like for example, in the example, we have a precondition that X should be equal to Y and then we execute the body and the post condition should hold. So basically this is what we want to check given the precondition does executing the body, guarantee the post condition. And this can be done or this is done by translating the specs and the function to SMT formulas by Boogie and feeding it to solvers. This is what Boogie does in the background. So if we start with the same X and Y and then we increment both of them with one, then can we prove that they are afterwards the same? And this is what SMT solvers can basically prove. And if interestingly, if there are other functions that call each other, like for example, here's a function G, which has its own pre and post conditions and it can call F. Then in order to be scalable and efficient, basically we replace, we substitute the call with the specs, which basically act like a summary of what the function is doing. So just speaking a little bit about these implicit and explicit annotations, you can see a similar contract here, which has X and Y and there's a function to add some integer which will just call another function to add it to X and then in a loop it will increment Y just for illustrative purposes. It does some of this stuff. And Solidity provides some of these implicit specs, like for example, you can write require and assert statements, but in order to give more flexibility, we developed our annotation language to explicitly provide specs. And I will tell you or introduce you all these in a demo. For now I just want to quickly summarize what are the possibilities. So of course we support pre and post conditions. You already saw contract level invariants, like X should always be equal to Y. We also support loop invariants. This is required because of this modular and efficient reasoning. These are basically formulas that should hold on entry and on every iteration and on exit for the loop. But we also support fine grained access control. We can, for example, specify that the function should only modify state variable X if a given condition holds. We are also working on specifying events. If I have some time and you're interested, I can show you some examples. And these annotations are essentially side effect free Solidity expressions that are given in code. So this way you don't have to learn a new language to write specs. And also the specs are not separated from the code. They are strongly tied together. And we also have some extra elements to be more flexible. For example, we can express the sum of the collections. I will show you some examples. You can refer to previous values of variables. And we are also experimenting with using quantifiers with which you can express properties like every element of an array is non-negative. Or for example, this array is sorted and this kind of stuff. So let me get back now to my demo to illustrate these features. Also in the meantime, if you have questions, feel free to ask. I will provide a link also to these examples and to the slides. So let's see now a little bit more complex example. This is a typical token. It's a fixed cap token. It has some total amount. And it has a mapping that keeps track of the balance of each user by mapping the address of the user to his or her balance, which is an integer. There's a constructor which will just set, which will put all the tokens to the creator. And there's a typical transfer function, which does some checks to make sure that the transfer can happen. And then it does the transfer by changing these values. So for these kinds of tokens, it's pretty obvious that you would like to have some high-level functional properties. For example, with the fixed cap tokens, it makes sense to specify that the total amount equals to the sum of individual balances. This is actually an invariant that it must always hold for the token. And you can express it in a way that saying an invariant, the total should be equal to verifier sum you int. This is a bit ugly. It's something that we are working on it. But for now, you can express it with this special function so that we don't have any collisions with other already existing functions balances. So the total amount should be equal to the balances. So now you can run saucy verify on this example. And it will tell you that, yes, your contract is correct. But for example, if you mess up something, it can tell you that the invariant is broken. However, this is alone not enough. Like for example, I can just swap the minus and the plus. So the transfer still takes place. And actually the sum of the tokens will stay constant. So the verifier will say that this is correct modulo the specs that you wrote because it still holds. In order to verify these kind of properties, we have to give some extra conditions. We actually have to state what a transfer does. And what the transfer does is basically you want to make sure that you have a post condition that after the transfer, the balance of the message sender should be equal to the old value of the sender minus the amount that was transferred. And the same or a similar should hold for the receiver. Let me just treat with that. So the balance of the receiver equals to the old balance plus the amount. So now if you run the tool on this example, which is still swept, so it's wrong. Now it will, oops, I made a typo. It's not balance, but balances. This is the benefit of reusing the compiler. And I guess I also have to fix it here. But now, so see, verify, we'll basically report that this condition does not hold. So then if you actually fix it, you make the transfer in the correct direction, then we will be able to prove that your specs hold. OK, so this is one example on why these specs are important. So let me show you, again, a little bit more complex example. This is the same token. It's just that now I'm using this batch transfer function, which can transfer some amount of some value of tokens to an array of receivers. It does some checks on the length of the receivers. It calculates the total amount. It checks that the sender has this amount. And then it subtracts this amount from the sender. Then it uses a loop to transfer this amount to each receiver step by step. And for most of the operations, it is actually using this SafeMath library, like for example, the subtraction and for the addition. But there was this famous bug where developers forget to use it for multiplication, which could result in a potential overflow and basically a generation of tokens out of thin air. So if you run, so as to verify on this example, maybe I will just move it a little bit so that it's visible. You have to turn on overflow checking that you can do with the special flag. And it will tell you that there's a potential overflow. But if you fix it, you are using multiple from SafeMath, then it's no longer reporting any false overflow alarms, which many of the tools actually tend to do. But just because there's no overflow, it does not necessarily mean that the functional properties are met. So now we can, for example, just put back our invariant that the total number of tokens should be equal to the sum of the individual balances. And we might want to try to prove that this will fail because we have a loop. And in this case, the verifier actually needs some loop invariant that must hold for the loop. This is required because of this verification approach. And one invariant that we need is that the loop count is always less than equal to the receiver's length, which it can be equal because when it exits the loop, it's equal. And we need also the contract level invariant, but a slightly generalized version. So when we are doing the loop, the total amount of tokens equals to the sum of the balances. But since we first subtracted the amount and we have not made all transfers yet, we have to add those transfers that we have not made yet. So we still have length minus i transfers to make. So this should be added. And with this invariant, now we can verify this example. And this is an interesting example because it's pretty complex from a verification point of view. There's a reasoning going on over, like bit precise reasoning going on over 256 bits. There's invariants. There's also some non-linearity. So this is a pretty nice example of what we can achieve in terms of performance. Let me show you another classic example. Do we have a question here? Yes, sure. Does it also work if you use the symbolic multiplication instead of the safe multiplication? So does it find the error? Reface, so does it find the error if you use the asterisk for multiplication but you do not turn on overflow checking? So this is a good question. If you do not turn on overflow checking, then it will not find the issue because essentially in this case your spec holds because if there's an overflow in the balances, then basically there will also be an overflow in the sum of the balances. So because we have this symmetry, there are two overflows. They will cancel each other. So yes, really good question. And this also highlights why you should pay attention on what you are actually verifying and what are your specs and what's your configuration. Yeah, thanks. Can I also ask some stuff or...? Yeah, sure. Okay, so yeah, thanks for showing this. Also verify this is really nice. And I think we're doing very similar things as everybody talked with Dayan. So I have actually multiple questions so maybe we check them offline. But one question that I wanted to ask now is for the loop invariant and also maybe for the contract invariants, do you try to verify them as well or do you assume them to hold? The invariants, we are verifying them. So you have to manually write them but if you write them, then basically we are essentially verifying them. So... Also the loop invariant. Yes, also for the loop invariant. So basically we will check whether it holds on entry and then we will use an inductive proof which says that like, okay, if it holds on entry, then let's check if for every iteration, if we assume them before the iteration and then we execute one step, can we prove that it will also hold after the iteration? So it's basically an inductive proof. For the contract level invariants, we do the same. We prove that the constructor ensures it. And afterwards for every function, we will assume in the beginning and assert in the end. Basically that's the main idea. And for these functions that you call like underscore and score, verify something because you didn't want to potentially have conflicts. One thing that I've been doing to get an interpret function is basically to write an extra interface with names of functions like with unimplemented functions that I want to use as an interpret functions. Yes, that's a... So if you do that, you can also get like a nice AST for the function and the function call. Yes, that's a good point. I've also seen other verifiers doing that. But I think our main decision, I mean, we were considering this method, but our argument against that was that, so in that case, I think you can, you will have to include some other source file in your contract. So you have to actually modify your source code a little bit. And what we wanted to actually do is to like have the original source code completely unmodified and just include everything in comments. So basically when you would compile this contract with the original compiler, it would still compile and produce the same binary or AST output without these verification specifications. So I guess both methods have their advantage and go back. I can pause now if people have more questions, but otherwise I still have more questions. Okay, maybe I will just continue for a while and then in the end we can discuss or we can also create a breakout room and take some of this stuff offline. Yes, just a heads up. We have like seven to eight minutes left. Okay, yeah, that's perfect. Okay, so let me just get back to this other classic example, the re-intrancy, which is usually illustrated by these simple wallets where you keep track of balances in terms of ether and you have some deposit function which is payable so that users can deposit, you memorize their balance, and then you have a withdrawal function where the users can specify some amount to be withdrawn. You do some checks and then you use this call function to make the transfer if it fails, you revert, otherwise you deduct the amount. And this is a well-known bug, like for example from the DAO for re-intrancy. And many of the tools actually, like even the linter from VS Code, we'll tell you that you should not use this call because it's dangerous. And here we also have an invariant which says that the sum of the balances should be less than equal to the balance of the contract. It's less than equal because on self-destruct, this contract can receive some funds. So it's a slightly generalized invariant, but also in this case, if you run source, you verify, it can tell you that, yes, this is dangerous. There are errors because when you are making this external call, your invariant does not hold, your contract is in an inconsistent state. However, like what many of the tools tend to do is just like if you have a call function, then that's dangerous and you should not use it at all. But for example, here, if you actually fix the contract and you first make the deduction and then you do the transfer, then if there's even a re-intrancy, it's not an issue anymore because the check will not succeed. So this is actually safe to do. And in this case, if you run the verifier, it will tell you that, yes, basically your contract is fine. Modulo, of course, your specs. So it can check when you make the external call, whether your contract is in a safe state to make this external call. Okay, so just because there's an external call, it does not necessarily have to be dangerous. And let me just get back to the final example for this fine-grained access control. So here's a contract which implements a simple storage. Users can store some data. And for illustrative purposes, let's assume that it is now just a simple integer and we have a flag that indicates whether this data has been set or not. So we have a mapping for entries. We have an owner who will have some special permissions. We have a constructor to set up the owner and we have some other functions. So let's first start with the function for changing the owner. And here what you want to really make sure is that it can first of all, it will first of all not modify any of the data and then it can only modify the owner if it was called by the actual previous owner. And you can specify it with the following tag which says modifies owner if the message sender is the owner. So you can only modify the owner if it is indeed the old owner. Here just for illustrative purposes, I introduced the set data private helper function which will take an entry in storage as a storage pointer and some data and it will set this data and it will also set the flag. So since this is a storage pointer, it can basically modify any of the entries. So we just specify that it can modify the entries. Now you have the add function where a user can set some data by first checking that the data has not yet been set. Then it takes the pointer and calls the set data. So now you can specify it that it modifies entries but only at the address of message sender. So you are not allowed to modify someone else's entry if and only if this has not yet been set. Then you're allowed to do this modification and you can do similarly for update and so on since I'm running out of time, I will not show you that. But what's interesting is that for example, if you specify update, it will call the set data which modifies both the set and the data. There's syntactic assignments going on but since the update will require the entry to be already set, you can specify that it will not actually modify this set field because it's true, it should be true. And when you make the call, you set it to become true. So just because there's some syntactic assignment going on, we can still prove that it's not a real modification. So yeah, but I guess I'm not gonna finish this example. Let me just go back to my slides and wrap things up. You can find these examples in a complete form in the demo repository, you can try them out. So to summarize, I've presented Sourcey Verify, a source level formal verification tool for checking high level functional properties with in-code annotations. We have lots of supported annotations and we are using Boogie, modular verification and SMT solvers to achieve this task. And you can find it online on GitHub, it's open source, we also have a Docker image. You can also take a look at my website or my Twitter and you can find these examples and some papers. So I guess I'll finish now and let me know if it's still time for a few questions or we can also discuss online. I've seen in the Gitter that there's some discussion going on with the tags. Right now we use this notice because it was the easiest. So we could not really introduce our own one like we could not write like for example at invariant or at precondition, but if there will be some support for custom tags as discussing discussion is going on, we could also switch to that. So that would be something interesting for us as well. Okay, thank you so much for your talk. I think we would have time for like one more question if there is any more questions in the room. Yeah, Richard. Yeah, so it looked like when you didn't have the loop invariant for example that Salsy Verify reported an error, right? But I would assume that the SMT server in that case would have run up in like, you know, an unknown. So it's like, it didn't really disprove the claim. It just didn't manage to prove it. Yes, well in this case, I think the actual report that we are getting back from Boogie is that because there's a loop, the loop will basically have all the variables. So in the end, because of the loop, the conditions will not hold. But I think we could possibly detect that there's a loop going on without some invariant. So we could give some hints to the user that like, okay, we are reporting an error, but it might be because there are some loops which are not annotated. Yeah, because it would be interesting to see like when you get an error, I guess what you want is, you know, a counter example. Here's something that violates, right? And when it's an unknown, it's an unknown. We couldn't prove it. So, you know, you need to hit it more. Yes. Boogie doesn't support that or? Boogie has some limited support for generating error traces, but there's actually a different verifier called Corel. It consumes the same Boogie language and that's basically a bounded model checker. So I think what we could essentially do is to first run Boogie to say, to check if it reports an error or it can prove the property. And if there's an error, then we could do a second run. With Corel, which will perform a bounded model checking. But since we already know that there should be an error, it will find a trace. Or if it does not find a trace, then we know that the original error should just be like an unknown result. Okay, thanks. Yes, but yeah, thanks for the question.