Mistakes in Smart Contract Implementation: Bugs and Pitfalls
Users benefit from smart contracts because they make processes like transfer, delivery, and exchange easier and more convenient. Smart-contract failures are a serious worry for creators and investors because they regulate a large percentage of existing ICO and Ethereum projects.
Smart Contracts Fail to Perform
Smart contracts, on the other hand, are only as smart as their designers, and contract design faults frequently result in issues.
When it comes to interacting with numerous smart contracts, errors can cause functions to run from the user address rather than the contract owner's address.
Several serious smart-contract flaws have been discovered at the National University of Singapore (NUS). As a result, Oyente, a smart contract scanning analysis tool, was established. They found flaws in 8,833 of the 19,366 Ethereum smart contracts they examined!
A typical problem, for example, caused GitHub user Devops199's Ether transaction valued $285 million to freeze. Maian was formed as a result of the need for a new detecting tool. A group of five cryptocurrency zealots used the Maian tool to find additional 970,898 smart contracts with flaws.
In the table below, you'll find a breakdown of the discoveries:
Prodigal contracts send Ethereum or tokens to the wrong wallet.
Suicidal contracts have a fault that renders them vulnerable to the contract's owner killing them.
Other users can take control of greedy contracts, leading in cash being locked.
Mythril and NUS are two more well-known smart-contract discovery tools.
Let's look at the most prevalent technical smart-contract blunders that have been identified in the last year.
Money/Tokens Sending and Receiving
While sending and receiving money or tokens, debugging a smart contract is not possible.
There are two alternative solutions without a message call when compared to contracts built for transfer orders:
The contract address is “mine to.”
utilising self-destruction (x).
You must have a backup function when receiving Ether with no called function. The Ether will be refused if this function is missing.
The contract can only utilise the “gas stipend” [2,300 gas] granted during the execution of the function — yet this amount of gas is insufficient to access storage. Always account for the gas requirements for function implementation to ensure success.
Using addr.call.value(x)(), similar to addr.transfer, you can receive additional gas (x). The user can transfer more gas and access a wider range of functions with this function. This aids in the recovery of “bad” code, preventing mistakes from spreading to other working components.
Making Use of Withdrawal Patterns
Instead of using a contract, the most common method of sending money is to employ a withdrawal pattern. A customer can also use a direct send call to send Ether. Direct calls, on the other hand, should be avoided because they can jeopardise security.
Consider the following scenario:
To become the "richest," sending a large sum of money to a contract.
Here's another way to convey the pattern:
In this situation, an attacker can use the richest contract address backup mechanism to cause a failure and place the contract into a ‘injured' condition.
Invoking revert() or spending more than the 2,300 gas stipend will cause this. As a result, delivering funds to a "damaged" contract will fail, and funds might possibly remain in this position indefinitely.
The first "withdraw" pattern merely results in a withdrawal failure and has no effect on the rest of the contract's operation.
Limiting the quantity of Ether (or other tokens) in a smart contract is the best strategy to limit loss.
Calls to external functions
An instant working crash of external function calls is another common smart-contract failure cause. Before interacting with your contract, hackers can enhance the value of the call stack.
Keep in mind that the functions.send(),.call(),.callcode(), and.delegatecall() all work in the same way.
Make sure tx.origin isn't used for authorization.
Assume that this is your wallet:
and someone pushes you to transmit Ether to a dubious wallet:
If the wallet's authorization was checked by msg.sender, the Ether would be sent to the erroneous wallet, not the legitimate owner.
The tx.origin function instructs it to choose the wallet's address for initiating the transaction. As a result, all of your cash will be transferred to the hacker's wallet address.
Checking a Send Method's Return Value
During a transaction, many contracts do not check the return value or gas levels. The transfer is likely to fail if the stack depth exceeds 1,024, or if the gas supply runs out.
Make sure to check the return value and use a transfer or a pattern to allow the recipient to withdraw funds to reduce the risk of loss.
It's worth noting that information about transaction content and state variables is public, even if it's tagged as private.
Encryption can assist you avoid this problem, but it's still possible if the data can be read.
Other contracts can restrict access to a contract reading by default. Making the state public is a simple way to change it.
It's also feasible to limit the number of people who can change the state of the contract and use its features.
Here are some general source-code quality recommendations:
Limit the number of local variables that can be used.
Functions should be limited in length.
The purpose of a document is to make your intentions clear to others. This will aid in determining whether the code's action varies from your own.
Consider the following scenario:
Contract (A) gains control when contract (B) interacts with contract (A) (B). Contract B has the ability to call back into A until the process is complete.
Because of the limited gas required to send, the code below shows an example of a related bug that can occur:
The issue is with the following: smart contract transaction failure
The code execution takes place throughout the Ether transfer process. The recipient's contract can be called back into withdrawal, allowing for multiple refunds and possession of the contract's Ether.
During the transaction, smart contract errors occur.
To avoid situations like this, use the Checks-Effects-Interactions design described below.
All tests on who called the function — if there are parameters in range, if enough Ether was provided, and if the user possesses tokens — should be done right at the start.
After that, make modifications to the contract's state variables. At the end of any function, think about how the contracts interact.
In the past, a contract had to wait for external function calls to be in good standing. One of the most significant consequences of re-entry is this. Calls to the right contracts can also lead to calls to contracts that were not planned.
Loops and Gas Limits
Be cautious of loops that don't have a set number of iterations or loops that are affected by storage values. The amount of gas available is limited, therefore transferring takes up a certain amount of it.
In any event, the amount of loop iterations may raise the gas limit. At some time, this can result in a freeze. However, it's possible that it has nothing to do with constant read data functions. Other contracts or linked processes can call these functions, causing them to stop working. Please be aware of such situations in your contract documentation.
Some of the advantages of a new code can be gained by removing an intermediary. Adding new self-checking functions to a smart contract can aid in determining whether any Ether has been leaked or if the sum of the tokens equals the contract's balance. Gas constraints can also be calculated at various points throughout the chain.
If a check discovers a contract flaw, it switches to “failsafe” mode. Many functionalities become inaccessible in this circumstance, and control is transferred to a third party, or the contract requests the cash be returned.
ATTENTION! SUICIDE FEATURE
When a token freezes on all Parity multisig wallets, this is a crucial illustration of the suicide function.
One high-profile event involves a failure caused by irresponsible management of the library's code, resulting in the loss of around $280 million in Ether.
Only two transactions were made by the user:
To change the owner address status, use the initWallet function.
The following were the primary causes:
Only one initialization of the wallet was possible.
Only an uninitialized modifier might perform the function.
Smart contract errorsThe Wallet Library is missing wallet functionality. As a DELEGATE CALL, the code was transmitted to other contracts.
2. The act of "killing"
So, how can you find out about these bugs in advance?
Make a list of all SSTORE instructions that could be used by anyone.
Check the SUICIDE function's information and try to remedy any present or future issues.
If you discover that some SSTORE indexes are insecure, you can remedy it by using the SUICIDE block.
This unsecured SUICIDE call aided the Parity disaster and could have been avoided if Mythril had been used.
With formal verification, the customer makes sure that the source code carries out an appropriate formal specification.
This helps determine the difference between the specification you had and the process you carried out. In this way, you can avoid critical mistakes.
To implement formal verification, you need to:
Provide an audit of all arithmetic operations with user-supplied data
Verify all working elements before the arithmetic operations
Use the safe-math library to be sure that all functions work appropriately, and to see if there are any overflows.
Despite all of the above-mentioned risks, there is a solution.