 Right, welcome everyone, we're about to kick off. We're gonna be conducting today, for your very eyes, a competition in golf, a round of golf, you might say, that will also be formally verified. So, we're writing smart contracts, and whatever language you prefer, and you'll submit your contract to our CEIs, which will run a suite of formal verification proofs against that byte code. And if you do manage to pass our specs, we have approved the correct contract, then there's a competition for making the most gas-efficient such contract. And in particular, we'll be doing it today within the RC20 since this is the one with the most straightforward specification. So, of course, there's some caveats on what the specification actually looks like, we'll make a little bit of an opinionated choice here, we'll go with that in due time. So, when I said that we'll be doing a competition, I actually meant we'll be doing two competitions at the same time. Namely, there's this EVM Golf competition that I just described, making the most provably correct RC20 implementation. But there's actually a second contest going on behind the scenes, and if you're feeling like the first one might not be too much of a challenge for you, or if you're thinking that formal verification is just a sham, then you can participate in the second competition, which asks you to submit byte code that has some sort of bug in it, but it still passes the specs somehow. If you believe that the specs and the methods that we have created with the formal verification process that has been set up, then you can try to fool it, and you can try to submit byte code that passes those specs, but otherwise faulty in some way. I mean, it's hard to define what there's no more respect to talk about, but I'm sure we'll be able to figure something out. So, you'll be able to compete in both of these competitions, and we'll get to the details soon enough. First, I'm not going to be talking too much about the theory behind what we're doing today because we need to have time to actually do some hacking, but let me just mention that the proving methods that we're using here for this workshop are going to be using the K framework, and we'll be writing our specs and then looking at our specs written in the Act Smart Contract specification language. We'll get into it. Okay, so let's get deeper into the specification. I'll come over to the other side, too. So, sometimes people say that an ERC-20, the ERC-20 specification has a specific sort of specification. It's really not. It's more of an interface that really just tells you what the methods should look like in your contract. It doesn't actually say anything about the behavior, of course. I mean, we have some intuition around what this should be, but there's actually a whole range of different kinds of behavior of different ERC-20 tokens, and it gets kind of annoying when you start dealing with them, and then you have to check whether this particular ERC-20 actually did return a Boolean at the end of its transfer function or what's happening in the case where you're sending to the zero at this and stuff like this. So, in order to actually have the specification, we need to make a choice on all these decisions. And let me present our choices, but before I do that, I'm gonna show you the language in which we're presenting these choices. So, this is our specification format that we have developed in order to be a literate language that should intuitively make sense, uses common mathematical words, and we have created it in such a way that from a small specification like this, we should be able to generate a suite of reachability claims, so proof obligations, essentially, that you can port to right now on the cable, there are more backends that we're planning. And so, let me show you how this works. We have, on the left-hand side, we have the specification in the ACT language, and on the right-hand side, we have a simple transfer function. A simple transfer function for the term contract. So, what we do is, well, actually, let me go back and just say, the main claim of this specification is in this storage header. So, under this storage header, we're saying how the balances of the people involved are being updated. So, we're saying that whatever their initial balances were from valid to valid, they're gonna be updated with a corresponding value as a result of calling the transfer function. Okay, but this doesn't always happen because there are some scenarios where you do call the transfer function and your balances aren't updated, namely, if you don't have enough money to send, or if there's an overflow when you're trying to add the value to the receiver. And so, this should be reflected in the spec. And here it is. We see that there are actually four conditions associated with this particular ACT. And the first three are associated with an if and only if header versus IFF. And that means that in the two specs that are gonna be generated, or the two reachability claims, or two proof obligations that are gonna be generated from this ACT, we are going to assume all of the conditions in the if and only if header. To be true in the past case, and to be false in the failed case. So, what the nature of that claim is, is that it amounts to saying that only if in the case where you are still within range of a Uint256 bit word, then these storage updates should be performed. And when any of these conditions are not met, then we should be in the failed case, and the failed case asserts that everything must revert. No storage updates are to be performed. But there's another type of condition that we can make in an ACT. And that's the if header. And the if header, the conditions in the if header are assumed in both the fail and the past cases. So, obviously this restricts the nature of your claim and makes it slightly weaker by assuming additional assumptions. And so if you have an if header like this, you must have a corresponding next spec that assumes the negation of that if header. So, in this particular spec, we're saying that the caller ID should be different from the two, meaning that the sender should be different from the receiver because we have to treat this case somewhat differently. I mean, actually that would only be referring to a single entry in the storage rather than two separate ones. So, we need to write that spec a little bit differently. And so then during the course of this competition we'll be making, we have a suite of these specs that we'll go over soon enough. And they will generate these proof obligations and your byte code will be associated with that proof obligation and the proof will be run on our CI machine. And after they have been run, if all of your specs are passing if all of the proof obligations have been proven, then we will be taking the execution traces. So, the method by which we're proving things is through a symbolic execution and that gives you a complete exploration of the possible things that could happen through the execution of this method. And through this execution trace, we're able to walk down all of the branches and look at the final result in the gas. The final gas results, so the final gas consumed by this method in each branch. And when we do this gas extraction, we take the maximum values of the worst case scenario calling this function. And this is going to be your value that you're competing with. So, we get a symbolic expression like this. If you know your EVM, then you know that whenever you do a storage update, what actually determines the cost of that has to do with whether the storage that you're updating was zero or whether you're putting it to zero. And so we'll see conditions on this forum when they are the most general, but of course you can take the worst case scenario of this and this is what we'll be using. Okay, so with that, let me just say how this will all go down. As I said, all proofs will be running on our service. You don't actually need to do much yourself. Of course, if you're interested, you can come and chat to us and we can talk about it. But for the purposes of this competition, the only thing that you need to do is to write an implementation, get the bytecode of that implementation, the runtime bytecode. And make a PR to a repo that we'll be showing you soon. And everything that is proved to be correct will be analyzed for gas. Okay, so I think it's time to go over the specs. They can be found at this repo, which is the repo we'll be working with. It's kit-opt.com slash applet slash year C20 dash golf. And there's, as I mentioned, there were some opinionated choices we made while making the spec. Well, the first one, the most important ones that are listed on this page will not be caring about logs, so you can forget about logs for your implementation for the purposes of this contest. Everything must revert if call value is greater than zero. So this is following the convention of both vibrant and solidity. And well, this is actually true for the minor caveat. The storage location, so if you know the way that the EVM deals with the storage under the hood is a simple key value mapping of integers to integers. And so in order to translate the variables that are being used in year C20, like balances and total supply and allowances, you need to place these at a particular location of these integer to integer mapping in the EVM. And there's a convention for how you do this. And the most crucial thing to know here is how do you make sure that the, you need to encode basically the balances and the allowances of mappings in a particular way. And this is a particular hashing scheme that solidity uses in order to make sure that whenever you look up the balance of a particular address, you won't have any collisions with any other address in the balance of mapping. So we can go over those details if you want to. But you can actually also use the vibrant convention, which is extremely similar. It hashes the arguments in the same way, but only in a reverse order. But if you're using solidity or vibrator to write your contracts, this will be, of course, not for you. And it's a final opinionated choice. If the allowance of a particular person with respect to some executor is the max possible event, then transfer from should not decrease the allowance of the spender. Okay, and all the submissions are due at 12.25. Let's go over the specs. Hey, sir. So I'll demo now the specs for you and also how the CI server works and I'll cover in detail everything you need to know to participate. Can you see the screen? Yes. Yep. Is the size okay? I think it's a bit bigger. So first, I want to show you the structure of this repo. Has everyone been able to find it, first of all? So we're looking for this repo and I'd get up to ever hopefully hasn't. And I'm gonna show you first just the structure of this. So we've got, we've got in here a readme and configuration file that you don't need to worry about. And the most important stuff is in the source directory. And here, first thing we have are specs, which is in spec.md. And this is the specs that Martin was referring to. This is written as a markdown file and it's got these specs that will be tangled out of it. You do not need to edit this because this is the part that's given to you for target. But you do need to read these if you want to write limitations that will actually pass to make sure that you understand all the nuances. Hopefully not too many nuances, but there are some. So Martin was already covering how these specs look but I'll maybe just pick another one and quickly go over any other features. But the idea is you want to read these and these interface lines correspond to the familiar DRC-20 interfaces. And we use the solidity where you have to be there in contract ABI for competing interface selectors from the call data, which is, I guess, I assume, but basically what that means is that if you're writing the total supply function, that means that the force for bytes of the call data have to be the shot three of this string. So that has to be something like this. So just keep that in mind in case you were wondering how to write this. Of course, if you're writing in solidity or vibra, then this is done for you. So I think hopefully these are pretty readable and the best way to address any difficulties would be when we get to the practical part if you raise your hand or just say something and we can discuss the meaning of these specs if I'm clear. But I mean, you know, I hope these are pretty readable. So you can see, for example, the convention with the return values here at this last line. The bottom means you return one when this thing is successful. So now I want to show you actually how to run this with your own implementation. So if you go back to the source directory, you'll see some other files. And the most important one for you to edit is this file of data runtime, which contains the runtime bytecode of your submission. And you see that there's already one in here, which is the sample one that's provided. This is coming from a basic solidity implementation, the source of which is available or not. Where is it available? It's in the repo in the main directory. So if you go up a little, yeah, that's okay. So this is like the par that you can compare yourself against in the competition. And it also assists you if you find these specs hard to read then you can simply read the solidity and get a handle on what's going on. Yes, so to be clear, this solidity that's in the root of this git repo passes the specs. So your thing needs to be behaviorally equivalent to this. So this is one way to check. And except for the constructor, which we will not be verifying, you don't need to worry about how this contract is deployed. You don't need to worry about how this data is issued once it's deployed. Right, so going back to this, what you need to do is put your bytecode into this file. So you can delete this file and put your bytecode into it. And I think you're actually allowed to, because you'll see in the repo that this is in the, in the read main, this repo, here's how you do that with cell C. So you're with cell C, which you can also use cell C to output, output UL to consume UL and output UL bytecode. And these are the instructions for Viper maybe you have your own funny tools that you want to try. So it actually, apparently it's allowed to have this header, but it's not necessary. So, you know, just specifically having the hex in here as ASCII is fine. I don't know what it, what passes. How cell C will output it. So now I'll show you how this gets processed. And this is enough for the repo structure. So what's going to happen when you submit a pull request where you've edited this in runtime file is our CI server will automatically pick up the pull request and it's going to run the K-frame work groomer using the specs generated from the root of the act language against your, against your bytecode. And then after that execution is done, you know, here's an example of where the CI is running. When this execution is done, if you get a successful proof of the, all the specs, all the reachability that you were successfully proved, you will, it will actually do a symbolic analysis of the gas of all these execution traces and it will calculate as far as what it said the maximum gas consumed. And then there will be a report produced. So here, once you want to track your progress, you can go to dap.ci slash erc20 hyphen golf. And here for every pull request of this repo, there will be a, which is your entry. As you can see here, the last one was 1042. And if you look on the right-hand side, you'll see that there are 16 specs in total and 16 of them were accepted, and you'll see that this is the correct implementation that's been formed in the verified. And now if you click on this link, well, you'll be able to see an overview of how everything passed, but also, very importantly, you'll see next to every passing behavior. You'll see here on the right, you'll see gas and gas analysis. So I want to show you these. So gas is a bit technical, but this is the K term that we extracted from the symbolic execution that represents the state of the gas at the end of every execution of this. This function, the reason it's been complicated is because there's going to be branching in here and it's going to depend on the state of the call data. But that's a little bit hard to read, but the thing that the submission will look at, that the contest will look at is this gas analysis. So if you click that link, you'll see that we have actually said that. Can you guys see this? So basically, what this is showing is that this balance of function is actually going to consume 530 gas in every case. So this is actually a very simple example. But now if we move on to something a little bit more complicated, like this transfer function, and we look at the gas analysis for that, it's a little bit more complicated. So here you can see the first thing is the tree, so that's actually the gas in all the cases. You can see that it's conditional on the starting balance and the ending balance. And then we also compute the minimum the maximum gas used in your contest entry is going to be the maximum gas used. And we will completely ignore what happens in cases where your execution is supposed to revert. So if someone is over-underflowing or they're non-cruel or something like that, we don't care how much gas it used because the user made an error and we're not trying to try to save any gas. So you can do whatever you want in those cases, providing your revert. So I think this probably gives you everything you need to get started. So we're encouraging you to get started as soon as possible because it will take about 15 to 20 minutes for these executions to run in the CI. We can run a few in parallel, of course, but to increase the chances that your submission executes by the end of the workshop, please submit it as soon as possible and don't worry about getting the absolutely best implementation you can because I think simply getting something that makes some improvements over the validity will already give you a good shot. And the goal is to have fun and learn something and, of course, we can continue playing for the next couple of weeks or months if you want to continue to get it down. So just to make this workflow completely clear what you need to do, I'm going to just quickly make a submission to this thing to show you. So here I'm in the ERC-20 Golf repo and let's say I want to make an optimization to the bytecode. And in particular, I'm going to use a tool. I'm going to use a tool that's very nice called SED which stands for Solidity Enhanced Diploma. And what's really nice about it is it actually comes with most unit systems. And Solidity actually uses this to implement these compiler optimizations. In fact, most of the compilers have mentioned this this is the state of the art way to work with optimized bytecode. And I'm going to apply a trick. The syntax is a little bit arcane but it's a really powerful tool. So, for example, if you do a push zero in the EVM what that does is it puts zero on the stack and that costs you three gas. But in this particular case, Pismaric we're in this non-payable regime where he's not supposed to set any value and otherwise it's going to revert. We can actually make sure that most of the time the call value is going to be zero but the call value opcode only costs two gas. So we can actually, every time we need to push zero which would be quite a few times because that's quite a useful number to have. We can actually replace every occurrence of push zero with call value opcode which I'm going to look up here in my little cheat sheet is actually 34 in hex. So if we do this to the fight code that should actually implement this one of the pretty sophisticated compiler optimization. So now you'll see that we've actually, you know, we actually changed the fight code in some way. And I can actually, I'm actually going to commit this and make it pluralized. So now you just go up here and now of course you'll make the pull request on your own fork or whatever. So once I click create full request here it's going to get hopefully picked up by the CI straight away and then we can start verifying and see how much gas we save from implementing this solidity enhanced deployment optimization. Great, it's already running. And if I go to this website which again is dap.ci slash erc20-golf which is a nice dashboard for viewing these builds. You'll see that there's a new one. There's a new one at the top because he's running 16. So none of them have been accepted yet. It's going to take a little while. Form of verification is expensive and slow. And then you'll, but you'll see these, you'll see these gas analysis coming in as these as these groups happen in the next, you know, two minutes probably the first one will come in and also coming in so you can see how you're doing. And you know, hopefully it'll take under around 15 minutes to complete all of them. And then this will be the first, it'll be the first mission and I challenge you to see if you can, if you can beat this one using any tools of your choice. You know, even in an easy way places start is to apply some of these manual kinds of bytecode optimizations. You can also try writing sol-c with the optimized flag because the included submission was not compiled with that. So that should already give you some, some pretty good savings. And then of course, you know, rain and raw Indiana or Yule or whatever, whatever you like will probably give you even more. So I think that's it. And I think it's a good time to start working. And please, this is a little workshop. The idea is you're supposed to ask questions and we might help you understand the spec language or this tooling. So please, please don't hesitate to engage with us and your neighbors. Are there any immediate questions? Yes. Is there a gas at the time function starts or is it a dispatch or all that? Yeah, the dispatch and all that is included. None of the deployment is included. So, you know, the length of the bytecode doesn't matter, but everything that happens from PC counter one until a return is accounted for. You've got to assume that the value is four. Yeah, so exactly, like storage could be anything. But I mean, the storage locations that you'll likely be working with are the ones that come from the mapping of the balances and allowances. So, yeah. Sorry, there's one really important thing that I missed which is to do with storage and thanks for bringing that up. So, I mean, one of the ways that generally you can try to optimize storage is by like packing things together to try to use a storage or something like that. And regardless of whether that would be a good idea for a nearest pony token, that isn't really compatible with our approach here because what we're formally horrifying actually needs to specify exactly where the storage lives firstly and secondly, the semantics of your contract actually change if you start to reduce the size of the integers that you're using because you'll start to overflow in situations where you otherwise wouldn't have. So, that's not really going to work. And in particular, you actually need to look at one more file in this repo potentially depending on how crazy your approach is which is the storage.md, this is the storage specification. And if you're just using Solidity to Viper then all you need to do is follow the order of the storage layout that we gave as an example which is balances total supply and allowances. So, as long as the variables are declared in that order they'll be put in the right places. But formally what's actually going on and if you're using a more low level approach then it's actually mapped out in this file. So, I mean, I hope that probably if you're going to do something like this then you'll already know how Solidity places its variables otherwise it's going to be a few things to learn. But for simple variables that are mappings like total supply, they're just going into the slot number that corresponds to the starting from zero the order that the variable is declared. So, total supply, for example, it lives in slot one. So, if you want to store the total supply you just do s store one in the supply. While mappings like balances and the 2D mappings like allowances are a bit more complicated they're computed using hashes and there's actually two different conventions as Martin said, they're Solidity and Viper and they hash things in the opposite order and by default this file is going to be configured to use a Solidity order. So, this is why it says Solidity here. And if I wanted to use Viper instead what I would do is I would, you need to actually replace this with Viper and then we're going to hash things in the opposite order. For the details of how it's actually hashed I think the best thing would be that if you're interested in experimenting with this is that you raise your hand and one of us could come and show you to make sure that it's correct just because maybe most people aren't interested in that it's not worth spending five minutes explaining it right now. Allowances like, yeah, I think the easiest way to remember source destination, so owner and spender is also correct. Okay, so then let's get started. If you have a question please raise your hand or just ask one interesting piece of information. So, one that I tried with the set to replace six zero zero zero with 34 it actually corrupted something and the formal verification just came back negative, very bad, very bad results. Don't try this one, you will be wasting a time. So, I don't think this is such a success for formal verification. Maybe a bad day for a set. Did it all of them fail or? No, actually just one. Well, the way we set it up is the first failure causes it all to stop, so it doesn't waste time, so it looks like the transfer function failed. So, nothing actually passed before that, it all failed pretty quickly. Again, if that's your kind of, if that's your cup of tea, you know another thing to investigate would be to figure out why this optimization is on stage and maybe how to make it safe. I haven't taken, I only took a small break to get this. So, I don't know what I'm supposed to take watch. I can ask for someone to cover. Okay. I should, do you have a slot? Yeah, I'll type in. I could search for my username. RJ, I don't know what's that. 4-1-5? I don't know what I have right there. Uh-huh, mm-hmm. Yeah, I'll be there for the party, mm-hmm. Okay. But you're supposed to take a look at what you're doing at the party, and that's going to be the end of the session. Okay. What? It's not going to be, it's not going to be an hour, it's going to be 30 hours. See, I don't know, it's not going to be 30 hours. I just want to go by. Okay. I'm just going to start with the debt, that's what I'm going to do. Okay. I'm just going to start with the debt, well, we need to, I'm just going to start with the debt, I'm just going to get it to itsleri. I'm just going to keep it as my debt and see what it actually requires. So I just want to start with the debt, OK. So which one's, let me just, minus M4 to the fourth side line. Why you calls, I'm going to, If you have any questions or comments, feel free to leave them in the comments. It's okay to leave them in the comments. It's okay to leave them in the comments. It's okay to leave them in the comments. It's okay to leave them in the comments. We got a new submission which is a solid D-run with an optimizer flag which comes in clocks in at exactly the same amount of gas as the solid D-run without the optimizer flag. So this literally didn't actually run any optimizations here but we do see that the meta data is different so this can change something but it didn't actually change any of the relevance amount of code. Should try with more optimizer runs. The default means you run it as soon as you like to get this 200 times so it probably just tries to reduce the size of your bytecode to reduce the deployment cost. That's how it works. The bytecode is identical to the very last one. The first one. Yeah, I guess the case here. Again, hard to... Efforts.