# Thinking Like an Auditor to Develop Safer Smart Contracts by Dominic Bruetsch | Devcon Bogotá

- Speakers: [Dominic Bruetsch](https://streameth.org/speakers/dominic-bruetsch)
- Channel: [Devcon](https://streameth.org/devcon)
- Date: 2023-10-09
- Duration: 47:21
- Topics: Science & Technology
- Watch: https://streameth.org/watch/yt-zzpMvNvhAaw
- YouTube: https://www.youtube.com/watch?v=zzpMvNvhAaw

## Description

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/thinking-like-an-auditor-to-develop-safer-smart-contracts/

Since 2017, ChainSecurity has audited countless smart contracts. Based on this experience, our experts will present a methodology for secure smart contract development.

During the workshop, we will coach attendees to think about their project like an auditor would, to help them develop safer smart contracts using foundry and forked mainnet tests.

Speaker(s): Dominic Bruetsch
Track: Security
Keywords: security,solidity,defi

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] you should have good specification on what you want to implement for example this can be a white paper so you know what your project will actually be doing right and next you're gonna model how you wanna implement the different parts of your system here the first step of security starts because what you should actually be doing is you should develop a threat model listing risk possible attack vectors and possible mitigation of different so how different actors May trade your system and already at this stage think about what can go wrong or How could an attacker attack your project this is similar like in traditional information security that is stride attack models or many more which you can use to think with this you can start modeling your system and then start implementing it but please don't just implement it implementing and documenting what you're implementing should go hand in hand ideally you start documenting what your system does what your function do early and when you're writing code when you're writing a function you're at a very low level very deep and very concentrated on this very function making it efficient making it nice making it safe but you risk losing the whole picture writing documentation helps you to place this function in the big picture and gives you another angle to look at your project and how this function interacts with multiple parts of your system and where project should arise so writing code and documentation should go hand in hand unfortunately we see it often with our clients that they write documentation as one of the last steps and sometimes then the documentation simply describes what they see the code is doing right and then it's not really helpful for us as auditor because reading the code we can do this ourselves hmm so I understand writing documentation is really boring and stuff we won't cover this in the workshop today if you want to see a perfect example on how good specification and documentation is done I point you to UNI swap they have a very nice white paper which contains the specification what the system does and in addition to that they have very nice documentation on what the implementation of the smart contract does what the individual function expect is input what they do and what they have as output so I recommend checking out this for your project what we're going to focus on in the workshop today is testing your project unfortunately that's auditory seen it often that projects are not really tested and testing covers lots of things so sometimes we need to guide our clients and point them to links on how they can efficiently and meaningfully test the project so we want to give you some pointers and some initial training so you can get it started with your own project this will include unit tests property-based testing forked mainnet test at the very end we'll talk about further things you should consider to get your code in an auditory State and even after the audit is completed once you deploy your code to mainnet there are further steps you should take to ensure the continuing security of your smart contracts yeah that's the only slide covering specification and documentation as I said you should tackle this early on I put from the threat model we discussed you should think about invariant things that should always hold things that should always hold when the system is in this state and new invariants when the system changes into another state this can help you find bugs early and the last point this is not only to have nice documentation for the auditor or for you during your development process no the documentation is also critical for the users of your project so they know what they interact with for other D5 protocols that interact with your protocol they need to rely on your documentation that it's extensive and accurate when they integrate with your functions they mostly refer to your documentation and this is also reason why you should have good documentation to avoid problems when third parties integrate with your system so in the next part of the workshop we are considering a smart contract example and based on this example we're going to step through the workshop the example is a simple fund it's a smart contract that holds funds in the form of erc20 tokens users can buy into the fund by depositing the underlying token of this fund and they get pool share tokens back and there's a fund administrator who can use these funds for trading with uni swap are very compound the goal is of course that he increases the value of the fund at a later point the users of the funds the holes of the pool share can withdraw their shares and hopefully made a profit of course it's a toy example and the code in the repo is for this Workshop only shouldn't be used for production because I'm very sure there are tons of bugs in it so in our smart contract we will have the users the fund owner as the two parties interacting with that let me switch to the code as it's important to get a good understanding of the code before we think about the properties and write the test to avoid confusion yeah this code is all in the GitHub repo I shared earlier with you interested to follow along so here I opened our code sorry that's already the test that's the fund so it has a lot of code to get it going right is our fund we inherit from sc20 so we have our pool shares right built in we have some admin staff or stuff to attract the tokens I will find supports we'll take a fee it's three percent so as we it's when we deploy the fund we deploy an underlying asset this is what we will use for the fund's value calculation and we'll add the token as supported asset the owner can add further tokens the order owner can remove tokens both functions are not really relevant for our test cases each token needs a price feed so that its value can be evaluated here we have deposit and we draw these are the two user-facing functions and these are also the two functions our tests gonna cover so let us quickly go through them so we have a good understanding we deposit an amount it gets transferred from the user we estimate the total value of the user we need to handle the special case for the first depositor separately as the funds doesn't hold any value back then and we would have zero value so the standard case after the fund is live and holds fund is the else Branch we calculate the total Supply calculate how many pool shares to mean for the user and how many pushes to mean for the admin fee will go in more details here shortly with the test withdrawing Pusha is basically the opposite you give up shares you hold and you get a proportional amount of tokens transferred to you obviously your pushers are burned then there's a manage function which the admin can use to trade with the funds tokens but it's not going to be used in this Workshop down here we have a functions to estimate the value of the fund yeah lots of information a quick time frame and I hope you have the code in front of you and should any questions arise during the tests we can always go back to the code and let me switch back to the slides Let's Get It full screen perfect yeah so most important points of our funds is its interface because that's what we're gonna interact with in the test everyone can call deposit and redraw only the owner can manage the fund at Price fits and add tokens foreign implementing a smart contract it should adhere to some specification right in our example the fund should have a fee of three percent and this fee should be taken when the user deposits money and gets his pool shares at this time three percent of the value should go to the owner and the owner another property is the owner should always get this fees when a user deposits no user should be able to deposit without paying fees to the admin another important property we want to have in our smart contract is that Redemption for user always worked and cannot be blocked let's first start with testing and there are different kinds of tests in your smart contract you have small internal helper function which may also be public like our estimate function which estimates the value of the fund you have the buy shares functions the deposit functions which is a bit takes more steps right and ideally you have tests on every level you test do you need tests for each function you do integration tests for processes that include multiple steps but in the end you should also like test your system like it is in production and with smart contract the production environment is the blockchain right you deploy it into the blockchain and there it interacts with other existing smart contracts so you should properly test it integrating with the smartcock tracks on chain so you don't get any negative surprises or any incompatibilities or anything let's see what we're gonna discover with our contracts with this let's go to the first warm-up exercise and in this Workshop we're gonna use Foundry there are lots of Auto grade tools available which all work similarly for example brownie or truffle you for your project or hard hat you may use whatever you like yeah the same basic concept we are presenting here apply similarly so for the warm-up exercise we should want to write a test case which are certain that the user's pays three percent fee to the owner and if the user redraws daily gets 97 of the value so if the user deposits and withdrawals immediately he should get 97 of the value back because the other three percent is the fee for the owner yeah so let's jump into the code we are back into the code editor and we move from The Source directory into the test directory so if you want to follow along you can open the integration test file how Foundry works you write your test files in the test sub folder and you call them integration.t.sol and it's basically solidity so as you can or as a developer you're familiar with solidity no need to learn another language or anything else you can just use what you're used to solidity and continue you inherit from test and you have all this fancy stuff available which allows you to impersonate another account distribute E4 to another account move in time or whatever you need in your tests and with your test you cannot simply test your function as is right it's a like a blockchain you first need to set up the state meaning in our case we first need to deploy our font but deploying the fund alone is insufficient we need to also tokens the underlying tokens of fun interacts with the token the user deposits so we first need to build up our environment so that's what the function setup does in Foundry we deploy in a new wrapped e for token we mean one token so one is to the deployer we impersonate the deployer address which we've just taken a default address up here we deploy our test fund we convert our ether into wrapped if as our fund works with es20 token the owner deposit so the fund is already initialized and holds some value and at this point we have the fund up and running ready for testing now we also need users interacting with the phone right so we give some money to Alice also in the form of E4 and down here also to Bob we impersonate Alice deposit the if as rap defer and approve the fund so we can easily call deposit and he can transfer from so after setup we have an environment set up where a fund is live and both addresses of Alice and Bob have rep div and have already approved the fund so we can easily use them for testing and writing a test is really simple and remember what we wanted to write a test for we wanted to see if the code of the fund we implemented adheres to our requirement that when depositing three percent of the fees go to the admin and that the user can withdraw at all time so of course this is like the high level statement we need to make this a bit more Concrete in order to be able to write a test case and the test class we want to write is if you deposit and immediately withdraw you should get 97 back right so that's what we want to implement and it's really easy we first impersonate Alice so don't worry this one is already pre-coded but we will have two more examples with kind of live coding so we impersonate Alice we query her balance we use her balance we deposit this no sorry we get Alice's balance of rep div but we know that from the setup that it's a unit so we deposit everything and adjusted everyone know Unis stands for 1 to the power of 18 so one if and which has 18 decimals as well right now that you get confused here so we have Alice balance up here we deposit it we check how many shares of the fund Alice has we we draw it we check Alice's balance afterwards so now we did our actions right and here we write our condition we want to assert we want to assert that the Alice difference in balance is equal to the C she is supposed to pay for depositing one unit of her share tokens and this should hold if we implemented our smart contract correctly so with this test case uh let's run it um sorry so what I'm basically doing here with match I just take which test case to run it runs the test but it unfortunately doesn't pass reason the assertion failed now we can tell Foundry to give us more variables output to figure out what went wrong and minus V you can go up to five it defines the level of verbosity the output should have so now we see the reason is that the assertion failed we should have paid three percent so she deposited one that's three percent of 1 to the power of 18 but actually what Alice paid in fee is a bit more so either our test is written incorrectly or the smart contract has a bug let's quickly go back to the test the test itself looks fine right we deposit we redraw we are certain that we didn't pay more than the fee so let's go to the smart contract what happens up on deposit we transfer Alice's money first to the smart contract and only afterwards estimate the fund's value and based on the value with already the funds of Alice transfer to the fund we meet the shares and this is incorrect as tokens which it transferred to the fund shouldn't be taken into account into so if we transfer the tokens from Alice first to the fund the value of the fund already increased but we haven't issued shares for this increase yet right so the shares the existing Shares are overvalued and Alice gets less shares and then can only redeem less and doesn't get enough so the solution is we need to estimate the value of the fund first only afterwards transfer Alice's token to the contract and then issue the shares based on the amount before Alice transferred her funds we did this we store this and now let's see if what happened to our test and as we see writing a test case while developing our smart contract um helped us uncovering a stupid bug which otherwise would have remained in the court until maybe the auditor would have found it way later of course this test just shows that it works with these values we passed it's no guarantee that it works in general with all values so in the next step I want to talk about property-based testing this is like random input test this is similar to fuzzing I guess it's a terming issue because fuzzing is mostly seen as Black Box testing while property-based testing you call it when you do randomized input when you know the code you interact with so Foundry allows us to test a function call with a random value and see if our assert condition hold for all these random values we check we still have no guarantee that it works in any case but of at least we have a way better feeling that it works with big numbers which may have triggered an overflow somewhere it works with small numbers where maybe rounding issues lead to failure it just allow us to cover a much broader space of potential values and we have a much higher chance to actually detect issues on our smart contracts yeah this is basically what describes the difference up we have the unit test case where we just test one fixed value the example we just did below we have the property-based testing where we write the set of action depending on a random value where we assert something and we just tell our tool to test it with many value and see if it can break the condition yep so for this example we want to try to work on the condition the owner should always get fees when user deposits so back to the code still in integration and we want to test that the owner gets a non-zero fee so the rough idea behind our test case is we safe store the owner's share balance we let the user deposit we query the owner's share balance again and it should have increased right after a after a user deposited because disassigned fees to the but I just saw that I've already implemented it forgot to delete it so yeah codewise we query Alice's balance sorry now let me delete it and let's not worth it okay Okay so let's consider this part first we created that's the code for storing the fund owners balance before then we impersonate Alice Alice deposits we create a fundamentals parents afterwards we checked that the difference is bigger than zero of course this is a simple demo test case you could actually expand on this condition and check that the expected amount has been received but just for demo purposes and Foundry will use random values here but based on the full range of the unsigned integer which is way too large for actual valid amounts of photo combatants is right and especially in this case we're also constrained by Alice's token balance so we can tell Foundry to bound the value in this case we bound it to the up to the balance Alice has because if we exceed Islands Alice's balance and try to deposit it obviously won't work right so let's see how this test works and what we see here this one passes now and actually while developing this Workshop we had a minor Buck which we first didn't realize only caught it in testing and we see here for demo purposely only did 256 run right with 256 random value but didn't find any breaking value you can create tweak these numbers for actual project so this gives you already a way better feeling that your code works with lots of different values instead of just with one but still not good enough right sure yes no specific reason I mean yes absolutely correct yes you need to get more elaborate here and for an actual project you need to think more about what is a meaningful test case and tweak these values good catch yeah good actually I don't know but it definitely makes sense to text the edge cases the 400 doesn't do it by default they definitely should be part yes test deposit yeah good question yeah yes I agree you probably should use the expected value to avoid errors there yet basic test is that you need to write the dealership yes admittedly writing good tests takes time right you need to design them properly you need to implement them properly and you also need to see check that your tests are back free I remember a story once we audited the system after two weeks of audit we found a broken functionality while manually reviewing the code but we're really puzzled why is this code broken because they have a test case and it seems to work only later we realized that the test case was broken and the small contract was actually wrong so tests help you but don't guarantee you that it's Error free yep and now the best of all mainnet test your D5 project won't be Standalone it will interact with many tokens many different defy systems even if you took great Care Reading their documentation reading on how the special behavior tokens might have you should still test as like in production as close to the production as possible and lots of tools out there now enable testing against Forks of the mainnet what this basically is you run tests against the local copy of the real ethereum Network so you integrate with the real Ave with the real uni swap you use the real usdt code and this helps you detect more Corner cases and other unexpected one big Advantage this helps you to uncover is to help the tech undocumented and unexpected behavior of protocols which may break your integration again we have the example that Redemption should always work and let's see if it actually works in our code or not for this we go to the Fork mainnet test file and what we want to implement is a function test USD and I give you 30 seconds to quickly think about how you would implement this I I just deleted it you probably saw its three lines of code give you 30 seconds to quickly think then we'll gonna do it together we want to test that withdrawing always works and we know from the okay in the integration test we had the setup function right we deployed our own grabbed if token we deployed our own fund and everything we deployed our own because we had to build the whole environment ourselves and if you note the Zapped up function here is different we don't deploy our own tokens instead we interact with the real wrapped e for token on chain and the real usdt token on chain and the real chain link price feeds um Foundry is gonna do a local copy of the current mainnet state of this contracts and their state locally which our tests will run again so we're not deploying a default mock token contract but we're running it against them so we are basically again testing um the setup function still deploys the fund right and the fund owner deposits his one wrapped E4 so at this point where our test starts the fund owner already has shares of the fund and can simply redraw and we simply want to test if we drawing works so let's go to the implementation and first we need to impersonate the deployer it's hard to hold the microphone and type that's how you do it next we wanna see so our withdrawal function takes an argument the share amount right so we need to see how many shares we have like foreign balance of front shares and now we actually try to withdraw and do you think it's gonna work because you've seen the code of the fund right it should work the withdrawal function looks good have you checked the withdrawal function is it going to work you would need to add your own API key here and I have it as environment variable work last chance ah guess the internet is not good so I need to order a hot spot here yeah so if we need to download many data we need internet and I forgot to spin up my Hotspot in time which hopefully will allow us to yeah couldn't get no I also lick my key so I'm gonna need to revoke that one damn I especially did it in the environment variables in order to avoid this but okay ah thanks no I'm not connected to my phone that should work yeah okay if it doesn't work I'm gonna have to explain you yeah fortunately no chance so actually the surprise is as we're now running our code against live on chain code and we're integrating with wrapped if and usdt and the rep transfer will work as expected however usdt doesn't really adhere to the esc20 standard in the sense that their transfer from function doesn't have any return value and our code of the fund actually depends expect the return value here on esc20 dot transfer the interface specification of esc20 expects a return value so it's solidity so if that we don't get a return value back and our transaction will revert and this is a well-known strange Behavior case of the usdt token which have led to problems in real life and every developer integrating with this token should be aware of and this should also serve as an example even if you have expectations that you interact with legit esc20 tokens there can always be weird Corner cases or weird unexpected behavior and that's the beauty of forked mainnet test and that's why Fork maintenance tests are absolutely essential to have with your project yeah of course but then it takes a long time to set up your local test environment when you need to do the real stuff always and the alternative doing fog main test is so easy so straightforward investment yes yes true true but you need to carefully inspect everything and it's hard when you don't know the bug in advance right how to look up what you need to look out for yes please yes yes yes that's what didn't work now because I don't have internet connection we would have quite Alchemy to give us the state data we needed for our tests yes it's really these lines of code you give up you point them to an address and it all every does everything you don't see how it works it's easy and simple to use so you download the state and based on this date you start evaluating your stuff right you start progressing the state and I guess there you do dynamically generate your addresses you can't right but we've specified the addresses of the contracts we interact with yes yes you need to see the right that's what we list up here and then use finder yes yes you can specify the block so if you don't specify one it just takes the latest one but you can also specify a previous block yes cool um that was the interactive part of course this only covered multiple tests in case there's way more you can do and what we didn't talk about you should have proper development process right this includes code reviews you do merge requests another one member of your team reviews it gives you feedback you discuss you have proper coding standards and guidelines you adhere to and important to note not all bugs can be uncovered by tests if your tests pass it's never proof of correctness there can still be bugs because you didn't think about test case that would have triggered the bug and formal verification of the code can bring you to the next level but again doing formal verification you need to come out with properties and specification if you don't write the pro pro specific specs you may miss out on other bugs and what's absolutely necessary is to have a external to have an audit of the code both internal and external ideally to get the code into an audit Readiness state after you've done all of this after you're done with your project it shouldn't be shouldn't go directly to the auditor instead the internal team should take a step back and audit the project themselves and this already helps to uncover a lot of bugs and then in the next step give your code to an audit company which is going to do a total review give you an intermediate Report with potential issues and then give you time to address this discuss potential problems and how they can be mitigated after that your code should be ready for deployment but that's not the end of the road right even after deployment even after you did everything perfectly before and you should monitor your system especially if it's a D5 system right depending on finances and stuff check if it's healthy in a financial way or if unexpected Behavior but this is very Project Specific but you should look out for and a bug Bounty is a very valuable uh addition for your project so if there is a bug that has been missed it gives incentive to the writer to actually report it to you so they can fix it in time and approach that we didn't talk about here you could what we've seen from clients is you could also implement it as testy driven development so you don't start with the code but you write the tests and then you write code which make these tests pass and you approach it from this way we've also seen this but very rarely and then this yeah stateful testing there's way more you can do here you but then I need to refer you to the internet or invariant based programming yeah that would conclude my workshop I'm here open for any question open to discuss your project also if you see us walking around at Defcon don't hesitate to to us if you have any questions thank you any questions yes foreign okay um so you you seems to you are you're using the deployer you're you're using three addresses deployer Alice and Bob yes these are two three and four uh yes so Foundry provides you with some default addresses which you can just access with two three and four okay they resolve the proper addresses yeah this but but these are pre-compiled contracts no these are just random addresses you can add funds to but I mean in the on the on the AVM these are pre-compiled contracts no they're just like EOS you're right that zero X1 is a pre-compiled contract but this is something completely different this is Foundry specific this results to such an address okay such a long address as you used to ah okay okay I see yeah because otherwise if you yeah you're right pre-compile contract but that's something different okay okay great um the other thing I wanted to ask is so you you have a test that asserts that in the same let's say in the same block if you deposit uh you will get like if you for instance if you deposit and we draw yeah you will you will get the same the same amount of shares or less yes because of the fear yes so but what if you what if something happens in the middle and you don't get the same right how you can because it seems like everything is it's one transaction specific and you're not going to you're not going to uh assert about uh things that can happen in the middle yeah no we are not asserting so in this test case we write these tasks are Atomic right and of course you are right in the real world if you deposit first if another action happens in between and the fund value changes when you redraw then of course then the value doesn't add up anymore all right all right but yeah if you need test something like that how you can do it in this in this context because you you can still do it like this right but you need to write these actions in between yourself or somehow come up with how you can simulate them but you don't want to write the actions right you know you you want the faster to be able to simulate them without needing to write the actions because you don't know what can happen it's a very good idea yeah but it's probably not in the scope of this testing you probably need other tools enabling that I'm not sure if it's possible because I I think Foundry has a invariant testing but I haven't tested yeah there's way more there's also great documentation on Foundry which you definitely should check out okay thank you thank you please so most of the time for King for mainnet you need during your setup function and then it's basically done then if the tests don't themselves also fetch new data should be fast otherwise it guess it depends on the Node you connect to yes it really depends on the individual application what quite clever yes please we're running out of time it's one part of but it's not the Holy Grail right it's not enough yeah yeah I mean any auditory part of a reportable firm should make it clear in the executive summary or somewhere that it's unsafe right yeah so you need to read the audit report carefully to figure out if there are doubts yeah I think there are other companies which are way further in this way like runtime verification and certificate I mean they have a lot of talk here which I recommend to attend yes are there other advantages of testing in solidity other than just not having to use a new programming language versus testing in like JS or something it's just simple for the average developer right if you need to do this JavaScript mocha JS test with the icing create it's way simpler here for the average developer cool thanks a lot everyone [Applause]
