New Ethereum talks, every Monday. The week's conference uploads by event, in your inbox.

Loading player…

Solidity Tips and Tricks

ETHGlobalWed, Oct 9, 2019, 12:00 AM

The team from Polymath explains Solidity at a workshop at ETHNewYork. ETHNewYork was a hackathon organized by ETHGlobal and a team of hackers, developers and enthusiasts interested in building and creating in the ethereum ecosystem. ETHNewYork ethereum focused hackathon took place May 8-10, 2019 at New Lab at the Brooklyn Navy Yard. For more information visit https://ethnewyork.com/ and https://ethglobal.co/

Transcript

everyone welcome to my dog so my name is Moses Gupta and I'm a wannabe hacker from India and let's get started so I have some holiday tips and tricks to share with you all so let's start with variables so let's talk about variable locations variables can be stored in stock memory and storage but then there's a basically called data and global variables data kind of special so global variables for example message sender is one of the global variables then called data our variables are basically the function parameters of external function so they are directly stored in called data let's talk about the difference between those so the main difference is the cost of accessing and editing them you can't edit called data function called data variables because call data is generated when the call is created but you can modify the stack memory and storage variables stack is the cheapest of all and that's why like if you have the option always use the stock variables memory and called it I have comparable cost for reading then storage is like way more expensive than any other thing so avoid using storage if you can then there there are also constants in solidarity and they are directly encoded into the byte code so neither they are stored in the start nor in the basically memory or anything that directly hard-coded into the byte code and as I said always try to use stock and for example let's say you can use stock as cashier to basically keep your costs low so for example you have an array in storage and you want to pass all the elements so you create a loop for I equal to 0 I is less than length of the array and I plus plus but if you do that then you will be reading the length of the array for like n number of times for every element you will be reading the length of the array you can optimize that by storing the length of the array in a stack variable and then using that to compare Dylan so basically rather than making n calls to the storage variable you are now making n calls to the stock variable which saves a lot of gas now let's talk about the variable visibility so we have internal private and public internal variables are like generally the most useful ones if you don't need get the information and internal variables can be inherited by different contracts directly edited by different contracts so let's say I created a token and then somebody creates a new token on top of that and inherits my contracts so they'll be directly able to edit the variable so let's take an example of open Zeppelin so open Zeppelin have asset 20 tokens so in the open Zeppelin's contract if the balances were declared internal like they were a few versions before that earlier basically so if those were defined internal then I could create a new contract let's say more the token inherit the ERC 22 hakuna by open Zeppelin edit the name of the token the decimals if I want to so that's my own token and then I can directly edit the balances of users directly implement my transfer functions and do stuff like that but if open Zeppelin were to declare them private like they have done now so even if like with private variables the contract that inherit them cannot edit them so as it stands open Zeppelin has private balances so even if I create mother token extend the open Zeppelin token with that and if I try to edit the balances directly it will revert so it won't allow me basically I can't do that so if I have to edit private variables I need to use functions defined in the original contract so open G airplane has that's why defined internal transfer functions internal transfer from functions internal mint functions so basically I'll never have to directly modify the balance functions I'll always use the functions D basically internal functions that will in turn modify the private functions so this is kind of abstraction concept and if you are familiar with object-oriented programming you will understand why this is a good approach so let's talk about public functions they are different from internal functions in the sense that for the public functions this already compiler will or automatically create a getter function for you so what's a getter function get a function is used to basically read the value of that variable outside the smart contracts so if you have a DF and you want to read the value of your variable basically so you have two ways either you can query the ethereum blockchain directly using get storage at RPC calls and that is a bit cumbersome because you need to know a lot of things for example which storage slot your variable is what type of it is and stuff like that but if you have a get a function defined in the contract then you can just directly call that function for example if my variable name is modded so the data function defined by this oddity will solve C compiler will be also named modded so I can just call my contract that dot method and I'll get that variable the value of that variable directly accessible to me so that makes stuff easier for the DF developers for debugging and first off like that but the downside of that is defining a getter function takes some space so and like on aetherium space matters because you pay for the space if you deploy a contract do you basically have to pay for all the storage space and the storage space is actually limited to 24 kilobytes so if you are making a complex application basically you might run into storage space issues and at that time you might have to like define your public variables as internal and use the ethereum blockchain get RPC storage as rather than decatur functions and another thing to note is that even for private variables and internal variables they are not really private you can always access their values through the a380s Target store is a function so don't ever think that if you are defined a function variable nobody can see that it's never secret on the ethereum blockchain everyone can always access all the data it's just a bit harder to do it then function access types so function access types matters as as well just like the variable types in functions also we have internals public and private so let's talk about internal functions internal functions or functions which can basically be called by the contract and like the other contracts that inherited basically like any outsider person cannot call that contract cannot call that function so if I deployed a contract with an internal function no outside party like any aetherium user or any other third-party contract can call that internal function it must always be called by a function which exists in that contract itself then we have like public functions so public functions can be called by the smart contract itself as well as outside entities so anyone can basically call the public functions there are no restriction on public functions external functions bring in a kind of a weird restriction that you can't call the same contract cannot call the external function itself it must be called from an outside source so like if I have an external function defined in my contract I can't have another function called that external function I'll have to create and basically that external function is meant to be called by the outside world only not the contract itself and but this can be bypassed basically so even with my own contract I can create an external call which will call that external functions this will cost a bit more gas but this is like something which can be done so even if you have external function defined this can be useful like when auditing something or basically stuff like that do you imagine that it's an external function so this contract cannot call it but it can happen that it can be called you can use this which is like a pointer provided by salt see that points to the your current contract and then you can call the function name basically and it will create an external call that will call that function and another difference about these functions is basically how they store their parameters in in public functions all the parameters you pass are copied to the memory so copying takes a bit of gas but not much then we have external functions in external functions the parameters are not copied to the memory by default in external functions we know that the parameters exist in a call basically in call data because we know an external call was meet an external call always has called data so we don't need to copy those parameters to the memory we can always use the call data as called data is available throughout the call basically it won't ever be deleted or anything it can't be modified the internal functions are the cheapest of the lot because in internal functions it doesn't use call data it doesn't copy variables to the memory it uses references to the variables now as we know extra internal functions cannot be called by the outside world so like we don't need to provide them any data we only need to provide them references if a public function calls an internal function then public function has its variable in memory already so you can just give the address of that memory to the internal function and it will directly use those variables it won't have to copy them again if an external function calls an internal function then basically the external function will have to copy those variables into memory for the internal function and then pass the reference to it so then it becomes like the gas usage gas savings goes away but it's still better so yeah that's about the gas usage and let's move forward if anyone has any questions feel free to ask let's talk about packing of variables so packing is one of the things that saves you the most gas energy Liam because as I said using storage slots in aetherium is the most expensive thing one slot costs 20,000 gas for comparison in memory it costs only about 200 so that there's a big Jam 100 times M that's why optimizing this storage is very important and packing of variables come into play when we are optimizing this storage on the VM side D storage is divided into slots of 32 bytes so if you have to store even like one bit of data either 0 or 1 you'll have to buy a slot of 32 bytes at the minimum you can't buy it any less than that and you can stack 32 bytes you can buy 64 128 and stuff like that so what we can do so let's say you need to store five different of data every each data uses one bytes so by default like the normal use case what people normally do normally will do is buy five slots and store the data in every one of them but that is very unoptimized because storage again is very expensive so you can pack those five variables together and store them in one store in a single store it's not so this is done default by default by salt-sea for most of the variables variable types so if you define gluant 8 that's 8 bits 32 bytes is 256 bits so 8 by 256 you can combine 32 given dates into a single variable this will happen automatically if you define if you have defined them in a in a continuous manner so if you say you ain't it a joint 8 B you went a HC they'll all be combined together but if you write your code like you ain't ate a u in 256 B you ain't ate C so you ain't basically a and B cannot be combined because B takes all this space so in this case the solidi code will take 3 slots 1 for a 1 for B 1 for C but if you define that joint 8a you ain't it see you ain't 256 B now Ian C can be combined together so it will now take only 2 slots and you have directly save 20,000 gasp that's how you basically pack variables then one thing to note is that variables in storage can be packed together but variables in memory cannot be bad together this doesn't really matter much because memory is cheap as I said but basically like this means that if you have something in memory for example you are taking parameters in the function then you must understand that those parameters will be unpacked variables they won't be packed this can have implement in implications if you are using like low-level languages and then you pick up the data and say like I only want to pick up 8 bits of data but you need to understand even if your data type is 8 bits you'll have to pick up all 256 of bits you can't just assume that it's packed so that's a bit advanced topic but got something to remember and this also means that you don't need to optimize memory for packing basically because you can't and one thing to note a few developers I've met don't understand like that different data types can be packed together as well it's easy to imagine that you and it can be packed with you ain't it but you must also like grasp the fact that address and doing sixty-four can be packed together and because address is sixty-four address is 20 bytes basically and you in 64 is 8 bytes 8 + 20 28 it's under 32 so it can be packed anything any combination which is under 32 bytes can be packed together now boolean are one thing that are not really optimized in salt-sea a boolean only really needs one bit because 0 or 1 but how salty is coded boolean czar actually you ain't 8 under the hood so they actually use 8 bits now in normal use case that's fine because even that even then you can pack 32 boolean together in one slot but let's imagine you are creating a super optimized applications and you need multiple billions for multiple flux in that case you can define your own boolean which will be one bit you can pack and pack it on your own I have quote sample on my blog for that basically and if you define your own boolean with one bit you can store 256 boolean sin one slot 256 bit 256 boolean you used to need that but now solidi basically evm inherit lee has a bit bit shift operation so you don't need assembly anymore now EVM works with 256 bits as I said storage already works with 32 bytes that is 256 bits but even the manipulation of data works with 256 bits so let's say if you have packed your variables together and you have multiple given dates and then you need to add a U and a to you ain't it so what solidity will do under the background is convert those given it to you in 256 first both of them and then add them together and get a good given 256 as an answer so what this means is that if you are not packing your variables together it's better to just use the 256 bit data a full data type because you will just end up converting that internally to you in 256 anyways so it's cheaper to basically use full data types if you are not able to pack your data types together this is not much of a big difference because it's very cheap to like convert basically variables into types so if you have any chance to pack your variables I recommend that you use smaller data arrives but let's say there is no chance that you can pack your variables if you have only a single variable then it's better to use go in 256 even if you only need 8 bits of data okay there's that then try-catch is one thing I created a while back so testing on solidity is a nightmare debugging testing and stuff like that in most of the other languages do you have triquet so what you can do you can code you can write code in a try block and basically you can see if this code fails then run the catch block don't revert the transaction don't exit the whole program don't crash my windows just clear on the catch stuff and I'll be good to go but in solidly you can't really do that by default if your transact if any part of your transaction reverse even if a sub call to a different contract reverts your whole transaction will be reverted and there's like no we're back by default but we can create our own try-catch wrapper so this is a fun thing basically when a function reports typically whole of the transaction is rolled back as I said but in certain cases we might not want to revert the transaction for example if we are doing batch functions I'm transferring tokens 200 users but let's say one of those users has a bad address or something and I cannot transfer tokens to that user so I would ideally want that I just skip transfer to that user and transfer to rest of the users but by default if one of them fails all of them will fail so we need to create a try-catch wrapper basically around that and we can use we can do that by using low-level calls so that's not really assembly but solid of a slow level calls for example dot called or delegate call and stuff like that so we can use just dot call directly funny thing about using dot calls and or delegate calls is basically if those transaction fails like if that reverse if that transaction reverse the whole transaction won't be reverted the low-level call will just return a false statement false result to you and then you can compare the result like you can say if the result was false means the transaction was reverted so you can do stuff like you can maybe mark that address as malicious or something like that and if the transaction is successful it will return true so you can check if it's true then everything went fine and you don't need to really do anything or you can clean up the code and continue your journey so this is one fun thing but this can be like a bit hard to use and there is no really easy way to implement this but if you need to do it this can be done and it's fun well all the state updates under that low-level call will be rolled back but nothing outside that low-level call back so everything done after creating that low-level call back call will be reverted but everything done before or like after the low-level call has returned it will be as it is okay now I have some miscellaneous tips so mappings and arrays are basically two of the complex data types in solidity so it is like in every other language basically it's a continuous data and same type of data mappings are a bit more useful or funnier in solidi so in with mapping you can say when it's basically key value storage space so every key has a respective value one funny thing about solidity is that mappings are actually cheaper than arrays in implementation usually in most of the languages arrays are cheaper because data is continuous you don't have to seek or seek read or basically stuff like that but how the cost is calculated in solidarity it's that data is always stored in a key value pair it doesn't understand continuous data it's always in key value base key value pairs so basically even with arrays your key keeps increasing by one but it it may not be continuous in the actual physical memory that's why arrays are more expensive so if you have a choice you don't need to like enumerate the mappings basically then always use mappings arrays are for only when you need whole of the keys or basically enumerate the array then we have using fixed ID data rights like bite study do is very more cheaper than using dynamic types like bytes this is because when you use a dynamic type for example string or bytes or an array you need to use one slot to basically store the size of that variable so if you even have like one bit bytes so if you have a bytes variable and you are only storing one character in that even then you will need two slots one is to store the length of the data and one is to store the actual data but instead you can just use bytes 32 or even smaller bites like bytes to one basically to just store the data and like as these are fixed length you don't need to store the length you directly save half of the cost one more like good thing to do is basically use short-circuiting short-circuiting rules to your advantage so from this I mean that we always use and and or or stuff like that in our requirement require statements or if statements or basically yeah other stuff so what we can do so you can always predict which of them will River will be true more often or false more often so let's say I have a statement which says a must be true and B must be true and I know that possibility of a being false is 99% like most of the times a is false and I know that most of the times B is true so how should I optimize this I can either do if a and and B or I can do if B and and a let's condition consider both the scenarios if I do a and and B then first of all I'll check if is correct or not in this 99% cases a will be false so this will just revert this will just like skip rest of this stuff and you won't be spending rest of the you won't be spending any gas in comparing B but if I do if B and and a in that case B is true for 99% of times so you will see base true okay you will move to the next comparison you will see a you will see that is mostly wrong mostly false because like you previously knew is usually false so you will be reverting now rather than basically reverting before so this means you have spent you have literally wasted some gas in comparing me because it was just gonna revert anyway so in stuff like this it's really dependent it really depends on how well you can guess or imagine what these answers or what the use case of your app will be and you can optimize accordingly the function modifies function modifies are super cool and super useful in solidity so what function modifies do is if you define a function modifier then there is a special variable underscore basically which will put cop till the function score and put that in replace that with the underscore so let's say I've defined a function modifier only owner and I have marked my function like sample function as it can be called by only owner so you would imagine that the modified code respective and put in the put on top of the basically some function but that's not the case how it actually works is the code of the function basically some function is picked up and picked put inside the function modify where the underscore is so you can do stuff you can even do check on top of the code you can do checks on the bottom of the code even after underscores you can write a additional require statements and you can do checks on that but this can be problematic because like you are copying the code again and again to the that stuff and like creating separate copies of the code so let's say if your function modifier has ten lines of codes you have doing some complex checks and you are using that function modifier on five code five functions basically so if by default what will happen that five different copies of that function modifier code will have to be created because like every copy will go with copy of a separate function so every separate function will have a copy of function modifiers code that will obviously use a lot of gas you are duplicating the code the whole 10 lines of the function modifier are being duplicated again and again so to avoid this situation you can rather than creating a function modify you can create an internal function and you can do all the checks in that in the internal function and just revert if the checks fail this is not as powerful as function modifier but this is cheaper in most of the cases it's cheaper so how functions work in functions you don't copy the whole code to the other location if you call a function it will basically jump to the location of that code rather than copying the whole code it will just jump to the existing code so there is no redundant code and that's why using function for internal functions is cheaper than using function modifies and this can basically be seen in effect in one of my peers d-link is available on my block I'll share it later so basically what happened I created one of the contracts in which the modifier was being used again and again and again and this is how I actually found about this and the contract port ended up to be 26 bytes and it was a very simple contract like not much code to do and that's when like I scratched my head looked into it why the code size is so big man like I'm not doing it the code looks small but I'm repeating the function modifies so I'm trying to understand what's happening here and that's when I realized that the function modify code is being replicated that every places and then I just replaced the modified with a function code size drop from 26 kilobytes to 12 kilobytes and in solidarity you pay for every byte of data every bit of data so yeah that saved a lot of easier for me okay just ending this now this is a simple like a very basic tip so if you have to swap two variables a with baby with a you can do this directly you can create a double hello comma world equal to world comma hello so this will put the value of world into hello and value of hello into the world you even you can do like even more than two variables and you can swap values between them we make proper use of the optimizer solidly optimizer is pretty good not at the level of GCC but yeah it's pretty good and you should always enable that if you can then use node native salt-sea rather than salsa J's that's basically salsa CSS javascript native salsa is C++ and you can imagine which is faster and I guess that's it thank you if anyone has any questions [Applause] it's yeah it's actually more gas efficient because even with the other methods either you will be creating a third variable that takes more gas or you can do like and and or or bitwise operators and bitwise operators are also expensive in solidarity so this is actually gas efficient yeah I basically like looked into the bytecode and I saw like the same bytecode was being repeated again and again and again and then the process was finding out what that actually was so it took a while but I figured out it was the modifier and yeah okay let's wind up any one way up once and polymath had plenty of them thank you everyone

Automatic transcript — names and jargon may be misspelled.