Secure development of smart contract systems

DeFi Security Summit 2024 · 7 Nov 2024 · 58:04

Elliot Friedman of Solidity Labs sorts smart contract systems into immutable, governance-minimized and governance-heavy types, then lays out a security stack from unit and integration testing to fuzzing, symbolic execution and formal verification. At DSS 101 2024, attendees look for subtle bugs in intentionally flawed code and see how governance proposals can be checked by printing call data and state diffs.

Speakers

Topics

From DeFi Security Summit 2024, hosted by DeFi Security Summit.

Transcript

okay hello everyone I am Elliot fredman and today we're going to be talking about secure smart contract development um I'm the founder of solidity Labs we're a boutique consulting firm and uh we build smart contracts uh for our clients and uh we have a very keen interest in security and so really excited to share um some of the stuff that we've been working on with you guys here today okay so hi level uh what we're going to be talking about um first we're going to talk about the different types of um smart contract systems and the types of governance that that come along with them we're going to go over the

security stack which is the types of tools and testing methodologies uh processes and procedures that we can use to find bugs we're also going to go over blind spots and how they emerge and how we can remedy them um and that will be around where bugs are commonly missed and then we're going to do an exercise so this will be interactive where you guys will be able to pull down some uh some code um and uh use use some tools to to find these bugs okay so it's it's important to first start off with categorizing the

different kinds of smart contract systems so the first kind uh of system is immutable so this is one that has um no admin Keys No upgrade ability um there's you know very little there's not going to be any tooling around governance because you know there there is nothing to govern there's no trusted third party um a good example of this would be like amm contracts that don't have any fee switches so you could talk you could look at the unisoft factory contract you could look at router contracts where you know different actions are bundled together then we have governance minimized and so um these uh these these

systems they have a few knobs and dials that governance can turn um you know you definitely would need some tooling around here to generate your proposals and safely parameterize the system so you could think about like the fee switch on Unis swap or um the fee switch on Moro or Oiler where they can set the fee um but then the rest of the system is immutable so you you can't actually upgrade the smart contracts the Third Kind is a governance heavy system so in a governance heavy system the um the governor the governors or whoever this trusted third party is they can upgrade the contracts they can

change the system parameters um and to do this safely there's you know there's a lot of times there's sharp edges uh with these kinds of of code bases so uh an incorrectly parameterized uh system is um you know is can cause a lot of money to be to be lost so lending markets I think and rollups are really good examples of these kinds of systems because uh you know there's a lot of knobs and dials there's a lot of like different delays or you know collat factors uh interest rate models um that have to be set and these also tend to have upgradeable smart contracts where the admin can can come in later and and

make changes to things okay so now let's talk about the security stack so um the most the hardest to find um I guess the most pernicious bugs they're they're always the ones that um no one is looking for and when you're building uh contracts a lot of blind spots will emerge because um you know let's say you're writing a new amm and there's some really complicated math formula so you're saying okay let me make sure that okay the math here is like bulletproof it's super solid there's no issues and so you spend all

your time writing tests to make sure that your math is good good um and because you're doing this um uh you know a simpler bug might just kind of slip through the cracks because you're spending all your time uh on this one thing that you think oh man this is like where the bug is going to be um okay I guess it just skipped head here okay so there's um I don't I don't it's kind of skipping ahead now um okay right so then also let's say

you're writing a uh lending Market uh smart contract then you know you're going to be spending a lot of your time on um liquidations or reward logic or interest rate models um and then there's going to be other parts of the system that you're going to spend less time on and again that's another place where um blind spots will will manifest so we can think of um security for smart contracts as a stack so we have all these different kinds of tools and techniques that we can throw at a smart contract system to try to find bugs and at the very bottom of the stack we can see um we can see the cost of finding bugs so uh we can

write fuzz tests unit tests integration tests um we can run static analysis and these are the this is like the cheapest way to find bugs so you should invest in making a really solid test Suite because that's going to you know that's going to let you you find all these things um and find the bugs as as cheaply as possible so once you've exhausted like the really easy way to find bugs um you're going to go and say okay well how let's let's get a little deeper and so then you'll be going to something like um symbolic tests so that could be um helmos that could be hvm there's a lot of different tools that allow you to do

symbolic testing so that will you know give you certain um that will tell you certain things about about the contracts that maybe your fuzz testing or your unit testing wouldn't find then you can go on to you know formal verification and you could use a tool like the Sor approver or uh kevm or control and those would would you know find really really hard to miss bugs um but they're definitely a lot more expensive to write a extensive um Suite uh formal verification it's like specification of your smart contract system than you know other other testing methodologies we also have code reviews and code reviews are really great for

um almost just like rubber ducking things right where you say okay you go through the code line by line and say okay what what does this code do what are all the assumptions what are the Upstream things what are the downstream effects um and so those are great obviously it takes you know more time to coordinate um code reviews um then we can go on to audits so audits are going to be yet like an even more expensive way to find bugs but definitely you should definitely do that but that's like comes at the end of the development uh life cycle and then uh we we get up to bug bounties and so the bug bounties um this is the most expensive way to find a bug in your system you should

definitely have this um it will give white hats an incentive to try to find uh bugs in your system and then not pictured in this uh diagram but it it it does happen are um you know black hat hats where and that's the most expensive way to find out that you have a bug uh is to get all the money stolen so um it's definitely worth investing to make sure you don't get there okay and so one thing that um is everything goodt okay and the other thing is um giving

yourself space and time after you've um finished building um and you're you're you're a code freeze or you've you've stopped you know major Feature work um often times when you when you've really been like just throwing your head against the wall against a certain problem or a code base for a long period of time you're going to be blind to the bugs that you've written and so just you know giving yourself a weekend off or like just some time where you don't think where you like con like consciously don't think about the code you've just written um will give you the space so that when you do come back and and you think about those problems again then you'll be able to you'll be able to

to you know find bugs and come at it with a fresh perspective that you wouldn't have if you were just you know constantly um trying to find find the issues in the system um and this during that period of time when you come back you can look for gaps in test coverage you can look for you know um yeah things that you missed okay so this is a quote from tenet um no one cares about the bug that didn't get exploited uh only the one that did um it's the bug that didn't get exploited the danger no one knew is real that's the bug with the real power to change the world and I include to this in the presentation because I I feel like security for the most part is a very

thankless job um because when it when it doesn't work you don't see it and then or when it when it does work you don't see it but then when it doesn't work uh like everything has gone uh horribly wrong um and and yeah so that that kind of leads us on to um the bugs that we will um the bugs that that regularly get missed um and so I think that a lot of times when we when we're talking about you know building new contracts we'll you know we'll be testing like all the new system components but kind of the the parts

that get overlooked uh often are your deployment scripts and your governance proposals so um you know usually deployment scripts don't have tests so this just means like there's basically uh just less less eyes on things in general and less less rigor than the rest of the code base and I guess in thinking about this you know if if someone submitted a PR and had a whole new you know a whole bunch of contracts and they didn't test it you would say well wait a second you need to write the tests but somehow when it comes to deployment scripts uh we don't apply that same that same logic which is not good and I think the same thing goes for governance proposals so governance

proposals can be parameterizing and upgrading and moving things around in these systems that have all this money at rest in them um but often times you know and we've actually seen where you know a governance proposal uh is not malicious it just contains a logic error um and that logic error manifests um in in in the system shutting down and not working as intended so I think it's very um important to test both of these things and I think also one thing to call out is that like earlier stage projects um you know if you're a later stage project you have the the budget you can go hire security researchers and

they can go and review all of your governance proposals so that's good but earlier stage projects often don't have that uh um you know don't don't have the resources to do that so it's uh it's very important that you that you test these things okay um wait can we transition the slides back to my machine okay yeah okay so now we're going to

give um a bit of an overview of the um Forge proposal simulator so uh FPS is a um a tool that's built on on top of Foundry um it offers developers a a standard interface for creating their deployment scripts and governance proposals and we treat um these two things as one and the same because a governance proposal might deploy a new smart contract um and then you know wire that into the system or upgrade a contract um and this framework makes it really really easy to uh test do integration tests on both your

deployment scripts and on your governance proposals so I think this is like a a really big uh unlock for for projects where you have like governance heavy systems and you're moving around a lot of knobs and dials and you're upgrading a contract here and um turning a bunch of different knobs um so it it lets you really easily connect your uh integration tests into your um uh into your governance proposals and so then you can say oh I already have written this huge integration test Suite um and then with that uh integration test Suite you can you can test you can run the integration test Suite uh after the governance proposal is applied so

maybe you have like a bunch of invariants and a bunch of like States the system like shouldn't be in or a bunch of behaviors um that You' expect users to take and then you can see okay if I do this governance proposal um are these invariants you know are they still true and then are these actions uh are they still like can users um still take them uh other other feature are we allow um we allow State diffing so we can we can give you a full printout of everything uh and all those different contract states that changed including like erc20 token balance changes uh ether balance uh changes um and we also like help you manage addresses and we have like a type

system for addresses whether they're like eoas or contracts so it can help you catch issues um if you're if you're accidentally your governance proposal accidentally talks to the wrong contract okay so the first the first thing to cover is the um addresses contract so this is just a place to really easily store um all of the addresses associated with your project um and then do type checking on them so uh before you run a governance proposal or before you do your deployment script um you'll create a new instance of this address object and then it will read in Json files in your repository um and and store uh these

addresses uh and then it also does type checking so if you say like oh this address on mainnet is a contract and this is its name uh if you load that contract up and it is in fact uh not a contract uh it will it'll fail loudly and it'll tell you like hey there's something there's something going wrong here and in the future we want to be able to support um we want to be able to actually like support like B code checking so you could say like oh I expect this contract at this address to have like this bite code and then if it doesn't you know you say okay there's something wrong throw an error here um and so we can see on the right here we have um what this Json structure looks like so we just have some token

addresses um and then you know then if you're like changing the addresses around it doesn't need to like recompile your smart contracts and then you know there's some more getter methods like you can say hey is this an address a contract um is this address on this chain a contract um and is this address like set so do we actually have this Alias uh is it is it used or not okay and then we have a deployment script here so um we're g to just go ahead and um we're going to say okay hey um have we already do we already have this uh Bravo vault uh set in our addresses object uh if not we're going

to go ahead and deploy a new one and we're going to go ahead and transfer ownership to um our protocol time lock and then we have the same uh check for the The Vault token we say okay hey is it already deployed um if it's not deployed we go ahead and deploy it um and this allows us to really easily like simulate let's say we have a governance proposal that needs to add a new contract to the system this means that we don't need to like you know in our test file we we have like we we parameterize and deploy this contract um and then oh but then we have a different way that we actually deployed in the real deploy script like we actually just get to combine um our deployment script and our governance proposal uh into one

thing and our tests can go ahead and leverage that and so this is the uh build stage so this is this is this is some really cool stuff so normally if you're writing a governance proposal you know you'd have to like specify it in Json and you would say okay this is the target uh this is the call data and then this is the value right and now you read that in and then it gets you know turned into um whatever format that uh your Governor uh expects so maybe it's like a multisig maybe it's a time lock maybe it's a like Governor Bravo um but here what we can do is we can actually write this governance proposal in plain solidity and uh the first step we're

just you know loading some addresses up so we can see like we use these keys and the keys will tell us like will give us the the addresses back um and then um then we start making the calls so we say Okay um we need to um whitel list the token number one we need to approve it and then we need to deposit it to prevent maybe like a share price attack um and then what that does is under the hood with uh Foundry cheat codes it records all mutative calls um okay and uh all those calls then get translated into the governance type that that you're working with um and this is

completely modular so whether you use a multisig a time lock Governor Bravo or even your own completely bespoke um system all these components are really modular you could just pull them off the shelf and use them to um build whatever kind of um system you want and um yeah so we have support for nois safes um out of the box we have support for open Zeppelin time lock controllers so that's like very nice to be able to just kind of like pull those off the shelf um we also allow you to generate uh call data so we can have we can have multiple um developers we can all be running uh these scripts

locally um and generating this call data and then comparing them when they they go on chain and the other really cool part is uh you can actually track proposals so if you send a proposal to the Dow uh you know and let's say your computer's been compromised and you send call data on chain um that's not what you expect because your computer's you know compromised then what what what can happen is uh your integration test Suite can track the proposals that are on Chain versus what you have uh locally in your repository and then it'll say oh wait a second um there were supposed to be three calls and they were calling the The Vault the token and then the Vault again but what's on chain is actually you know calling some other addresses so

we don't actually know what's going on here um so that can help you um catch catch those kind of issues and governance attacks okay and then finally we have the validate function so this is part of the standard um Forge proposal simulator just interface so you can fill this out and it works for both governance proposals and your your deployment scripts you just say hey here's you know these two contracts the token and the Vault uh and maybe also the time lock that were um that were like that we touched in this proposal and then say Okay um did this token actually get whitelisted um so we do like validations

and make sure that the state uh is is correct oh did the balance of the Vault change oh is the total supply of the token what we expect is the the token owner correct is the owner of the Vault correct um and this allows us to validate both post- deployment script and post governance proposal execution um and see that everything um looks exactly like we want it to okay so now we're going to go to an example application if you want to follow along locally um I'd encourage you to scan the QR code or if you don't want to scan the QR code you can just enter in the uh URL um I'll give everyone just a minute to get that pulled up and so um what we're going to

do is there will be uh we'll do a little code walk through here we'll go through uh an example application and we're going to go and try and find and fix bugs um using this tool and um so we'll go ahead and start on um Vault 0000 so basically um you've been asked to build a I guess this is kind of the the specification of the system you're building a vault contract that allows users to deposit tokens uh of the same value and then they can uh withdraw those tokens at a later date um and so an example could be like you you would whel list or you'd have a vault that's

for um we uh we eight and we nine right just two versions of wrapped ether let's say and so then um we would would go ahead and create a vault with those two tokens um go ahead here okay so um I guess we can start on the interface for the vault so we have a deposit function and we have a it's a little smaller we can make it big so we have a deposit function we have a

uh withdraw function um you can specify a token we have a list of authorized tokens here um and so you just pass in a token address that tells you whether or not the the token is is uh whitelisted um we then have um we have the first governance proposal SL deployment script here um and so we can you know we have to do a little bit of setup here we have a description we have some other you know setup items and then we have our deployment script here which goes ahead and you know checks hey have we gone in you know

have we created this new Vault with these tokens um and then we have the validate function we're saying hey you know was usdc Di n usdt are they all um deposited or are they all like authorized tokens within this fault um so I'm going to give everyone just a minute or two to I guess think this through look at the code see if they can find uh any issues with it um and um yeah and then I'll I'll take questions or answers from the audience so here in the Constructor we

go ahead and we set our authorized tokens we have a deposit function where you can go ahead and deposit your tokens um we make sure that you know the token is authorized when you deposit it um and then we we we increment your balance we increment the total supplied amount um and then we we transfer the token from the sender into this Vault contract and we emit a deposited event and then the withdrawal function um we have a you know pass a token an amount um we do a we have an implicit check here on the withdrawal um and then we also have um

integration tests here so this integration test you know we have all the different tokens that are deposited into this Vault um we set some environment variables and we go ahead and run the the the proposal we and we deploy the contracts and then we just go ahead and run the validate function so we make sure that hey you know does does it all check out does it do what we expect it to do um and then we have some integration tests here where we're actually running against uh a main net and saying okay um you know is our can can we can a user deposit die can a use

it deposit usdc um and can they can they deposit uh usdt uh and then okay well now that they've deposited can they withdraw um and then now we have another use case where oh we have someone deposits usdc and someone deposits usdt and then um then each user decides that they want to withdraw a different token than they deposited and so you know making sure that those State transitions um also work and then we have a helper function uh down here so has anyone been able to spot the the

bug okay all good um it is a it is a subtle one so um the bug in this system is so the Vault actually the Vault actually works fine um for light kind tokens um but it makes a makes an assumption and that assumption is that all Vault tokens um have the same denomination uh the same decimals it doesn't do any decimal normalization um so we have all these integration tests here but unfortunately uh they didn't test uh the one the one case that would have uh that would have shown the bug which is that if you

deposit one die you could just steal all the usdc um because it would be very unlikely that there would be you know trillions or you know some huge number of of usdc in the contract and so if we look at the uh deployment uh script um we have you know our tokens here and and this was the bug was in our deployment script like we incorrectly parameterized uh this system okay just wait for this

to okay so um yeah all the all the invariance that we would expect uh in this system um based on based on the Vault and the total Supply you know all that works but again we have a bad um we have a bad deployment script so okay we're going to go on to the next exercise now okay so this um this next Vault um we're going to

just like make things uh simpler here so we have like the same uh data structure so this is all you know we have an author we have a list of authorized tokens we have uh user balances and then we have the total amount supplied um you know across all the different tokens and we expect the the same you know invariance to hold from the previous vault which is is that the total supplied is going to be equal to the sum of the the balance of all users and that the sum of you know the balance of the Vault of of all authorized tokens should be greater than or equal to the total supplied amount um that's pretty

pretty simple and um now instead of treating all tokens uh and like not worrying about decimal normalization we've added a decimal normalization function and and so so here we deposit um we make sure hey the token is authorized we pass in the amount we then get the normalized amount based on the uh the amount passed in we increase the user's balance by the normalized amount and we increase the total supplied by the normalized amount and then we go ahead and transfer the tokens in you know nothing really changed there um we added an additional check which in the the first version of

the Vault um 0 we were able to withdraw an unauthorized token um and so while that wouldn't break the invariant it would have been like undefined Behavior or something we weren't expecting so we're just going to go ahead and add that add that in for just good measure and so um yeah we have our withdraw function here and now we have our um get normalized amount function so BAS basically um if the amount is uh already has 18 decimals we don't need to do anything um and then if the decimals are uh under 18 then um you know then we

need to normalize that up to 18 so that uh things just work and we're going to say hey just like for for the sake of um you know for the for the sake of this exercise we're going to say that like if you have more than 18 decimals like that's that's not standard we're not going to support that um but that is you know that could be something that does happen Okay so now we're going to go ahead and take a look at our uh deployment script so we allow usdc and usdt I don't think we need to you know use die like that's that's fine we can just uh simplify things here um and then we have our validate

function and then we can go down to our tests and so we just have usdc and usdt and you know we go ahead and we have a um we test depositing we test multiple users depositing we test users with drawing depositing and then withdrawing uh you know we we test uh two users are swapping so one deposit it's usdt one deposits usdc and then they both withdraw the um asset that they didn't uh deposit it um and yeah we still have this helper function so that'll just that'll just help us out here um but there is I think there is a

problem here I think there's something I think there's something wrong somewhere here and if anyone has any ideas let me know okay so the issue that we have here um is this is of course the issue that we didn't uh test for so if we take a look at the um if we take a look at our deployment script here we removed D and because we removed D um if we would have run if we would have run the validate function we

would have gotten an error that said hey you know die should be authorized um but we forgot to run that in our uh in our integration test suite and so we we didn't catch that there was like some discrepancy here between oh did we want to include it or did we not and because we forgot that um because we forgot to include D um in our our test Suite then it meant that we didn't test any token that had 18 decimals because usdc has six and USD usdt has eight and we didn't test one with uh 18 but if we look at the um Vault one implementation um we can see here that

uh if the decimals are 18 uh there's there's nothing here that will that will handle that so we would have a pretty big problem and so that would mean if you deposited any amount of a token that had 18 decimals um you would get nothing uh and it would and this contract would then have your funds which would not be not be good um so basically um because we didn't uh because we didn't have a test here uh and we forgot that in the deployment script we you know we missed out on like a pretty easy bug that we like we really could have found um had we been looking for it okay so now let's move on to um the the next version of The Vault so

we have some new requirements here um we need a way for the owner to add new tokens on the Fly um and the business team said that they wanted a way to um remove tokens as well but you said hey look sorry if we um I can make this bigger if you want to read it um but if you said hey look if we if we allowed you to remove tokens then uh then you know then you wouldn't be able to withdraw because anyway so it would it would increase the complexity you'd have to have you'd have all these different edge cases and they say okay that's fine um but they just want to be able on a way to add uh tokens uh on the Fly and uh we so we

went ahead and we added U just a single function here uh the add token function and then um we went ahead and and fixed the bug with the normalization so the normalized amount starts off as the amount and then if the decimals are under 18 uh we go ahead and handle that now of course we're assuming here that you're not going to have any like 19 decimal plus tokens which I think is like a pretty safe assumption uh for most defire protocols um and there's actually uh there's there's no bugs here um and so then let's go on to the next um contract so now we need to make the contract um upgradeable we need to

preserve all the EX in functionality um and we're going to we're not doing a forc you know upgrade because the previous contract uh it wasn't even upgradeable so we couldn't we couldn't Force the users into this upgrade if we wanted to and so we would say Okay users you know you have to migrate into this by optin so you have to remove your liquidity from the old contract and deposit it into the new one and we're going to zoom out okay so we have this contract here oh this is the this is the deployment script so we can just go over that now so if the Vault implementation isn't deployed we go ahead and deploy it we

add it as an address uh we whitel list all three of these tokens usdt die and usdt our owner uh is going to be the our our time lock contract um then we're going to go ahead and we're going to build the uh initialization call data for for um for the proxy and I guess the proxy admin now is created when you create the proxy so uh we don't even need to worry about creating the proxy admin but the proxy admin owner is set to the owner that you pass into the transparent upgradeable proxy so you just have to keep that in mind sorry um so we go ahead and add the address we

fetch the proxy admin from the um from the proxy and then we go ahead and add that to our our address's um contract now we have our validate function here so what this does is it makes sure that D usdt and USD uh C are all authorized tokens um it also makes sure that um you know the the Vault the proxy admin is um the proxy admin owner is the compound you know time lock um contract and sorry so that's the high level of our

deployment script and then we can take a look at the Vault contract itself so this contract is going to be it has its storage um in another contract so that'll just go ahead and manage everything and keep um keep the code clean for us and um yeah then we have our events here we pull our libraries in disable the initializers and the Constructor so you can only you know talk to the proxy contract you have our initialized function our initialized modifier we've added some events to just kind of make things uh easier more um just give us

some visibility into State changes when we initialize the contract we've added a add token uh function and a set max Supply function so we've also added a new introduced a new variable into the mix which is the max Supply so users can only Supply up to the specified um maximum Supply amount um and and only the owner can call this now our logic here um for the deposit it's unchanged except we added this uh one new require and we just say hey make sure that the total amount that you supplied plus the normalized amount um make sure that's not um going uh

above your your your supply cap and everything else is is pretty much uh the same here um it's the same contract it's the same system and you know our our normalized function is fine um everything looks you know pretty good and there's not any any bugs here as as far as I can tell um okay so now some some new requirements have gone ahead and come in again so if we go to the exercise four the the final exercise um we need to actually be able

to make the contract possible and and but we also want to preserve all the previous functionality um you shouldn't be able to deposit when the contract is paused um and but you users should be able to withdraw their liquidity even if the contract is is paused and only the owner can pause and unpause um and then once we finish this implementation we need to actually go ahead and upgrade um uh this is going to be like a forced migration where users uh of the old contract are now going to be forced to use uh this new contract and if they don't agree with it like that's fine um they can go ahead and and withdraw uh before the upgrade

lands so we can first start by taking a look at the deployment script um so we're going to go ahead we set things up we have our description here um the deployment is pretty simple because it assumes that we've already ran um the the third deployment uh script which deploys the proxy admin um the proxy itself and you know make sure that's all set up just right um and then yeah if if this new V4 contract has not been um deployed then

uh we're going to go ahead and deploy that here and we'll add it to the addresses object um and now we're getting into the meat of the governance proposal here so you know we load up our different um different contract addresses here um and these are all static calls or calls to the addresses object so they're going to get filtered out by the forge proposal simulator framework and um then we go ahead and call the proxy admin and we upgrade the uh Vault proxy contract to the V4 Vault implementation and then we also need to set the max Supply to you know some new

number so it's going to be a million so that'll be like the supply cap uh in terms of this fault so maybe it's a guarded launch and we're only going to allow so many so many users to deposit um and then we have our you know validate function here so we're going to say okay hey are our tokens are they in the authorized list um is our Max Supply is that set correctly um is our owner still the time lock because it it shouldn't have changed right that's that should be should just remain static um and so now we can go over uh here and so here we have um a new storage um layout so that's the uh Vault

Storage posable contract so that just extends The Vault Storage ownable and just um basically just adds the new posible contract at the end and then we can go through this contract so we still have the initialized function nothing has changed there the Constructor everything's you know that's the same um our ad token function yeah we still have that our setmax supply that's that's um still in place um and then we have our are pause and unpause functions so only the owner uh can call these and um yeah it's it's

pretty it is uh yeah that's pretty simple our deposit function um yeah you can only call it when it's not paused so again that's like a very very small change um and then we have our withdraw function there's no you know pause modifier on it you can always withdraw so so but I think there is there is an issue here and we're going to we're going to we're going to dig in so um we already have the deployment script we have um this build function so here it's going to you know this framework for proposal simulator it's going to pull out all of this call data and package it up to talk to the uh Governor Bravo um and so that happens

like all under the hood it extracts these calls and turns it into a format that uh this Dow contract can understand and then we're going to go to this um test Vault um o4 contract and this is going to you know we have a setup function here where we deploy the um the Sip uh 03 so that we deployed the um the old contract versions um and we set up the proposal we deploy it we set the addresses in uh this contract to be the addresses that we already deployed so we just like share a reference to that that same addresses object so we have all those addresses that have already been uh

deployed here you know we set uh our our token our stable coins and we set our uh Vault contract um I guess we go ahead and bump up the max Supply uh as the owner because why not um we set up the proposal we deploy the uh the contracts that are that are here so we just go ahead and deploy deploy the V4 Vault implementation if that's not already deployed and then we're going to go ahead and you know just we have like one test here so we have to kind of figure out like what what is the test that we um we should write that will that will catch the bug um but you can see there's there's

nothing um and yeah there's there's there's some things that are uh we don't have so we're we don't really check any state of the contract like what was this contract State uh before we executed this and then um what was it afterwards and so um basically because we don't have any tests here we we we don't really know uh what this governance proposal did um and I think there is okay it looks like some cod. deleted

that would make it easier to test this um but uh basically there there's the the bug in this contract is actually it has to do with the storage slots so if you had deposited your uh tokens into the um V3 of this Vault um into the Vault Storage ownable and then uh you deposited and then the V4 upgrade happened um you got Force migrated over um and so we have the ownable we have the supply but then the authorization and balances mappings got flipped in storage and how they were inherited so the solidity compiler just says like oh wait a second the the

storage um the offsets are now different and so that's actually um in in this the fourth um uh deployment script we because we don't have anywhere in the tests where we run this validate function um like a simple like if we literally just said um if we just did that that would give us a failing integration test because it would say wait a second uh you've built this proposal and then you ran it and then for some reason all of these tokens are no longer authorized um what what's going on there and so basic basically this would allow you to to really quickly and uh easily um catch catch

bugs like that because hey all of your all of the state of your system is just really easy easily available to you um and you can leverage your entire integration test Suite um to catch these bugs um and so maybe you don't even need to like write new tests because you just say oh can a user deposit into this Vault that I expect is authorized and then oh for some reason you can't um and all of a sudden you're getting a revert that the Vault isn't authorized and so you say oh wait a second there's a there's an issue with um the storage offsets okay so yes so V4 the the

exercise for um we had an issue in the storage s offsets um so what did what did we learn from all this so um you really have to use all of the the testing methodologies that are available um starting from the bottom of the stack and then uh working your way up so you start with the cheapest you start with your unit test you start with your integration tests and then you go to more expensive methodologies um over time and you you will you'll always have blind spots you know no matter how much uh you try to think about all the different edge cases of the system and all the different Corner cases um inevitably there will be uh bugs or

there will be there will be things that you don't foresee and so that's where um you know using a lot of different testing methodologies and a lot of different tools will help you find these these harder to catch bugs and getting Auditors and and the The Works involved um you should you should be a aim to be as rigorous as possible you know think through all the edge cases think about your program as a finite State machine um what are the possible States it could be in um do we have relationships between variables or invariance that we think should always be true um if so how do we express that in um in a testing framework um can we yeah can we write invariant tests can we

form prove it can we use um yeah how how can we prove this to ourselves that this that this thing is true um and then okay you say okay we have these relationships between variables but also you need to verify your state transitions right um and make sure that okay certain program states are unreachable or certain you know State transitions uh can't happen um and you know I think that there's there's really like as the industry has like progressed we we've invested a lot more in fuzzing and in formal methods and in in like test methodologies that really are good at um discovering logic

errors in in code and I think that as we've done that and as we've gone you know gotten better and better there um like our Focus as an industry has like gone away from um these other kinds of bugs which is why I think it's really important to you know also test your governance proposals and deployment scripts and um like really get a lot of eyes there because um where attention kind of goes that's where bugs disappear and when you when you have all your attention in one place then you have these other spots that okay hey things kind of slip through the Crocs and then I think one one final call out is that you know a lot of a lot of time should be spent on

um uh on like I think the majority of of time should be spent on on testing and should be proving to yourself that you know this system really does what I think it does um because uh if you're spending all of your time actually writing smart contracts then you're going to get to the end and you're going to have a really buggy system and there's going to be just a lot of things that you you've missed and so I think it's just really important to like be cognizant of where you're spending your time as a developer and make sure that you know you spend a lot of time um testing and hopefully a majority of your time um because I think that's really the hard part is is proving to yourself that okay yes uh

this code that I have written is correct and I'm like sure you know beyond a shadow of a doubt that like it does what I think it does and there's no weird uh weird cases or Corner cases or if there are weird edge cases I understand them um and so I think like a good test of that is like if you show up to an audit and you have um a you know a full specification sheet or a full sheet of all the edge cases that you already expect and know about your system and so then that way um you know it it it'll be easier for uh for your Auditors to like actually go and find the higher impact uh bugs okay so if you want to um use the

forge proposal simulator uh um you can scan this QR code or you can go to one of those these two links so if you go to docs. solidity laabs doio um you can go ahead and uh yeah you can go and check out all the documentation we have on things um oh and one more thing um there is a uh State diffing feature uh in in the tool so if we can if you can see here so in the um in the fourth proposal we go ahead and we upgrade uh the contract and then we uh we set the max Supply variable from zero to I think

it was a million uh or scale to 18 decimals and so if you look at the uh proposal changes uh the Vault proxy contract had two changes so the first one is it um it changed uh its implementation address so it changed from the V3 implementation to the V4 implementation and then the second one was the um slot I think the zero slot which was which would be the um the maximum Supply and so then you can see that it started off as zero and then it got updated um to a million um and that was you can do that all from the command line you just specify um a few Flags here so you say

okay uh run the proposal print out the call data um and you know we don't even need to run the validations here just just print out the call data and show me the state diff of what I actually um did yep and you can uh feel free to uh stay in touch um I'm on Twitter at Elliot ZX um my email is Elliot solidity labs. um and we are hiring smart contract Engineers so um please get in touch thank you so much [Applause]