A Solidity Digest - Oleksii Matiiasevych - [Toronto Ethereum Developers Meetup]

ETH Toronto Talks · 27 May 2021 · 33:35

ChainSafe Solidity engineer Oleksii Matiiasevych gives a deep dive into the code and state of Solidity for Ethereum. He runs through the latest compiler changes that arrived with the Berlin hardfork and reflects on smart contract design considerations after Berlin. Matiiasevych is a lead on the ChainSafe Audits team, and the talk was given at the Toronto Ethereum Developers Meetup.

Speakers

Topics

From ETH Toronto Talks, hosted by ETH Toronto.

Transcript

okay so this is my screen yes code cool yeah well that will be mostly code because well solidity um yeah so uh what i wanted to do for quite some time now is uh basically go over all of the solidity documentation uh once again i did a number of times before but it changes regularly and so yeah i just decided to went all over it and collect the stuff that somehow um that i somehow missed during the i'll do in the years of doing smart contracts and also

to not just stuff that i didn't see been used much even though i think they are pretty usable uh and yeah and so i redid all the examples have all the code here and so the idea is i'll be telling each point and then if anyone will have any questions about the particular particular topic after after i describe all the uh after i present all the topics in the end you can just tell okay i have questions about that one i can scroll back to it we can run it in remix and have some details on it okay so first of all

something that they haven't seen and been used by anyone yet and well it's not yet fully supported in the language but you can define errors in solidity and that is really helpful when you want to to report meaningful error messages like you need rewards so as you know right now the community standard is like to do something like revert or it's usually required condition and then like swap k something like this well without much uh

details like it gives you the idea of where is it where is the problem but then you need to go and figure it out well now you can do something like divert and you specify which role you want to invert you can pass the arguments in there it will all be maybe encoded and then the one who sees their or can pretty much uh get more idea of what's going on yeah this first thing uh second one yeah so since solidity version eight uh you know that the or you don't know so the safe mass uh like safe um underflow and overflow checks are

enforced now by default uh this seems to be a cool thing and mostly will just make all the code uh been easier to read because every like almost everyone just uses safe mass without thinking just to be on the safe side of side of things even though there is one pitfall that i think some people might fall into so if you notice this particular function you could see that what i'm doing here is i'm subtracting two from one then i'm adding three so in like before like in older versions of solidity this would be all right because in the end you would just get a result

of two and everything will work you won't even notice that there is a problem uh right now in the like eighth version only this one will work because on after every operation it checks for the underflow and overflow uh so i think that at least some number of the contracts of like before version 8 use it the ones who didn't use the safe mask could have points in their code where they went underflow or overflow for as part of the expression but in the end the result of the operation was all right

and so now it will it will start to fail probably at run time when uh they will launch their contract so should be careful with it the people who will uh like go from the safe mask to the new syntax of always checked always using safety checks will not won't have any problems yeah but still you can use the unchecked blocks which allow you to disable like to tell compiler to disable the safety checks and it will all work as before and it is a bit cheaper if you're sure what you're doing this is cheaper than doing the extra checks every time

okay now funny thing when you push to state a race you get i think previously as far as i remember the push just returned the length of the new length of the array now the push returns the reference to the element that has been pushed into the array so for instance you can do something like push and then to whatever is returned you just assign the new element uh pretty much the same as you did before by pushing the element right into the array but yeah just allow some some new syntax the

funny thing though is like the old syntax and this kind of new syntax uses like equal amounts of gas if you use the optimizer but if you save the reference into a local variable first and then you fill in this element in your in your array after the fact it will be cheaper uh for whatever reason i don't know why optimizer uh optimizer is having easy time optimizing like this this storage accesses better than than these ones for instance yeah so

uh this was the case before too uh so yeah in general if you want to make things cheaper i just write your structs in this particular way and now there is a new thing which is which allows you to concut arbitrary number of bytes variables fixed and dynamic size ones so basically you have a member can cut on the bytes symbol and yeah it works pretty much the same as ibm code packet but only they only allows you to supply it with bytes uh by its

parameters so you basically you supply it something like this and in the end you get just long byte terrain um now yeah one other thing that i totally didn't know before i don't know when it appeared and it could be useful i guess in some cases so you could also do push and pop on just on bite just on by the race themselves so basically you can push a single byte on top of the byte array you can pop from it uh only works on the state variables well because because in memory where we don't have we don't really have dynamically

dynamic by trace um okay yeah so a number of cool things that you could um derive from the type function which gets the information about type of what's passed into it so i don't know how this could be used yet but perhaps it could be in some way so you could get the name of the contract that you're passing into but it's pretty much the same as just using the name itself if you already have it at compile time now previously you could usually only get

i don't even think you could get creation code or runtime code from inside of the uh compiler uh like in from inside the contracts but now it allows you to get um to get deployment code and the code that's going to be deployed after the constructor is run and yeah one interesting thing is that um it's common um common standard to use the eep 165 interface ids inside your contract and to define them now you don't really need to like have those constant constants inside you could use the type on the interface and it will have a member the interface id

which will produce the same signature as you would probably put in the contract yourself um yeah create 2 is on the block for quite some time now but still i see that mostly people use it as um through some open sampling libraries and through assembly uh while salinity also allows you to just supply the salt during the contract creation and it will have the same effect just much easier to read so it's interesting that next time you need to create two now funny thing about cheap events i've never seen anyone use anonymous

events before and that's a pd i think because they make your events cheaper and really like indexed parameters are cool but they are more expensive and they are not mandatory for any uh functionality that you want to have so yeah they make the ux easier especially maybe for light clients but i think ultimately uh whatever can be done of change should be done off chain so uh you shouldn't really be using indexed uh indexed arguments in your events and

probably you could even scrape the name of the event itself and just use anonymous events well in this particular case it will save you 400 gas on on every event meetings so yeah consider using non-muslims never see i've never seen anyone using that before [Music] yeah one one inconvenience which might be surprising to somebody that bulls in state uh take eight bits instead of one beat so when you're trying to save on storage as hard as you can you try to put your very pack your variables real hard and you might think that you

could put eight booleans in a single in single byte well you can't every boolean will take the whole bite so you can have only 32 of them in a single storage slot and i hope that eventually solidity solidity developers like i mean the ones who who develop the language itself will introduce a type that will take one bit instead of one byte and that will make usage of bitmaps easier for everyone now uh yeah a few other things uh you could declare

functions and arms and structs outside of contracts so that they are globally available and does not pollute your contract code so you could right now put some uh utility functions outside of your contracts you could declare structs outside of them so that multiple contracts get used could use the same structs so could be useful useful uh now yeah i didn't notice uh like i noticed that people use short circuits a lot in other languages but somehow not in solidity while solidity supports the same shortcut to circuit logic as as plenty of other languages

so in this particular example um solidity will not will not execute the fail function if the first function returns true so uh well the logics is just the same as in many other languages it's just probably you could use it to something some things could be useful and especially when you're having some conditions uh like you have an if and you have plenty of conditions inside of it you could always make use of this to make the condition verification cheaper so that when the first part of the condition is uh is true you can be certain that the

other ones won't be even checked and won't spend your gas um now yeah another thing about the types you could get members which are called max or ming to get the smallest and biggest possible value for the type so yeah for instance previously people could have used something like unit minus one to get the biggest wind uh but right now it's not allowed not not allowed because of the underflow safety checks because this is effectively an underflow so you need to use the type mux

there is one strange well not not strange but probably unexpected thing that when you're using integers well people don't use integers in solidity a lot but some people do and if you have the smallest possible integer if you'll try to negate it the overflow check will fail the transaction at runtime it will compile and everything will be cool but at runtime this this will revert because the smallest um like you can you know we have a zero and so on the negative side of of the type you have more uh more space than on the positive side so if you take the smallest number

negate it you get over the highest possible number so don't do this uh okay finally some people don't know such a cool function that is available in api namespace which is called a function called gcode which basically allows you to decode ib encoded data easily without going into assembly and managing the memory yourself so yeah this one's pretty pretty useful and uh flexible when and you have uh raw bite arrays and

you don't even have to specify exact um like you don't have to specify the types for the whole byte array like if you if you if the byte array will be for instance much longer than the types you specify then the solidity will just take uh start from the beginning of the byte array and cut the slices of what you what you wanted like you i specified that i need two units so it will take first 64 bytes and that will be two units i need and no matter whatever else is there in the end so this is usually very useful and uh the same uh the close thing that you also don't

don't know often is that we have slices in solidity they only work on cold data but you could easily split your input byte arrays into whatever chunks you like by using python-like syntax now yeah some things about readability improvements that you could use so since recently underscores were introduced to split the numbers and that might be might make things more readable but it also might make them not so readable so use sparingly and make sure that you only use them where needed now the cool thing which i haven't seen

in any other language i think uh before is that strings can be concatenated without any concatenation operators so basically you just write your strings and they that there won't be any new lines or stuff you just keep writing and you keep stuff in in your like column bounds and they will be concatenated you can do even like in the simplest in a single line but that probably doesn't make much sense uh but yeah using it to to spread long strings over multiple lines really cool i think now the same thing works with unicode

you just need to use the unicode prefix and then you can use all the funny emojis and stuff and the same works for hexes if you have some pretty pretty long hacks absolutely no need to have a super huge line in your function just split it in pieces of uh 64 characters it will make it much more readable um now as recently there was uh immutable variables introduced which are super cool because they you have to use them you must use them they make your contracts much cheaper to execute uh so it's basically a constants that you can define at

construction time uh there is one um pitfall that people sometimes having hard time to overcome is that uh for instance imagine a year c20 contract and you were [Music] you're having some checks in the before token transfer function which are checking some immutable variables well it will fail like this particular contract won't compile if i do this because in the constructor i define this immutable variable and then in the mint function i'm trying to read it and well basically i cannot read immutable variable from the code because during the construction

there is no code in the contract so cannot use this this won't compile well to overcome this thing well basically you just shouldn't use shouldn't rely on immutable variables in your constructor and instead you could split your construction process in a couple of functions then use a deployer uh helper deployer contact that will do the deployment then we'll call your linking function for instance which will check the immutable value but yeah but always use immutable variable wherever you can uh yeah now another thing that is i haven't seen a single time being used i think except of

in my own contracts once subs like long time ago but yeah you could uh we have in solidity since some time we have function function types basically so you could define uh like functions as arguments in your your other functions or uh like in state variables and and things and they could even be external so you could pass a function from one contract to another contract for that contract to execute it and that could result in for example in the callback or things and it could really add some um some nice syntactic sugar to uh

to your uh to your code so for instance this part in this particular example uh so we would call it call it um function in the color we will pass the address of the of this contract and so what we see here is that we're gonna call the call me back function and this is function callback so i'm just basically passing the function well the the reference to the function callback and it will contain the address of my contract and the signature of of the function so i will pass it here and well call me back function already expect something like this to

be passed in and it can now execute it which because of this is the external function function it will result in my contract being called back some actions happening and so on yeah so not only the external functions can could be passed around you could have like some pure functions or view functions that for instance i know you have some function that checks some condition and you have plenty uh plenty options of how you of what you want to check but you are always checking it as part of an often another function so you can just pass it in well basically yeah you could pass functions as parameters in solidity and

this is really cool this is used commonly in many other languages and yeah i wanted to finish my presentation with a bit of design considerations post green hard fork so as you all know one of the major things of the building hard work was the increase of the gas costs of reading storage writing into storage especially the cold storages and and calling contracts that haven't been called in the context of this transaction yet so well with the storage everything is well basically the same as before storage was always expensive

and you we always thrive to um to use it very optimally to do as little rights as possible to store as little information as possible and to read as rarely as possible but with contract calls it they they weren't really expensive before and so it was common to for instance split your big contracts into yeah into like modules or it was a good idea to use for instance uh like so-called clones or proxy contracts well i don't think the proxy contracts are a good idea anymore because uh with every call that

you're like that your contract makes during the transaction you're spending at least 2 400 more gas and so the more contracts your contract calls uh the more of those 2 400s you add to that transaction and that's just it just loses all the point of using proxy contracts or of using modular architecture very quickly you'll just waste gas for everyone so i think at this point unfortunately we need to start like getting a monolithic approach to contracts building again and

which is even more depressing is that we cannot have multiple instances like cheap cheap deployments of multiple instances of the contracts so for instance unislap already like deploys every payer as a full contract because we'll probably figure out that beggar is something that's going to be used often and so it should should not have an overhead of additional calls but right now those additional course calls are getting so expensive that even if your contract is not used very often probably uh in the in in like in your in the lifetime of your

of your contract it will still be cheaper to have the full code version instead of proxied version so yeah that's pretty much what i wanted to say and if anyone have any questions about any of those points i can scroll back and look at it closer yeah i have a question do you hear me well yeah so why why did they decide to incentivize to go to more a monolithic contract approach instead of uh building blocks like before is it because it seems like a strange choice you seem to

to be a step backward if from the feeling i get from you well the thing is that that wasn't the purpose of the change the purpose of the change was to protect the network from uh denial of service attacks and basically to properly um to properly price the resources that are node operators use in order to basically run nodes uh because like yeah calling the uh the contracts uh is a really expensive operation usually because the contract uh in like country code will probably

not be sitting in your memory it will be stored somewhere on the disk even if it's uh ssd disk and so calling some random contracts in context of transaction is an expensive thing uh i think in the future we could overcome this by introducing accounts which like proxy accounts on the evm level so if we will have some accounts that will be like aliases to other accounts and will not impose any uh gas costs on uh on on like this proxy and logic basically when

you have like a hundred of contracts that use the same code so right now all these hundreds of contracts will be paying overhead every time they are executed but if we have a native proxy account in the ethereum then we could make it free to use aliased contracts and or like make it super cheap because uh they they they could be made optimal on the node implementation and so yeah i think in the future we will overcome this problem problem but for now just uh yeah just try to use as little contract calls as possible unfortunately thank you

i have a new question i'm sure how does how's gas uh calculated like like when you're hovering over stuff that shows like a gas value uh on the search of some of the operations on this ones uh like in the bottom of the editor i had like a gas cost so in coming i'm coming from like a non uh from like the python world where i don't ever think about the execution underneath like it's just that's technically free for me um so is it like per write or read or is there some other yeah well so

there is the whole table of um so and basically any operation that the ethereum virtual machine performs it has a cost or a price uh in um in in the unit of god which is called gas and so like storage operations when you're writing to the well basically to the disk is the most expensive one and uh it's like like 20 000 gas and for instance like putting a variable on the stack will be like two gas so you could you know if they are they are trying to be uh priced properly

i came into the cheap operations are actually cheap and that the expensive ones should be discouraged because they are expensive and so this uh creates incentive for the contract developers to basically write effective and the optimal code interesting i like how there's the incentive uh you're like no developer don't write that code you you need to do it this way otherwise it's going to cost a boatload of money yes yes sweet um i and i think uh antoine are you still here did you have a question um um yes just wondering um you mentioned that anonymous events were

cheaper and um well i don't understand why oh yeah okay that they can easily explain yeah so you see uh the pricing for the events uh are calculated like that so you have um you have 375 per index set up per topic basically per topic and then you have plus i think uh another 375 per 32 bytes of data something like this and so when you don't have a minimus event like in this particular case

if being an event is not anonymous so what you get here is one topic plus 64 bytes of data right but if the event is anonymous then you have zero topics plus 64 bytes of data so you you basically like there is there is no no no topics at all so in this case it will be just like uh yeah like this and in this case it will be like this close to this like there is of course like uh like solidity does some there is some uh additional thinking the event itself

also costs 375 right in both cases and that's that's why it gets up to these numbers yeah okay that's cool i yeah i have no idea um do you if you don't if you make it anonymous can you still fetch it can you still watch it yeah absolutely you could watch all the events well you won't be able to uh like distinguish them by topics and you won't be able to filter them but you could still do something something like uh say if you emit an event with a struct and like some nightly bucket struck and you could just

identify the event well either by the uh by the context like you know that your particular contract uh have events that either emit uh two parameters like 64 bytes or um 96 bytes yeah and then you could pretty much know which one you're looking at just by looking at the lengths of the of the data in the emitted event uh also you could well basically you could add an identifier in the data field and the data field is a bit cheaper than the topics field and it would still allow you to identify the event properly

and it will still be a bit cheaper oh that's crazy okay thanks that's really nice awesome alexi thank you for um thank you for again for the talk um i think anytime we can dig into the code um everyone appreciates it extra much um so appreciate it fun to learn about solidity um coming from a non-uh programming developing uh background for me personally so um