paint-brush
SOLID Principles in Smart Contract Developmentby@iamshvetsov
113,056 reads
113,056 reads

SOLID Principles in Smart Contract Development

by Daniel ShvetsovSeptember 6th, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

SOLID is a mnemonic acronym that stands for 5 principles formulated by Robert Martin. These principles originally appeared in the context of object-oriented programming, but with the rise in popularity of other styles, particularly functional programming, the principles were found to be applicable to the software development process as a whole.
featured image - SOLID Principles in Smart Contract Development
Daniel Shvetsov HackerNoon profile picture

SOLID is a mnemonic acronym that stands for 5 principles formulated by Robert Martin.


These principles originally appeared in the context of object-oriented programming, but with the rise in popularity of other styles, particularly functional programming, the principles were found to be applicable not to a specific paradigm, but to the software development process as a whole.


As with any set of instructions, these principles should not be followed blindly, but rather as a set of recommendations. In general, a computer doesn’t care what kind of code it runs, beautiful or ugly, it will produce zeros and ones either way, but we as developers want to get readable code that is maintainable and scalable, so following the SOLID principles will help eliminate the signs of a bad project.


In this article, I will look at the SOLID principles in the context of working with smart contracts. For each of the principles, I will provide the definition given by Bob Martin and then give an illustrative example that shows the essence of the principle. Let’s get started!


S – Single Responsibility Principle

A class should have only one reason to change.



If you look at OpenZeppelin, this library supports modularity. There are contracts that support the ERC-20 standard, ERC-721, ERC-1155, etc. Each of these contracts can be modified to add access control, upgradeability, and other functionalities.


In my example, there are two contracts (Ownership and Token) with different responsibilities; if Ownership functionality is needed, then inheritance can be used to add it to the other contract.


This principle helps avoid spaghetti code and keeps transparency for working in a team.


O – Open-Closed Principle

Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.



This principle says that a contract should be closed to direct modification, but open to extension. This means that you can’t change the original contract because that would break backward compatibility. However, you can build on an existing contract to create an updated version with additional functionality.


For example, you might want to override the protocol or the address where the token will be stored, but you can’t please everyone at once, so the tokenURI function is declared as virtual so that it can be overridden in another contract. Another example is the beforeTokenTransfer and afterTokenTransfer functions, which are called before and after the token transfer. The functionality can be specified in a contract that inherits the functionality of the contract with the original functions, and if you need to call these functions and do something of your own, you can use the super keyword.


L – Liskov Substitution Principle

Functions that use pointers of references to base classes must be able to use objects of derived classes without knowing it.



This is the most difficult principle from a theoretical point of view. If you try to read the mathematical definition, it says something incomprehensible. The point is this: if there is a chain of smart contract inheritance, then contracts should be written so that the parent class can be replaced by a child class and vice versa. This means that when functions are overridden, they should take the same number of arguments, be of the same type, and return the same type.


This principle is easy enough to follow in Solidity (unless you write code in Notepad, of course), because if you pass different arguments to a child function, the compiler will start screaming. The same goes for return type, scope, and other modifiers.


I – Interface Segregation Principle

Clients should not be forced to depend upon interfaces that they do not use.



This principle fits well with the Solidity paradigm because that language has interfaces. It says that an interface should only contain the methods necessary for implementation, i.e. the contract that implements the interface should not describe methods that it does not need.


In the example above, the interfaces have their own area of responsibility and, for instance, the IToken interface does not dictate which role model to use or whether to use it at all. When implementing a contract, you can inherit from different interfaces as needed.


D – Dependency Inversion Principle

High level modules should not depend upon low level modules. Both should depend upon abstractions.


Abstractions should not depend upon details. Details should depend upon abstractions.



The principle is that you can introduce abstractions into contracts to maintain extensibility.


In the example, the implementation of the doSomething function in the Test contract required the sending of a token. Based on the IToken interface, two contracts are described with transfer functions, one that transfers a value and one that transfers from a specific address. Since the transfer function takes the same arguments in both contracts, the implementation from the TokenTransfer and TokenTransferFrom contracts can be selected at the time the function is called.


Inversion means that it is not the interface or the parent contract that expects the implementation of the transfer function from the Test contract, but vice versa. The interface makes demands on the contract in order to work with it.


This principle allows the Test contract not to be rewritten when a new transfer function is used provided that the signature of the function is identical.


Conclusion

As we studied the illustrative examples, it became clear that there is nothing complicated about the SOLID principles. In fact, many of us use SOLID principles in our daily work. I hope I have managed to cover the subject without leaving any gaps!