Introduction to Solidity : Units and Global Variables

In this blog post, we will explore two concepts in Solidity: units and globally available variables. We'll explore the different time and Ether units offered by Solidity and how to use globally available variables to streamline your code. By the end of this post, you'll have a better understanding of how to leverage Solidity's units and globally available variables to build smart contracts.

Let's start with the unit

Ether units

In Solidity you can define Ether values with three different units wei, gwei, ether.

The following table shows the different units available in Solidity and their corresponding values in wei.

Unit NameUnit Value
wei1
gwei1000000000
ether1000000000000000000

By default Ether number without a unit are interpreted as wei. So if you have to specify a value of uint to ether value you can something like this :

pragma solidity ^0.8.0;

contract ExampleContract {
    uint public price;

    constructor(uint _priceInEther) {
        price = _priceInEther * 1 ether; // convert input price in Ether to wei
    }

    function purchase() public payable {
        require(msg.value >= price, "Insufficient payment.");
        // perform purchase logic
    }
}

Time units

In solidity you can use time suffixes after number literal to specify time units. The following suffixes are available: seconds, minutes, hours, days, weeks where seconds is the base units. Suffix can't be applied to variables. So to convert a variable to a time unit, you have to multiply it by the corresponding time unit like this :

endTime = block.timestamp + (_durationInMinutes * 1 minutes);

endTime = block.timestamp + (_durationInHours * 1 hours);

In this exemple _durationInHours is a variable of type uint.

But be careful, the time units are not always the same. For example, a month is not always 30 days, and a year is not always 365 days.

Global variables

Solidity provides a set of global variables and functions that are available in all contracts. These variables are useful for retrieving information about the blockchain, the current account, and the current function.

If you want to get the full list of global variables, you can check the Solidity documentation

We will see some of the most useful ones after.

Block and Transaction Properties

  • block.timestamp (uint): current block timestamp as seconds since unix epoch
  • msg.sender (address): sender of the message (current call)
  • msg.value (uint): number of wei sent with the message
pragma solidity ^0.8.0;

contract Purchase {
    address public seller;
    address public buyer;
    uint public price;

    constructor() {
        seller = msg.sender;
        price = 1 ether;
    }

    function buy() public payable {
        require(msg.value == price, "Incorrect payment amount");
        buyer = msg.sender;
        seller.transfer(msg.value);
    }
}

In this example, we have a Purchase contract that allows a buyer to purchase an item from a seller for a price of 1 ether.

When the contract is deployed, the seller address is set to msg.sender, which is the address of the account that deployed the contract.

The buy function can be called by anyone, but requires a payment of exactly price in ether to be sent along with the transaction. The msg.value property is used to get the amount of ether sent with the transaction. If the payment amount is incorrect, the function will revert with an error message.

If the payment amount is correct, the buyer address is set to msg.sender, which is the address of the account that sent the payment. Then, the transfer function is called on the seller address, which sends the payment amount to the seller.

Error handling

Here is a list of the most useful global functions for error handling:

assert(bool condition) causes a Panic error and thus state change reversion if the condition is not met - to be used for internal errors.

require(bool condition) reverts if the condition is not met - to be used for errors in inputs or external components.

require(bool condition, string memory message) reverts if the condition is not met - to be used for errors in inputs or external components. Also provides an error message.

revert() abort execution and revert state changes

revert(string memory reason) abort execution and revert state changes, providing an explanatory string

Mathematical and Cryptographic Functions

keccak256(bytes memory) returns (bytes32) compute the Keccak-256 hash of the input. This one in combination with abi.encodePacked() is very useful to compare strings for exemple.

function isLanguageFR(string memory _language) public {
  if (keccak256(abi.encodePacked('FR')) == keccak256(abi.encodePacked(_language))) {
  }
}

We need to do this because we can't use operators like == or != to compare strings. And we need to use abi.encodePacked() because keccak256() only accepts a single bytes type argument.

Members of the Address Type

And to finish this article, we will see some useful members of the address type.

<address>.balance (uint256) balance of the Address in Wei

you can combine this one with Ether units to get the balance in ether.

uint balanceInEther = address(this).balance / 1 ether;

<address payable>.send(uint256 amount) returns (bool) <address payable>.transfer(uint256 amount)

They both allow to send ether to an address. The difference is that send() returns a boolean and doesn't throw an exception if the transfer fails, while transfer() throws an exception if the transfer fails.

It's recommended to use transfer() because you don't have to hanlde error yourself. Otherwise you need to do something like this :

bool success = <address>.send(amount);
if(!success) throw;

End

In this blog post, we explored the concepts of units and globally available variables in Solidity. By leveraging this concept, you can build more efficient and secure smart contracts. I hope you enjoyed this article and learned something new. If you have any questions or comments, feel free to contact me on twitter @\0xtiby.