Continuing on the previous article concerning units and globally available variables, we’ll learn about members of address types and type information.
It’s part of our long-standing tradition to make this (and other) articles a faithful companion, or a supplement to the official Solidity documentation.
▶️ Play: Feel free to watch our article explainer video while going through the article content:
Members of Address Types
In this subsection, we’ll list the members of
address types. The same list is available in official Solidity documentation.
Returns balance of the Address expressed in Wei.
<address>.code (bytes memory)
Returns the code at the Address (can be empty).
Returns the codehash of the Address
<address payable>.transfer(uint256 amount)
This function sends a given amount of Wei to an address, reverts on failure, and forwards 2300 gas stipend (this behavior isn’t adjustable).
<address payable>.send(uint256 amount) returns (bool)
This function sends a given amount of Wei to an address, returns false on failure, and forwards 2300 gas stipend (this behavior isn’t adjustable).
<address>.call(bytes memory) returns (bool, bytes memory)
This function issues a low-level CALL with the given payload, returns success condition and return data, and forwards all available gas (this behavior is adjustable).
<address>.delegatecall(bytes memory) returns (bool, bytes memory)
This function issues low-level DELEGATECALL with the given payload returns success condition and return data, forwards all available gas (this behavior is adjustable).
<address>.staticcall(bytes memory) returns (bool, bytes memory)
This function issues low-level STATICCALL with the given payload returns success condition and return data, and forwards all available gas (this behavior is adjustable).
Below are several warning and note boxes, which I usually edit and make more readable for all of us, regardless of our experience level. However, these ones are written clearly and concisely, so I’ll just quote them. Pay extra attention to their content!
⚡ Warning: “You should avoid using
.call() whenever possible when executing another contract function as it bypasses type checking, function existence check, and argument packing.” (docs)
Explanation: It’s much better to make a call to another contract’s function as
This way, we’re using Solidity’s core functionalities, such as type checking, function existence, and argument checking.
In contrast to this approach, we can use
However, we should treat these functions as a last resort because they’re low-level functions that bypass much of Solidity’s safety and security mechanisms. Think of them like hot-wiring a car; it can be done, but it’s not recommended, to say the least.
⚡ Warning: “There are some dangers in using
send(...): The transfer fails if the call stack depth is at 1024 (this can always be forced by the caller) and it also fails if the recipient runs out of gas. So in order to make safe Ether transfers, always check the return value of
transfer(...) or even better: Use a pattern where the recipient withdraws the money.” (docs)
There are three common ways of transferring the currency:
- by calling
- by calling
- by letting the beneficiary (recipient) withdraw the currency himself.
The most preferred way is the latter one: let the beneficiary withdraw the currency, as it conforms to security and safety practices. Safe, but in practice, the less preferred way of getting the currency from a sender to the recipient is via the
The third, most discouraged, but still possible way of transferring the currency is by using the
send(...) function can run into all sorts of problems, including overstepping the stack depth at 1024 (this scenario can be intentionally forced by the caller), or it can happen spontaneously if the recipient runs out of gas.
In other words, to be on the safe side and compliant with good contract-making practices, put the currency transfer functionality on the beneficiary’s side and make him withdraw the currency.
💡 Note: You’re surely noticing a recurring tone surrounding the use of low-level functions: there’s always a scent of risk and insecurity with them.
⚡ Warning: “Due to the fact that the EVM considers a call to a non-existing contract to always succeed, Solidity includes an extra check using the
extcodesize opcode when performing external calls. This ensures that the contract that is about to be called either actually exists (it contains code) or an exception is raised.” (docs)
The EVM has an interesting feature: it always presents a call to a non-existent contract as successful.
Given that an external function call can be done in two ways, via a high-level call or a low-level call, we have the means of finding out about the real status of making an external call.
👉 Recommended Tutorial: Internal and External Function Calls
A high-level external function call checks the contract size by reading the
- If the
extcodesizeindicates a zero-size contract, the external function call is terminated with an exception thrown.
- Otherwise, the opcode will show a non-zero value, meaning that the contract exists and that the external function call is successful.
This is a before-call check.
A low-level external function call (
<contract_address>.delegatecall(...)) is based on checking the return data (after the external function call) by the ABI decoder.
The decoder will check the data after the function is called, and if it finds the data to be valid, it’ll conclude that the contract exists and that the external function call was successful.
This is an after-call check.
A low-level external function call may be used because it uses less gas, but once again, it’s less safe.
Note: Before Solidity 0.5.0, we could’ve accessed the address members by a contract instance, i.e. via syntax
this.balance. This approach is not available since Solidity 0.5.0, but instead, we should use an explicit conversion to address, i.e.
One more difference between a high-level and a low-level external function call is in accessing the state variables.
👉 Recommended Tutorial: State Variables in Solidity
When we access state variables via low-level
<address>.delegatecall(...), we must ensure that the storage layouts of the call-originating and the call-receiving contract are aligned.
Otherwise, when using high-level libraries, the storage pointers are passed as function arguments and the storage layouts of the contracts don’t have to be aligned.
Note: Before Solidity 0.5.0,
<address>.staticcall(...) functions returned the success condition, but not the return data.
Also, Solidity versions before had a member called
<address>.callcode(...). It had similar, but slightly different semantics than
<address>.callcode(...) was removed in Solidity 0.5.0.
In computer science, semantics is equal to meaning.
There’s a very neat expression
type(X) available for finding out information about some of the types, denoted by an
X. At the present time,
X can stand for a contract or an integer type, but future expansions of supported types are possible.
There are three properties available for a contract type
C stands for an arbitrary contract):
By reading this property, we’ll find out the name of a contract.
By reading this property, we get a memory byte array containing the bytecode that was used to create the contract.
We can use the bytecode in inline assembly for building custom creation routines, particularly combined with the
create2 opcode. There is a limitation to accessing this property, i.e. it cannot be accessed in the contract itself or any derived contract.
It would cause the bytecode to be included in the bytecode of the call site and create a circular reference that is not possible.
By reading this property, we get a memory byte array containing the runtime bytecode that was used to create the contract.
Runtime bytecode is the code usually deployed by the constructor of contract C. If C has a constructor using inline assembly, the runtime bytecode might differ from the deployed bytecode.
We should note that libraries modify their runtime bytecode during the deployment to prevent regular calls. These are the same restrictions as with
Along with these three properties, we should also mention a property available for an interface type represented with
I stands for an arbitrary interface):
By reading this property, we get a bytes4 value containing the interface identifier of the given interface I, corresponding to the Ethereum Improvement Proposal EIP-165. This identifier is defined as the XOR operation applied on all function selectors defined within the interface itself, but excluding all inherited functions.
For an integer
T stands for an integer type), we have two available properties:
By reading this property, we get the smallest value representable by type
By reading this property, we get the largest value representable by type
In this article, we learned about members of address types and type information available for contracts, integers, and interfaces. It’s expected that we’ll see more of the supported type information in future versions of Solidity.
First, we went through a list of address member properties and functions, followed by several warnings and notes, i.e. recommendations and guidelines on using the address member properties.
Second, we got more closely familiar with what type information is, what it offers and how we can use it in our Solidity coding endeavors.
This tutorial is part of our extended Solidity documentation with videos and more accessible examples and explanations. You can navigate the series here (all links open in a new tab):
I’m an experienced computer science engineer and technology enthusiast dedicated to understanding how the world works and using my knowledge and ability to advance it. I’m focused on becoming an expert in Solidity and crypto technology, with a passion for coding, learning, and contributing to the Finxter mission of increasing the collective intelligence of humanity.