Table of Contents
It's been 6 years, 3months, and 25 days since the infamous DAO hack that shook the entire web3 world.
The one where we witnessed more than 3.5 million Ether being stolen away due to a bug in the smart contract.
This hack introduced us to one of the most dangerous attacks, The Re-Entrancy attack, possible in a smart contract.
Since then there have been enormous improvements and attempts to mitigate the risk of introducing such attack vectors in smart contracts.
Automated testing tools started including warnings about re-entrancy bugs, smart contract auditors became particularly cautious with codes dealing with external calls, and loads of blogs as well as video content were created (and are still being made) explaining the re-entrancy attack.
However, if you have observed the exploits in the past few years, along with all other new attack vectors, re-entrancy exploits are still a major concern and can be seen as a considerable bug behind many smart contract exploits.
Let me share with you a glimpse of all the smart contracts attacks due to re-entrancy, check out this list below.
In fact, the latest re-entrancy exploit that I read about was just a few weeks ago when Stader’s NearX smart contract was exploited with the exact same very well-known re-entrancy bug & $830,000 were lost.
Scary, isn’t it? 😰
Well, this isn’t just about re-entrancy bugs in smart contracts and neither is the blog going to explain re-entrancy bugs ( not again ).
Re-entrancy attacks are just one of the many well-known attacks that the smart contract dev community is well aware of but we find traces of such bugs in smart contracts even today, which leads to incredibly massive exploits.
Now the question is — WHY?
Despite having enormous tools, libraries, and educational content around such common bugs and attack vectors in solidity, why do we still witness such well-known attacks time and again?
This is undoubtedly one of those broad or open-ended questions that might have multiple narratives or answers.
However, after being involved in multiple smart contract security audits now, interacting with quite a few smart contract devs, and being part of some really active smart contract developer communities, I came across 2 extremely simple but imperative reasons behind this.
Most importantly, these reasons revolve around the very basics of how we approach Smart Contract development and learn Solidity, especially in the initial stages.
1. The “Security-is-NOT-my-job” mindset
It can unquestionably be stated that the majority of the developers in the web3 space believe contract development and smart contract security to be two different things, which is true, to some extent.
However, completely denying any correlation between these two things is probably a mistake.
The rationale behind this is the fact that, at the very heart of it, the process of smart contract development broadly includes 3 most significant steps:
- Design & Development
- Security Validations of the Contract
While we are quite well aware of the first two steps, we most often forget, or even worse, do not consider Security Validations to be part of the contract development procedure at all.
It’s concerning because even if we consider a basic fact about smart contracts, i.e., their immutable nature after on-chain deployment, we can quite clearly see the significance of adding security checks and validations as a mandatory step in the smart contract development process.
The fact that you cannot change a line of code in your contract even if you found a major bug just seconds after deployment, is daunting in itself.
Well, just in case you think you can always upgrade your smart contracts or upgradeable contracts are extremely secure, let me stop you right there. 🛑
Read about the Wormhole Proxy Bug and think again.
Upgradeable contracts can have bugs too. 🪲
What should you do?
In very simpler terms, consider security checks & validations as an imperative part of any smart contract you develop, and learn at least the basics of smart contract security if you haven’t already.
Once you consider this, you can no more rely on just writing test cases for your smart contracts.
Test scripts are undoubtedly helpful, however, they are more inclined towards ensuring the contract functions execute as intended while the security of a contract is much more than that.
Even as a smart contract developer, you should definitely be good with at least one of the following (actually more than just 1) security tools to validate the security of your contract:
- Static analysis tools like Slither, Mythril, Mythx. These tools effectively help you identify any well-known smart contract bugs that you might have in your contract.
- Fuzz Testing tools like Echidna or Harvey help identify potential exploit scenarios or contract execution failures by throwing random & unexpected data into your contract.
- Scribble, which is an amazing runtime verification tool by ConsenSys that allows you to annotate a solidity smart contract with crucial properties.
- While auditing complex & bulky smart contracts, the one thing you need the most is a visualization tool, and that’s exactly where Surya comes in.
It provides an incredibly simplified version of all crucial details about a contract’s structure including call graphs, inheritance graphs, etc.
- VS Code visual auditor extension is an extremely helpful tool that provides security-oriented syntax as well as semantic highlighting and quite a few other tools that make the secure development of contracts easier.
My favorites → Slither, Surya, VS Code visual auditor, and Echindna. 😎
Why should you do it (If you can get contracts Audited)?
Well, to begin with, no one will ever care about the security of your smart contracts more than you (and your team). The very first person responsible for the security of your code should be YOU.
Secondly, the smart contract audit industry has been (and still is) in a “ High Demand & Low Supply of good security auditors “ phase.
This basically means there are just a handful of extremely experienced auditors and relying on just a few of them won’t really be scalable enough for the web3 world to expand its boundaries exponentially.
Additionally, there is a huge line of projects waiting for their contracts to get audited, which is why, when it's your turn you need to be ready with the right set of contracts, adequate test cases, coverage reports, and most importantly, the crucial discussion points you might want to have with your security auditor.
However, you won’t really be capable of having any effectual discussion with smart contract security experts if you yourself don’t understand, at least, the basics of smart contract security.
The inclusion of the security validations step in your smart contract development journey allows you to filter out all the well-known bugs beforehand with the help of static analysis tools.
This allows security auditors to specifically concentrate on the more important potential threats of the contracts instead of identifying and reporting the ones that could be easily found with the tools mentioned above.
Therefore, performing your own security checks before an audit doesn’t just shorten the lengthy audit durations, it helps achieve an adequate & better result out of the whole procedure.
This is a concept that the Secureum community brilliantly defines as CARE (Comprehensive Audit Readiness Evaluation) which intends to prepare your contracts before an audit to ensure that the outcome from the security audit is comparatively better and effective.
It’s important to note that including security as a part of your smart contract development process isn’t a substitution for an audit of your contract, but a preparation for an adequate security audit.
2. Avoiding Mistakes & Experimentations with Solidity (and the lessons that come from them)
Alright, while the first point was for developers already familiar with solidity(and basics of smart contract security), this part is more inclined towards those starting out with learning solidity or the ones in the very early phases of smart contract development.
Here is a quick tip for you:
Along with learning and developing simple solidity smart contracts, don’t be scared of experimenting and making mistakes while learning solidity.
Since its inception Solidity or smart contract development, in general, has been incredibly interesting but daunting as well as difficult for many developers at the early stage because of some very obvious reasons:
- The immutable nature of Smart Contracts and the idea of getting almost everything right in one single shot before on-chain deployment.
- The open-source nature of smart contracts and the fact that every single line of code is accessible/readable by anyone.
- The whole concept of programming and storing money within Smart Contracts and the security risks that come with it.
- Solidity still being at a very nascent stage and the difficulties in keeping up with the rapidly changing/evolving language.
Now, the concerns and risks associated with making mistakes while developing a smart contract can’t be overlooked. However, these have an adverse effect on the journey toward learning smart contracts effectively.
With so much at stake, it becomes harder for developers to experiment with Solidity or make mistakes on their own.
The popular idea of “Not reinventing the wheel or trying something new, to avoid introducing new bugs in the contract” stops developers from exploring different concepts of solidity and thus limits their potential with smart contract development, as a whole.
In fact, it is now considered a lot safer to simply fork an already existing audited contract instead of writing one from scratch. While this definitely does minimize the risk of contracts having bugs in production, it does stop developers from dealing with all the complications one might(and should) go through while developing the same. And, therefore, keeps us from deep-diving into solidity concepts more often than not.
While simply following a blockchain development course or consuming online content on smart contracts might help you get started with any specific topic, it won’t let you dive in deep and learn the fundamentals. Not unless you get your hands dirty with the same.
Most importantly, not experimenting with Solidity smart contracts also keeps us from learning about the various kinds of security vulnerabilities a contract might have and the plethora of ways a contract can be broken.
For instance, going back to the re-entrancy example:
Every smart contract developer might have heard, read, or discussed the reentrancy attack vector, but how many of us actually went ahead and tried to experiment with the same on some dummy contract, just to understand what happens behind the scenes?
This is precisely where smart contract security-based war games or CTFs like Ethernaut or Damn Vulnerable Defi also play such a significant role in learning smart contract development and security. They allow you to directly interact with a contract directly, make mistakes and eventually figure out the vulnerabilities.
Now, there are definitely some smart contract developers who experiment with Solidity and smart contracts a lot and therefore are becoming the best in the industry (check the Underhand Solidity Contest winners or their Challange Submissions, for instance).
However, as I previously mentioned, for the web3 world to expand its boundaries and scale with utmost security measures, we cannot simply rely on a handful of experts in the space.
Instead, every smart contract developer should aspire to learn more and dive in deep by not simply learning the basics but experimenting, making mistakes, learning from them, and then sharing them with the community.
Because, quite similar to life in general, the best learnings in software development also comes from mistakes.
Significance of Mistakes & Learning Experiences
Mistakes or Learning Experiences every smart contract dev should have at least once:
If you are just starting out with Solidity, there are a bunch of mistakes/experiments you can (and should) do while learning.
Dropping off some of them from the top of my head.
Solidity 101 mistakes/learnings:
- Using memory keyword instead of storage and then realizing you never really stored the crucial contract states permanently on the contract.
- Relying on block timestamps for random calculation and realizing how they can be manipulated by the miners is therefore a bad practice. More details are here.
- Storing secret information on smart contract state variables with a private visibility modifier only to find out that nothing is private on a smart contract.
Every state variable value can be seen by anyone. Read more about this in SWC-136
- You will always believe that a contract with no payable function (or payable fallback function) will never receive any ether unless you learn how selfdestruct can forcefully fund such a contract with ETH.
- Failing to provide required input validation in functions & realizing how this can lead to unwanted scenarios any invalid arguments can be passed without proper validation. ( SWC-123 )
Gas Optimization & Security mistakes/learnings :
6. Writing expensive for loops only to find out how this could lead to a block gas limit issue and fail the complete transaction
7. Violating check effects interaction patterns and realizing how this could lead to the classic Rentrancy attack.
8. Using delegate calls between two contracts with different storage layouts and realizing why storage layouts must be exactly similar for delegate calls to work.
It's extremely important to ensure storage layout between two contracts involved in a delegatecall() is exactly the same. Otherwise, this leads to storage collisions. Read more about storage collisions here.
9. Including inadequate access controls for imperative functions of the contract and realizing how this could lead to any bad actor executing transactions without approval. SWC-105 covers this topic quite adequately.
10. Trying to send ether from one contract to another using the transfer function only to find out that it always sends a hard-coded 2100 gas only. This will help you realize the significance of the .call.value(…)(“ ”) function which allows adjusting the gas to be sent with a transaction. Read more about why .call() might be better than transfer().
11. Realising how tricky it is to delete mappings values in struct or in general and learning the right way to do it.
12. Trying to swap Token A with Token B using Uniswap and being vulnerable to sandwich attacks. This might help you realize the dark side of transaction order dependencies, MEVs, and the DARK Forest of Ethereum .
While these are just some examples, there are way more interesting topics in smart contract development, from basic solidity to advanced deep dives in EVM Opcodes, that one can learn and experiment with.
Wrapping it UP 😊
Web3 has come a long way in a very short span of time, however, the security of smart contracts is still a major concern.
The two ideas discussed above aren’t gonna make it all sunshine and rainbows at one go. There is undoubtedly a lot more we need to do to make Web3 a safer place.
However, imagine a whole generation of developers that includes the two ideas mentioned above while learning or developing smart contracts, prioritizes learning the basics of security, experiments with Solidity, learn from their mistakes, and then shares those learnings with the community.
That will, slowly but surely, lead us to a much safer and more secure web3 world than what we have now.
All it takes is some commitment be to consistent, the will to learn more from mistakes and experiments, and the aspiration to be the better version of the smart contract developer that you already are.
If I can leave you with just two sentences 👇
Don’t forget Experimentation while Learning Smart Contracts
Don’t forget Security while Developing Smart Contracts