 All right. Welcome everybody. This is going to be demystifying Ethereum assembly a practical zero-to-one guide So my name is Joshua or JT Riley an EVM smart contract engineer and so a little bit about why You know why I'm here giving this presentation There's a really really big gap between the intermediate solidity developer and the wizards that can do the assembly black magic Right that we see you know in some popular GitHub repos so I kind of went through this the hard way reading through you know as much documentation and and paper and as many papers as I could And hopefully I've condensed this down enough that you know Everybody here can kind of pick this up a little bit faster, or maybe if you already know this You know, maybe this will kind of reinforce or you can learn new things So this workshop is going to be you know pretty interactive So if anybody has questions if I go over something and maybe it's not clear You know feel free to stop me raise your hand ask questions We're gonna get to the bottom of pretty much anything you guys have questions on the day So as we get started Section one obviously understanding the Ethereum virtual machine here is a link tree. It's going to be the link tree slash EVM assembly and so this is going to have some useful links throughout this presentation The most important one You know for you to be able to follow along and kind of play with these things is called EVM codes So I'm going to be Using that as well periodically throughout basically what this is is a fantastic resource. It's got you know Everything you need to know about instructions and then it also has a playground and that's what we're going to be demoing some of these things So we can really visualize and internalize what's going on So first up we're going to talk about the instruction set So the EVM is a stack-based virtual machine with a relatively small instruction set and these instructions can be categorized by one of the following Roughly first is stack instructions. So these are things that have to do with the stack the values that are on the stack and swapping duplicating things like this arithmetic instructions are Addition subtraction pretty basic stuff comparison instructions tend to compare two values and then push Zero if it's false or one if it's true Bitwise instructions has to do with like bit shifting and other really cool stuff Memory instructions are going to be interacting with The memory in the EVM which we'll get into a little bit more on that page and then contextual instructions So these are like reading and writing to the environment to storage and other things like that So first we have stack instructions So stack instructions involve manipulating the position of a value on the stack or values on the stack So push in value basically pushes a value to the top of the stack where in is the bite size of that value Pop will pop a value off the top of the stack Swap in will swap a value on the stack with another and then dupe in will actually duplicate a value based on the index So as an example here, this is mnemonic bite code Basically, it's just a human readable format of what the actual EVM bite code is. So this is at the lowest level so first we have push one right so we're pushing a value of Size one bite the value is going to be one and over here these Orange bits here. This is actually just a comments to kind of represent exactly, you know what the stack should look like after any given instruction This next one it's going to push one and that value of one bite is going to be two So now we have two and one on the stack You can do swap one which will swap We have dupe one which can duplicate the first value on the stack and then from there We just do pop until we run out of values on the stack So if you guys want to open up the EVM codes website, this is EVM code slash playground. You can also find it in the link tree So first we're going to do Basically pushing the number one to the stack right so we push of size one the value is going to be one We'll do the same thing with the number two We can do it with three and then we can start playing with this right so let's say we want to swap The top instruction or sorry that the top stacked item with one. That's two items down from that so we can do swap two And whenever we run this We can basically step through every instruction here So the first one pushes the one to the stack The second pushes the two to the stack you can see the stack represented down here in the bottom right We'll do the same for three and then swap two should swap the number three with the item two items down from it And there we go and it swaps So from stack instructions, we're going to go to arithmetic instructions So of course add we'll add two values sub will subtract two values Mole will multiply two values s mole does the same thing But it treats the number as a signed integer. So what this means is that the number can be represented as positive or negative Same thing with div div is divide and then s div treats them as signed integers Mod will actually do the modulus. So the remainder after division exp does Exponentiation and then we actually have some instructions add mod and mole mod that will consume three items and basically do Addition then modulo and then multiplication then modulo And so here's our arithmetic example. We push a one to the stack We push a two to the stack and then we call the add instruction It consumes the two and the one adds them together and pushes a three to the stack Next we'll push the number two to the stack We'll duplicate it so all right, sorry, we'll duplicate the second item on the stack, right? So now we have three two three and then we'll multiply which will give us three or sorry We'll multiply it'll give us six and so now the stack is six and then three and then we'll divide those which will give us two So hopping over to the playground again. So we'll push two push three multiply and Let's push Let's say five and then add Says we step through this See the two has pushed to the stack then the three Those are multiplied so the two and three are consumed. There's the six We push the five we add it together and that'll give us 11 Now this looks like B because this is actually in hexadecimal representation So instead of going you know one to nine then rolling over it actually goes one to nine Then a to F and then it rolls over to the next So next up we have comparison instructions So what this will do is pop one or two values from the stack perform a comparison and then based on the result It'll either return true or false Those are actually reversed If it pushes true, it's going to be one if it pushes false it's going to be zero So LT will push if the top value is less than the second value SLT of course is with signed integers Greater than or GT does greater than so if the top value is greater than the second value then we push one EQ will compare and see if the top two items on the stack are equal and then is zero will push a true to the Top if sorry, it'll push true if the top value is zero now something to note about is zero You're gonna see this a lot in you know You'll files another like low-level assembly stuff is zero is commonly used as a sort of inverse operator Right, so it's same as the exclamation point in front of a Boolean So as a comparison example, we push one then we push two we'll check equal of course This is going to push false. That's going to be zero And then we can use is zero to invert that if we need to for whatever reason So it'll invert it the true and then at the end we pop there Next up we have bitwise instructions So bitwise instructions pop one or more values from the stack and perform some bitwise operation on them now and Performs a bitwise and on the top two stack values So what this means is that every bit or every one is one or zero within this number if you compare two values The result is only one if both bits are one, right? So if actually I think we'll have a bitwise Example in a second, but basically it's only a one if the two bits are one And or operation will be one if either of the two values are one Xor is exclusive or so basically it will only push a one if one of the two values is one, but not both Not will invert it so whatever the value is it'll invert that if it's zero one if it's one zero and then shr And shl will perform bit shift operations where we can shift the bits left and right So as an example over here on the left there's a little bit of encoding here just showing you You know basically what these numbers look like in binary format and then on the right We have push one push two familiar with this, but the shl operation is Going to shift the number one by two bits right so it's going to move it over to so we can see Here it starts at zero zero zero one, but after it moves over to it's going to be zero one zero zero And then we can do the same thing to shift it right so we push another two to the stack shr is going to shift it back to where it was And then the not instruction as you can see it's going to flip every bit so zero zero zero one becomes one one one zero So memory instructions can read and write to a chunk of memory. Now memory is this Linear data that can be read and written to during the execution of a program. So it's a different place in the stack It's good for storing things like arbitrarily sized values and things like that So M store will store a 32 byte or 256 bit word to memory everything in the EVM operates on these 32 byte words So we can store full 32 byte word. We can actually also store a single byte or eight bit word to memory And we can also M load. So what this does is loads a 32 byte word for memory given some index in that memory So here we're going to push the number one Then the number zero to the stack and what M store is going to do is consume these two and it's going to say at position zero We're going to store the number one and it's going to be padded out to the full 32 bytes and so if we Basically after we've written to memory if we push a zero to the stack or if we push, you know any Any index and memory to the stack we can call M load It's going to consume that index and then return to us the value there So in this case pushing zero and M load is actually going to pull out this one We'll go ahead and do an example for this one Summon Is that better? So we'll push the number one to the stack We'll push one to the stack then we'll push zero to the stack and M store Is going to treat the zero as the index and the one is the value And we want to reach when we want to retrieve this later we can push one. Oh Sorry push zero and then M load and so if we step through this We'll see the stack has a one then a zero M store is going to put in memory that first word is a one And whenever we want to load from this we started index zero and load and it's going to push that value back onto the stack Now getting in the context instructions Specifically the ones that read from the local context and this is not a comprehensive list They're actually quite a few but some of the important ones caller pushes the address that called the current context So if you're familiar with solidity, this is the same thing as message dot sender We have timestamp which pushes the current blocks timestamp again the same thing is blocked out timestamp Then we have other things like static call which can make a read only call to another contract So if there's a function that sits on another contract We can call that through static call and as long as it doesn't write to persistent storage or doesn't update the state of the evm Then we can read from that Call data load can load a chunk of call data into the current context And so what the call data is is basically the data that is sent to your contract To tell you a little bit about what function to execute what arguments it has And things similar to this so call data load can actually pull some of that data for you S load can read a piece of data from persistent storage on the current contract. So contracts can only read their own storage But basically we can read any slot using S load So as a quick context example, we can use the caller instruction and it'll push that message sender to the stack We can for example let's say we want to See if the caller is an owner of a contract right and let's say the owner is stored at slot zero in storage What we can do then is we can use Push zero and then s load and that's going to load from the first slot Whatever data is there in this case. It's our owner and then we can compare them by using equal Right, so this is like the simplest representation of how to check if a contract is owned by somebody Next we have the context instructions that can write And again non comprehensive lots of stuff going on here, but some of the really important ones s store can store data to persistent storage Log in can append data to the current transactions logs where in is the number of special indexed values In the log, right? So if you've again, if you've written solidity you see the events you see sometimes that keyword indexed all of these are actual indexed index topics within this event now something to note here is Insolidity you can only have three indexed arguments in an event But you can actually have up to log four and the reason for this is The very first topic in any log is actually the signature of an event, right? So it's basically just a shot three hash Of this event signature, which we'll get into a little bit more in a moment And then the rest are the index topics that you specify Next is going to be call so it makes a call to an external contract or to external code But this actually can update the global state, right? So there's no restrictions on solely reading or anything like that And then finally have create and create to and so what these can do is actually deploy code to a new address Creating a new contract So it's just a quick example of what you can write for example Maybe we want to store the last time stamp that some specific thing happened, right? So we can use time stamp to push the blocks time stamp to the stack We can push a zero and then s store and what's what that's going to do is store that time stamp at slot zero So as a quick review again, the evm has a fairly simple instruction set most of what we've gone through This is the bulk of what's actually going on under the hood Of course this section didn't cover every instruction, but it serves as a foundational understanding for yule in the following sections and so to the left There's a simple contract that will store the caller's address and persistent storage and then return true To indicate success Right. So stepping through this we have the caller We're pushing slot zero. We're calling s store. So that's going to store the caller the caller address and storage Next we want to store true or one in memory and we can store that in the very first slot So we'll push one which is true. We'll push zero which is a slot in memory We'll call m store which puts that in the memory And then to return we actually have to give it two arguments one is the offset in memory Where we want to return data from and then the second is the word size or the Memory size that we want to return in this case. It's actually just going to be a single 32 byte word So we'll push o x 20 push zero and then return So any questions so far on the instruction set? Right here, yeah, so these are the representation of the instructions here So here, you know, we have like push one push one and store etc Whenever we run this we can actually step through We can step through the instructions and this is just a representation here So we can Right. So memory is here anything that's written to memory goes into here Anything on the stack is going to be here and then persistent storage If any is going to be down there and then return value if any The stack can be 1024 Items and now you'll notice and in solidity you'll get the stack to deep error And actually the reason for this is you can only swap and duplicate items 16 deep Right. So there's like a limit to how many local variables you can have because solidity actually stores these variables on the stack So once it gets beyond that solidity doesn't have like an easy way to access that without losing information So that that's why you see that a lot But yeah, it actually can technically go like way way way deeper So the memory is the frame dependent, right? You know, we'll just go ahead and I'll just go ahead and answer some of these quick questions right now While they're while they're getting a set back up. So you mentioned Like return data, right? Okay, so there are actually special instructions to handle return data You have return data size and return data copy And so what you can do is whenever you actually make a call You're not actually getting all that stuff back so literally kind of does does a little bit of magic for you there But basically what you can do is use the return data size instruction It's going to give you the size of whatever was returned and then you can use that to copy in the memory Yeah, so it's it's not stored in memory per se like it's only stored in memory whenever you explicitly do it. Yeah Of log zero Oh, I guess we have to spec now Yeah, actually I don't want to get too too far ahead of myself because we are going to do some more logging stuff But I will go ahead and let you know so Basically every log instruction Um, it takes you know up to the number of topics which are on the stack And then it actually also takes a memory pointer and size So even if we don't log any topics per se, we can actually just log like a big chunk of memory And so that's that's one way that we can you know push data to that as an anonymous event It should be within the event logs. I'm not exactly sure how the client libraries decode anonymous events Obviously, it's a little um unusual to see those nowadays, but yeah, we can uh, if we have time at the end We can actually probably just break up in a remix See how it works Are we good? Yeah, so Create two one. It's I believe a bit more gas efficient And there's actually a way to deterministically deploy To specific addresses from on chain using create two So it's just kind of a choosing a utility of exactly which one you want to do I hope this wasn't being live streamed. Are we good? Okay, cool. Sorry. I didn't want to go too far and then we have to go back Great, so The hardest part is over That was all the really like in depth stuff that will kind of give you this foundational knowledge for how to write yule Yule thankfully has a much higher level syntax So what yule is is it's a low level language? That can be written in inline solidity or as a standalone language You can write yule files by themselves, but you can also use it as a compilation target So it also acts as an intermediate language that you can compile other high level languages to Built into the language are most evm instructions. These are callable as functions There's basic control flow support and functions Well user defined functions Now you'll notice whenever we start looking at yule code that the stack is largely abstracted away With the exception of a built-in pop function So if there's some value that maybe you don't need that's returned from a function You can just pop that immediately So here we have the syntax overview Now note that the keywords object and code are actually only used in standalone yule files So chances are you're not going to be touching this if you're just writing inline assembly One other thing to note here before we start stepping through yule does have the if statement, but it does not have the else statement So to handle multiple cases we can actually just use a switch And then the for loop functions very similar to what you see in other high level languages Obviously the syntax is a little bit more funky, but it still does the same thing So stepping through starting at the top We have the assignment syntax as soon as we have a colon and equal sign So anything on the right side is assigned to the left side variable If the variable is being newly declared if we're not reassigning then we need to use the keyword let For the function syntax we have the keyword function the name of your function any arguments and then return variables from that And so inside this function you can see The letter c is already declared as a variable implicitly by saying this is a return value And then colon equals will assign the sum of a and b Right, so you can see this is a lot simpler to read than just pushing things and adding and things like this And another thing to note about these internal functions is that the return is implicit Because return itself is actually an instruction that returns to the caller itself. That's not what this is doing This is actually just Manipulating some items leaving an item on the stack and then jumping back from wherever this function was called So here we have the if statement In this case, we're checking if a is equal to zero And if this is true then we can execute some code inside of that in this case. We're just doing a revert Below this we have the switch So the switch we have a value that we want to compare against and then each case is going to have the value that This variable could be right. So case one means if a is equal to one Then we'll handle that Yeah comments are wrong here Case two is if a is equal to two and then default is our fallback, right So if nothing else matches if we need to just do some logic to handle the rest we use default And then finally we have the for loop, right? So first we have let i We're assigning it zero. So this is your i equal zero part in the middle We have that comparison. So it's less than i and a so as long as i is less than a we're going to continue iterating And then next we have i being assigned basically the sum of itself in one, right? So we're basically incrementing it and then inside of there we can iterate and do our looping logic So as a quick comparison to mnemonic bytecode over here on the left That was the code that we wrote a moment ago to You know store the caller in storage Store true in memory and then return that from memory and then over on the right side is how we do it in yule So a lot less a lot less for both And so now we're going to jump into yule in solidity And this is really important because not very many people write standalone assembly files these days Most of the time you're going to be operating within the bounds of a solidity file So you need to understand what standards and abstraction solidity has created So some of these include the call data layout, which is a layout of that data that gets sent to your contract the memory layout The storage layout event logging and errors So call data layout per the application binary interface or abi standard The call data layout is as follows. So first the first four bytes are the selector of a function And basically this is the shot three hash of a function signature And that includes the name and then any argument types within that Next after the first four bytes each argument is padded to 32 bytes no matter what even if it's a un8 It's always up to 32 bytes If an argument is of dynamic size Then the slot where it should be is actually going to hold a pointer To a place and call data where the rest of the data is so if we have let's say a string that's over 32 characters It's not going to fit in a single slot. What we have is a pointer that says okay after everything else Here's where the string starts and it's just going to go and there are other ways to determine, you know, how long the string is So as a quick visual representation here, we have the transfer function, which this comes from the erc20 standard So first up we have the signature. So that's transfer. It has a dress un 256 Now what we're actually hashing here is transfer parentheses address Un 256 that's the only part that we're hashing here So we can see the the hash full hash digest here We're actually going to clip that To the leftmost four bytes and then the actual call data layout of one of these Is going to be those four bytes Followed by an address in this case. It's going to be the target to whom we're sending the erc20 And then the next is going to be the value, right? So even though these you know, like an address is not a full 32 bytes We're still padding it all the way Next is going to be the memory layout. Now, this one is really really important because per the solidity documentation The first four slots of memory are reserved now slot zero and slot OX 20 or slot 32. These are actually scratch space. So you can use these, you know, whenever you're doing things in inline assembly The OX 40 though is the free memory pointer So basically what this does is as memory is expanding Solidity will actually track how big memory is and when you can start adding new things to memory And so what we'll actually do in some of this in some of this yield code Is we'll actually load from that to figure out where we can start storing things in memory So it's really important if this does get overwritten You have to make sure that you have that variable on hand, right? So you can increment it if you store more things in memory, etc, etc And then OX 60 is the zero slot So the zero slot is reserved specifically for Whenever you're allocating new arrays, I believe This is like basically a big big no-no zone anytime people start overwriting this, you know It's not a very good security practice. You have to be very very careful about it So I'm not going to recommend it, but we have seen it before and we'll probably take a look at somebody who does that a little bit later Now dynamically sized arrays will occupy one slot Which holds that pointer to where the actual value is in memory There's another slot that indicates the length And then every every slot after that is reserved for a single element So if you have five elements, even if there's small values, they're going to occupy five full slots Now byte arrays and strings are similar with the exception that their elements are tightly packed and aligned to the left so For strings, it's you're basically using a single byte value to represent a character So in this case on the byte level it is equivalent to byte arrays We're going to pack those as tightly as possible So as a quick memory example We're going to step through this and that's a function. Uh, it's pure. It's going to return Some bytes right and so what we're going to do in here is first load the free memory pointer So again, we're going to load from o x 40 we're going to Declare this or assign this variable data because remember anything that's declared bytes memory Good Zoom in. Oh, yeah. Yeah I thought so It is not letting me zoom in Yeah, sorry, this is not going to let me zoom in here um But i'll go over Basically every lines what we'll step through here. So uh, yeah the data Basically what we're doing is storing that pointer to where the bytes are going to be restoring that Next we're going to store the length Uh, you know the length of the byte array at this pointer So at the free memory pointer, we're going to store bytes length in this case. We're just going to store four bytes It's going to be one one two two three three four four Um, we're going to store that Then in the next slot We're going to well, sorry first. We're going to increment the free memory pointer, right? So we're keeping track of this we're keeping track of how much Um, you know, how much memory is being used here how much memory is being written to so we're going to increment that By 32 bytes or by a single word Then we're going to store At this new location The bytes that are going to be pad They're going to be packed to the left After this we're going to increment the free memory pointer one more time And we're going to store the Basically in the free memory pointer slot. We're going to store this updated free memory pointer, right? So basically as we're incrementing this we're keeping track of where the new free memory is And then once we're finished with all of this we're going to store it At the o x 40 slot Any questions All right, so it's going to I get let's say you append an extra byte to the end of this It's going to store that extra byte at the end It's going to keep it packed and then it's going to increase the It should I haven't tried that we can actually try that as well. We can hop over to remix and try that Um, yeah, I It would make sense if it would right because then it would just get really really confusing, right? Right, right, you're right. Sorry. Thank you storage Yes Right, so in solidity, uh, you do have to specify the length ahead of time. Thank you Right So like the like a like a struct like a memory struct So memory struct so struct actually acts similar to a dynamically sized value So it's actually going to be a pointer to that as well Right, right. So whenever we store So the first one we're storing the bytes length, right? We're storing the number four Even though it's just a single value that's actually occupying the full 32 bytes So we want to increment it by the full 32 Any other questions Great. So next we're going to have storage now storage is actually going to be a few different cases here First we'll start with statically sized variables So persilidity documentation storage layout starts at slot zero The data is stored and the rightmost bytes of that slot Now if the next value can fit into the same slot Obviously, this is determined by type and the maximum value It will actually be right aligned into the same slot so you can actually pack variables into storage You do the same thing with storage structs And then immutable and constant values that you see in the contracts, they're actually not stored in storage at all So basically what these are is they're replaced at compile time with the exception of immutables So if you have an immutable that can only be determined at deploy time whenever it runs to the constructor It'll actually replace every instance of that in the bytecode So as a quick example, we have a contract. It's got Five values in it first is going to be a full uint 256. So we want to occupy an entire 32 byte slot with this Next we have two values of 128 bits. So basically 16 bytes each So these can actually be packed into a single slot and we'll see that they are so b is two it's here in the rightmost bit And c is packed in the next rice rightmost place in that slot And then we have finally two uint eights which are stored here All right, so everything is packed as tightly as possible in storage whenever we're doing this layout Next for dynamically sized variables Again per the documentation and solidity a mapping slot is the hash of the key value Concatenated with the storage slot, right? So let's say for example in a balance mapping in an erc 20 it maps an address to a uint 256 Let's say it's in slot zero what it's going to do is whenever you need to read or write to this We're going to take that address pad it to 32 bytes Can catenate that with another 32 bytes, which is the slot It's going to be zero and then we hash all of that and that's where the actual value is stored Now dynamically sized arrays It stores the current length in whatever slot it occupies And then its elements are stored sequentially starting at the shot three hash of that slot number Bite arrays and strings are stored the same way as other dynamic arrays unless the length is 31 or less If this is the case then what's going to happen is all of that's going to be packed into a single slot Left aligned and then the length of that is going to be packed or is going to be placed at the right most byte And it's going to be 2x the length So looking at this one we have three storage variables. We have a string Array and a mapping So looking to the storage layout first Over to the left. We have six one six one six one six one. These are the four a's And then over on the right. We have eight which is two times the length In the next slot. We're going to store the length of this data value It's going to store one and two right. So what's going to happen here is Whenever we run whenever the code is executed It's going to see that the length is to here at slot one and whenever it wants to access these values It's going to hash slot one And that's going to point down here to o x b 1 etc etc And then we can see here's the first value and here's the second value And then finally we have the mapping it's mapping an address to a un 256 The actual storage slot of a mapping is always zero That doesn't mean that it's not incremented the storage is still incremented But this slot is always going to be zero and then any addresses that are mapped to of course will be That hash right so o x z 9 etc etc So one more thing to mention something that's really important to note Is storage in regards to inheritance. So Solidity uses c3 linearization Um, so basically in the context of storage what what this means is storage slots in a parent contract precede the child contract storage When a child has multiple parents the order of the parent storage is set by the order of inheritance And this process is repeated recursively And storage packing rules are still in play when applicable So here we have a basic inheritance example. We have parent zero. It's going to store a it's going to be one Parent one is going to store b at a value of two and then finally we have child Which is going to inherit parent zero and parent one and it's going to have its own value c and that's going to be three And looking through the storage layout First we have parent zero storage then parent one storage then child storage This is really important to note especially with things like proxy contracts You'll see in some of the open zeppelin implementations these storage paddings and things like this for upgradeability So next up we have event logs. So per the standardization Event logs follow the following rules. They can have up to four topics The first topic is always that hash of the event signature. So it's going to be the event name parentheses and any any types And then non-index topics are logged by storing them in memory and then passing to that log instruction a pointer to where that memory starts And how big that memory is So as an event log example At the top we have a hash and that's going to be that the event signature hash Next we're just going to have you know a and b simple constants now in solidity This first function is how we'll log this right? We say emit the name of the event and then any arguments that go to that event Next we have log in yule. So this is actually what's happening under the hood whenever you do this in solidity. So first we want to store Note that uh b is not indexed. So we want to store that in memory And then a is indexed so we're going to have that you know on the stack right doesn't have to go into memory So first thing we do is we're going to store b. We can just store it at slot zero We're not going to be dealing with memory after this right the function's going to end So we store at slot zero b And then we call the log two instruction and what we're going to pass to it is a memory pointer in this case slot zero of memory the size of memory which is going to be 32 bytes The event hash which is going to be topic one And then the index topic a which is going to be topic two So next we have errors So per the standardization an error consists of a four byte selector and some error data Now solidity actually has two predefined errors This is error and panic But since solidity 0.8.4 developers can actually define custom error types Where you can have names and arguments and it's actually a bit more efficient than the standard Required boolean string the reason for this Is the the solidity defined errors first is panic and it's got a unit 256 So this is things like Divide by zero overflow under flow. That's when the panic comes into play each one has its own code. It's in the documentation And the next is the actual error error This takes a string in memory and you can see So first is a panic code. So we have these four bytes Which is the panic selector and then the next 32 bytes is going to be the actual panic code in this case It's one Then the next one is going to be require Or what is standard with require we pass it a boolean if it's false It's going to throw this error with a string in it now Note this actually takes up a lot more memory because we have the error selector and remember per the standards of Strings we first have to have a pointer to the string and then the actual string itself Right, so that's going to occupy extra data right so Basically because the error selector is only an offset of four bytes that's actually not padded to the full 32 So right after those four bytes, we're going to start putting other data Any other questions? Great. All right. So next we're going to do applied yule plus solidity. So we're actually going to hop over to remix And that's a remix dot ethereum.org And from that link tree earlier, we actually have some boilerplate code here. Make sure this is big enough to see So you might want to just copy paste this from that link if you're going to be following along because You know lots of already pre encoded data and all that kind of stuff. So I'll give you guys a minute to Get that pulled up copy pasted all that good stuff Okay, we'll give it just another second Right Yep, so that's going to have the code it's going to be an adjust but you can just copy paste that Yes, one moment Maybe not i'm going to write it right here So it's going to be the link link tr dot e slash ethereum assembly Hopefully internet doesn't rug pull us here All right We'll go ahead and jump into it So everything here that's predefined This is just for the sake of us not having to go and encode stuff and hexadecimal and all of that You know right in the middle of the workshop. So just looking through these constants really quick We have the length of the name in this case. The name is actually going to be Yule token So the length is going to be nine characters and then in here we have Padded over to the left the the string Next we have the symbol that's going to just be Yule yul all caps And so here we can see the length is three and these are the three characters for that string After this I went ahead and hashed the selectors for Two two custom errors that were defining here one is going to be insufficient balance And the other is going to be insufficient allowance And though insufficient allowance We're actually going to pass an address of the owner and the spender in here And this is going to give better error messages for client libraries and for users So hopping into this first thing we're going to do is we're going to define the name function Now we're going to make this pure we're not storing anything in storage. We're actually just going to return a constant It's going to return a string in memory Now what we're going to do if we remember the standard for storing strings and memory first is going to be the pointer Then the next slot is going to be the length and then the slot after that is going to be the actual data itself So we're going to do three memory writes and then we're going to return from that So first we're going to mstore at slot zero We're going to store OX 20 And that's where the actual data is going to be it's going to be a 32 byte offset Next we're going to store Oh, no Hold on safety Memory pointer We want to load from memory So we load that now This actually should be fine in this case like we know exactly where it is because nothing's been written to memory But it is good practice as often as possible to use this free memory pointer So we're going to load from o x 40 Sure. Yeah, so let's say So, I mean if you just start writing to the zero slot and in this case you're going to occupy three slots That's going to take slot zero o x 20 and o x 40 So it's actually going to overwrite the free memory pointer now in this case We're actually returning from assembly. So we are safe here But the problem is if we're doing some assembly, let's say within you know a function And maybe there's stuff that's happening after that Solidity is going to trust that whatever value is in o x 40 is the right value So if we overwrite this to something strange in this case, you know, it would be The string padded over to the left, which is actually technically a massive number This would actually consume a massive amount of gas and realistically probably revert But yeah, so basically as much as possible we want to use Yeah, basically load whatever is at o x 40 that's going to point normally it starts at o x 80 So that's after the four reserve slots But again for the sake of you know keeping it safe, we're going to do it like this So we in store at the mem pointer We're going to store the length or sorry at the length The o x 20 pointer After this we're going to Store 32 bytes later. We're going to store the actual length So we're going to add the mem pointer And o x 20 so we're increasing you know by a single word size We're going to store the name Next we're going to store at an o x 40 bite offset So You mean like after the first m store No, so this is actually an offset from this value itself So yeah, so the o x 20 offset is just pointing to the actual length itself So we stored the pointer restoring the length and now finally we're actually going to store the data And so what memory should look like right now is starting at the free memory pointer We have the string pointer the string length and then the actual string And so this is going to occupy three slots Which is o x 60. So what we can do is return Starting at the mem pointer We can return o x 60 bytes So very quickly we're going to compile this i'm on solidity 0.8.17 We'll check that the compiler is 0.8.17 Everything's all good. So now we're just going to deploy this to a local virtual machine And here we have the name function Whenever we hit it it's going to return yule token for us All right, so next we have the symbol function It's going to do basically the same thing the only difference being we need to store the symbol length and the symbol data So first we load our mem pointer Then we store in memory at the mem pointer the string pointer at an offset of o x 20 We'll store the symbol length and at an offset of o x 40 Or we'll store the symbol data And then same thing is before it occupies the same number of slots because everything is padded we'll return mem pointer o x 60 Now note in this case, we're actually not Incrementing the free memory pointer and that's because we actually are returning from The assembly itself if we were to break out and continue doing things we would actually want to Actually store, you know, what the new free memory pointer is in this case. We would increment it by o x 60 Next up we're going to do decimals It's going to return a un8 so first Now in this case, we're actually not occupying very much memory at all. We're actually only occupying a single slot So we can't actually just write to the very first slot Right, it's mostly whenever you need to write Three or more slots that you want to actually do it at the memory pointer in this case We're going to do a little bit of a hack there to keep that in the lowest slot So we m store at zero The number 18 Because we're not bad people and use sixes decimals And then from there we're going to return From slot zero in memory We're going to return o x 20 even though it's a un8 It's stored at the right most bytes. So we need to return that full 32 byte word All right Now that we're done with All of that basic boilerplate stuff Let's go ahead and set up a mapping. This is going to be Mapping an address to un 256 And we're going to make this internal in this case because I want us to actually Like do it in assembly and see exactly what's happening Obviously, we can just make this public and make it a lot easier, but We'll say internal underscore balances Next storage slot. We're going to need after that Is going to be the allowances. So we're going to map an address to a nested mapping Which maps an address to a un 256 I'm just going to comment here. Uh, what this is actually mapping to Great All right, so next we're going to do the balance of function It's going to take an address It's going to take an address And we're actually not going to Name this variable because we're going to manually load it from call data So we can see how call data is working, but it is going to take an address It's going to be a public view function because we are going to read from state And it's going to return a un 256 Quick time check Okay, so this is where it's going to get weird. Um, remember we're mapping an address to a balance and per the standard What we need to do is we need to hash The address and then the slot and memory or a slot and storage in this case at zero So we're going to Pad an address And then we're going to put zero and then we're going to hash it But the hashing function actually reads from memory. So we're going to use some memory to do this So first thing we do is we're going to in store actually no We're going to assign this variable for readability. We'll optimize a little bit in a moment So first thing we want to do is get the let's say account And so what we need to do is load the address from call data Now if you remember the first four bytes are the function selector and then the next 32 bytes are The first argument in this case our address. So what we'll do is we'll call data load And we're going to do that at an index of four So it's going to take the next 32 bytes and put that into the account variable Next we're going to store this in memory At slot zero, we're going to store account and then at slot O x 20 or at the 32 byte slot. We're going to store just zero Next we're going to get the hash which is going to be cacac 256 And what this is going to take an assembly now in in You know high-level solidity what this is going to take is like a string or bytes But in low-level yule, it's actually going to take a pointer in memory And the size in memory. So in this case, we're going to start at slot zero and we're going to Consume two different slots right so o x 40 So this should actually be the key to This person's balance. So what we can do from here is we'll say balance And that's going to be s load. Oh, no Balance is a reserved word. Hold on There we go account balance. It's going to be s load at that hash, right? So we're loading from persistent storage this slot Next thing we want to do from here is we want to m store Now at this at this point, we're not actually going to need what's in slot zero and slot. Uh, well 32 slot one Like zero and 32 here. So we can actually just overwrite that with the account balance Okay, and then finally we're going to return And so that's going to start at slot zero and it's going to be o x 20 bytes So this is kind of like a more verbose version so we can see exactly what's going on with variable names and things like that but Let's go ahead and do just a little bit of Optimization and seeing like what what you might actually see in production, right? So account is only used once That's loading here from call data. So we can actually just remove that And put that here, right? So we're Storing in memory at slot zero whatever's in call data starting it for Here we have this hash Instead of declaring a new variable we can actually just load that straight from storage And then instead of declaring an account balance variable. We can actually just replace that as well Right and so now it should be functionally Exactly the same we're storing the first argument in memory at slot zero We're storing the slot number in slot o x 20 And then we're hashing that loading from storage Putting that value in the memory and then returning from memory And so this is the full process of what a balance of function is doing That's the length of memory so far so these these two slots Sorry Exactly, yeah Exactly because it's in the it's in the first storage slot We can do that at zero actually something else we can do here since this is already implicitly zero We could remove that but again for the sake of safety. We'll just leave that for now Return with like just parentheses Right so in this case, it's actually going to give us an error because it actually is Oh, right, right. So like basically if we don't say like return down here Oh Yeah, so in that case, it's actually not going to return anything like if we did this It would actually like load from call data load from storage put it in the memory But it wouldn't actually return anything to the caller Right, so I mean you can see here it gives us a compiler warning Let's see unnamed return variable can remain Unassigned that's actually a weird one. Let's try that It's going to give us a warning, but we're going to try it anyway By the way, this is using remix is a fantastic way to try out these little edge cases If you're ever not certain how something works, this has been an invaluable resource to try these things out So let's go ahead and give it a try Balance of uh, we're going to need an address. We'll just copy this one It returns a zero Okay, so I guess in that case if you don't explicitly return then solidity will Actually insert a return statement for you and it's just going to return nothing at all So call data only loads using call data loaded only loads 32 bytes So in the case of like for example, if you need to figure out what the function selector is right You can only grab the full 32 bytes. So in this case, you'd actually use bit shifting You would want to there's those four leftmost bytes you want to shift them all the way to the right Right it does not wrap around the lobby zeros So just adding this alone shouldn't add any extra op codes. This is just like a abstraction for the high level language But I believe we could actually just access it like this So this and what we were doing a moment ago is actually functionally identical Right. So either we can like manually load from call data or if we name the argument We can actually just put that in there right Yeah, because the the function selector will be the same and then if it's not actually used it shouldn't consume And I mean, of course it'll there will still be a place in the call data for it But if we're not explicitly using that variable, then You know, there's no point in which that's going to be loaded from call data by solidity Um, no, so what this actually does is uh, so the mem pointer tells you obviously where in memory to get the value and then Oh x 60 is the length But what this is actually going to do? In this contract, it's going to finish executing it's going to stop executing And then whoever called this contract, whether it's neo a or Another contract it'll actually return that data. So it'll it'll take that chunk of memory in this case 60 byte or Oh x 60 bytes from free mem pointer and that's actually going to be returned You know into It's it's going to be returned either to the client library like ethers or web 3js Or it's going to be returned to another contract encoded as a string Right, right as soon as we call return it's actually going to stop execution from there So even if we put anything after that as long as it always returns, we'll never go there Each function has its own memory stack when you execute or they share across like if you go You know Right great question. So Anytime that you call a contract it's going to create what's called a context And so this context is going to have a clean stack a clean memory It's going to be able to access the storage at whatever address is there, right and then other contextual information So basically any time a function is called on this everything is a clean slate So it's going to read and write from that same clean slate and then once the function finishes it stops and the memory is gone right, so in this case like They're technically writing to the same sort same storage slots, but they're never executing at the same time So we don't have to worry about any kind of like memory overwrites or anything If you call an internal yes, so if you call an internal function Actually at compile time that this is a little bit confusing about Solidity is that you have like public and internal functions Um public functions explicitly, you know, take some information from from the caller And then it's going to return some information based on that whereas internal functions. There's actually What what it does is it takes a number of items on the stack Let's say your internal function takes three arguments It's going to expect three values on the stack and then it's going to operate on that And if it returns anything it's actually just leaving values on the stack So inside of internal functions whenever you return from that it's not actually Using the return instruction. It's actually just leaving it on the stack and jumping back to where it came from Right, so the first thing that happens During the execution of a contract at least per solidity is we're actually going to Store a free memory pointer. So like the first two slots, you know zero and o x 20 These are always going to start out as zero o x 40 is going to have that free memory pointer and then The next slot after that of course is a zero slot. So we don't putting anything there. We can actually take a look briefly If we compile this And we look at compilation details So you'll notice the first Few op codes here. We're going to push o x 80 Then we're going to push o x 40 And then we're going to m store and what that's doing is storing o x 80 which is the first free memory pointer We're storing that at slot o x 40 and this is the first This is actually like a big tell tell sign that a contract was written in solidity This always happens first every solidity contract starts with you know 6080 6040 five two So So you mean Like when you have multiple internal functions or right? Yeah, exactly. So What what solidity actually does is those first two slots The reason that they're they're kind of reserved is that's actually normally used as a sort of scratch space for hashing So normally you're hashing two values together So that's where we'll put that and then hash that together right so Per the standard of how strings are stored in memory It's always a pointer to the string and then the length of the string and then the actual string itself So in this case, it's kind of a formality But it's really important to have that o x 20 there because whenever we return it the client libraries expect it to be encoded as Actually, no it does that return that we're going to try it So let's call name And what we can do here. This is another great way to look through transactions and find things you might not be sure about Let's Break this out. It's not going to let me make it bigger. Okay Uh, right. So what we'll do right here at the very end. Let's take a look So we have some memory laid out here mostly zeros it looks like Hold on Let's step through this really quick. I really wish this would make me let me make it a little bit bigger here but So here's the state of the stack right in this case. We're going to push The free memory pointer, which is o x 80. We're going to push The length which is o x 60 and what that's going to do is point to slot o x 80 in memory Which is going to be here and it's going to go for o x 60 bytes. So here's the first Here's the second And then here is the third right so Because this actually gets returned with it client libraries are going to use this to actually decode strings So if we actually didn't if we didn't return this properly It would actually throw an error like it wouldn't know how to decode it would just give you a bunch of bytes back, right? So in that case, it is sort of a formality, but it is important You know to make sure that the client libraries know how to decode. I think there was one more question Right Call it internally. Oh, you mean like if I say like this dot name So Right, so we wouldn't want to use the the return instruction inside of an internal function like if it's being called internally Um, I mean you can use that but just know that that's going to stop execution at that point Uh, which is not what you want, right because that that'll actually return back to the caller So if you're trying to like return from an internal function, you're actually just You know jumping back to the main function and then that's where the actual return instruction is going to be In the line 36, yeah, you can store 20 Yes, and that's an option Right, okay, so where you're starting the data Okay, so if you start the data, I don't know the 16 for example Yeah, so what that actually is is it's it's 0x20 from that place in memory Right, uh, so balance. Let's go ahead and put that return back Okay, uh next we're going to do the transfer And this is where it's going to get weird. We're going to have uh, basically some conditional logic in here Remember that overflow and underflow checks only happen in solidity 0.8 and after Um, so basically anything before that and then anything assembly is unchecked Which means if somebody tries to transfer out more than they actually have The evm is not going to stop that from happening It will actually underflow roll back over to the very top And then they're going to have some obscenely massive amount of tokens So we want to make sure to check beforehand That the sender has enough tokens before they transfer out So for the standard, uh, it's going to be a non-payable So we're actually just going to call it public and it's going to return a boolean So first thing we want to do is load the callers balance Right, so what we need to do is hash, um, you know that that information that we did before so let's say Let's No, we're going to mstore one more time So we're going to get the mempointer By loading from memory From the mempointer We're going to store the callers address In this case, we can get that by calling caller with parentheses at the next slot So mempointer plus o x 20 We're going to store the balance slot, which is actually going to be zero Then we're going to hash And let's say caller balance So we hash these two starting at Mempointer with a size of o x 40 And we're going to take that hash and use it to load from storage So now we want to check and make sure that the callers balance is at least As big as the amount now now that we've done the call data loading We're actually just going to do this for the sake of time. We'll say value and Receiver Just to keep things succinct So we want to check and say if less than So if caller balance is less than the value to transfer We're actually going to do some revert logic So we're going to take insufficient balance selector And this actually isn't going to take any argument So all we really need to do is actually just store this one value Again at this point we're reverting. We're about to stop execution So we can basically just you know write to the zero slot. This can't cause any harm So m store at zero the insufficient balance selector that's going to be that four bytes selector padded to 32 bytes And then we're going to revert And revert actually functions the same way as the return instruction So we're going to give it a pointer and a size in memory In this case, we're doing the full o x 20. I guess we could also just do o x o 4 And so that's going to revert with that and this is good because whenever we revert with the selector Client libraries are going to have access to this abi It's going to see that there is you know this error Called insufficient balance and that's the selector and then we can actually do some you know Better error handling on the client side Sometimes you'll see people revert with zero zero and that actually just reverts with nothing and obviously this isn't super helpful So as often as possible, even though it does take a long time and it's annoying make sure to store Proper error information Okay, so from here, we know that the caller balance like if we're at this point in the code We know the caller balance is sufficient So what we'll do is we'll deduct that we'll deduct the value from the caller balance and then we'll add it to the receiver's balance Just one moment. Perfect. Just double checking. Okay, so we add these two together And then we'll say Let's say receiver balance. Oh, right. Right vice versa. Thank you sub Okay, so next we want to get the receiver balance and so we'll do this in a similar way. All right balance. Thank you Sweet. Okay, uh, so to get the receiver balance, we're going to do the same thing except We're actually going to use The receiver as opposed to the caller Again, we're not going to need these two slots of memory anymore. So we can actually just overwrite these and So oh So the receiver balance is going to be what we load from storage Which is going to be the hash Starting at mem pointer and of size o x 40 So this is where we get the receiver balance actually Let's do let's do this caller balance slot because we're going to need this later So let's take this And we're basically just going to create a variable with this right same thing Great. So functionally identical, but now we can actually access the slots a little bit later whenever we need it So now we want to get the new receiver balance. Again, we'll optimize a little bit later New receiver balance is going to be sub Thank you There we go. Okay, so subtract from caller balance add to receiver balance The value Okay, so now we have our new balance is set up. We've done You know the addition the subtraction. So now we just need to store these back into storage So we'll say s store we need the key and the value in this case The key is going to be the caller balance slot And the value is going to be new caller balance and same thing receiver Balance slot new receiver balance Cool. Um, yeah, so just as a quick review of what we're doing with this transfer Loading loading the free memory pointer We're going to store at that memory pointer the caller then zero because that's a slot of the balance mapping We're going to hash that to get the slot. We're going to load from it to get the caller balance Assert that it actually does have the balance that it needs. If not, we're going to revert Then we're going to ink. Sorry Decrements or decrease the new caller bound to the new caller balance Then we're going to do the same thing for the receiver. Obviously, we don't need to check For the overflow. We're going to check that a little bit later But we can be certain that this is not going to overflow So we'll increase the receiver balance to give us our new receiver balance and then we'll store that in storage here So last thing we need to do after all of this is we need to return from Return to the caller a boolean in this case. We're going to return true. It's the way we do that Again, it doesn't matter now. We're at the end of the execution So we'll store at slot zero O x 1 which will be our true value And then you can return those 32 bytes The log. Yes, the log. Thank you. Okay, so we're going to have a transfer event Probably should have hashed this as well, but we'll get those hashes in just a moment Um, so it's going to be a transfer event. We're going to have an indexed Sender An indexed receiver and an amount Great. So, uh, for the rules that we mentioned earlier, uh, we're going to have so this is going to actually be log three, right? We're going to log the event signature of the event hash Then we're going to log the sender as a topic the receiver as a topic and then the amount because it's not an indexed topic we're going to put that in memory and then Um, basically pass the pointers to that to the log instruction So i'm going to quickly Oh well, so small Um, so in another place that you can do this if you want to get this stuff online and you might not have Um, if you might not have cast installed on your local machine What you can do is go to this online hashing tool and you can basically punch in this stuff I think internet is going to rug me once again, but oh no, there we go. Cool. Cool So we can just say transfer And then same thing right so oh x dd f2 etc, etc So we're going to set that as a constant All right, i'm going to add some comments in here Just a moment. Okay, we're going to store and now log Okay, so Before we do any of the logging remember we have to store the amount into memory So in this case again execution is about to end so we don't have to be too too careful about where we put things in memory So let's go ahead and m store At slot zero the amount think in this case we called it the value, right? So we're storing that in memory and now we want to call log, but remember we want to Have the two indexed arguments and we want the Event signature, so that's going to be log three First thing we want to pass is the pointer in memory. It's going to start at zero The size is just going to be this one value right everything else is going to come from the stack We just need these 32 bytes at the beginning Then we're going to say transfer hash The next topic is going to be the caller And then the final topic is going to be the receiver So at this point we handle the balances Update the storage log the event and return The values in the log are only the non indexed so the actual indexed arguments are on the stack and then the um Then the non indexed are in memory, which also, um I believe this is also in the link tree the full like yield documentation Basically everything you need to know about this. It's also going to have Our log instructions in here right so log zero one two three and four So if it's the same it uses the same slot It first increases, right No, no, so whatever your val whatever the value is at that slot it will decrease And it loads increases. Oh, yes. Yes. Yes. Okay, right Great catch. Thank you Yeah, so let's do that as well. So let's just say equals if the caller is equal to the receiver subtract store load add store Right. Oh, yeah, so it evens out. I mean, it's better check for sure, but Right, right. Yeah Um, yeah for the sake of time We're going to be bad developers and we're just going to revert with zero zero and we're just not going to tell anybody about that All right All right, great. Um, good catch. Thank you So after transfer Let's do allowance Yeah, let's do allowance Great. Okay, so um in this case What we need to do is So we're going to take the owner we're going to map that to a spender which then we map to a number So the way that this works in storage Is we're going to hash The owner and slot Make sure I get this right Owner and slot and then the result of this Will hash again so owner slot hash then The final key let's double check the documentation make sure we get that right Great. So uh, here is the standard function and then Nesting that would be Right idea wrong order So the mapping rules are applied then recursively So we're going to bring that down here so we can remember Okay, so first we're going to do the inner hash So we're going to m store the owner Yep, we're going to m store the owner At slot zero We're going to m store at the next slot The slot which is going to be zero Inner hash is going to be Shot three hash of the first two slots And then we're basically going to do the same thing again with the inner hash Wait slot one. Thank you. Okay, and then Spender, okay the allowance slot then is going to be the hash of these once again Okay, and so now what we want to do is load from storage And then we want to store this in memory and return that back to the caller Whoops not caller owner okay, so We hash together to form the first slot and then we hash again to form the second slot this mapping We load from that slot Put that in memory and then we return it from memory to the caller So next thing we want to do is going to be approve So in the interest of time, we're going to copy paste As all good developers do Okay, so We're going to do the same thing so the hashing figure out the slot And then from here, we want to set the value at this slot to the new amount So s store at allowance Slots the amount Next thing we want to do before we return is we want to create a log and that's going to be the approve event It's going to have an owner a spender And an amount and so functionally this is going to be the same as the transfer We have two indexed arguments. So it's going to be log three and we'll have to store the amount into memory Is it approve or approval? It's just approve Just a moment approval great So that's going to be approval So from here, we'll take this hash make sure that's hex Okay, so before we log the event again, we want to store the amount in storage And then we want to log Three we'll start at slot zero. It's going to be of size o x 20 because it's just the amount First topic is going to be the approval hash Next one is going to be the owner in this case. That's going to be our caller And the final one is going to be the spender the person Who can spend on their behalf Okay, now that we've logged the event last thing we want to do is store One which for true then we want to return That full amount easy right so on the lowest level a bullion is actually represented as as um Basically a un eight that can only be zero or one. So it just occupies a single byte But in this case, we're returning the the o x 20 just returning the full word Okay, we're going to do transfer from so this is going to have a sender a receiver and an amount All right, so we're going to do a very similar thing to The transfer from function we're actually going to just copy paste most of this with an exception Oh, I don't know if I'm going to be able to do this with this tiny mouse. Hold on Nice, okay. No, we're just going to do it from scratch We'll do it live Okay, so we'll load the mem pointer. We want to generate the slot first First for the approval So we'll do the same thing as here Owner it's going to be okay. So the owner in this case is going to be the sender And then the spender in this case is going to be the caller, right? So basically we want to check um, what what we're trying to load here and what we're trying to see is The spenders permissions on behalf of the sender. We'll call that caller allowance Okay, we're going to load from that slot And so basically we want to do the same thing we want to check and make sure that caller allowance Is not less than the amount, right? So if it is less than then we're going to revert In this case, we're going to be storing the insufficient allowance error So notice this is going to take two arguments. That's going to be the owner and the spender Since we're going to be storing a bit more information here even though it is going to end execution again formalities Mem pointer. So we're going to store The insufficient allowance selector. So that's going to be the first four bytes now remember It's only four bytes in memory that we're occupying here. We actually want to write after this the next 32 bytes so instead of Adding a full o x 20. We're actually just going to add o x o 4 Right, so it's going to be the four bytes then the next 32 bytes and then the next 32 bytes after that So storing at this slot. It's going to be the owner In this case, that's going to be the spin or the the sender And then the next one is going to be So the next one is going to be the actual caller So notice we're still increasing by o x 20 at this point, but because it is at an offset of four first slot is going to be The mem pointer the next one is going to be mem pointer plus four and then the next one is going to be the mem pointer plus four plus 32 And so this is our entire Chunk in memory where the error is so we're going to revert starting at the mem pointer and then it's going to be o x let's see four 24 and then 44 So o x 44 and so that's going to you basically assert that they actually have the allowance to spend there So now that we've done that let's go ahead and So next thing we want to do is we want to get the balance slot from the sender Balance. Thank you Okay, so doing the same thing like I'd hold on This compiler is being obnoxious. We're just going to do this This is not actually important. I'm actually just doing this so the compiler doesn't give us all this yellow text everywhere We're going to get rid of that in a moment. Okay So we're going to load the sender's balance Basically again, we're storing the sender Storing the actual pointer and memory where this or pointer and storage where this is going to come from We're going to hash that to get the slot from the slot. We load the balance Then we want to assert that it is at least that amount Otherwise we'll revert with insufficient balance Then we will s store at the sender balance slot the sum of sender balance And amount great next we'll do the receiver So basically the same thing And then we will store inverse Great. So same thing is before basically once we pass this approval check We're going to check the balances make sure that it's all good Send all this stuff through Lastly, we want to do before we return is we want to do the log So again m store at zero the amounts Log Come on three It's going to start at slot zero offset of o x 20 Transfer hash is going to be the first second is going to be the sender And final is going to be the receiver Oh and one last thing before we do this for your return Is we want to make sure we decrease the allowance if it is not The max you went to 56 value. So if it is less than We're checking I'm not sure of a good way to get this max value in assembly in a second No, we're going to have to log that or something Oh, sorry Right. Yeah, I didn't want to have to do it that way, but it looks like I'm going to have to You could underflow Which that that is one thing that works, but also that does add like that that is computation at runtime Best way And the reason that we actually can't just use So normally you could do something like type you went to 56 max But this actually is disallowed in assembly because this is you know a higher level Um Higher level functionality. So we'll just do that, right? So if the caller allowance is less than this enormous value What we want to do then is decrease the caller allowance and we'll store that back in the allowance slot And again, since we already checked to make sure that they have sufficient allowance This should never underflow. Okay, then finally we're going to Store true and then we're going to return it Right. So so there's a common pattern. It's not part of the standard, but this is something that seeing increasingly basically To give like an infinite approval You could just set it to the max you went to 56 value And so instead of actually having to do like math on that, right, we can just you know check that Right, so if less than then we s store. So that's all good. All right. So just reviewing this one function one last time We're going to Generate the allowance slot Check the allowance make sure it's sufficient. If not, we revert if it's less than the max value We're actually going to uh decrease the caller allowance Next we want to make sure the sender balance is sufficient If not, we revert Then we want to subtract from the sender balance this amount Then we want to add to the receiver balance this amount Then finally we're going to log and then return true 165 Right. So so this is to generate the hash. So we're storing at slot zero the sender And he said the other was 205 201 Um, yeah, same thing, right? So at the memory pointer and then at the memory pointer plus x 20 Right. Okay. So we have transfer from we have approve. We have allowance We have transfer balance of total supply And again, we're just going to keep this internal just so we can You know for the sake of the exercise write this out Now of course all of this that we're doing here is like very much over abusing assembly and in general you don't want to use it this much Um, this is just to give like a good picture is like what's actually happening into the hood here So total supply is going to be a public view function Now this one's actually going to be really easy Basically what we want to do is store the value at this slot. Now. This is slot number three Or slot o x zero two. This is o x o o o x o one o x o two So we can m store at slot zero The value that is loaded from o x o two Then from there We're turned o x o o x 20 a mint Right, so I was actually going to do that in the constructor Um, but if we want to do a mint function we could I don't well actually are we short on time Right. Yeah, so it's always zero Best token Uh, was there another question Transfer from Hash three slots at the same. Oh, right. So if you want to hash three slots Then you the first argument would be where in memory it is and then you would just say o x 60 Just three slots. Uh, in this case, we're only ever hashing two. That's just per the solidity standard But you could hash bigger stuff Uh, not quite so the the reason that we did the like separate hashing here is because Um, the second time we hash we're actually Um, concatenating the caller and the first hash like digest. So it has to be two different hashes. Okay, so in our constructor We will once again abuse assembly Uh, so basically What we'll do here is we'll just do a static total supply for the sake of simplicity. We're going to store Into the caller slot the initial supply So first thing we want to do m store the caller Next is going to be the slot for balances We're going to s store at the slots Total supply, what do we want to call this? max int Yeah, I like it Cool. All right, so we'll do that Um Now since this is kind of technically a transfer from the zero address We'll go ahead and and log an event for this as well So, uh mounts we're going to store into memory And then from there we're going to call log three Transfer hash no no no no no log three memory pointer. It's going to be zero memory size. It's going to be o x 20 to store that value transfer hash zero address we can just say zero here then receiver And all three topics perfect Now from here, we're not going to return because in the constructor of a contract The way that like a contract is actually deployed to the chain is we take the entire contract's bytecode and then some And basically what we're doing is returning the entire contract's bytecode Like we're putting the whole bytecode in memory and then calling return on that That's how the actual deployment process works and part of the reason why deployments get so obscenely expensive because memory gas cost expand gas expansion Um, sorry memory expansion gas cost scale quadratically so it gets very very expensive But the reason that I bring this up is we're not going to call return here Because the constructor needs to do its job and return the bytecode at the end of this constructor's execution So everything compiles Moment of truth Oops, hold on All right. So first let's check decimals should be 18 name should be yule token symbol should be yule Total supply. We did not increase the total supply lull So s store uh, that's going to be slot o x o two and it's going to be max uint 256 Great good coin great tokenomics All right Total supply now it's this enormous number And let's do a quick Transfer now we're just going to transfer to some random address with here's I don't know first. Let's call balance of on this address just for the sake of seeing what's what So balance of and this should be the deployer's address Is it not just a moment interesting Caller o x zero zero Ah, that's it. Okay. Okay. Okay. There we go one more try so balance of here Our enormous number and then finally we'll just make a quick transfer here for let's say 10 Because we're stungy And then we call and the balance is now 10 and that's an erc 20 token written purely in yule And that concludes today's workshop so Really quick. These are some other resources that you guys can check out educational resources evm codes Obviously the yellow paper if you're the generator and you're into that kind of thing developer tooling and languages huff language Is basically a low-level assembly language that will teach you a lot about the evm The foundry development environment really good for testing and trying things and the remix browser ide For that local testing and the browser You had a question From can be used to know why Add liquidity or swap so that's not possible now Right because all it has access to is like the message sender, right? Now, maybe Right, right Any other questions I So Right, so some things are trivial But some things you can actually like very heavily optimize and that's that's actually why a lot of people tend to use assembly like for example Take a bi dot encode So this is actually like fairly gas intensive, especially like let's say you want to loop A number of external calls. It's actually a bi encoding every time, right? And there's like a lot of gas it's being consumed there Whereas like maybe you're calling the same function over this entire loop Instead, maybe what you could do an assembly is you run that loop But let's say instead of you know doing this encoding the entire time every single time You just store it once and you kind of like cash that right and so things like that will actually give you massive improvements and gas efficiency Right it is improving so a little bit about the state of the solidity compiler Originally solidity used it's basically it's on pipeline. So it would go from you know solidity to intermediate representation to bytecode Now there's actually a migration happening Where basically solidity will compile to yule and then we can actually use yule's pipeline to optimize because the yule optimizer is very well developed This was actually sort of the original reason of using yule is that like many high level languages could compile the yule And have the same you know basically intermediate representation where everybody can take advantage of the same optimizer Welcome