Blockchain and its smart contracts. The decentralized nirvana? Well… not exactly… They introduced a new paradigm in distributed and trustless computing – with all manner of application possibilities – the first of which to make major headway are Decentralized Finance (DeFi) and Non-Fungible Tokens (NFTs).
But those of us familiar with centralized application design, or Web2 design – with a three-tier architecture – there is still a LOT to be developed to reach parity. At best, there is a two-tier architecture approach – Front end and smart contracts/blockchain – since they are inseparable – but we can approximate a three-tier architecture if we follow some good design templates.
So, what are the design trade-offs to consider for smart contracts?
As always, there are trade-offs that must be made – just like in traditional application architectures – except, go back about 30+ years and you’ll be more in the trade-off arena of the modern smart contract environment that exists today.
The trade-offs and constraints with smart contracts are many – here are a few:
- Once you store data on the blockchain – it’s immutable… so be careful.
- Smart contracts have a size limitation. In ETH/EVM it’s 24k, or about 500 lines of code.
- There is a cost to executing any given smart contract, so you want to also be conscious of cost getting incurred for the processing being done.
Learn from the Errors that have already happened…
In addition, there is a lot of hubris with Blockchain developers – just as we saw with TerraUSD/Luna – the wipe-out of $45+ billion in value, due to an algorithm exploit that de-pegged the UST algorithmic stable coin that was pegged to the US dollar. Even though the vulnerability was identified while UST was on the testnet (pre-production deployment), it was dismissed by Do Kwan, the thirty-something founder of Terra. So the vulnerability was there from its inception – but the hubris of youth, thinking they are invulnerable won the day… until it didn’t.
That’s one example of logic flaws that get implemented. More thorough testing would find many of the errors and issues, but in the “land grab” that is Crypto today, speed and hubris seems to win investors dollars. Not too different from the Internet boom of the late ‘90’s. The rush to riches is many times a path paved with potholes. Investors hate it only AFTER the inevitable happens, and then they scream and blame.
And as I blogged about the smart contract security flaws that have led to huge amounts of stolen crypto, MOST if not all of them track right back to the way that the smart contracts were designed and coded:
- $44 million was stolen when a bug on a BNB to ETH minting process of a cross-chain bridge assumed trust incorrectly.
- $610 million was stolen when a smart contract that allowed transfers had both the keepers AND the ability to change keepers (and verify the change) in the same smart contract.
- $80 million stolen by a bug that allowed a deposit function to be called with an address of 0.
There are more – but you’re likely getting the picture. If smart contracts are not designed and coded well and thoughtfully, and TESTED thoroughly, this could happen to you.
Understanding the use cases, and functionality, allows for the design of smart contracts in ways that optimize the trade-offs. Too often, efficiency wins the day, while developers don’t think the “worse case” scenario could or would occur. This is naïve thinking.
In blockchain decentralized development, the code is all open source. ANYONE can look at the code and examine it – and find vulnerabilities. Is the race to getting functionality out there – to be ‘first” – worth being exploited in a way that will ultimately lead to your demise and huge losses for your customers?
The Design of Your Smart Contracts Matters…
The structural design of smart contracts matters in terms of execution cost and data storage fee (as it’s stored on the blockchain). Some of the patterns in conventional software design are applicable in smart contract design as well. So don’t throw the baby out with the bathwater!
Taking a look at execution cost – it’s a function of time, space, and message complexity. Time complexity refers to the smart contract instructions and their possible execution combinations. Space complexity is all about the ledger and storage consumption, and message complexity looks at the messages and volume of data in and out of the blockchain client.
Keep in mind that smart contracts increase the overall size of the blockchain as more blocks are added to it, and no blocks can be detached from it. Paying attention to the design of smart contracts can have a big impact on performance because of the number of transactions that may be required when executed.
Here are some templates that could prove useful when developing your application:
A way to maintain a registry mapping a smart contract name to it’s most current version – so all parties can execute the latest version.
We utilize this in one of our client implementations, so all smart contracts within the application look in ONLY one place for the address to the smart contract that needs to be called.
It’s worth highlighting that the contract address is stored as a variable in the registry contract such that it can be updated. And to prevent just anyone from updating a registry value, the creator of the contract should be tracked and validated at the time of the update. Smart contract version control is achieved by extending the (name, value) pair to (name, version, value) tuple. Understand, as with conventional software development, that the Implementation (i.e. the variables required in the call to the smart contract) cannot be changed without breaking dependencies.
Logic and Data requirements change – especially if bugs needs to be fixed. This pattern essentially decouples logic from data (just as conventional software design in three-tiered architecture does as well). It’s a bit more basic than what can be done in conventional software development, as this approach implements something more akin to a simple database.
The separation occurs by creating two separate contracts. As reviewed in the link above, the first contract stores and exposes basic data read and write functions with access control. The second contract reads and writes the required data by calling the data contract. The data store is isolated from the rest of the code.
For a few more specific functional ideas:
If you want to have investors receive rewards or dividends/interest – if you were to do this on a regular basis across all investors, then you’d have an ever growing array (as more investors come in), and you’d be paying all fees for making those payments.
Rather than that, create a withdrawal function – that each investor can implement when they want to claim their rewards/dividends/interest. Then each investor pays the fee for claiming and withdrawing. The function is also much simpler and straight forward.
Utilized when you want to make sure that only authorized users/functions can execute a smart contract function.
A straight-forward implementation that incorporates modifiers that allows the owner address to be stored as a static variable, and functions can be restricted to only be invoked by a user with that same address. Of course, it can be made more robust, but that is essentially one way to restrict access where necessary. Check out this post for more specifics on different access restriction implementations.
And a very common pattern, especially within Solidity smart contracts is the
If your application needs to use similar smart contracts, but different data, then a factory pattern is your best bet. The factory contract essentially spawns child contracts – each with the specific data variables to be applied. Here is a specific code example in Solidity.
Good – Fast – Cheap. Pick any TWO
These patterns can be used in concert – again – when designing smart contracts, you want to pay attention to the various trade-offs, so you end up with a structure that can work over time, and that is written to be most efficient, yet secure for the intended purpose.
I’m reminded of the saying, “Good, Fast, Cheap. Pick any two.” And it’s a true today as it’s ever been – especially around blockchain development (or any app getting coded no matter whether Web2, Web3, etc). When you look at your vision for how much growth you could experience, and the difference that will be made by your application – is it worth getting it right from the beginning?
This is where youth is not necessarily a good thing – because the downside of youth is lack of true experience, and the hubris (i.e. ignorance) in thinking that you’d never assemble code that could be hacked. Beware those that ridicule others – karma is an interesting thing. That’s why development and great developers are humble – because we’ve learned all too often, that there are always things you’ve not anticipated.
The best bet is to go with experience, and thoughtful smart contract architecture and designs that will be maintainable over time, and set you up to achieve the vision that you have for your initiative.