What is Smart Contracts?
Smart contracts are self-executing contracts in which the contents of the buyer-seller agreement are inscribed directly into lines of code.
According to Nick Szabo, an American computer scientist who devised a virtual currency called “Bit Gold” in 1998, Smart contracts are computerized transaction protocols that execute contract conditions.
Using it makes the transactions traceable, transparent, and irreversible.
Top 9 Smart Contract Vulnerabilities
1. Indirect execution of unknown code
A possibility of indirect execution is there due to the presence of the fallback function feature in smart contracts. There are several reasons why this function can be called:
Calling a function of another contract using ABI: if there is a typo in the signature string passed for encoding or a function with such a signature does not exist, then the fallback function will be called;
Deposit to another contract generates a call to its fallback function;
Calling a function of another contract using the API: if the developer makes a mistake when declaring the interface of the called contract (for example, mixes up the type of a parameter), then the fallback function will be called.
In Ethereum, calls to functions of other smart contracts occur synchronously. That is, the calling code waits for the end of the execution of the external method before continuing its own work. This can cause the called contract to use the intermediate state of the calling contract.
This situation is not always obvious during development, if possible fraudulent actions on the part of the called contract are not taken into account. Let’s consider the following example.
Suppose there is a vulnerable contract that tops up the balance of the caller by 10 Gwei one time and writes its address into a variable to avoid double-spending. At each next call, the contract checks whether the balance has been topped up at this address earlier, in which case another deposit does not occur. In order to steal all the Gwei available on this contract, one should simply re-enter the pay function before the address is written into the variable. Since the call function is used for deposit, re-entry can be performed by writing the appropriate logic in the fallback function of the fraudulent contract.
For a successful attack, one needs to place such a contract on the network and call the pay function of the vulnerable contract, passing the address of the fraudulent contract as an argument.
3. Incorrect calculation of the output token amount
Most modern Defi smart contracts deal with enormous amounts of money depicted in tokens or ETH value. Thus, a lot of operations in contract logic are connected with tokens’ transfers to and from the contract. It creates a wide field for different mistakes connected to correct percentages, fees, and profits calculations. Top of such errors are:
incorrect decimals handling, especially when dialing with such token as USDT;
incorrect order of operation during fees calculations, which leads to a significant accuracy loss;
actually forgotten accuracy constant in the mat operations
All these errors lead to lost users’ funds or even tokens locked forever. Thus, one of the tasks of the contracts auditor is to check the correctness of the math operation. Such checks are also implemented in the most modern automated tools and static analyzers.
4. Interface/naming issues
An example of this is the Rubixi smart contract. Rubixi was a Ponzi game, in which its owner could transfer the fees accumulated in the financial pyramid to himself. Usually, the owner is set in the constructor of the contract, which is called when it is created, the same logic was implemented in the Rubixi smart contract. It should be noted that in the Solidity versions prior to 0.4.22, constructors were defined as functions with the same name as the name of the contract. At some point, the contract was renamed from Dynamic Pyramid to Rubixi, but the developer forgot to change the name of the constructor, so anyone who called the Dynamic Pyramid function could become the owner of a contract and could steal the accumulated funds.
5. Dependency on the order of execution
The state of a contract is determined by the values of its variables, which are changed by calling its functions. Calling a smart contract function is the same transaction as a transaction of ETH or ERC-20 token transfer. These transactions are finalized by the network only after the next block creation is complete.
Thus, when a user sends a transaction to call a contract function, he cannot be sure that the transaction will be executed in the same state of the contract in which he was at the time of sending. This can happen because other transactions in the same block have changed the state of the contract.
Moreover, miners have some freedom in ordering transactions when forming a block, as well as in choosing to include a particular transaction in a block. In some cases, the impossibility of determining the state of the contract, in which the transaction will be executed, can cause vulnerability of the contract itself.
It also becomes especially dangerous to interact with contracts written in such a way that their behavior can be changed over time.
6. Time component
Sometimes the logic of smart contracts can be time-dependent. The time for a contract is only available in the context of a transaction. The timestamp of a transaction, in turn, is equal to the label of the block in which it is included. Thus, consistency with the state of a smart contract is achieved.
However, this also creates an opportunity for the miner to abuse his/her position due to some freedom in setting a timestamp for the block. So, the miner has some advantage over other parties to a contract that he/she could exploit to his/her own benefit.
7. Using the block hash function
Using the block hash function is similar to the timestamp dependency, it is not recommended to use it for important components for the same reason as with the timestamp dependency, because miners can manipulate these functions and change the withdrawal of funds in their own favor. This is especially noticeable when block hash is used as a source of randomness.
8. Incorrectly handled exceptions
There are many situations where an exception can be thrown in Solidity, but the way these exceptions are handled is not always the same. Exception handling is based on interactions between contracts. Thus, contracts are vulnerable to attack from malicious users, if these exceptions are not handled properly, transactions will be rolled back.
9. Incorrect work with ERC20 token
There is a well-known OpenZeppelin implementation of the ERC-20 token, overused in modern protocols. In most cases, it is completely applicable, and its functionality is enough for correct financial operations. Though there is a place for custom implementations of a token standard. Thus, it creates a place for discrepancies between the newly created token and the actual ERC20 standard – small inconsistencies like missing return value in the transfer() function. Though, such a small change may lead to the non-functional method of the contract, since it will not be able to recognize the interface. It is a very tiny mistake, almost not noticeable during testing, but in production, it leads to stuck funds and blocked contracts.