Smart-contract Testing Using Waffle Framework by Bartek Rutkowski | Devcon Bogotá
Devcon·Mon, Oct 9, 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/smart-contract-testing-using-waffle-framework/ Let’s learn how to effectively and easily write TypeScript smart contracts tests suites. We’re going to explore how to create a testing environment, execute transactions, assert balances, state and event emissions. This is going to be the place where developer experience and smart contract security truly meet. Speaker(s): Bartek Rutkowski Track: Developer Infrastructure Keywords: smart contract testing,waffle,security 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] Workshop which title is testing smart contract since waffle my name is I'm also one of the contributors to the waffle open source library and I'm gonna take you through some of the key features of waffle and we're gonna code some stuff together so this is what we're going to start with as the Internet situation is not great everyone that want to go through the tasks with us and I recommend cloning the repository right now and going so in the readme there is going to be a small section that's going to describe what you need to do basically it's just installing the dependencies and doing some few basic steps so yeah I'm gonna wait a second for everyone to do that I am okay the URL is going to be visible also later on so um moving on um there are two basic approaches to testing smart contracts one of which is testing smart contracts with other smart contracts which has its very nice properties but also the other approach is to test smart contracts with JavaScript or typescript code and that's something that we're gonna focus on it has some nice things about it so first of all it's very easy and intuitive so it is very fast which is important if you want to test drive your code or if you want to make sure that the tests are being added to the code as it as it's being created all the time it's also very very flexible I would say even more flexible than testing solidity with other solubility code and it's also kind of the app native in a sense that you interact with your smart contracts the same way your application is going to interact with your smart contracts later on um and then moving on to the waffle itself which is a library that utilizes JavaScript and typescript and it takes a very very minimalistic approach so it's not a very large framework that forces you to follow some certain rules but it rather provides you with some set of utility functions that just help you with the largest pain points also it's blazing past as for the JavaScript or typescript library because some of the solidity testing can be a little faster but as for a typescript framework it is extremely fast it also has a very nice and friendly syntax and it obviously is open source so anyone having any issues or any problems can reach out and also if anyone has any ideas what we can improve then they're welcome to do so um and also what is very important that waffle uh works well with hardcat so if you are already using your hard cut setup then you can use some of the features of waffle as a plugin to your hard hat setup so you can use either waffle Standalone as a complete testing solution or as a plugin to hard cut which is very nice as well we're gonna focus on using waffle Standalone but recommend checking the documentation for the hard cut integration as well and what waffle actually does so it goes you it takes you through all the steps of smart contract development so first it helps you compile your code nevertheless this is Piper of solidity or solidity for most people it's probably going to be solidity if you are team vitalik and you prefer Viper then um yeah you can use Viper as well then you can deploy your code whether you want to actually prepare a deployment script for the mainnet or some other actually working Network or just deploy for the testing purposes and then provides with all the testing utilities like matchers fixtures and smart contract mocks I'm gonna dive deeper into that later on and what are the waffles components it's typescript obviously with type chain it's Mock and chai which are JavaScript libraries for just JavaScript testing but we utilized them here as well and ether's JS which is a very nice library that wraps all the complexities of interacting with the ethereum network so now what the whole process um looks like so first we need our solidity code in this case this is some solidifile called exchange dot Sol which is some smart contract and then we need our configuration for Waffle this configuration that we see here is not actually a needed configuration because these are all the default values so you don't need to create that configuration you can just like skip completely this the step that is this step but mentioning this if you want to have something custom and then we run a command which is just invoking waffle and Waffle will produce the bytecode the ABI which is going to be helpful when interacting with smart contracts and a flattened code which is helpful if you for example want to verify your code on etherscan or a similar similar site and then the smart contract deployment itself which is extremely simple so basically you only need to import the the artifact from the solid from the smartphone compilation and then you need to use the deploy contract function and super easy first argument is who is deploying second argument is what is being deployed and the third one are the arguments for the Constructor of the of the function uh important thing to notice here is that the token object that we are creating here which is the instantiation of the of the of the token is tied to the wallet that deployed the the contract so whenever we're gonna be interacting with the token we're going to be interacting from uh from the wallet so the wallet is like the default sender of the transaction and we can change that by just connecting the contract to some other wallet so by invoking contract.connect we can have some other person some other wallet interacting with the with the contract foreign thank you I think I'm gonna find some charger or someone will help me with that thank you thank you very much uh yeah that's that the computer is a low battery um Moving On Moving On so now interacting with the network so there are basically three pieces of the interaction between the user and the network as a whole so first we have our d-app or a testing Suite or a script and that's the app or whatever else uses a Json RPC interface to talk to to interact with any ethereum node which is like one of the nodes from the ethereum network and we're gonna break down all these like three pieces and how sort of to wrap all that so it's so it makes sense from the developer perspective so first of all we have the node the node itself usually instead of talking to the node or talking to the network we use some API like inferior Alchemy OCT or a default provider injected into the browser by the metamask but also we can use an emulated Network like ganache or or ability VM or whatever else that is just a local institution that simulates the behavior of the ethereum network or we can run our own ethereum node at B and the hero of the community which I obviously recommend but I understand this is not the case for each solution and then when we have the Json RPC level we need to wrap the Json RPC complexities with nicer functions so we have two libraries two main two two major libraries that um that help with that one of them is ethers JS second one is web free.js actually web3.js is older um ether system like the preferred one by by me myself and a lot of my colleagues but just to mentioned about that that there are two of them at least um yeah and what ethers JS does is that basically it wraps the whole management of the keys it helps you construct the transaction so as we see here we have the private key then from the private key we can create a wallet object and then we can add a provider of the network so sort of connect the the wallet to a link to the particular node to the particular API of the of the ethereum network and then instead of just constructing some very bulky Json and creating a HTTP request and sending that to the node we can just use like like a simple call like wallet.getbalance and we'll get our balance and the interjs also helps us override some uh some additional properties of the transactions so with liters.
js we cannot only construct like a basic transactions but also override gas limit and non's value chain ID whatever else and then if we construct the wrong direction this is like the very bottom line and we can just add all these overrides and perform like a very custom transaction and now we dive into the actual features of the of the waffle because right now we are sort of discovering the whole environment that's around around the uh the car of the presentation and what waffle can do on the testing field so first of all we can have like a basic testing so we can just invoke some function or we can have some value and we can demand equality so first equality but necessarily it doesn't necessarily need to be equality we can just we can also have we can also expect the value to be within a range of some values or to be close to some value which is kind of dangerous because obviously in the smart contracts we want everything to be super sharp and super precise but in some cases for example if we are doing some calculations based on time um this is not going to be super precise and you can use something like this then we can test all the events and that sort of corresponds to what what I was talking about um when it comes to the the up nativeness of JavaScript testing because events are not crucial to the logic of the smart contracts it's themselves but very often they are super important from the the app standpoint because we need to be notified about some certain action that happened and we can test that very easily with waffle by just expecting some transaction to emit um an event with some arguments or like emit multiple uh events or whatnot we can also test external calls so in this situation this is a very simple thing because we just call a function and then expect the function to be called but we can have a very Advanced use cases where we have like a contracts calling other contracts and then we can check whether some particular contract within the whole call stack was called by some other contract with some particular arguments which can be quite powerful and allows us to check some of the complex cases of many smart conduct interactions then we have the reverts which are also uh super cool super uh in my opinion syntax is super cool and which are obviously super important to test so first we can just test whether something is reverted but we can also be very precise about the uh reverting reason so the what the what the error is um and also we can which is one of the newest features we can check the arguments of the revert that's one of the newest features of the solidity and we have the reflection of that in Waffle as well then we have wrapper for the token balances as this is something that we test very often so we can check whether some transactions modifies balance of some tokens um on some wallets and so we can do this for just one wallet that we expect some transaction uh to modify the balance of a token overall it of particular value or we can perform that for a pair of wallets or a triple of wallets or whatever or whatever else you can also have mock contracts so mob contracts are just like a artificial things that pretend to be smart contracts so we can set up a mock contract deploy deploy a more contract and then very easily Define the behavior of the mock so basically just say that mock when called when like a particular method on the mock is called is gonna behave in a certain way so this is gonna return something or it's gonna revert we can also specify the arguments and we can just basically uh model any behavior on the on the mock and then the example of the usage of the mocs would be like this so we have the some setup that would retire in contract and a mock your C20 and then the mock we sort of like program the mock to return um to return some value and then we can for example check whether our contract correctly reacts to the value returned by the mock so in the first case that our contract after examining the mock will return false or in the second case it will return true because the monk just returns some value that we expect or do not expect um and the last major feature are the fixtures so this is an extremely powerful thing because normally when we have like a set of tests basically like a good rule of testing is that all the tests should be uh should be shouldn't depend on each other so each test should have like a separated setup we should be able to change the order of the test and everything should still work so basically what fixtures allow us is is sort of reverting the state of the blockchain to some other state from the past so basically we can perform some setup transactions deploy some contracts and maybe perform some initial transactions and then save that as a fixture and then revoke the picture fill the fixture in each test so each test starts with the same state and we do not need to redo all the transactions and perform all these transactions again we just sort of point to that state in the past that we want to sort of start from this from this particular moment and and that that actually is one of the things that make waffle so fast um because yeah and now this is going to be like a first full setup of the tests so uh actually this is this is going to be shown also later on so they cheat sheet when you start coding but here we can go through the whole the whole thing so first we need to import the stuff from chai that we need so we need the expect utility um that's the most important thing we need to type from eaters that defines contract and then from ethereum Waffle we want a deploy contract function we want a mock provider function which is the the emulated Network that we're going to be using instead of the actual ethereum Network in the tests and the solidity which is just a object that contains all the all the custom matches and then we import the contract itself so we inject the solidity matches into the chai itself and then we can start writing our tests so here we can see that we created this provider so we create this emulated virtual Network then we create some wallets in this case only one wallet which is called Alice we create some contract and then in before each setup this time we are not using fixtures we just deploy the contract and then we can have like a in this case free tests that just assert some certain properties of the of the contract yeah and now it's time to it's time to dive in uh so we're gonna have a two difficulty tracks and so first it's gonna be like a beginner track so you can use already uh done a smart contract code that we have in the repository that you downloaded or you can go through the advanced and difficulty track so basically start with an empty smart contract and test drive your code so um this is an interesting technique that instead of just writing the code and then testing whether the code is correct you first write the test see that the test is failing so you are make sure that the contract that you are testing does not have this particular property or this particular function and then after you have the failing test you implement the smart contract logic and then see the Test passing and then do a necessary factoring if you need and then repeat so go on to the next property of the contract that you wanna that you wanna add and what we are going to be working on so the first task is going to be to create a very simple splitter smart contract so a contract that is going to have a split function and when uh and when some if is going to be sent to that function and while copy while the well this function is being called it's just going to split The Ether in half and send to different addresses and it's gonna revert when the value of the if is going to be zero and if the value of the if is not dividable by two then we're just going to refund the reminder um to the original sender and we want to write the test for that and task number two um which is that they're gonna be sort of actually uh quite a few quite a few of them and you can you can do them in in an add new order so first at the proper event when the split happens so just signalize that the split was called with a with a particular result we also want to signalize whenever the non-zero remainder was returned because the um because we have that without we have that function as well and we also want the owner to be the only person that is allowed to use uh to use the contract so the split is going to have a restriction that there is only owner the only one owner who can um who can split the The Ether using the contract and then we can also use a dynamic array of addresses so maybe uh these are not going to be set for good in the smart contract but they're gonna be um but there are going to be arguments the function and maybe we're gonna have like more addresses than two maybe three um or for or just a dynamic number of them and of course we want to create tests for that so and that's like a cheat sheet that I'm gonna stop on so first QR code that you have is a waffle documentation that should help you with everything the bottom QR code is the repository that we are going to be working on on the left you see the list of the tasks that are to be um to be done and on the right you have a and you have a sort of a basic template that shows you uh what's the general idea of the of the test and how they more or less should look like um yeah so if you have any questions or if you need to or you need help with with something then feel free to reach out I'm not the only person here to help I have Daniel chameleon Houston from my team here as well so if you'll have an uh any problems or any questions uh we're here to help you foreign yeah so are there any questions do I need to repeat anything is everything clear okay by the way a question [Music] um were you guys able to download the repository Because the Internet situation is not great so if anyone was was downloading it was yeah okay I see some thumbs up so so I guess there was a success okay cool oh that's uh that's a good point so actually I'm gonna switch from the presentation uh oh nice so here's the repository this is what it looks like so the first thing is the contracts folder here there is The Ether splitter the empty one so if you go for the solid team lamentation yourself then you should start your work here if you want to use the already created contract it's gonna be here The Ether split are ready and with the split function it doesn't have all the features from the task 2 you can add them later on and then the test section so first there's some reference in the in the templates file and the tests themselves should go into the ether splitter and the tests where everything is set up and here you can also see that we use ether splitter so we import the the empty third splitter if you want to use the already created one you should just swap the name here from interpreter to either speed already which basically would be just uncommenting this line and then commenting this one and that would make the change for you okay uh yeah read me that's uh that makes sense uh so actually I'm gonna okay so first of all you need to have the node installed and which I would guess that most of us you should have that installed already and then you will need yarn to install the um the repository if you don't have it then here's command for you to run so we install yarn and then plug in the repository and entering the repository directory and installing the uh installing the repository and then the two commands that you're going to be using more frequently yarn build compiles the the code and the iron test runs the runs proper tests yeah and also some documentations link um if you need something okay so I think we're gonna give you some time now and uh actually I think in a few minutes maybe we're gonna do some live coding if someone's gonna be lost and show you some of the things that are here uh but yeah but for now uh giving you some time so okay so I'm Jen I don't know there's a kind of panel stuff like that apparently yeah since I'm just waiting for an install in the independency so anyhow um uh well I really interested in writing some tests and when it comes to web 2 there are tons of the um methodologies and um tips for writing tests right so um kind of um kind of uh for example this is a writing um kind of given when then pattern or kind of um or PVD or the behavior driven test or that kind of things so that kinds of the things in the web too so when it comes to Webster it seems like the security is also in the important so I think that the um uh the mental model for testing in Dion web3 uh world is really important so I think also I really wonder you guys are thought in Beyond testing especially in the Webster world yeah absolutely so I think that one super important technique and sort of like mental model around like not only testing but the developing smart contract as a whole is is that test driven development uh because uh like when you're like writing a so I'm actually gonna stand up so if you are writing a solidity code it's super important that the contract actually does what it needs to do so that you don't have some unnecessary lines that you don't have any things that were put somewhere and you thought that they're gonna be useful but then at the end of the day they ended up being not useful that all like cost gas and and this is all you know like a bloat that that needs to be reduced so it's so a very nice technique is to actually test drive your contract so actually write a very explicit expectations about your code and Define what you wanna what Behavior you want to achieve from the from the contract and then Implement only the code that that sort of like performs the logic that that does it that is required to to fulfill the sort of to make the test pass [Music] yeah and then you need to go like through the whole stack so so there is sort of no no differentiation like whether you should use unit tests or integration tests or or some other type of tests you probably need to use like all of them so like you like it's the easiest to just test drive your code using uni tests so that's these are going to be the tests that you're going to be writing you know like most of the time and then if you like test drive all the functions test drive all the you know like small bits of the logic that that you need to put in place then you probably want to move on to the integration tests and see how larger transactions how like more complex cases resolve how performing multiple transactions in a row affects the state and sort of like how you know this whole thing behaves and that that's sort of also not the end of the story because then you need to have like the whole or you should have the whole like testing deployment you should play around with the whole thing you need to uh you also probably should develop like the up or the script that is meant to interact with the smart contracts in parallel to the smart contracts themselves so all of like these two like remain uh in sync um yeah [Music] I don't know if there are any other tips that I can think of maybe someone can add something okay actually maybe someone has any any more questions and yeah [Music] oh yeah microphones coming is there Viper support uh yes so basically uh the only thing that so on the for for Waffle the only thing that's uh is different for for waffle and the and the Viper is that you just need a different compiler and then you basically end up with like a the very same compilation products which one is like the bytecode that's just gonna go straight to the to the to the chain and the and the API and all the abstraction of you know like wrapping the the calls with like eaters.js is also you know like the same for Viper and uh and uh and and solid so yes definitely there is a there is Viper support um you just need to use a different compiler which is also like I think you need to specify in the waffle conflict config that you are compiling not solid but you're compiling viper well that should be it I'm pretty sure this is somewhere in the documentation I don't use Viper personally so I'm not very familiar with that setup but yes I preserving this part so uh can you also expand the discussion about testing in solidity and testing outside solidity because that's that's kind of a Hot Topic with other Frameworks like Foundry that they use uh sorry and they have you know limitations or things that are uh that need to be work-rounded what is what is your view on this sure so actually uh I think that the main advantage of of using solidity testing is uh the fact so there are two things okay one is the the pace so the the testing in solid is definitely faster and uh and just having everything inside of the of the evm um just makes the test run faster so which might be important for some people uh and it is sometimes actually important if you want to you know like run the tests very frequently which makes sense and you should do that so this is this is a nice property but then like if you have like some continuous integration system on your repository then it's probably not that important because even like the JavaScript tests are not gonna be that long so that's that's one thing which is which is just the performance which is which is better on the solidity side and the other is uh the fact that when using like typescript or JavaScript test there is this Json RPC middlemen and the there is also like casting of all of the types so for example the when you call a function and then something is being returned this is like a some Json and and the big number that's being returned from the from the solidity or Viper code um is just a very large number that's not able that JavaScript is not able to process so you need to cast it you need to have special types and there were times when it was actually a problem that there were that we didn't have like a good implementations of of big numbers and some people were just casting to Strings then comparing strings and this wasn't great um but now I think that uh it's not that much of a problem anymore of course like we you have all the native types and Native type compatibility within the solidity itself and while using JavaScript to typescript this is sort of external and in some casting so on and so forth but I think this is not that big of a problem I think at the end of the day uh the bigger difference is uh like sort of uh collapse to what you just prefer uh from the sort of developer experience standpoint because I truly believe that both of these approaches can be um can be just very precise and can really you know clean your code from from any bugs or or any or in errors and and are just you know like yeah both very good yeah uh is uh have you uh yourself or seen instances where you're actually testing the user interface so you're basically doing the same UI testing to do because yeah actually yes I think I haven't been reading this kind of tests myself but we definitely still do test if our front ends with a typical like web two tools that even just you know like display the whole thing and then like click into like the particular like virtually like click into like the particular sort of uh components and yes and actually perform transactions though in this case we would need uh something that would just work very fast so we as a ethereum network we wouldn't use like a test net or a magnet of course of course but just an emulated Network yep I might try to expand so we what we do in true file we run the whole local uh blockchain with financial hard hat we also run a local the graph and then we run the front end and click through the front and send you the transaction through metamask on this local network so this is what we do but it's separate from Waffle right okay hey guys did you manage to download an Instagram build Etc yes hit it and how is it going yeah so my proposition is we are going to plug in a different computer and Live code with you and if you didn't manage to download it you can come here and like we can do it together so it's more interesting and some people don't have computers so you can do it with us here how does it sound great yeah so let's start okay so I'm swapping the HDMI to some other computer and we're gonna Live code yeah and let's start with this task one and shamek will you understand yes all right guys so do we have volunteer to start the first task we'll go the harder track yeah so come here and pick a round of applause for our volunteer okay so task one is we have a empty empty contract empty tests and we're starting with writing test for one like functionality of the contract then we write the the function in the smart contract and then the test should pass so some technical problems give us one second so we're basically yes better programming for for now but yeah I'm up yes so we want to write splitter so just a simple exercise we take an intervalue to the function of a smart contract and divide it into two addresses and make sure to return the remainder if it's an odd value so just something simple there's hey guys can you see the text great and it will test the leader function right okay so we are going to start from the first test ready one you just want to see how the function looks like right now it's Peter splitter and empty I guess we just have to call the uh to uh sorry this is called we have an instance of splitter belong so take a look at line 30 31 yeah there's speed already deployed there are addresses to be used here we have an instance of splitter and we have some uh accounts like Alice Bob Charlie and David uh and we have to catch the value right um does it return something no it doesn't so you want to test the balances of uh the accounts before and after two times wrong there um yeah because right now you use the heater splitter which is not implemented take a look at that contract right here um this one yeah right now we use this which is not implemented yet do you want to implement this or do you want to use the implemented one um let's let's implement it okay yeah so that's normal that's when we do like tdd like test driven development and we want to call some function and see the results of the function then we just like have like error and we need to first uh create a signature of the function maybe let's start with just the uh the function let's make the test pass Maybe just make looks nice we can save it and then build right and then run our tests see that it works yeah so we need to run yarn built-in terminal so can you please and probably yeah here we use yarn so yarn build so now we are combining our spark contracts with uh with a waffle and could generate types with type chain and now we can run yarn test to see that our tests are passing let's see yeah for now great it works so now let's maybe here we have like this split and now what we want to see we want to see that as a result of split some balances will change so what should we put here we have what addresses we have Charlie and David as those two yeah so we have Charlie and David these are like random addresses and as I understand we want them to get let's say let's send one a third either to split and splitter and then expect these two addresses to just before so I'd say that yeah so what does the Charlie and maybe David zero before so maybe let's start with just one address this Charlie okay so we can do like proper tdd which is just start with simple example and then go to more advanced ones so right now what we want to do is the first check that the initial balance is equal zero then call this split splitter and split and then let's expect for example this this balance to be equal one let's be ten into five and five right okay with this one right yeah I'm gonna I'm gonna instance a big number for uh that expresses one token so I'm talking um and you still said okay you will need to import big number okay it's there it is actually you don't need big number you can do it differently you can use a properties of eaters you can call no no not like this you can leave it can I show you okay yeah thank you so we can um I'll play this format I always have a format one let's import eaters and now one token should be one eater right but you you probably want to move it higher so you can use it in this split function foreign so these are those overwrites all right yeah this volume the function lesson receiving yeah so this would be this message value and foreign we can just divide right let's start with one just one address okay our terminal is broken our Visual Studio code is broken yes all right okay it doesn't expect that what if I do try to do this book goodness value why is it complaining oh yeah yeah it's because it's not payable in the in the actual code yeah to build we're using actual TV wait so you want to build because we changed the smart contract so we need to build this and then test it I'm used to use it alongside hard hat so it's just one command but okay we're on a red test so now we can implement it nice okay do you want to break or do you want to continue let's continue okay let's go on so we should yeah so we need those two addresses so you can either take them in the spring function or take them in the Constructor yeah um let's keep it simple because we only have right so just so it was Charlie and Bob yeah thank you we're gonna take the two addresses that we split uh in the Constructor thank you just yeah underscore this one uh I'm gonna start yeah so we have a Constructor with two addresses and we'll be splitting the funds into those two maybe you can start implementing the split right away we have the message value so we'll do address one let's transfer or send a message value is the amount that was transferred through the payable function yeah I'm trying to test yeah so this would be half of it right only two okay okay what's going on yeah it's from foreign not yet but it's hanging so we don't have a lot of time but if anyone would like to continue we'll hang around somewhere here we can help it's frozen good he performs it happens okay guys we need to we need to finish um so we didn't manage to do a lot within a short period of time and not very perfect Internet situation but if any one of you wants to continue then we are here like outside of the outside of the room and we have our booth so you can drop by we can talk and we can help you to continue with everything so yeah thanks a lot and see you around the Defcon
Automatic transcript — names and jargon may be misspelled.