Top 5 Common errors in Solidity programming Language

What is common between Parrot, Platypus, and Kangaroo? Well, in case you don’t know, it’s ‘Homoio-thermy’. It’s a process to maintain their internal body temperature through metabolic processes just like auditing is the process to maintain the safety and security of smart contracts. 

But in the near past, we have witnessed various events that put a question mark on the security of smart contracts. We were subjected to vulnerabilities which resulted in huge financial loss as compensation. Though with time, the security of the Smart contracts has improved. But we should be cautious and be prepared for any potential threat. We believe the fact that the majority of these attacks take place due to a few common vulnerabilities in smart contracts. 

In the coming sections, we will introduce you to the 5 most commonly encountered errors in Solidity and the vulnerabilities associated with them. Hence, let’s deep dive and look into these loopholes and how we at QuillAudits approach them.

Errors in solidity programming language

1. Unchecked External Call

We are pulling this issue in the first place because it is one of the most commonly observed Solidity pitfalls. Generally, to send ether to any external account is carried out through the transfer() function. Apart from this, the two most widely used functions to make an external call are; call(), and send(), here mainly the call() function is extensively used to perform versatile external calls by the developers. 

Though the call() and send() functions return a boolean value specifying whether the call was a success or not. Thus in this case, if any of the functions call() or send() fails to perform the task, they will revert with a false. Hence, if the developer doesn’t cross-check the return value, it would become a pitfall. 

The Vulnerability

Consider the example below:

 contract Lotto{
 boolpublic payedOut =false;
    address public winner;
 uintpublic winAmount;
 // ... extra functionality here
 function sendToWinner()public{
 require(!payedOut);
        winner.send(winAmount);
        payedOut =true;
 }
 function withdrawLeftOver()public{
 require(payedOut);
        msg.sender.send(this.balance);
 }
 }

In the Lotto-like contract above, we can observe that a winner receives winAmount of ether leaving a little leftover to be withdrawn from any external agent. 

Here, the pitfall for the contract exists at line [11], where a send is used without cross-validation of the response. In the above example, a winner whose transaction fails (either by deficiency of Gas or if it’s a contract that intentionally throws in the fallback function), authorizes payedOut to be set to true irrespective of whether the transaction of ether was a success or not. In this event, any exploiter can withdraw the winner’s winnings via the withdrawLeftOver function. 

QuillAudit’s Approach

Our in-house team of developers tackles this bug with the use of [transfer] function instead of [send] function, as [transfer] will revert if external transaction reverts. And if you’re using [send], always cross-check the return value. 

One of the robust approaches that we follow is utilizing a [withdrawal pattern]. Here, we logically isolate the external send functionality from the rest of the codebase, and place the strain of potentially failed transactions on the end-user, as he is the one to call the withdraw function. 

2. Re-Entrancy

The Ethereum smart contracts call and utilize codes from other external contracts, and to conduct this, the contracts are required to submit external calls. These external calls are vulnerable and prone to attacks, one such attack took place recently in the case of DAO hack. 

The Vulnerability 

Attackers carry out such attacks when a contract sends ether to an unknown address. In this case, the attacker can create a contract at an external address that possesses malicious code in the fallback function, and this malicious code will be invoked when the contract sends ether to this address.  

Fact: The term ‘Reentrancy’ has been coined from the fact that when an external malicious contract calls a function over the vulnerable contract and then the code execution path ‘re-enters’ it. 

Consider the example below, it’s an Ethereum vault that allows depositors to withdraw only 1 ether per week. 

contract EtherStore {
uint256 public withdrawalLimit = 1 ether;
   mapping(address => uint256) public lastWithdrawTime;
   mapping(address => uint256) public balances;
   function depositFunds() external payable {
balances[msg.sender] += msg.value;
   }
   function withdrawFunds (uint256 _weiToWithdraw) public {
       require(balances[msg.sender] >= _weiToWithdraw);
       // limit the withdrawal
       require(_weiToWithdraw <= withdrawalLimit);
       // limit the time allowed to withdraw
       require(now >= lastWithdrawTime[msg.sender] + 1 weeks);
       require(msg.sender.call.value(_weiToWithdraw)());
       balances[msg.sender] -= _weiToWithdraw;
       lastWithdrawTime[msg.sender] = now;
   }
}

In the above contract, we have two public functions, [depositFunds] and [withdrawFunds]. The [depositFunds] is used to increment the sender’s balance, whereas [withdrawFunds] specifies the amount to be withdrawn. In this case, it will be a success if the amount to be withdrawn is less than 1 ether. 

The pitfall here lies in line [17] where the transfer of ether takes place. The attacker could create a malicious contract with [EtherStores]’s contract address as the only constructor parameter. This would make [etherStore] a public variable, hence more prone to be attacked. 

QuilllAudit’s Approach

We follow various techniques to avoid potential reentrancy vulnerabilities in smart contracts. The very first and the best possible way is the use of the built-in [transfer] function when transferring ether to any external contract. 

Secondly, it is important to ensure that all the logic changes in the state variables should be done before sending ether out of the contract. In the [EtherStore] example, lines [18] and [19] should be put before line [17]. 

A third technique can also be used to prevent reentrant calls; through the introduction of a mutex. It is an addition of a state variable that will lock the contract during code execution. 

3. Default Visibilities

There are visibility specifiers for the functions we use in Solidity, and they prescribe the way they can be called. It is the visibility that determines the calling of the functions; externally by users, by other derived contracts, only internally or only externally. Let us look at how erroneous use of visibility specifiers can cause huge vulnerability in smart contracts. 

The Vulnerability

By default, the visibility of the function is [public], hence the external users can call the functions with no specific visibility. The bug arises when developers forget to specify visibility on functions that should be private (or can be called within the contract itself). For example;

contract HashForEther {
   function withdrawWinnings() {
       // Winner if the last 8 hex characters of the address are 0
       require(uint32(msg.sender) == 0);
       _sendWinnings();
    }
    function _sendWinnings() {
        msg.sender.transfer(this.balance);
    }
}

The above contract is a simple address-guessing bounty game. In this, we can see that visibility of the functions is not specified, particularly the          [ _sendWinnings] function is [public] (by default), hence this can be called through any address to steal the bounty. 

QuillAudit’s Approach

Our in-house team consists of seasoned developers who always follow the best audit practices, here the visibility of the functions should be specified explicitly, even if they are to be kept public, it should be mentioned. 

4. Safeguarding the Use of Constructors

Generally, Constructors are called special functions that are used to perform critical and privileged tasks while initializing the contracts. Before Solidity [v0.4.22], constructors were holding the same name used by the contract that contained them. Now, consider a case where the contract name is changed during the development phase but the constructor name remains the same, this loophole can also provide attackers an easy entry to your smart contract.  

The Vulnerability

It can lead to severe consequences if the contract name is modified but the constructor’s name is unchanged. For example:

contract OwnerWallet {
   address public owner;
   // constructor
   function ownerWallet(address _owner) public {
       owner = _owner;
   }
   // Fallback. Collect ether.
   function () payable {}
   function withdraw() public {
       require(msg.sender == owner);
       msg.sender.transfer(this.balance);
   }
}

In the above contract, we can see that only the owner can withdraw ether via calling the [withdraw] function. Here, the vulnerability occurs as the constructor is named different from the contract (the first letter is different!). Thus exploiter can call [ownerWallet] function and authorize themselves as owner, and then withdraw all the ether in contract by calling [withdraw]. 

QuillAudit’s Approach

We comply with version [0.4.22] of the Solidity compiler. This version has introduced a keyword; [constructor] which requires the name of the function to match the contract name. 

5. Tx.Origin Authentication 

Here, [Tx.Origin] is Solidity’s global variable, it contains the address of the account that originally executed the call or transaction. This variable can’t be used for authentication, as doing so makes the contract vulnerable to phishing attacks. 

The Vulnerability

Contracts authorizing users through [tx.origin] variable are exposed to external attacks leading users to perform authenticated actions on the erroneous contract. Consider the below example:

contract Phishable {
   address public owner;
   constructor (address _owner) {
       owner = _owner;
   }
   function () external payable {} // collect ether

   function withdrawAll(address _recipient) public {
       require(tx.origin == owner);
       _recipient.transfer(this.balance);
   }
}

Here at line [11], the contract authorizes [withdrawAll] function with the help of [tx.origin]. 

QuillAudit’s Approach

We generally avoid using [tx.origin] for authorization in smart contracts. Although, the use of [tx.origin] isn’t strictly prohibited, it has some specific use cases. We can use [tx.origin] to deny external contracts from calling the present contract, it can be executed with [require] of the form [require(tx.origin == msg.sender)]. It is done to avoid the calling of intermediate contracts to call the current contract which limits the contract to regular codeless addresses.

Final Wrap-Up

We have comprehensively covered the five common pitfalls in the Solidity language. While developing smart contracts, we must not forget that they are immutable by design, which means that once we create them, there is no way to patch the source code. 

This poses a great challenge to developers to take the advantage of available security testing and auditing tools before deployment. 

Discovering potential malicious threats to the smart contracts, and the risks some of which we mentioned above are performed in a very unique and robust way by our in-house team of auditing experts. We at QuillAudits put our best efforts into security research to keep your contract updated with all the software security practices to keep your contract safe and secure.

Reach out to QuillHash

With an industry presence of years, QuillHash has delivered enterprise solutions across the globe. QuillHash with a team of experts is a leading blockchain development company providing various industry solutions including DeFi enterprise, If you need any assistance in the smart contracts audit, feel free to reach out to our experts here!

Follow QuillHash for more updates

Twitter | LinkedIn Facebook

Leave a Reply

Your email address will not be published. Required fields are marked *

Top