Quantstamp 🛠️ Smart Contract Development - Martin Derka, Jan Gorzny
ETHGlobal·Mon, Oct 9, 2023, 12:00 AM
Speaker
Join Martin Derka and Jan Gorzny from Quantstamp for a workshop titled "Smart Contract Development". Welcome to back to ETHGlobal Waterloo, where it all began! Returning to Canada's tech hub, we're thrilled to bring together international enthusiasts and local talent for an unforgettable hackathon experience. This year we had 650+ attendees from 29 different countries and 47% were new to Web3! We concluded the weekend with 168 projects submitted and gave away $250,000+ in prizes ----------------- Follow us: https://twitter.com/ETHGlobal Check us out: https://ethglobal.com Email us at: hello@ethglobal.com
Transcript
foreign [Music] guys uh thank you very much very happy to be here my name is Martin I'm from Quan stamp we are smart contract uh security Auditors so you know I'll be talking about best practices for smart contract development and best practices for developing smart contracts and solidity specifically the talk is aimed towards beginners so you know if you are very well worth solid D programmer uh probably you will be bored in here uh but you know hopefully for the rest of you I will be able to pass some good notes so the agenda for the talk at first I'm going to introduce solidity as the language and the evm just so that we are on the same page of what kind of execution environment we are talking about then I will be talking about some specifics that uh solidity as a language has over other languages and then you know I will be covering the best practices those do's and do not so introduction to solidity and evm uh you're at a ethereum hackathon right so you probably have heard the word uh ethereum virtual machine before ethereum virtual machine is a virtual machine that is powering the ethereum blockchain and that is powering the execution of smart contracts on ethereum blockchain it is a stack based machine and when you develop smart contracts uh pretty much what you are doing is you know typing some high level programming language that gets compiled to byte code and that bytecode then eventually just contains op codes that somehow manipulate the data on the stack so we have you know op code such as ADD which adds two numbers on stack we have op codes such as skull that sends transactions we have op codes such as load or write for writing and uh you know reading from uh the memory or storage of a smart contract and so on then the evm uses something called gas to measure how much computation was carried out and builds the users who triggered the computations for that right it's for a very good reason because all the miners or validators in the network will be interpreting those transactions and will be running those computations and we don't want anybody to be running you know computations on our machines for free right so that's why gas actually exists and the actors on blockchain are identified by addresses we have two types of actors one is the general people uh who uh transact through something that we call externally owned accounts and then we have Smart contracts the difference between smart contracts and externally owned accounts is that smart contracts do not have private keys or nobody knows what those private keys are so smart contracts cannot sign or initiate transactions everything comes from people's own externally owned account for which we do have private keys right this is how we have created and generated those accounts so this is kind of the setup that we are going to be talking about now the language that we use to develop smart contracts I already mentioned is called solidity so solid D is an object oriented statically typed language it's similar to you know JavaScript Java C plus plus or anything of this similar category but it is specifically catered towards developing smart contracts so it provides us with data types that we will find on blockchain right those addresses those unsigned ins and bytes and it will provide us with blockchain specific functionality so for example functions for sending and receiving ether can be controlled through Primitives of this language right unfortunately compared to other languages that you might be used to it actually has a limited standard Library so it doesn't contain a whole bunch of things that I will be talking about a little bit uh you know later that you might be used to and it also has many blockchain specific gotchas that you know other languages do not really have or are not relevant in other settings so let's take a look at something that is kind of sort of special to solidity first of all solidity similarly to other programming languages contain some access modifiers that allow us to indicate whether certain variables or functions should be accessible from you know other smart contracts right so it has something called keyword private which if you are used to uh developing in other languages just signifies that external classes or external actors cannot access our data right so this is the first gotcha for solidity anything that is marked as private really means that you cannot query it directly on chain but it doesn't mean that that data is private you know at some point it made it on blockchain using a transaction the transaction can be queried from the archive nodes and any participant in the world is essentially able to derive what the particular private variable currently holds as data so even though stuff is sometimes marked as private it's actually not truly private private only means that you cannot query it on chain it's definitely possible to you know learn what is in those variables uh the next special thing that solidity has over other languages is that stuff that allows us to interact with blockchain and reference the blockchain context itself right so we have context that relates to blocks at any point we can query these uh environment variables that are initialized with the block number time stamp and hash and then we also have uh some environment variables that relate to the context of a transaction and message itself so who was the initial person who sent the transaction what was the gas price that was you know allowed for this particular transaction and for this given message that arrived directly to this smart contract who is the sender what is the value that is being sent along with this message what is the call data and so on okay so this is something that we have available in solidity what do we do not have availables in solidity over other programming languages is floating Point arithmetic and floating Point numbers that doesn't exist at all solidity only has integers okay so that comes uh in question when you are dividing or when you actually want to use those floating points numbers right so all division in solidity will always be integer devotion division so one over two will result in zero and similarly and analogously other divisions will behave and when you need to uh somehow have access to floating Point numbers you essentially have to simulate them right so what we usually do in solidity is that we say okay we will work with really long numbers and past certain point uh all the zeros past this point will be dedicated to representing the decimal positions past the you know floating point right so uh that's something that is very important to remember whenever you work with something especially like ether you are always working with super long uh decimal numbers okay so solidity doesn't have floating another thing that solidity doesn't have is random number generators there is no way for you to go and say hey smart contract generate a random number people really like having that functionality right because we want to develop games we want to develop lotteries we want to develop stuff that actually has some Randomness in it but unfortunately it doesn't exist and again it doesn't exist for a very good reason because we know that the transactions that will be executed will be executed by a whole bunch of Miners And validators in the internet right and they have to be deterministic they have to have deterministic results so that everybody arrives to the same state of the blockchain so therefore generating random numbers is not a thing in solidity so what people very often try to do they actually try to exploit those environment variables that I was talking about a little bit you know earlier so what is my blog hash what is my transaction hash what is the current timestamp and use that as a source of Randomness but that is not really random right and it is also predictable so it's actually wrong to be using that for Randomness and if you at some point require using random numbers in your software development what you need to do is actually use a random number generators in the form of oracles which I will be talking about a little bit later as well so we don't have random numbers and we need to remember that I already said that smart contracts are accounts that have some code associated with them but they do not have private keys so you know another thing that you might want to do but will not be able to do is uh schedule some transaction and have it be initiated by a smart contract that doesn't work smart contrast cannot initiate anything it's always people and externally owned accounts that actually can sign and create transactions on this particular slide I have two words that you know I want you to understand for the rest of the talk so one word is called immutable uh it means that once you put something on blockchain it stays there forever you cannot change that and you cannot delete that if you work with immutable data it will just be there and it will not change even if you work with data that is not immutable and you somehow explicitly allow for your smart contracts to change and you know for these pieces of information to be somehow replaced with other pieces of information the history will still be available as well so you know just the sheer fact that you've put something on blockchain that you've changed it doesn't mean that nobody will ever be able to see what was in that variable in the past the other thing that I want to uh you to understand is atomic execution so what do we have on ethereum is transactions and those transactions are being executed in an All or Nothing fashion and nothing can interrupt the execution so there is nothing like a processor where you would have multiple processes and you know sometimes this guy gets the course sometimes this guy gets the core and that's you know somehow interleaved and and uh preempted that doesn't exist transaction starts running and it finishes all the way to the very end unless it reverts if it reverts nothing ran it rolls back all the state okay so that's the property of the runtime that we are having so transactions are atomic the other thing that we do not have available and we would very often like to have available uh is access to external data so you might want to develop a smart contract that for example provides entrance for delayed flights right and a normal web to developer the way how they would think about such a system is okay well I need a system that will query some API that will learn whether my flights were delayed or not right so sometimes developers try to stuff access to API and internet into smart contracts but there is no such thing it just doesn't exist you cannot query the internet from a smart contracts smart contracts are only capable of querying data that is already recorded in some other smart contracts and so companies and the ethereum community developed something called oracles where oracles are smart contracts designated specifically for this purpose they are made to record some kind of information and then communities and external actors they record this type of information into smart contracts so if you want some specific data you have to find an oracle for it okay and then you can query that oracles API so this is kind of the situation that we are at with solidity and now I will be going through a very long long laundry list of you know what you should be doing and what you should keep in mind when developing your smart contract so the first thing that I want to cover is check effects interaction pattern so you might be able to Google whole bunch of lists with best practices for developing smart contracts uh in solidity and every single list will contain this check effect interactions this rule specifies the order in which you should be making actions inside of your functions so here I have a snippet of code it has two functions one is called bad withdraw one is called good withdrawal both of these functions have some state check right that's the require at the beginning then it applies sum or they apply some changes to the state that the line where I am decreasing the balance of the message sender so in the good withdrawal that happens raft right after the uh required check and in the bad withdrawal that happens at the end of the function and what is in between in the bad withdrawal is the interaction it's the transfer of a balance that is actually making a call all to uh you know the message center right and that actually transfers the execution power to the message sender because when we send ether in solidity uh what gets invoked are some special functions in smart contracts so in the bad withdrawal we are actually not respecting this pattern uh check effect interaction that interaction should come at the very end in the order that I have it in the good withdrawal and the reason why this is really really important is a very profoundly known vulnerability called re-entrancy so what can go wrong if you have effects sorry if you have interactions in the middle of an execution somebody in the middle of that transfer of eth can come and they can make another call to your function to your bad withdrawal and this pattern is designed so that in 99.9 of cases it actually protects you from such you know defects so whenever you are doing stuff whenever you're interacting with other contracts the interaction comes always at the variant okay after you update the state the second item that I want to cover here are pull patterns and push patterns so what people want to very often do is somehow give something to other people to other addresses and accounts on ethereum right so imagine that you have some Treasury and the treasury wants to distribute some Rewards or some nfts or something like that so you have two options either you actually do it actively you are the treasury and you are going to dish out the gas and you are going to pay for transfer of the asset to every single person right or you can adopt the philosophy where you say you are entitled to get something from me but you have to come and you have to grab it in such a case you will not have to pay gas for transferring the asset to every single person right the person came initiated transaction they are paying for the gas on their own so we always prefer pull patterns over push patterns the reason why we actually prefer pull patterns over push patterns is not that push patterns would be way too expensive that's usually the secondary reason but the real reason is that the push pattern can actually fail because if you imagine that you are transferring etherf to a Smart contract to whole bunch of smart contracts one of these spark on tracks can just say no I'm you know rejecting this if I don't want it I'm going to revert your transaction and if you remember my spiel about Atomic transactions that means that nobody is going to receive the ether right because the entire transaction is going to revert and is going to be rolled back so that's the problem with the push patterns they have so many so many interactions that can actually dose your system so we prefer pull patterns over push patterns the next thing that new developers in solidity are very confused about is the difference between assert and require so both these keywords serve the purpose of reverting a transaction if some kind of a condition is not fulfilled but they behave differently with respect to refunding the gas to the transaction initiator so in the case of assert if you are asserting that some condition holds in solidity if the condition doesn't hold your transaction will be reverted but all the gas that was provided to this transaction will also be consumed the invoker of the transaction will not receive anything back on the other hand would require the person who invoked the transaction will receive all the gas that is left back and that kind of drives the difference between the two functions we use assert to make sure that some conditions some kind of state that we are assuming uh holds so you know if you're assuming that your system is in some particular State you check it with assertions and if one of those assertion fails it means that either you have some bug in your smart contract and that's mark on track got to a state where it should never never never work or somebody's actually attacking you right they found some attack vector and they decided that they will use this attack Vector to bring the smart contract to a state where it shouldn't be and shouldn't operate and in such a case is actually very okay for you to burn all the remaining gas for this transaction and say fine we are done we are just gonna revert in here right on the other hand require is used to check results where you fully expect that the result of an external call to somewhere might have failed you know you called some smart contract the smart contract was like nah this is this is I I won't do that and you'll say yeah this is fine this is a perfectly fine outcome maybe I don't know I didn't provide enough tokens I didn't have balance or you know your smart contract just like rejected and that's okay so require in the cases where we are anticipating a possible failure and just wants to say no uh and assert in cases where we are making sure that we are in a good State before we do anything cool the next thing that I want to talk about are modifiers so this is a special feature of solidity as a language you will not find you know custom modifiers somewhere in Java you might find something uh similar in Swift in the form of extension but essentially modifiers allow us to add code that will be executed before or after or both uh to a function here I have actually an example of a modifier I have function non-ranter non-ra and friend where I do something and it's annotated with that word lock right and lock is a modifier that at the beginning acquires a log so it checks that the lock is oh sorry sorry uh a log checks for us that nobody else holds a lock right and it requires a lock then I have that underscore and semicolon which says execute my function and then in the end we have release the log so those are modifiers what people started doing is uh the modifiers they are originally intended for checking stuff right so there's this that would be where you do your uh checks in the checked effect interaction but people actually started stuffing a lot of logic into these modifiers we see it very often in audits what it results in is a spaghetti code if you go back to your you know intro to programming courses and the languages that contain the go to statement you've probably heard many many times do not ever use go to because like it just you know breaks the flow of the code and it results in spaghetti same thing happens if you start stuffing the modifiers with too much logic the example that I have in there this is the only acceptable example of where you can put some right logic and some side effects into a modifier there is very little of it it's literally three lines of code so that's probably roughly the maximum and the effect is not permanent right so this modifier itself it locks and then unlocks so uh it's predictable so do not put too much logic in your modifiers uh I'm going to skip this slide because I want to get to some more interesting stuff so you know for this slide only remember if you want your smart contracts to be able to receive ether you have two options you either can Implement function called receive or function called callback or both of them and there is a difference when these functions get invocated so look it up in the documentation if you do not know the one gotcha that I am going to definitely mention in here is you can never assume that your smart contract has zero ether balance even if you do not Implement these functions so even if you say hey my smart contract can never never accept ether it is absolutely possible to force send ether using instructions called self-destruct so never make an assumption that your contract has a zero balance unless you actually check it directly in the code okay uh here is a slide about pragmas so pragma declares for us what kind of version of solidity we are going to be working with and what kind of version of compiler we are going to be compiling these high-level words that are very similar to English into bytecode right so that's that like line that I have there in the example underlined we can declare pragmas in two possible ways we can either declare a pragma that is fixed version that's right here 0.8.15 or we can put that like little carrot character in front of it or the greater sign right which allows floating number uh floating pragmas which essentially mean that I will be able to compile this code with anything that is 0.8.
15 or higher okay so best practice here is always log pragmas of your smart contracts and the reason why you should be locking the pragmas of your smart contracts is the software development life cycle so imagine that you're developing right and you're working on a big project and you're done and then the rest of your team is not done and you are just waiting for one to two more months for the rest of the team to be done and then maybe audits to happen right eventually somebody is going to come and will wants to deploy your code on blockchain but within that time span those two months three months however long this took it's totally possible that the compiler has changed right that you know two new versions actually came out and so then when somebody starts deploying your smart contract it's totally possible that they compiled it with a different version of the compiler then the version that you actually have tested it with so the code that ends up on blockchain is different from what you've tested and that might be wrong and yes compilers do have bugs they get fixed all the time but the way how you should keep up with your pro uh with your uh compiler and have it be up to date is by declaring fixed pragmas and then updating them deliberately every once in a while uh I'm still running out of time here so uh let me maybe mention something about timestamps I had a lot more slides but we will not be able to cover that that's fine so uh using timestamps in smart contracts for any kind of logic and decision making is an anti-parent when we were in the ethereum proof of work situation uh we would have multiple miners right they would be trying to solve the proof of work puzzle and at some point they would be successful and they would start propagating these blocks throughout the network through something called gossip protocol well the gossip protocol it's a peer-to-peer communication protocol it has some latency right it takes time for me to produce a Blog and send it all over the world to the other side so this network it admitted tolerance in timestamps to roughly you know 15 seconds so me as a miner I would always be able to push the timestamps a little bit to the Future a little bit to the Past whenever it was convenient for me now this is no longer the case because since ethereum transitioned to proof of stake I am not able to do that anymore we have a fixed interval of 12 seconds where every single block has to fall into right so timestamps are predetermined but there are many chains in the world that still do not have predetermined timestamps because they do operate on some kind of a proof of work system and there are Forks of ethereum so never assume that timestamps are not manipulable and the good rule of thumb is you know think about also tolerance for yourself think about those 15 or even 30 seconds if you're smart on track can operate with 15 or 30 seconds tolerance then your usage of timestamps is okay if it cannot and if you required the time that it got reported is absolutely accurate then you're doing it wrong okay so very careful about timestamps uh all right looking at the clock 10 seconds left I'm gonna stop right here uh if you have any questions please shoot them right now or find me or Yan sitting in the back after here at the hackathon we'll be happy to help
Automatic transcript — names and jargon may be misspelled.