Gas Optimization In Solidity: Strategies For Cost-Effective Smart Contracts
Gas is the “fuel” that powers smart contract execution. This article offers practical strategies for Solidity gas optimization.
🇺🇦 Hacken stands with Ukraine!Learn more
A smart contract is the most viable technology for exchanging digital assets in DeFi. However, errors that occur from time to time lead to catastrophic losses. In our audit practice, we face identical bugs over and over again. The most common representations of smart contract vulnerabilities are Unchecked External Calls, Suicidal and Greedy Contracts, and Block Info Dependency.
To stop the endless cycle of losses and avoid bad headlines for crypto, our auditors share findings with the community. Here we take a more detailed look at the list of the most prevalent smart contract attacks that can cost millions of dollars.
According to CWE Registry, a Reentrancy Attack is an example of Improper Enforcement of Behavioral Workflow.
One of the features of Ethereum smart contracts is the ability to call and utilize the code of other external contracts. Contracts in many cases send ether to various external user addresses. The operation of calling external contracts, or sending ether to an address, requires the contract to submit an external call. Cybercriminals steal those external calls and force the contract to execute and call back to itself (using a fallback function). The execution of the code “re-enters” the contract.
The attacker can carefully construct a contract at an external address that contains malicious code in the fallback function. Such contracts make a recursive call back to the original function in an attempt to drain funds. When the contract fails to update its state before sending funds, the attacker can continuously call the withdraw function to drain the contract funds.
From a historical perspective, the reentrancy attack is one of the most destructive attacks in the Solidity smart contract. The reentrancy attack led to hundreds of millions of dollars in losses over the last years, including the Ethereum fork in 2016.
Exploited Vulnerability: The attacker publishes a malicious contract whose callback function contains a call to the deposit function. Deposit function returns proof of investment to the user. It will call the callback function in the malicious contract again to obtain multiple proofs of investments. This allows the attacker to gain more additional revenue.
Exploited Vulnerability: ERC-777 (a standard for token contracts interfaces and behaviors) allows transaction notifications to be sent to the recipient in the form of callbacks. This means that ERC-777 token indirectly results in the recipient having control of the execution
The DAO hack of 2016 still remains the most significant reentry attack in Ethereum because it lost 5.6% of all ETH in circulation at that time. On June 17th, 2016, The DAO was hacked and 3.6 million Ether ($60 million at that time) were stolen using the first reentrancy attack Ethereum Foundation issued a critical update to roll back the hack. This resulted in Ethereum being forked into Ethereum Classic and Ethereum.
Where found: Smart Contract Code Review and Security Analysis Report for a client.
Issue: There is a possibility to re-enter the function for the message sender. If the transaction would be created from the contract which does have a fallback function it will be able to reenter the function again and again, until not drain all balance of the Colexion contract
Affected Functions: withdraw, end_auction
Recommendation: update the balance to zero before doing the transfer
So, the name “reentrancy” comes from the fact that the external malicious contract calls back a function on the vulnerable contract and “re-enters” code execution at an arbitrary location on the vulnerable contract. Reentrancy is an especially common error that could lead to catastrophic losses if left unchecked.
According to CWE, Default Visibility is an example of Improper Adherence to Coding Standards.
Functions in Solidity have visibility specifiers which dictate how they are allowed to be called. The visibility determines whether a function can be called externally by users, by other derived contracts, only internally or only externally. The default visibility for functions is [public]. Therefore, functions that do not specify any visibility can be callable by external users.
Default Visibility becomes a problem when developers ignore visibility specifiers on functions that should be private (or only callable within the contract itself). It is good practice to specify the visibility of all functions in a contract, even if they are designed to be public.
A noticeable example of the effect of this issue was the Parity MultiSig Wallet hack when about $31M worth of Ether was stolen from primarily three wallets. The wallet smart contracts had two functions that were accidentally left [public], so an attacker could call these functions, changing the ownership to the attacker’s address. After becoming the owner, the attacker was able to drain the wallets of all their ETHs (≈$31M).
Summing up, incorrect use of visibility specifiers can lead to critical vulnerabilities in all smart contracts.
The fixed-size data types for integers are specified by the Ethereum Virtual Machine (EVM). What it means is that it can represent only a certain range of numbers. Without taking the proper measures the variables can be utilized in case if user input is unchecked which is the reason why numbers can be outside the range of data type they are stored at.
It usually happens during an operation requiring a fixed-size variable to store a piece of data or a number surpassing the variable’s data type range. Such smart contract vulnerabilities are utilized by cybercriminals in order to misuse the code and benefit from the process.
Example: Adding numbers that exceed the data type range is called Overflow. As soon as the uint (unsigned integer) reaches its maximum size, the next element added will overflow. For example, for uint8, the maximum number is 255, and if you add 1 more to it, then the variable will be overflowed and will equal 0 (if added 2, then the variable would be 1).
Every transaction on the Ethereum blockchain has a global impact on the entire Ethereum ecosystem in a calculable way. Basically, it means that any randomness or entropy is impossible inside the blockchain ecosystem. Therefore, ‘rand()’ function is absent in Solidity, and achieving decentralized entropy is a problem many experts address.
Some programmers are trying to write their own “random” functions, but as they are not well familiar with the ecosystem of the ETH – they mess up; as a result, vulnerabilities appear.
The Ethereum blockchain nature implies the combination of external calls to other contracts and a large number of users which makes it possible for cybercriminals to determine Solidity vulnerabilities by racing code execution for their benefit.
Due to blockchain technology, Ethereum nodes form transactions into blocks that are considered valid as soon as a miner solves a consensus mechanism.
Before a transaction is added to the block, it goes to the mempool where everyone knows what will occur. Such circumstances can be troublesome for decentralized markets as a transaction to buy some tokens is seen, and a market order is implemented before the other transaction is included. It’s almost impossible to be protected against it, as front running is a specific feature of a contract itself. However, it would be better to implement batch auctions (also to stay protected against high-frequency trading issues) or to use a pre-commit scheme (“I’m going to submit the details later”).
Being a very wide category, DOS attack implies leaving contracts dysfunctional for some time or even permanently. This attack can freeze ether contained in those contracts for an indefinite period or even forever. Moreover, DOS attacks can violate the logic of a smart contract.
Being specific functions, constructors carry out the most important and special tasks during the contract initialization. In the earlier Solidity versions, constructors were named the same as the contract that has them. This way, when the contract name has been changed during the development, and the constructor name hasn’t, it turns out as a regular callable function. Therefore, the vulnerability became the reason for numerous cybercrimes. This type of vulnerability is rarely met nowadays, as the majority resorts to the constructor keyword.
Ethereum blockchain has a global variable – tx.origin. It runs through the entire calling process and returns the address of the account that was sending the transaction. Utilizing the variable for smart contract authentication creates a serious vulnerability for a phishing cyber-attack.
We rarely come across this vulnerability. Developers with little experience in solidity cannot distinguish it from the variable msg.sender and therefore write contracts with vulnerabilities, using it where it is not advisable. So, never use tx.origin for authorization.
Regardless of how innovative blockchain is, even the best developer in the world can make an unintentional mistake that can cause serious problems. This is exactly why we decided to provide a list of the most common vulnerabilities we have found when auditing our clients’ smart contracts (there is also a separate class of logical mistakes which may have critical nature but we’ll describe them in further articles). Maybe, it will help someone to avoid facing devastating consequences and losing the company’s reputation. As they say, forewarned is forearmed.