 of smart contracts and how inductive variants help prove assertions that should be true on the contract level concerning an unbounded number of transactions. So this is all implemented inside of the compiler in the module called S&T Checker, which runs, if you use a program experiment on S&T Checker, and simply run the compiler to the static analysis trying to prove that the assertions you have in your contract are never violated, no matter the number of transactions. So first, I'm gonna start with the demo. It's pretty small, I'm not sure if you guys can read this, but I have a really tiny contract that's similar that thermo-families are related to toy state machines that have a state variable x, a function and that changes x from zero to one, so x starts with zero in the constructor. G changes one back to zero, so you have the state machine that starts with zero and then you can go to one and go back to zero and just keep doing this forever. And then you have this third function called invariant that asserts that x less or equal one, and this function varies public, so whenever you call it, it should be true, otherwise it's gonna be work, right? So the thing is assertions true. So because of the implicit constructor, x is the initialized zero, right? So you will start at zero, you might go to one if you call f, you might go back to zero if you call g after calling f, so it is true, right? Yeah, and this is what the SMT checker is gonna try to prove that the assertion is always true. So if we look at this program as the actual state machine, this is control flow graph. We have the constructor initialized x with zero and then it goes to this block called this artificial node that I call the interface, which is the idle state of the customer contract, right? Doesn't have an active execution, you have to call it. So it stays in this interface until you call a function from the contract, which could be f, g or invariant. So f might change x, goes back to the interface, g is saying an invariant can go to sync error state if the assertion is false. So what the SMT checker is gonna try to do is statically see if we can have a path from the constructor all the way to the error state. And for that, you might actually go through this loop a bunch of times, right? So it's gonna try to see if we can get the constructor to the error state, no matter the number of transactions you might have to go through. So I'm gonna try it. So this is, yeah, this is the code, it's basically the same code. And I'm just gonna run the compiler on it. And it does not say that it's wrong, therefore it's false, sorry, it does not say it's wrong, therefore it's safe. But the compiler also puts a bunch of weird stuff. That still looks ugly, but it's gonna look nice eventually. But as part of it, what it tells us is this thing here, so what this is telling us, this is a contract invariant because it's an invariant on the interface node. So what this is telling us that on the interface node, it's always true for all values that X can have that X is less than two, right? And because we're talking about integers, this means it's the same as X less or equal one, right? Which in this case is the same as our assertion here. But if we change the code slightly here, so I added this function H, but now it's a new rule in our state machine that says, now if X equals seven, make it a hundred. And then I changed the assertion to X less or equal seven. Is the assertion correct? X can have a reason. Exactly, right? So this is a weird local state, but considering the global contract state, it's useless, right? But still we try to prove the assertion anyway. And when we do that, the compiler also says it's safe and it also gives us a bunch of information again about things that are true at certain points in the contract flow graph. And similarly, we can check what's going on with the interface node and it gives us the same invariant again. So before I tried to prove X less or equal one and it gave me X less or equal one, so it learned that. But now I'm trying to prove X less or equal seven and it still gives me X less or equal one, even though I'm trying to prove something else. So both these two properties are invariants of the contract, right? So the compiler proved that they're true, but they have to be different somehow. So what is the difference between X less or equal two and X less or equal seven? You know the answer, it doesn't count? Seven minus two is fine. That's true. So to check the difference, to understand what's fundamentally different, in this case, between X less or equal two and X less or equal seven, we need to analyze each of those invariants with respect to each function separately without considering the rest of the state of the contract. So we analyze each invariant, only looking at the function, each function, without caring as if the other functions don't really exist at all. So if you look in this invariant before body of F and then after as a post-condition for F, does the invariant still hold after F? It does, right? Because even if it changes, it still holds. And same here, right? This also holds, it's less or equal to seven. If you change it to one, it still holds. What about G? This invariant still holds, right? So even if it was one, which would fit the invariant, if you change it to zero, it still holds. And it's the same here for seven. You use it as pre-condition, even if you change it to still holds. But now here's the catch. So here for X less or equal two, for the artificial, useless something that I added, if we execute this function in this, with this invariant, is it still true afterwards? Yes. It is, right? So here you can see that X less or equal two really makes this function useless because it's not really gonna be changed. But with this invariant, we see that the invariant does not hold after the function is locally executed when it is invariant itself as a pre-condition. So what this tells us is basically that X less or equal two is inductive, whereas X less or equal seven is not. And what that means in a higher level is that if you take the invariant and conjoin it with the local behavior of the function alone without caring about the rest, the invariant should still hold after that execution. This is what differentiates an inductive invariant from normal invariant. An inductive will have to be true after the execution as well. So it implies itself with the variables being in the next state. So inductive invariant is, sorry. Actually, we'll go back to the previous slide. Yeah. I have one more. So in this, right, if I see the public variable, I can straight change the variable to maybe seven and then... Oh, but the contract, so... Kind of private. Sorry? Oh, yeah, sorry. Yeah, this is the contract. Okay, so it's not public. Yeah, I mean, yeah, the variable's not public. But even if it's public, you couldn't change it, right? If the variable is public, you've got a public getter, but it can't change it by a getter. You wouldn't need to have it. Can you show the code with the seven in it again? So here you're starting at the invariant X is less than or equal to seven, but how does it learn the two when you're at the actual inductive invariant? Because these are the only ones that hold, right? That really matter. But I just don't see two anywhere in the code here. So where does it come up with two? Oh, with the one here. It's less than two because of integers. It's less or equal to one or less than two, right? I mean, I guess I'm just not understanding is it like a heuristic. It picks up or is it just picking a model or how is it, is Z3 just picking a model that says two is the smallest number I can find that it's true for or how does it come up with two specifically? Because that's inductive one. The others are not inductive. But every big greater than two is not going to be inductive. Okay. I think he's asking a lower level question, which is algorithmically how do I write a solver? Like this thing from this code. No, but the reason the character two. Yeah, but the reason for that is that it looks very inductive invariants. But three would be inductive, too, right? Like if you were to say X less than or equal to two. Three would be inductive, yeah. Yeah, so why doesn't it pick three? I don't know. You're probably right. It probably looks for the strongest one. Okay. Yeah, because three would be weaker than two. Yeah. In the sense that X less than or equal to two would imply it. But there's no algorithm you wrote that does that choose. Oh, no, I didn't write that. So that comes from the baser. Yeah, very good. You have the questions here? Okay. So, loop invariants, sorry, inductive invariants, they're used to summarize the relevant piece of code without really caring about the rest of the code. So you're interested in a single piece of code and you want to summarize that. And you might have information or you might not have information about the rest of the program, right? So you want to summarize that little bit. And this is particularly, and actually classically useful for loops, right? Because loops are the core of the challenges in program verification, right? That's the termination part. That's why you don't really know how to solve a nice way. So we try to not really, just people in general have tried to summarize loops in a way that it can still go on and prove your program. So now I'm going to show a little loop example. Is this assertion correct? It is, right? Yeah, so they're unsigned integers, so this value is zero, so that's safe. But I'm going to run it in a new way. And it tells us it's safe and also a bunch more stuff. And what if we look for the information that it gives us about the loop pattern, we see this. So this is an invariant on the loop pattern. And this is an inductive invariant for the loop. And this says that, this is actually y and this is x. So this is telling me y less or equal x. And here we see the condition is y less than x. So the difference between the two is that y less or equal x is an inductive invariant on the loop. It still holds after the loop is executed. So y less or equal x is the core property of this loop here. And after the loop, the control flow of this program, the condition of the loop has to be false, right? At this point here in the code, this has to be false, otherwise it would still be in the loop, correct? So we have that this is also true at that point, y greater or equal x, which is the negation of this condition here. So if we have these two things together, we can imply that y equals x, right? So deriving an inductive invariant for the loop right away enables us to prove a property that heavily uses the computation of the loop. And that matters in the inductive invariance can also be used to prove recursive programs where you can plug inductive invariant as the inductive hypothesis of the recursive function. But then how exactly does the, how exactly do the inductive invariance help us prove contracts, help us prove contract invariance, so invariance that hold on the contract level of state variables, so invariance that are true before and after the execution of any public function. And here when it's invariance, I actually mean contract invariance in this sense before and after each function and not the classical invariant definition that it has to be true at every program counter. So as I showed in the beginning, you can model the contract as this control flow here containing a loop, right? Where you go to the interface after the constructor and you always go back to the interface after the execution of a public function. And here, the really nice thing about this, this way of modeling it, of this way of seeing it is that we can model each of these transitions from these nodes to born classes. Born classes are further-logic formulas that have a very particular shape. This one, it's an implication where the had or the implied part of the implication is then here gonna be the predicate of the block we're going to where the parameters are the state variables, right? So here X is gonna be the parameter for all my predicates here. And the predicate is true if that, the predicate is true for a certain value, if that block is reachable for that value for X. The rest of the horn plots here on the left side of the implication we have constraints and here I get constraints from the execution of the constructor. And the predicate of the block I'm coming from. Also on the variables that the value that entered that block Yeah, so if there's only one predicate on the left side it's called linear horn plots and if there's more than one it's still a horn plots but it's called nonlinear horn plots and it's a lot harder to solve. And this is just an example of a bunch of wider rules that we generate from encoding this counter-flow graph into horn plots. Not all of them are here. This is just to show some of them but for lack of time I'm not gonna go by each of them we can talk later. Okay, so as I mentioned before the problem that we're trying to solve is can we get from the constructor to the error state? And in the first example that I showed the error state was unreachable and the way it finds out is that all these green nodes form a fixed point where every transition leads to a node that is already in that set. So you never get out of that set and you finally see that error state is not reachable at all. And this is all possible because of this really nice encoding that we can do directly from the counter-flow graph to the horn plots which looks very, very, very similar. And this is only possible because of these two results. So we go all the way from horror logic to existential positive, this fixed point logic which is actually what we're doing but there is a connection between this logic and constraint horn plots which gives us this really nice encoding. But what if now I change my first example from g changing y from one to zero to two now? Is the error state still unreachable or is it reachable? It's reachable. It's reachable, right? Yeah, so I'm just gonna run it quickly. And here it says, yeah, it's reachable, your assertion is wrong. X can also be two and what it also tells us and it's gonna look better eventually but which is really important is this thing is here. This is telling us backwards which transactions or which loop iterations led to the tool. And this is just encoding of that thing. So, but this is telling us that we can call invariant and make the error reachable but because axis two, there was a transaction that made x two. Before that there was a transaction that made x one and before that there was a transaction that made x zero. And if we look back in the graph, so this would be the first one. So we deploy the contract, right? So axis zero, this is the flow that we go through from constructor to interface. The next one we call function half which then changes x from one to zero and we're back at the interface. The next one g changes it to two. Now we finally call function invariant and reach the error where the sequence here is deployment half g and invariant. So this is how the S&T checkered those things and in the back end it uses these tools called foreign solvers which is similar to similar anyway to S&T solvers it also uses S&T solvers and foreign solvers basically they take all these rules that I wrote and a reachability query so you say can I get to predicate error and it tells you yes or no. And the way these foreign solvers do this is you can use predicate instruction all these techniques here but the one we use is actually a tool called spacer that does S&T based on bottom row checking with a technique called PDR meaning property direct reachability. What it does is from the error state it tries to does a backward reachability check trying to get all the way to the constructor which is the only fact it's the only predicate that is not actually implied by any other predicate. And it does that by generating a bunch of quantifier free S&T queries to invoke an S&T solver and using interpolation as a method for abstraction to find predecessors in the state doing the backwards of reachability and generate new lemmas and invariants. So this is implemented in this linear compiler in the module called S&T checkered. By now there's a lot of support to the language there's a few things missing but it can already verify a lot of things. And this module, this part of it the horn based algorithms can already find bugs or proven variants for multi-transaction properties so multi-transaction safety properties but there's still things missing. So what's next? Function calls are really important and we wanted to do that by creating what we call function summaries. So you create this function summaries and then you can assert that that summary is true for a certain set of variables. And this can also be nice to show that there's no changes in the state of the caller contract. So this is of course related to re-entracy. Synthesis of external functions that don't have access to the code might also be nice by generating kind of examples. This all would lead to the possibility of re-flying really complex contracts and setups when you have multi-contract, multi-transaction properties that might be really hard to find otherwise and maybe eventually model the entire state of the blockchain. Another thing is showing nicely looking other examples in the variants. So I showed all the output from the compiler so that's of course I had to change some of the internal into output, all that stuff that's not supposed to show all that that way. So it has to be nicer. Better usability in terms of right now, the only way to use this in the checker is actually compiler to compiler yourself with C3 or CVC4 linked to it. But we are working on having it enabled by SolcJS and the JavaScript version which I guess is what most people use by the JavaScript frameworks. And we are also working with other people from consistent verification and other projects on creating a very, very simple specification, formal specification language and we welcome anyone and everyone to be part of creating this language. It's weird discussing things in this repo so feel free to come in and give ideas. So finally, to conclude my talk, SSEMT solvers are very powerful and very fast at least we try to sell them that way. Eventually, they'll stop me that fast because they try too hard. The technique we use here is PDR. It's an unbounded model checking technique to solve the important plot that model our control flow graph and the goal we have here is to prove that safety properties are sound even when you consider an unbounded number of transactions. This is all in the solution compiler. You get it for free, simply by using the program experimental safety checker but you need to have assertions. So it's a property directly which ability as I mentioned before, so it needs to have a property to verify. It's not going to tell you random things just out of the code itself. You need to have safety properties in the program. And finally, the contract and the variance that we generate and without put from the compiler can also help verification of byte code afterwards which is a part of it. Thank you everyone. Yeah, yeah. Okay. So only asserts work or also requires? So requires are used as assumptions. Okay. And so right now this only works at the level of a single contract in Europe where we extend the multi-contract interaction. Yes. I would have a goal also to like put assertions in for that or... Well if you have assertions in the first contract and you're calling a second contract, the difference is that the second contract might do things you're not really done with and know what it's doing or sometimes you do. And there might be re-entrancy things. It changes the state of the first contract and then would affect the property that you want to prove afterwards. So yeah, this is how it can change. But by now it's mostly implementation effort. Just putting it in the code. Okay, thanks everyone.