Demystifying Ethereum Assembly by Joshua Riley | Devcon Bogotá
Devcon·Sat, Oct 7, 2023, 12:00 AM
Visit the https://archive.devcon.org/ to gain access to the entire library of Devcon talks with the ease of filtering, playlists, personalized suggestions, decentralized access on Swarm, IPFS and more. https://archive.devcon.org/archive/watch/6/demystifying-ethereum-assembly/ # Demystifying Ethereum Assembly The EVM creates an economic incentive to minimize computation and data storage. This leads to extreme gas optimizations in a few different assembly languages and patterns. In this workshop, we will learn about EVM assembly through both a walkthrough of EVM basics and real-world, practical examples of assembly. This workshop assumes an intermediate level understanding of programming and at least a beginner understanding of Ethereum. Speaker(s): Joshua Riley Track: Developer Infrastructure Keywords: Assembly,Yul,Optimizations Follow us: https://twitter.com/efdevcon, https://twitter.com/ethereum Learn more about devcon: https://www.devcon.org/ Learn more about ethereum: https://ethereum.org/ Devcon is the Ethereum conference for developers, researchers, thinkers, and makers. Devcon 6 was held in Bogotá, Colombia on Oct 11 - 14, 2022. Devcon is organized and presented by the Ethereum Foundation, with the support of our sponsors. To find out more, please visit https://ethereum.foundation/
Transcript
foreign [Music] welcome everybody this is going to be demystifying ethereum assembly a practical zero to one guide so my name is Joshua or JT Riley I'm 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 um popular GitHub repos so um I kind of went through this the hard way reading through you know as much documentation and paper and as many papers as I could um and hopefully I've condensed this down enough that uh 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 uh 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 going to get to the bottom of pretty much anything you guys have questions on today so as we get started um section one obviously understanding the ethereum virtual machine uh 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 uh the most important one um 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 uh you know everything you need to know about instructions and then it also has a playground and that's where we're going to be demoing some of these things so we can really visualize and internalize what's going on foreign 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 n is the byte size of that value pop will pop a value off the top of the stack swap in we'll 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 a mnemonic bytecode basically it's just a human readable format of what the actual evm by code is so this is at the lowest level uh so first we have push one right so we're pushing a value of of size one byte the value is going to be one and over here these uh orange bits here this is actually just uh comments to kind of represent exactly you know what the stack should look like after any given instruction this next one is going to push one and that value of one byte 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.com playground you can also find it in the link stream so first we're going to do uh 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 I 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 the top stack 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 stat constructions we're going to go to arithmetic instructions so of course add We'll add two values sub we'll 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 uh 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 2 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 so as we step through this see the two is 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 an 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 um 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 we'll push a true to the to the Top If sorry it'll push true if the Top Value is zero now something to note about is zero you're going to see this a lot in uh you know you will file is 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 thank you so as a comparison example we push one and 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 bit wise 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 uh 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 uh an 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 uh not we'll 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 uh over here on the left there's a little bit of encoding here just showing you um you know basically what these numbers look like in binary format and then on the right we have push one push two you're 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 two so we can see uh here it starts at zero zero zero one but after it moves over two it's going to be 0 1 0 0.
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 too 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 8-bit word to memory and we can also M load so what this does is loads a 32 byte word from 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 stacker if we push you know any um 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 0 and M load is actually going to pull out this one we'll go ahead and do an example for this one zoom in is that better cool so we'll push the number one to the stack wait 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 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 start at index 0 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 there are actually quite a few but some of the important ones uh caller pushes the address that called the current context so if you're familiar with solidity this is the same thing as message.cinder we have timestamp which pushes the current Block's timestamp again same thing as block dot 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 color 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 foreign 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 n 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 indexed topics within this event now something to note here is in solidity 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 a lot the very first Topic in any log is actually the signature of an event right so it's basically just a sha3 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 we have create and create two and so what these can do is actually deploy code to a new address creating a new contract so just a quick example of what you can write for example maybe we want to store the last timestamp that some specific thing happened right so we can use timestamp to push the Block's timestamp to the stack we can push a zero and then s store and what's that 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 Ox 20 Push zero and then return so any questions so far on the instruction set right here yeah so these are the representation of uh the instructions here so here again we have like push one push one and store Etc whenever we run this we can actually step through foreign 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 the 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 I know you'll notice in in solidity you'll get the stack too 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 um but yeah it actually can technically go like way way deeper um 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 setback up so you mentioned um like return data right okay so uh there are actually special instructions to handle return data you have return data size and return data copy um and so what you can do is whenever you actually make a call you're not actually getting all that stuff back solidity kind of uh 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 into memory yeah so it's not stored in memory per se like it's only stored in memory whenever you explicitly do it yeah of Vlog zero um oh I guess we have the 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 open a remix see how it works are we good um yes so uh create two uh 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 utility of exactly which one you want to do foreign 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 uh that was all the really like in-depth stuff that uh we'll kind of give you this foundational knowledge for how to write Yule you'll thankfully has a much higher level syntax uh so what Yule is is it's a low-level language uh 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 too uh built into the language are most evm instructions these are callable as functions uh there's basic control flow support and functions well user-defined functions now you'll notice whenever we start looking at your 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 you'll files so chances are you're not going to be touching this if you're just writing inline assembly um one other thing to note here before we start stepping through uh 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 sometimes we have a colon an 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 our 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 foreign 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 um case two is if a is equal to 2 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 uh let I we're assigning it zero so this is your I equals 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 uh and one right so we're basically incrementing it and then inside of there we can iterate and do our looping logic okay so as a quick comparison to mnemonic byte code over here on the left that was the code that we wrote a moment ago to uh you know store the caller in storage uh 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 verbose 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 the layout of that data that gets sent to your contract the memory layout 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 sha3 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 um if an argument is of dynamic size then the slot where it should be is actually going to hold a pointer to a place in 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 address un256. now we're actually hashing here is transfer parentheses address un256 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 patting it all the way foreign 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 the seal 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 ox60 is a zero slot so the zero slot is reserved specifically for whenever you're allocating new arrays I believe um this is like basically a big big No-No Zone anytime people start overriding this uh 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 uh no dynamically sized arrays will occupy one slot which holds that pointer to where the actual value is in memory there's another slide 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 they're 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 now it'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 ox40. we're going to declare this or assign this variable data because remember anything that's declared bytes memory okay zoom in oh yeah I thought so thank you 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 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 point there so at the free memory pointer we're going to store byte's 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 and the next slide we're gonna 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 padded 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 ox40 slot any questions right so it's going to again let's say you append an extra bite to the end of this it's going to store that extra bite at the end it's going to keep it packed and then it's going to increase the value of something else like if you if you know there's something at this right after that array and then you push it will move essentially right all of it should I haven't tried that we can actually try that as well we can hop over to remix and try that um it would make sense if it would right because then it would just get really really confusing right okay right right you're right sorry thank you storage right so in solidity uh you do have to specify the length ahead of time thank you um okay uh and they don't hit on one it's like the like like a struct like a memory structure so memory struck so struct actually acts similar to a dynamically sized uh value so it's actually going to be a pointer to that as well right right so whenever we store so the first time we're storing the bytes length right we're storing the number four um even though it's just a single value that's actually occupying a 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 personalities 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 can 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 um so basically what these are is they replace that 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 byte code so as a quick example we have a contract it's got five values in it first is going to be a full un256 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 and the right most uh bit and C is packed in the next rice right most place in that slot and then we have finally two U and 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 a dynamically sized variables again per the documentation in 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 erc20 IT Maps an address to a unit 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 Pat it to 32 bytes concatenate 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 sha3 hash of that slot number byte 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 rightmost byte and it's going to be 2x the length so looking at this one we have three storage variables we have a string a 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 2 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 oxb1 et cetera 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 un256 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 um that hash right so oxe9 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 so basically in the context of storage what this means is storage slots an apparent contract precede the child contract storage when a child has multiple parents the order of the parents storage is set by the order of inheritance right this process is repeated recursively and storage packing rules are still in play when applicable so here we have uh 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 0 and parent one and it's going to have its own value C and that's going to be 3 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 types uh and the 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 the the event signature hash uh 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 it 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 is going to end so we store at slot zero B and then we call the log 2 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 require Boolean string the reason for this is the the solidity defined errors first is panic and it's got a unit 256. uh so this is things like uh divide by zero overflow underflow 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 yield plus solidity so we're actually going to hop over to remix and that's a remix.othereum.
org and from that link tree earlier we actually have some boilerplate code here make sure this is big enough to see uh 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 oh foreign just another second right yep so that's going to have the code it's going to be in a gist 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 uh link tr.e ethereum assembly foreign 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 in and code stuff in the hexadecimal and all of that yeah right in the middle of the workshop so just looking through these constants really quick how 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 uh the string next we have the symbol that's going to just be you'll 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 we're defining here one is going to be insufficient balance and the other is going to be insufficient allowance now no 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 and we're going to make this pure we're not storing anything in storage we're actually just going to return a constant I'm just going to return a string in memory okay 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 rights and then we're going to return from that so first we're going to m-store 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 uh now this actually should be fine in this case like we know exactly where it is because nothing's been written in the memory but it is good practice as often as possible to use this free memory pointer so we're going to load from ox40 sure yeah so let's say um so I mean if you just start writing to the zero slot and in this case we're going to occupy three slots that's going to take slot zero Ox 20 and Ox 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 ox40 is the right value so if we overwrite this to something strange in this case uh you know it would be the string padded over 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 um you basically load whatever is at Ox 40 that's going to point normally it starts at Ox 80 so that's after the four Reserve slots uh but again for the sake of you know keeping it safe we're going to do it like this so we m-store at the mem pointer we're going to store the link or sorry not the link the ox 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 nox20 so we're increasing you know by a single word size we're going to store the name length next we're going to store at an ox 40 byte offset uh you mean like after the first M store can we be storing the next appointment uh no so this is actually an offset from this value itself so yeah so the ox 20 offset is just uh pointing to the actual link that's itself so uh we stored the pointer restoring the length and now finally we're actually going to store the data foreign 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 ox60 so what we can do is return starting at the mem pointer we can return Ox 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 at 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 foreign pointer then we store in memory at the mem pointer string pointer at an offset of ox 20 we'll store the symbol length and add an offset of Ox 40 . or we'll store the symbol data and then same thing as before it occupies the same number of slots because everything is padded we'll return mempointer ox60 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 ox60 foreign next up we're going to do decimals it's going to return to 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 the right 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 six as decimals and then from there we're going to return from slot zero in memory we're going to return Ox 20 even though it's a un8 it's stored at the rightmost bytes so we need to return that full 32 byte word all right now that we're done with um all of that basic boilerplate stuff let's go ahead and set up a mapping this is going to be mapping an address to un256 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 and address to a nested mapping which Maps an address to a un256 I'm just going to comment here 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 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 uint 256. all right a quick time check great 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 it's zero so we're going to Pat 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 okay so first thing we do is we're going to m-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 accounts and then at slot Ox 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 256.
and what this is going to take an assembly now in you know high level solidity what this is going to take is like a string or bytes but in low level uh you'll it's actually going to take a pointer in memory and the size and memory so in this case we're going to start at slot zero and we're going to consume two different slots right so ox 40. so this should actually be the key to this person's balance so 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 is 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 uh what's in slot zero and Slot uh well 32 well slot one like 0 and 32 here so we can actually just overwrite that with the account balance okay okay and then finally we're gonna return and so that's going to start at slot zero and it's going to be Ox 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 uh 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 add slot zero whatever's in-call data starting at four 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 ox 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 uh memory so far so these these two slots sorry exactly yeah um 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 uh we could remove that but again for the sake of safety we'll just leave that for now uh return with like just parentheses volcano right so in this case it's actually going to give us an error because it actually is um 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 but the function itself is prepared will return some people 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 uh 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 gonna 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 you know nothing at all so call data only loads using call data load it only loads 32 bytes um 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 would 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 foreign 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 yep 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 yes um no so what this actually does is so the mem pointer tells you obviously where in memory to get the value and then ox60 is a length but what this is actually going to do uh in this contract it's going to finish executing it's going to stop executing and then whoever called this contract whether it's neoa or another contract it'll actually return that data so it'll take that chunk of memory in this case 60 by or ox60 bytes from free mem pointer and that's actually going to be returned you know into it's going to be returned either to the client Library like ethers or web through JS or it's going to be returned to another contract and coded as a string three still right right as soon as we call return it's actually going to stop execution from there so even if we put in anything after that as long as it always returns we'll never go there so each function has its own memory uh stack when you execute or they share across like if you go uh you know symbol they share the same memory or each uh right great question so um any time 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 anytime 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 uh in this case like they're technically writing to the same 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 so if you call an internal yes so if you call an internal function um actually at compile time but this is a little bit confusing about um solidity is that you have like public and internal functions 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 um but um um 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 Ox 20 these are always going to start at a zero ox40 is going to have that uh free memory pointer and then uh the next slide after that of course is the zero slot so we're not putting anything there we can actually take a look uh 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 ox80 then we're going to push Ox 40 and then we're going to M store and what that's doing is storing ox80 which is the first free memory pointer we're storing that at slot ox40 and this is the first this is actually like a big Telltale sign that a contract was written in solidity this always happens first every solidity contract starts with you know 6080604052 foreign foreign like when you have multiple internal functions or right yeah exactly so uh what what solidity actually does is those first two slots uh the reason that 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 thank you 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 um so in this case it's kind of a formality but it's really important to have that Ox 20 there because whenever we return it the client libraries expect it to be encoded as um actually no does that return that we're gonna 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 and 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 uh mostly zeros it looks like all done let's step through this really quick I really wish this would make me let me uh make it a little bit bigger here uh but so here's the state of the stack right in this case we're going to push uh the free memory pointer which is ox80 we're going to push uh the length which is ox60 and what that's going to do is point to slot ox80 in memory which is going to be here and it's going to go for ox60 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 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 which is not what you want right because 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 that's where the actual return instruction is going to be um [Music] like 20 yes that's an offset right okay okay so if you store the data I don't know if there's 16 for example uh yeah so what that actually is is it's it's Ox 20 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 basically some conditional logic in here remember that overflow and underflow checks only happen in salinity 0.8 and after 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 foreign 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 caller's 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 m-store one more time so we're gonna get the mem pointer by loading from memory from the mem pointer we're going to store the caller's address in this case we can get that by calling caller with parentheses at the next slide so mempointer Plus ox20 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 mem pointer with a size of ox40 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 caller's 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 color 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 now this actually isn't going to take any argument so all we really need to do is actually just store this one value foreign again at this point we're reverting we're about to stop execution so we can basically just you know write to uh 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 byte 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 ox20 I guess we could also just do oxo4 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 uh 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 is annoying make sure to store proper error information okay so uh from here we know that the color balance like if we're at this point in the code we know the color 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 oh just one moment foreign just double checking okay so we add these two together and then we'll say let's say receiver balance right oh 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 foreign new color balance thank you sweet okay uh so to get the receiver balance we're gonna do the same thing except uh 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 thank you [Music] so the receiver balance is going to be what we load from Storage which is going to be the hash starting at Min pointer and of size ox 40. so this is where we get the receiver balance actually Let's do let's do this color balance slots 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 uh 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 sorry thank you there we go okay so subtraction color balance add to receiver balance the value okay so now we have our new balances set up we've done um you know the addition subtraction so now we just need to store these back into storage so we'll say s store we need the key in the value in this case the key is going to be the caller balance slot and the value is going to be new color balance and same thing receiver balance slot new receiver balance all good 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 the 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 color balance assert that it actually does have the balance that it needs if not we're going to revert then we're going to Inc sorry decrement or decrease the new caller to the new color 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 um 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 uh 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 ox1 which will be our true value and if you return those 32 bytes [Music] the log yes the log thank you okay so we're going to have a transfer event probably should have hashed this uh 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 per the rules that we mentioned earlier we're going to have so this is going to actually be log three right we're going to log the event signature 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 great um so 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 so we can just say transfer and then same thing right so Ox DD F2 etc etc all right so we're going to set that as a constant all right I'm going to add some comments in here just a moment foreign 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 I 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 um event signature so that's going to be log 3.
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 great so at this point we handle the balances update the storage log the event and return thank you values and the values the values in the log are only the non-index so the actual indexed arguments are on the stack and then the um in the non-index 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 0 1 2 3 and 4. um 100 more heaters because you are having a new value 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 then it loads increases oh yes yes okay right right catch thank you right yeah so let's do that as well so let's just say equals uh if the caller is equal to the receiver subtract store load add store right oh yeah so it evens out right right yeah yeah great um yeah for the sake of time we're going to be bad developers and we're just gonna revert with zero zero and we're just not gonna tell anybody about that all right all right great um good catch thank you so after transfer let's do allowance no um okay let's do allowance great okay so um in this case what we need to do is all right 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 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 right idea wrong order so the mapping rules are applied then uh 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 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 3 hash of the first two slots and then we're basically going to do the same thing again with the enter hash wait it's not one thank you okay and then suspender foreign 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 foreign owner here we go okay so uh we hash together to form the first slot and then we hash again to form the second slot for this mapping we load from that slide put that in memory and then we return it from memory to the caller foreign so in the interest of time we're going to copy paste as all good developers do okay so um we're gonna 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 going to have an owner foreign ER 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 great is it approve or approval it's just a proof it's just a moment approval great so that's going to be approval say from here we'll take this hash make sure that's hex okay so uh before we log the event again we want to store the amount and Storage and then we want to log three we'll start at slot zero it's going to be of size Ox 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 great easy um right so on the lowest level a Boolean is actually represented as um basically a un8 that can only be zero or one so it just occupies a single byte but in this case we're returning the uh the ox 20 just returning the full word okay we're gonna 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 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 gonna do it from scratch we'll do it live okay so we'll load the mem pointer we want to generate the slot first for the approval so we'll do the same thing as here ah just a moment owner it's gonna 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 we're trying to load here what we're trying to see is the Spenders permissions on behalf of the sender we'll call that caller allowance foreign 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 color 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 foreign 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 Ox 20 we're actually just going to add ox04 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 it 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 in a second thank you so the next one is going to be the actual caller so notice we're still increasing by Ox 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 Min pointer and then it's going to be Ox let's see 4 24 and then 44. so ox44 and so that's going to yeah basically assert that they actually have the allowance uh to spend there so now that we've done that let's go ahead and oh come on great uh so next thing we want to do is we want to get the balance slot from the sender foreign balance thank you there we go okay so uh doing the same thing oh God hold on this compiler is being obnoxious we're just gonna 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 gonna get rid of that in a moment okay uh so we're going to load the uh the sender's balance um basically again we're storing the sender storing the actual pointer and memory where this are pointer and storage where this is going to come from uh 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 will revert with insufficient balance foreign store at the sender balance slot the sum of Cinder balance and amount great next we'll do the receiver so basically the same thing foreign and then we will store inverse foreign thing as 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 last thing we want to do before we return is we want to do the log so again M store at zero the amounts log common three it's going to start at slot zero offset of ox 20. transfer hash is going to be the first second is going to be the sender and the final is going to be the receiver oh and one last thing before we do this before you return as we want to make sure we decrease the allowance if it is not the max you went 256 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 a second no we're gonna have to log that or something sorry Alex sorry right yeah I didn't want to have to do it that way but it looks like I'm gonna have to uh you could underflow um which that is one thing that works but also that does add like that that is computation um at runtime best way all right uh and the reason that we actually can't just use um so normally you could do something like type un256.
max but this actually is disallowed and assembly because this is you know a higher level um higher level functionality so we'll just do that all 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 when is that what is the um right so so there's a common pattern it's not part of the standard but this is something that uh seeing increasingly basically um to give like an infinite approval uh 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 check them uh 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 decrease the color 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 two leaders 165. uh right so so this is uh to generate the hash so we're storing at slot zero descender 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 ox 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 this is just to give like a good picture as to like what's actually happening under the hood here so total Supply it's going to get public view function okay 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 ox02 this is Oxo Alexa one oxo2 so we can M store at slot zero the value that is loaded from Ox O2 then from there we're turned Oxo ox 20. I meant right so I was actually going to do that in the Constructor um but if we want to do a Min function we could I don't know 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 ox60 it says three slots uh in this case we're only over hashing two that's just per the solidity standard uh but you could hash bigger stuff foreign not quite so the the reason that uh 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 all right 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 caller next is going to be the slot for balances and we're going to s store at the slots total Supply what do we want to call this Max yeah I like it cool all right so we'll do that um now since this is kind of technically a transfer from zero address we'll go ahead 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 gonna be zero memory size it's going to be Ox 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 contracts byte code and then some and basically what we're doing is returning the entire contracts byte code like we're putting the whole byte code in memory and then calling return on that that's how the actual deployment process works and part of the reason why deployments go get so obscenely expensive because memory gas costs 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 a Constructor needs to do its job and return the bytecode at the end of this constructor's execution so everything compiles Moment of Truth whoops hold on all right so first let's check decimals should be 18.
name should be yield token symbol should be Yule total Supply we did not increase the total Supply so s store that's going to be slot oxo2 and it's going to be Max un256 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 addresses here's I don't know first let's call balance of on this address just for the sake of seeing what's what it's a balance of this should be the deployer's address foreign interesting caller ox0 Ah that's it 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 stingy and then we call and the balance is now 10 and that's an erc20 token written purely in yule and that concludes today's Workshop [Applause] so um really quick uh these are some other resources uh 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 uh 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 in the browser [Music] add liquidity or swap so that's not possible now um right because all it has access to is like the message sender right um now maybe uh right right uh any other questions 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 ABI 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 ABI 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 in 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 cache that right and so things like that will actually give you massive improvements and gas efficiency okay so the company always right it is improving so a little bit about the state of the solidity compiler originally solidity used it's basically its own uh pipeline so it would go from you know solidity to intermediate representation to byte code now there's actually a migration happening where basically solidity will compile to Yule and then we can actually use Yules 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 to Yule and have the same you know basically intermediate representation where everybody can take advantage of the same Optimizer welcome
Automatic transcript — names and jargon may be misspelled.