With this article, we’re opening a new area of our study, that of units and globally available variables in Solidity.
To begin with, we’ll learn about ether units and time units. After that, we’ll start with a block of subsections on special variables and functions, stretching through this and the next two articles.
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.
Ether Units
When mentioning Ether units of currency, we can express them with a literal number and a suffix of wei, gwei or ether.
These suffixes specify a sub-denomination of Ether. We assume amounts written without a suffix as Wei.
The purpose and effect of using sub-denomination is a multiplication of the denomination by a power (exponent) of ten.
assert(1 wei == 1); assert(1 gwei == 1e9); assert(1 ether == 1e18);
π‘ Note: There can be found denominations, such as finney and szabo, but they were deprecated in Solidity v0.7.0.
Time Units
Solidity has a nice and natural way of expressing time units with suffixes, such as seconds, minutes, hours, days, and weeks.
Seconds are the base unit, and units are considered to correspond to:
- 1 == 1 seconds
- 1 minutes == 60 seconds
- 1 hours == 60 minutes
- 1 days == 24 hours
- 1 weeks == 7 days
When working with calendar calculations using these units, we should take extra care, because only some years have 365 days, and because of leap seconds, not even every day has 24 hours.
Since leap seconds are unpredictable, an exact calendar always has to be updated by an external source (oracle), which motivated Solidity authors to remove the suffix years in Solidity v0.5.0.
We should remember that these suffixes cannot be applied to variables, meaning if we want to interpret a function parameter expressed in days, we can easily do so like in the example below:
function f(uint start, uint daysAfter) public { if (block.timestamp >= start + daysAfter * 1 days) { // ... } }
Special Variables and Functions
The global namespace contains special variables and functions primarily used to provide us with information about the blockchain. They are also available utility functions for general use.
Block and Transaction Properties
The following is a list of block and transaction properties, as shown in the official Solidity documentation. Parentheses next to each block/transaction property define the member
type:
blockhash(uint blockNumber)
returns (bytes32
): hash of the given block whenblocknumber
is one of the 256 most recent blocks; otherwise returns zeroblock.basefee (uint)
: current blockβs base fee (as defined in Ethereum Improvement Proposals EIP-3198 and EIP-1559)block.chainid (uint)
: current chain idblock.coinbase (address payable)
: current block minerβs addressblock.difficulty (uint)
: current block difficultyblock.gaslimit (uint)
: current block gas limitblock.number (uint)
: current block numberblock.timestamp (uint)
: current block timestamp as seconds since Unix epochgasleft() returns (uint256)
: remaining gasmsg.data (bytes calldata)
: complete calldatamsg.sender (address)
: the sender of the message (current call)msg.sig (bytes4)
: first four bytes of the calldata (i.e. function identifier)msg.value (uint)
: number of Wei sent with the messagetx.gasprice (uint)
: the gas price of the transactiontx.origin (address)
: the sender of the transaction (full call chain)
Note: We should expect the values of all members of msg
, including msg.sender
and msg.value
will change with every external function call. This expectation also applies to library functions.
π‘ Info: Off-chain computation is simply a computation that takes place outside a blockchain. Oracle networks can provide a trust-minimized form of off-chain computation to extend the capabilities of blockchains – this is known as oracle computation. (Chainlink)
Contracts can be evaluated both off-chain and on-chain, i.e. in the context of a transaction included in a block.
If a contract is evaluated off-chain, we should assume that block.*
and tx.*
members don’t refer to a specific block or transaction. Instead, the values we’d find in these members are provided by the EVM implementation executing the contract, and therefore, they can be completely arbitrary.
ποΈ Note: It is suggested to avoid block.timestamp
or blockhash
member values as sources of randomness. The reason for this suggestion lies in the fact that, in some instances, miners can manipulate both the timestamp and the block hash. The timestamp of the current block must be strictly larger than the timestamp of the last block, and the only thing we can know for sure is that it will be between the timestamps of two neighboring blocks in the canonical chain; how far it will be from any particular block timestamp is not known.
π‘ Info: “The word canonical is used to indicate a particular choice from a number of possible conventions. This convention allows a mathematical object or class of objects to be uniquely identified or standardized.” (Wolfram.com)
Only the recent 256 blocks’ hashes are available, due to scalability reasons. A hash of any older block will be zero.
In Solidity versions prior to 0.5, the current blockhash(...)
function was previously known and available as block.blockhash(...)
; the current gasleft(...)
function was previously known and available as msg.gas(...)
.
In Solidity v0.7.0 the now
alias (for block.timestamp
) was removed.
ABI Encoding and Decoding Functions
The following list contains ABI-appropriate functions for low-level interactions with EVM, as laid out in the official Solidity documentation:
abi.decode(bytes memory encodedData, (...)) returns (...)
: ABI-decodes the given data, while the types are given in parentheses as second argument. Example:(uint a, uint[2] memory b, bytes memory c) = abi.decode(data, (uint, uint[2], bytes))
abi.encode(...) returns (bytes memory)
: ABI-encodes the given argumentsabi.encodePacked(...) returns (bytes memory)
: Performs packed encoding of the given arguments. Note that packed encoding can be ambiguous!abi.encodeWithSelector(bytes4 selector, ...) returns (bytes memory)
: ABI-encodes the given arguments starting from the second and prepends the given four-byte selectorabi.encodeWithSignature(string memory signature, ...) returns (bytes memory)
: Equivalent toabi.encodeWithSelector(bytes4(keccak256(bytes(signature))), ...)
abi.encodeCall(function functionPointer, (...)) returns (bytes memory)
: ABI-encodes a call tofunctionPointer
with the arguments found in the tuple. Performs a full type-check, ensuring the types match the function signature. Equivalent toabi.encodeWithSelector(functionPointer.selector, (...))
ποΈ Note: “These encoding functions can be used to craft data for external function calls without actually calling an external function. Furthermore, keccak256(abi.encodePacked(a, b))
is a way to compute the hash of structured data (although be aware that it is possible to craft a βhash collisionβ using different function parameter types).” (docs)
Conclusion
In this article, we learned about ether and time units, followed by special variables and functions.
First, we introduced ether units and discussed the use of the main unit and its sub-denominations.
Second, we introduced time units and discussed the possibilities of expressing time in different time units.
Third, we took a closer look at the block and transaction properties, but also listed many of them with descriptions and notes on specific behaviors for a more thorough understanding.
Fourth, we also touched on the topic of ABI encoding and decoding functions, described them, and gave a usage hint in a form of a note.
What’s Next?
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):