We have just seen how a small loophole leads to a financial loss (of varying magnitude), in the similar way smart contracts developed over Solidity are prone to various known and unknown attacks. The exploiters take the advantage of bugs, and loopholes to peek into smart contracts and manipulate them to carry out attacks. Here we present a comprehensive list of the top 5 commonly encountered errors in the Solidity programming language.
At QuillAudits we follow the adaptive methodology to get the gist of every hack and implement its learnings on future smart contracts to avoid any potential threat.
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
Source: https://blog.quillhash.com/2021/06/04/top-5-common-errors-in-solidity-programming-language/
- 11
- Account
- ADvantage
- All
- audit
- Authentication
- authorization
- BEST
- blockchain
- Bug
- bugs
- call
- cases
- Cause
- challenge
- code
- Common
- company
- contract
- contracts
- Current
- DAO
- DeFi
- Design
- Developer
- developers
- Development
- Enterprise
- Ether
- ethereum
- Event
- experts
- financial
- First
- follow
- form
- Free
- function
- future
- game
- GAS
- Global
- great
- hack
- here
- How
- HTTPS
- huge
- Including
- industry
- IT
- language
- lead
- leading
- Line
- List
- Match
- Other
- owner
- Patch
- Pattern
- phishing
- phishing attacks
- prescribe
- present
- private
- Programming
- public
- pulling
- research
- response
- REST
- safe
- security
- Services
- set
- Simple
- small
- smart
- smart contract
- Smart Contracts
- So
- Software
- solidity
- Solutions
- State
- success
- Testing
- The Source
- threats
- time
- top
- top 5
- transaction
- Transactions
- us
- users
- value
- Vault
- visibility
- Vulnerabilities
- vulnerability
- Vulnerable
- week
- WHO
- within
- years