I wanted to use the opportunity to iterate on this tweet of mine a little further. A tweet and even a thread can only cover so much, but a few things are usually left out. But this issue is a perfect opportunity to go even further.
Solidity itself isn’t that complex. You might not fully agree with me, but believe me: Once you are really in, you will see that most of it is just some syntax. But like any other software you can develop, it takes a little more to make it really good. Let’s go over each point individually, so you understand.
Cryptography
Cryptography plays a huge role at the core of a blockchain, but it also has a significant part in your day-to-day job. You always need to keep in mind that everything on a blockchain is transparent, and everyone can see data if they want to. And this leads to an interesting problem: How do you handle more private details?
The answer is relatively simple but not what you might expect. There is actually no way to reliably encrypt data within a smart contract without it being visible within the transaction. This means that you need to work together with the client. When someone calls your smart contract’s functions, sensitive information needs to be already encrypted. This is the only way to store that data so that only the rightful owner can decrypt it in the client.
Why does that affect you? Well, because it is your job to know those details, and will be your job to work this out together with the clients. As someone who learned all this, it might even be your job to implement the specific functions for your clients, so they can continue to focus on the frontend. In other circumstances, you might want to build a backend for such use cases, and that would probably also be your job to implement.
Common Patterns
Patterns help a lot in making your Solidity code more readable, secure, and probably even performant.
There are a few of those patterns that you really need to learn well. To name some of them:
- Guard Check
- State Machine
- Oracles
- Access Restriction
- Checks Effects Interactions
- Pull over Push
- Emergency Stop
- Proxy Delegate
- Eternal Storage
- String Equality Comparison
- Tight Variable Packing
- etc.
Those are some pretty fancy names, but in the end, they only represent a particular style of doing things in Solidity. A guard check, for example, is nothing else than a pattern to use the require() function as early as possible in a function call to ensure that user input is correct.
It takes some time to learn them really well, but when you know them, you’ll be able to read Solidity code way more efficiently and make your own code more maintainable.
Smart Contract Security
It is straightforward to write Solidity code, but it can be way more challenging to write secure code.
A missing access restriction in a function is one of the more apparent flaws that can lead to a hostile takeover of your contract. It doesn’t take a seasoned Solidity developer long to spot such an issue, but an inexperienced developer might have more problems with it.
Other issues, like a reentrant attack, are way harder to spot. Calling another contract when you have problematic code placed before that call can lead to that contract calling your function again, in return leading to a loop. In one case, this led to the so-called DAO hack, where a reentrancy vulnerability cost the “DAO” (a fund) its treasury.
Learning about smart contract security is an ongoing process, and the field evolves as fast as attackers find new ways to exploit flaws in contracts. Although there are specialized smart contract auditors, having a good understanding of smart contract security as a Solidity developer definitely doesn’t hurt.
Optimization
Smart contract optimization is a pretty deep field, and the harsh reality is that sometimes writing only Solidity isn’t enough. Especially on Ethereum, every gas unit saved counts (at least right now). The more gas you can save, the better for your users. And if you have dedicated backend logic calling smart contract functions, you’ll also be happy to save a few thousand dollars or even more.
Optimization efforts can begin with monitoring the gas consumption of your smart contracts with specific tools and lead to rewriting certain parts with more optimized code. And sometimes, even Solidity isn’t enough, and you will have to use Yul, the EVM Assembly flavor of Solidity. You then quickly go from writing human-readable code to placing binary instructions carefully.
At some point, every smart contract developer will probably have to learn Yul and optimization techniques. Overall, it will probably take as long to learn Yul as it took to learn Solidity.
UX
Smart contracts are accessible by the public, but not in the sense that any backend API is accessible. It is way easier to access a smart contract on the client-side. All you need is the ABI of that contract, and off you go.
Many developers will use this to their advantage and build their own code around your contracts, which justifies taking a little more care of usability. Additionally, taking care of UX can also help you to optimize your contract. When one call is better, why make it two and let users pay their gas fee equally as often?
Sometimes, it’s just not feasible to let users call three setters in a row before they can finally call another function that contains the business logic you really need. Make it one function call in this case and help users to interact with your contracts conveniently.
Learning all this takes a lot of time, and it certainly helps to use your own contracts regularly. Start with your tests and also include end-to-end tests that model the whole user flow. This already gives you great insights into how well your contracts are really usable. From that point on, you can continue to iterate and improve the usability of your contracts step by step.