Table of contents
Solidity Attack Vectors: #8 No Address Zero Check
There's no lie in saying that the "address" type is one of the most used data types in Solidity, especially when dealing with any value or receiving of values.
The Zero Address
The Zero Address is an address usually used for the burning of tokens and Ether. Anything sent to the Zero Address is lost and lost forever because there is no corresponding private key for it and no one has the private key. It was made that way on the blockchain. Therefore, it cannot sign or initiate transactions on the blockchain.
Here's what it looks like: 0x0000000000000000000000000000000000000000
If you have a function that accepts external address inputs, perhaps from users of the contract or even yourself, it is important to validate that inputted address against the Zero Address. Depending on the contract's logic, things can get really messy, especially if that inputted address is a zero address and the intended address was meant to be used for something important or with values. Checks against the Zero Address will help stop things like Ether or tokens getting sent to an address that can't remove or use the Ether or tokens, hence it gets stuck there forever.
Let's take a look at a practical code example below:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract Database {
struct User {
address userAddress;
string name;
}
User[] public users;
function registerUser(address _userAddress, string memory _name) external {
users.push(User(_userAddress, _name));
}
function payUser(uint256 index) external {
address user = users[index].userAddress;
payable(user).transfer(1 ether);
}
receive() external payable {}
}
If we look at the "registerUser" function in the "Database" contract above, it pushes the inputted user address with the user's name to the "users" array. Then, in the "payUser" function, we just send Ether to whatever address that has been saved in the array. This is a simple contract, but mistakes like this can happen in our present day.
Two things can go wrong here:
- If the previously saved address in the array at that index is the Zero Address, when we send Ether to that address it gets stuck there forever.
- Solidity has default values for every "value" data type, which is zero. So if the "payUser" function is called with an index in the array that hasn't been initialized yet, Solidity wouldn't revert. Instead, the address gotten is also the Zero Address, because it is the default value of the address type in Solidity.
An attacker can use this to exploit the contract by inputting the Zero Address and wasting resources in this way.
To prevent all of this from happening, we can rewrite the "Database" contract in the following way:
contract DatabaseAmended {
struct User {
address userAddress;
string name;
}
User[] public users;
function registerUser(address _userAddress, string memory _name) external {
require(_userAddress != address(0), "Zero Address not allowed");
users.push(User(_userAddress, _name));
}
function payUser(uint256 index) external {
address user = users[index].userAddress;
require(user != address(0), "Zero Address not allowed");
payable(user).transfer(1 ether);
}
receive() external payable {}
}
Now, in both functions, we check that the address obtained is not equal to the Zero Address. This helps prevent sending value to an address that will cause it to get stuck forever.
Conclusion So far, we have learned what the Zero Address is and why it is important to guard against it. See you in the next article of my Solidity Attack Vectors series.