Ionuț Gîngu - Starter Security Practices for Builders
ETHCluj Meetup·Tue, Oct 7, 2025, 12:00 AM
This talk will be focused towards builders. I will talk about general security practices that each team can and should apply to strengthen their code and processes, as well as what questions they can ask themselves. Additionally, I will talk about the defensive development mindset, and what resources can be leveraged to learn from or use as checklists. I will give examples of audited, battle-tested building block contracts that builders can leverage, such that they can focus on implementation details rather than reinventing the wheel. I will give an example of a simple exploit that can happen in smart contracts, emphasizing why security is so important.
Transcript
My name is Yonut Velungu and I'm going to talk about starter security practices for onboarding builders. Uh a little bit about me. I started working with blockchain in 2022. I started doing security in 2023 and I've been a security researcher at Open Zeppelin for about two years now. Open Zeppelin is a security company which does traditional audits both onchain and infrastructure code.
We also develop a lot of open-source free tools uh for anyone to use such as the open zeppelin libraries. Uh I've been part in many audits since I joined and then also in my research time I've been studying a lot of bugs that happened outside the company or exploits that were popular. Sometimes I summarize some of those and we post them online. So if you want to read some of them, you can follow me on X. I let my handle there.
I'm not very active but if you have any messages for me I will see them at some point. Okay. So getting into the agenda I'm going to introduce the concept of the defensive developer mindset. What it is and how you can introduce it in each phase of your product. Then I am going to have a slide with further resources that you can look into after the presentation if you're more curious about security.
Then I'm going to leave a couple of minutes at the end for questions and answers. So the defensive developer mindset what is it is the process of allocating and spending time to increase security not only to implement business logic. You are going there to develop your code assuming things can go wrong not assuming they are going to go through the happy path. What does it entail? So for the implementation phase of your product, you'd want to reason about and ensure each line of code is secure by itself.
Like the actual code inside and every function, you'd want to reason about it and the whole contract and the way it interacts. You'd want to have a testing phase where you want to quickly and automatically assert that code is correct. This not only prevents you from having bugs and makes you discover them early doing this kind of quality assurance work uh but it also prevents regressions. So whenever you're going to make an update of the system, you'd want to assert that it still works correctly. And the ideal would be that you do that quickly on automatically rather than clicking something in Remix all your functionality and being really errorprone.
Then you'd have your deployment phase. You'd want something that is predictable and robust and sets up through scripts. Again, this reduces human error. Uh most systems are pretty complicated nowadays. There are contracts that leave behind proxies.
You need to deploy them, initialize them, connect them with each other, set up some config variables, and again, you wouldn't want to do that in Remix. Um and yeah, and then the operations phase. Uh this is a phase basically where you maintain your system and run transactions on your blockchain. Again with configurations, you're probably going to have some kind of ownership or permissioned entity that can make these important changes. So you'd want that ownership to be decentralized.
You'd want to use something like a multic. And then you also would want to make sure that whatever you're signing as part of the multisig is the int intended data. And this is going to tie into some um recent exploits that happened. So the implementation phase, how to get into the defensive developer mindset. You should build up this behavior of asking yourself a couple of security questions whenever you're coding anything.
So I'm going to list here a couple of them simple questions. What are the entry points to my contract? And are they access controlled? By entry points, we mean any external or public function that can alter state and hence can provoke any change in your system. Can my code have any corner cases?
Can users provide unexpected or malicious input or use my functions in a way that I didn't intend them for use? And then am I handling all return values or are there any functions that return a boolean which I'm not treating? And in this case, my transaction might lose its functional atomicity because I'm expecting it to do three steps. One of the steps uh doesn't work and my transaction doesn't revert. So it only happens that two steps occurred, right?
And then there's a couple of advanced questions you might want to ask yourself. In case of faults, can I pause the contracts and would pausing cause further impact? There is actually projects where pausing is not really a good idea because you might be in a case where you need to act swiftly and withdraw some funds, not pause the system while other things are ongoing at the same time, maybe declining prices. Does my system make economic sense? Uh a simple example here would be if you're building some kind of lending borrowing project.
There is a bunch of design questions which you need to ask yourself like um how much collateral should people put in this project? What is the percentage? What happens if there's bad debt? What is the percentage or the probability that the system could actually go bankrupt? What would happen then?
Is there any way in which I could limit damages in case of an exploit? Um here one simple thing that comes to my mind is the circuit breaker EIP. This is basically an authorized entity on your contract which could posit. This authorized entity is governed by a private key which is held by a bot. this bot is offchain periodically queries your contract for a key metric which you want to make sure is safe, right?
And whenever a big spike in that metric happens uh above a threshold you set this bot acts and pauses the contract. Uh similarly how in your house when there's a high electricity spike or something the safeties come down and your house is safe and then lastly can I fix my code if any bugs appear. So this ties into upgradability or maybe if you don't have upgradeable contracts can I replace my contract with something else and then plug it back into my system to fix any bugs or issues that happen. Here I put a simple example of the can users provide unexpected and malicious input. We have a really simple contract.
You can deposit any token inside and it's going to track get tracked in this balances mapping. And then we have a simple function called swap tokens. Through this function, you specify a source token, a DEX, and some data. And the contract is going to take your balance, approve it to the DEX, and then call the DEX, presumably to make a swap for a target token, and send it back to you. This is really good and safe if people use it for what you intended it to be used.
However, this is actually a really common vulnerability where an attacker can specify an arbitrary address and an arbitrary call data. So instead of of calling a DEX, they could specify the DEX to be an ERC20 token and the data to be a transfer from. What would happen then is that the contract would do a transfer from on the token with itself being the message sender. So, it would be authorized and you would be able to drain any ERC20 token that is within this contract. Um, it might seem a bit like a silly example, but this is actually really really common.
I've seen it more than 10 times probably in audits, in reports, and in exploits for a lot of money, this uh pattern. Okay. Secondly, what you'd want to do in the implementation phase is to try to use battle tested code. There are common functionalities where there's no need for you to reinvent the wheel. There are existing contracts which have been thoughtfully designed and are more secure by default because these popular libraries have been used and have been heavily audited.
Not only they are more secure but it they also reduce your development time because instead of spending time implementing ERC20 transfer functionality you can just focus on the custom logic that your project has. And here I wanted to give two examples. You have the open zeppering contract libraries. This the most popular library has been heavily audited and used in contracts for about 4 billion transactions. Uh it has a lot of functionality such as permissions, proposals, voting, token standards, proxies, a lot a lot of things and it's in many many environments not only solidity.
And then there is another uh pretty popular option soulmate. Uh this is a more experimental gas optimized solidity library that heavily uses assembly. It's less battle tested and as I said is still in an experimental phase. It's becoming a bit more popular but I would say treat it with a bit more care if you want to use it. Uh here I'm giving a simple example.
This is a UI tool we have. It's a browser IDE where you can play with contracts and play with the libraries. So basically you select whatever token you want and then you select uh what features you want and it just combines them all together. In this uh example, I just put a really simple token with a thousand premint. I want it to be mintable and burnable.
So, this adds public functionality to mint and burn the tokens. I want it to be access control through ROS. And I want it to leave behind the transparent upgradeable proxy. So, this is all put together. You copy paste this into VS Code.
And then you're good to go. Moving into the testing phase. As I was saying, you'd want to have a way to quickly and automatically test your functionality in a robust way. Also, the first and simplest thing that comes to my mind is unit and integration testing. Unit testing is when you take one of your functions, separate it, you give it an input, you expect a particular output, and then integration testing would be taking multiple functions together or your whole contract.
There are a bunch of frameworks that you can use to do these things. One is Foundry Forge, very popular. You can code in solidity. The other one is hard hat. You can code in Typescript.
Here I put a simple example of an integration test. I grant Bob the mentor role for my ERC20 token. I assert that Bob has that role and then I mint token a thousand tokens to Alice. and I assert that Alice indeed has a balance of a thousand. This would be a simple integration test.
A little bit more advanced technique would be the fuzz and invariant testing. Fuzz testing is something that again you could do with the help of uh frameworks. Instead of having just a simple test with one one input and one output, the frameworks run your test multiple times with many arbitrary inputs and assert every time that the output is correct. This also ties into invariant testing which is a way to uh test key invariance or key mathematical properties of your system. So a simple example for what an invariant would be, you have an ERC20 token.
You know that the balance of each holder summed up should always equal the total supply of the token. This is an invariant. This should never be broken. Um and that's what you want to test. So here um I have a simple example.
Again, you can use Foundry Forge or there's Akidn Medusa, two other fuzzing tools from a company called Trail of Bits. I have a simple example here. Uh, and what would happen if I plug this into the fuzzing and invariant testing framework. Uh, the framework could call all the functions on my ERC20 token in an arbitrary order with arbitrary parameters, minting, transferring from one to the other, and so on an arbitrarily number of times. And then at some point it's going to check the invariant.
Let's sum up the balance of each user. Is it equal to the total supply? If it isn't, it's going to error out. It's going to stop and it's going to show me a trace of all the steps it did such that it reached this inconsistent state. So then I can run it back and figure out where the bug is.
A last step you could do in testing is blockchain fork testing. So everything that I specified before was kind of assuming you have like a local simulated node. You run like a deployment on it, your tests, but it's kind of like this fake environment. What you can do is again with some frameworks, you can fork the blockchain, make a copy of the actual node, the actual state that is right now on Ethereum, and you run your deployments and tests against that, but it's against a simulated real thing, not actually in production. So you're not spending gas.
This is really good because it mimics the production environment and it lets you test things in a more real way. So imagine you have a some kind of D5 protocol which does a mathematical calculation based on unis swap liquidity. There's two ways you can go about testing it. You can either mock the unis swap pool and assume it has a thousand ETH or thousand ERC20 tokens or you can fork the blockchain and actually point your testing contracts towards a copy of the actual unis swap pool which has the real liquidity and it's like the real life conditions of it. Uh lastly, what you'd want to do in the testing phase is audits.
So you'd want to hire some security professionals to do a line by line review of our code as well as test it or help with overall design. After these reports come out of the audits, you would implement the fixes and then you would validate it with audit teams. So there's two ways to go about it. There's the traditional audits and here is companies like open zeppelin or trail of bits and then there there is this crowdsource audits way which is platforms like Sherlock or Code Arena. These are basically websites where you can go, you post a kind of a bounty.
I say I give $50,000 for a contest. Anyone can come and analyze my code. There's going to be a judging period and depending on how many vulnerabilities each participant found, they are going to receive a percentage of that pot. Moving on into deployment. I was talking about wanting to have a quick, automated, and robust way to deploy your system that would be easy and consistent.
This is most useful for big systems where you don't want to set it up manually because you can cause errors. Ideally, you would want your deployment to be atomic, which means you'd want it to happen within one transaction. There are systems that have certain uh marketplaces that tie together. So if you first deploy the marketplace without setting it up and there's a time in which someone can act on that marketplace, they can actually steal all the funds and this has happened before. So ideally you'd have like a kind of a contract that ties up all your deployment and you call that contract and it deploys everything atomically in one transaction.
So there's no place where anyone could put some uh like make a transaction of their own and act on an inconsistent deployment. Uh then having deployment scripts would also make your deployment be more flexible. You would have some configuration files where you can just make some small changes, rerun the scripts, everything gets deployed fresh or gets upgraded if your system is upgradable. and then going into the operations phase. Uh who is the owner permissioned entity of your system?
So most systems have some kind of owner that sets either upgrades a proxy, set some operational variables. Basically they have a lot of access and if you compromise that you probably can steal everything, right? So it's really important that that owner is protected by something. And here are multiple levels of security you could go for. There is the low security one.
The owner is a private key. The private key is just one. It's easy to compromise through social engineering as we'll see a bit further. Then there is a maybe a medium security one where the owner of your contract is a multisig. It has seven people that could sign it.
So whenever more than four of them sign off on a particular transaction is going to execute. In this case, you need to compromise at least four uh private keys. and then maybe a high security uh system. Uh your upgrades and all your operational configuration is managed through proposals that the community can vote on. Any proposal that passed would be behind the time lock that uh gives time for people who don't agree with it to withdraw from the protocol.
And then you'd also have an emergency council of three out of five people that can instantly run any upgrade in case anything really bad happens. You have distrusted people that they can kind of bypass all these protections. But again, you're going to run into a similar problem as above, three out of five. So people could compromise those three signatures. Lastly, uh about operations, how are you signing the transactions?
Ideally, you'd want to use a hardware wallet because that is cold. It's not connected to the internet and uh this is the safest and best practice accepted by everyone in the in blockchain. Uh the problem is uh there are multiple steps where you can get tricked to sign something else. So if you think about the process of signing, you're probably in your browser on a Nosis safe UI. You put some information there that you want to sign.
it gets uh hashed. You get this string of bytes. Those bytes go to your laptop, then to your hardware wallet. It gets signed and then it goes all the way back to the laptop to the NOS safe and on the blockchain. Here is a bunch of points that can be compromised to make you sign something else than you intended to.
And I linked three really big hacks that happened and this was the main cause. So this was about $1.7 billion. Somewhere along this line, all of these uh three um projects were convinced to sign something else either through malware that were showing them a different Nosis safe UI. So they were saying, "Okay, you're signing um some kind of operational change, but under the hood they were actually signing a transfer of ownership and they didn't know."
or maybe on the laptop there could have been a again a malicious thing that intercepted whatever the hash was from the NOS safe change it so when you're signing with the hardware wallet if you're not checking that it matches the thing on the nosy safe UI again you're uh you're um signing a transfer of ownership and that's exactly what happened in all the cases here um then I just wanted to talk a bit about this safe utils this is a tool that you can access online for free. It was made by an independent security researcher and then some of my colleagues put it in a nice UI. You basically go there, you put your multisig address, the target of your transaction, the data, the nons, and the value and it's going to compute the EIP 7:1 thing for you. And it's going to show you exactly what you're supposed to see in Nosis safe and also on your hardware wallet. So you can check against that.
Uh a a couple of further resources to look into if you're interested. So I put a bunch of resources for secure development. There's the secure smart contract development road map which is a more detailed resource about everything I talked about today and then some links to open contracts libraries and soulmate and then a couple of links about learning security. So the solidity security documentation then there is the ethernote capture the flag um these are a collection about of about 30 security challenges that you could do for free and each one of them highlights a certain security aspect we usually think about when we audit smart contracts you could treat it as like a fun puzzle to do in a break and if you can't figure it out the solutions are all are online and then uh consensus security best practices and then vulnerable defi it's another CTF related to uh D5. So thank you very much for listening.
I'm going to we can have some questions and answer now.
Oh, we already check your notes. There's so many of them and thank you so much for running us through contract security and everything. So let's turn over to the questions. So we have a few coming up here. So as you see on the top we have did one just disappear.
Why should I know all these as a developer if my code gets audited anyways? Uh yes. So the reason why this is important and you can't just offload all the responsibility on the auditors is because it's really important that you understand security also. First of all, this makes your code be a lot better. So this reduces the costs you would spend whenever you come for an audit because estimations take into consideration what things you are including as dependencies.
What is the quality of your code? We look at all of those to estimate how much is going to take to audit it properly. Secondly, if you understand some security is going to make you ask yourself questions that where you end up understanding your system a lot better. So this has a bunch of really good implications. If you understand your system really well, you can prevent vulnerabilities before coming to an audit.
Secondly, you can have a much better and swifter response in case of something happening. So, if you don't really understand what's happening in your contract security-wise, maybe it's some complicated DeFi thing. If any exploit or bug happens, you're not going to be able to react really well and in a swift manner. Well, thank you for the short answer.
And uh maybe just one other thing to add is that uh especially with the testing which I mentioned. So a lot of bugs that we find during audits are actually more like quality assurance things that could have been found through testing. So if the code comes uh to an audit and it's really really solid in terms of the behavior you envisioned, that allows auditors to spend more time thinking about exploits rather than figuring out that your system is not even working in the way you intended it to work. That's something you can catch with a test. Perfect.
We actually have two that came in from from zero knowledge. I think it fits perfectly into everything. Right. So you mentioned soulmate but not so soul lady.
Uh yes. So that's another uh experimental library. Uh I didn't mention it because my impression is that soul lady is a library where it's like precursor work that's going to get into soulmate. So yeah, they're kind of similar thing where they make experiments in sole lady and then if it's good they push it into sate or at least that's my understanding of it but that's another library. Yes.
So short and sweet. So we alo have one more forum. I know test and test fuss function names automatically picked up by foundry is test integration also taken. Um, this is a very specific question and I wouldn't know to answer right off the bat. Um, my example with test integration was more like a general example.
I didn't actually plug those into forge or hardhead to run them, but it was just like a generic concept. You can just uh look at the documentation and see which word you need to put there so it actually understands it.
So just that just came in here. I think that was the one we missed before. Wouldn't introducing a circuit breaker in introduce a new DOS attack vector?
Uh yes, it could do that. So my mentioning of the circuit breaker was more as as an example of things you should reason about and think about whenever you're designing your system. It wasn't necessarily an endorsement of the circuit breaker technology. I would say it depends on a case-byase basis. It's not a simple answer, a yes or no.
It never is. Does tax are like, let's bring down Google. So, anybody from the audience have any questions before we break for lunch? So, I want to remind everybody before we go, there is a gym session here on the first floor in the terrace. Please everybody go out and enjoy a good stretch.
And you I know everybody was going to sit and laugh in here. I don't know why, but thank you so much. And thank you for everyone. Please everybody give him a great hand. Thank you.
Automatic transcript — names and jargon may be misspelled.