Have you ever heard of the 12-factor app? That’s a document created more than 20 years ago that describes the best practices that must be followed in modern web applications. It has been written by some of the best developers and is widely respected.
The 12-factor app recommendations are great and should be followed by Dapps (decentralized applications). However, this is not enough for Dapps. Dapps also have a unique set of challenges, because of their blockchain environment.
In this article, I describe a set of best practices that should be followed by Dapp and smart contract developers. A sort of “12-factor app”, but for Dapps. If you are learning how to design a Dapp, these principles will give you some solid principles to follow. These principles come from my 2 years experience building Ethereum Dapps and Solidity smart contracts, as well as many discussions with my students on EatTheBlocks Pro. A lot of these design principles come from the fact that the only source of truth of a Dapp should be the smart contract code and blockchain data and nothing else.
These principles aim to optimize the following parameters, in this order of priority:
- You should NOT take control of your user private keys
- You should NOT sign transactions on behalf of your users from a central server
- You should put all the critical data and code on the blockchain
- You should always run security tools on your smart contract
- You should deploy your dapp on a public testnet before mainnet
- You should use Ethereum addresses to identify users
- you should explain smart contract update mechanism
- You should explain how the external data is collected
- You should verify your smart contract on Etherscan
- You should show feedback to users while a transaction is mining
By the way, if you want to learn how to develop Ethereum Dapps and Solidity smart contracts, create an account on EatTheBlocks Pro, my video screencast for Ethereum developers. I release new juicy videos every week!
The whole point of blockchain security is to not have any centralized vulnerability. If you store the private keys of your users to sign transactions on their behalf, this creates a honeypot for hacker and annihilate the security guarantees of the blockchain for your users.
To minimize gas cost and improve usability, it can be tempting to move the signing of Ethereum transactions from user browsers to a central server.
In this case, the application authenticate users with a traditional username / password mechanism, and sign Ethereum transactions on their behalf on the server, using a private key controlled by the application.
There are 2 problems with this: first, you need to trust that the application will not maliciously sign transactions that were not approved by the user. And second, similarly to the previous principle the private key controlled by the application creates a honeypot for hackers. They only need to hack one server to hack all users.
There is an exception to this rule: if your users sign messages with their private keys, and the server only acts as a proxy who broadcasts transactions with the signed messages of users, there isn’t much at risk on the server: at worst, hackers can stop the broadcasting of transactions to the blockchain, but they cannot modify the signature of users and falsify their intent.
Because storing and manipulating data on the blockchain is expensive, you don’t want to put all your data on a smart contract. Actually, most Dapp only put a small fraction of their data on the blockchain. However, even if you don’t put everything on-chain you still need to make sure that both the critical code and data are indeed on-chain. It’s up to you to know what is critical or not for your application, but usually everything related to the economy or governance of your Dapp is critical. On the contrary, files, user settings and metadata have less importance.
Security is a really big deal when developing smart contracts: they can manipulate real money, and they are immutable (cannot be updated). If a hacker finds a bug in your code, he/she could drain your smart contract of all its funds (someone said DAO? …). Security tools like Mythril can catch the low-hanging fruits for you. Still, once your project reaches a certain scale, you should hire a professional team of smart contract security experts to conduct a full audit of your code.
Public testnet networks like Ropsten or Kovran allows Dapp developers to test their Dapp in a safe sandbox using fake Ether, without any real-world consequences. Compared to a local development environment, it is closer to production (mainnet). It is quite common to find bugs on testnet that did not happen on your local development environment. Testing your Dapp on a public testnet does not guarantee you that your Dapp won’t have bugs on mainnet, but at least your users won’t accuse you of having skipped the public testnet phase.
In Dapps, like in most applications, you will probably need to identify your users to enforce access control. On most web applications, users are identified using either a username or email. However, in the case of Dapps, we want to avoid dealing with data generated from outside the blockchain.
Ethereum addresses are 160 bit pieces of data that identify the sender of transaction. Users can generate as many addresses as they want, using their wallet. Addresses are often represented with their hexadecimal form (ex:
0xf24657573522625f0E2FA43cA57Cd27e09506d41). They can not only be used for signing transactions, but also just for signing messages, which can be verified outside the blockchain.
When your smart contract receives a transaction, it guarantees you that the value of
msg.sender will be the signer of the transaction. You should use this to implement access control. You can still associate an Ethereum address to an off-chain generated userId, on a central server. But you should not use this userId to do any critical computation. It should just be used for convenience.
The code of a smart contract is immutable. However, developers often need to update the code of their application to fix bugs and add new features. How can they do this with a smart contract?
The solution to this is to make your contract updatable. There are different solution to this, but the cleanest way to do it is to use the proxy pattern, with 2 smart contracts:
- a proxy smart contract
- an implementation smart contract
Users send their transactions to the proxy smart contract, which itself forwards everything to the implementation smart contract. This is the smart contract that implements the logic for manipulating data.
When you need to update your smart contract logic, you:
- deploy your new implementation smart contract
- call a function on the proxy contract that updates the address of the implementation smart contract. Future calls to the proxy contract will forward to the new implementation smart contract, and ignore the old implementation contract
The problem with the above pattern is that it breaks the promise of the immutability of code on the blockchain. In order to mitigate this, you can implement a governance system like on Aragon to establish clear rules on who or what can trigger a smart contract update on the proxy smart contract.
In any case, you should be very explicit about this mechanism. Don’t fool your users into thinking that your smart contract code will never change, even though you put in place an update mechanism in place.
Often-time, Dapp needs to use data that is external to the blockchain: stock prices, results from external apis, etc… This poses a problem because the blockchain does not know how to fetch data from the outside world.
The solution is to feed a smart contract with outside data. Once the data is inside the smart contract, it can be used inside the blockchain. This is what we call the oracle pattern.
However, the oracle pattern poses a problem because a hacker could hack the external entity that feeds data to the blockchain, to change the result of a computation on the blockchain.
Some companies such as Oraclize try to solve this problem by having several external entities feeding the same data to an oracle smart contract. Only data with enough “vote” are deemed safe for use by other smart contracts.
In any case, whether you are using a basic oracle pattern or the Oraclize system, the mechanism needs to be disclosed clearly to users, so that they understand clearly what can influence the computations of the smart contract and what are the security risks of your Dapp.
The idea of a Dapp is to promise your users that the code that will run will be the code of a smart contract. The code of a smart contract is always public, and anybody can read it to make sure that they agree with what is inside.
However, when users interact with a smart contract, all they have at their disposal is an ethereum address and a vague promise that the code of this address is what the dapp developers claim it to be.
Etherscan has a feature to verify that a smart contract has the source code it claims to have. Dapp developers submit their smart contract after they deployed it to the blockchain. Etherscan uses some internal tools to verify that the claim is true, and displays the result publicly on their website for users.
This is not a foolproof solution, because Etherscan itself could be hacked or become malicious, but it’s still better than nothing.
In a traditional web application, after a user sends a request to the server, a loading screen usually shows up if the request takes too long. This feedback helps the user knows that everything is ok and he/she should keep waiting.
With Dapps, loading times are not only worst but also have a different nature. First, on Ethereum a transaction takes about 15s to be mined (i.e added to the blockchain). And second, contrary to a centralized database, on the Ethereum sending there is no finality after a data change, i.e we can’t say “now the blockchain has acknowledged my transaction for ever”. Blockchain is vulnerable to “block reorganizations”, i.e you are never sure that the chain that added your transaction will be the chain that “wins” in the long-run. Maybe that another part of the network was mining faster than on your part of the network, and your transaction will be discarded by the alternative chain. In reality, these block reorganizations happen rarely, and when they do they only happen for the latest 1 or 2 blocks. That means that you need to wait a few blocks to be more sure that your transactions won’t be cancelled. And the more blocks (~confirmations) you wait, the more certain you can be that there wont be adverse block reorganization that will cancel your transaction.
What does this mean in terms of UI? You need 2 kind of UI confirmations:
- First, after you send a transaction, show to the user that the transaction was sent
- Then, after the first few confirmations, show to the user that there was an extra confirmation
- After each confirmation, show a link to Etherscan for the transaction. This makes users feel safe that the transaction has been sent
Now you have some solid principles in mind when designing your Dapp! Where do you go from there? It’s time to go build some Dapps and smart contracts and apply these principles! Go create an account on EatTheBlocks Pro, my video screencast for Ethereum developers. I release new juicy videos every week! Free developers can access the source code of the free episodes, and paid members can have access to all my videos, as well as private telegram group with live help from me and other students.