Language Constructs Available in Solidity Language

Posted By : Amit Kumar | 30-Apr-2019

Like most statically typed languages, Solidity requires you to explicitly declare the type of each variable or at least needs the type to be inferred unequivocally by the compiler. Its data type system includes both value types and reference types, which I’ll present in the next few sections.

 

1) Types
A value type variable is stored on the EVM stack, which allocates single memory space to hold its value. When a value type variable is assigned to another variable or passed to a function as a parameter, its value is copied into a new and separate instance of the variable. Consequently, any change in the value of the assigned variable doesn’t affect the value of the original variable. Value types include most native types, enums, and functions.

bool
bool isComplete = false;

Integer types

You can declare integer variables either as int (signed) or uint (unsigned). You also can specify an exact size, ranging from 8 to 256 bits, in multiples of 8. For example, int32 means signed 32-bit integer, and uint128 means unsigned 128-bit integer. If you don’t specify a size, it’s set to 256 bits. The sidebar explains how the assignment between variables of different integer types works.

contract IntConversions {
    int256 bigNumber = 150000000000;

    int32 mediumNegativeNumber = -450000;
    uint16 smallPositiveNumber = 15678;

    int16 newSmallNumber = bigNumber;           1
    uint64 newMediumPositiveNumber = 
      mediumNegativeNumber;                     2
    uint32 public newMediumNumber = 
      smallPositiveNumber;                      3
    int256 public newBigNumber = 
      mediumNegativeNumber;                     4
}
1 Compile error because newSmallNumber is too small to contain bigNumber
2 Compiler error because uint64 can only hold positive numbers
3 small positive number is implicitly converted from uint16 to uint32; newMediumNumber =15,678
4 mediumNegativeNumber is implicitly converted from int32 to int256; newBigNumber =-450,000
To compile this code, remove the two lines that are causing errors, such as

TypeError: type int256 isn’t implicitly convertible to expected type int16
Then instantiate the contract (click Deploy). Finally, get the values of newMedium-Number and newBigNumber by clicking the corresponding buttons.

When an implicit conversion isn’t allowed, it’s still possible to perform explicit conversions. In such cases, it’s your responsibility to make sure the conversion is meaningful to your logic. To see an example of explicit conversions in action, add the following two lines to the IntConversions contract:

    int16 public newSmallNumber = 
       int16(bigNumber);                       1
    uint64 public newMediumPositiveNumber =
       uint64(mediumNegativeNumber);           2


1 The explicit conversion and assignment are successful, but newSmallNumber = 23,552.
2 The explicit conversion and assignment are successful, but newMediumPositiveNumber = 18,446,744,073,709,101,616.
Reinstantiate the contract (by clicking Create again), then get the value of newSmallNumber and newMediumPositiveNumber by clicking the corresponding buttons. The results of the explicit integer conversions aren’t intuitive: they wrap the original value around the size of the target integer type (if its size is smaller than that of the source integer type) rather than overflowing.

The principles behind implicit and explicit conversions between integer types apply also to other noninteger types.

Static byte arrays
You can declare byte arrays of a fixed size with a size ranging from 1 to 32—for example, bytes8 or bytes12. By itself, a byte is an array of a single byte and is equivalent to bytes1.

Address

Address objects, which you generally declare using a literal containing up to 40 hexadecimal digits prefixed by 0x, hold 20 bytes; for example:
address owner address = 0x10abb5EfEcdC09581f8b7cb95791FE2936790b4E;

It’s possible to get the Ether balance in Wei (the smallest Ether denomination) associated with an address by querying the balance property. You can try it by putting the following sample contract in Remix and executing the getOwnerBalance() function:

contract AddressExamples {
    address ownerAddress = 0x10abb5EfEcdC09581f8b7cb95791FE2936790b4E;
    
    function getOwnerBalance() public returns (uint)  {
        uint balance = ownerAddress.balance;
        return balance;
    }
}
You’ll see the return value in the output panel at the bottom left of the Remix screen after you click the Details button next to the output line corresponding to the function call. The address type exposes various functions for transferring Ether. Table 5.1 explains their purposes.

Table 5.1. Functions provided by the address type

Function

Purpose

transfer()    To transfer Ether in Wei—If the transaction fails on the receiving side, an exception is thrown to the sender and the payment is automatically reverted (although any spent gas isn’t refunded), so no error handling code is necessary; transfer() can spend only up to a maximum of 2300 gas.
send()    To send Ether in Wei—If the transaction fails on the receiving side, a value of false is returned to the sender but the payment isn’t reverted, so it must be handled correctly and reverted with further instructions; send() can spend only up to a maximum of 2,300 gas.
call()    To invoke a function on the target contract associated with the address (the target account is assumed to be a contract)—An amount of Ether can be sent together with the function call by specifying it in this way: call.value(10)("contractName", "functionName"); call() transfers the entire gas budget from the sender to the called function. If call() fails, it returns false, so failure must be handled in a similar way to send().
Warning

Because of security concerns, send() and call() are being deprecated, and it won’t be possible to use them in future versions of Solidity.

This is an example of an Ether transfer using transfer():

destinationAddress.transfer(10);        
Sends 10 Wei to destinationAddress
If sending Ether with send(), you must have error handling to avoid losing Ether:

destinationContractAddress.call("contractName", 
   "functionName");                             
Invokes an external contract function
You can send Ether during the external call as follows:

destinationContractAddress.call.value(10)(
   "contractName", "functionName");              1

Enums

An enum is a custom data type including a set of named values; for example:

enum  InvestmentLevel {High, Medium, Low}
You can then define an enum-based variable as follows:

investment level = investment level. Medium;
The integer value of each enum item is implicitly determined by its position in the enum definition. In the previous example, the value of High is 0 and the value of Low is 2. You can retrieve the integer value of an enum type variable by explicitly converting the enum variable to an int variable as shown here:

investment level = investment level. Medium;
...
int16 level value = int16(level);
Implicit conversions aren’t allowed:

 

2. Reference types
Reference type variables are accessed through their reference (the location of their first item). You can store them in either of the following two data locations, which you can, in some cases, explicitly specify in their declaration:

Memory—Values aren’t persisted permanently and only live in memory.
Storage—Values are persisted permanently on the blockchain, like state variables.
The third type of data location is available that you can’t explicitly specify:

There are four classes of reference types:

Arrays
Strings
Structs
Mappings
Arrays can be static (of fixed size) or dynamic and are declared and initialized in slightly different ways.

Static arrays

You must specify the size of a static array in its declaration. The following code declares and allocates a static array of five elements of type int32:

   int32[5] memory fixes lots;
   fixedSlots[0] = 5;

Dynamic arrays

You don’t need to specify the size in the declaration of dynamic arrays, as shown in the following snippet:

function f(){
   int32[] unlimited lots;
}
You can then append items to a dynamic array by calling the push member function:

unlimited shots.push(6);
unlimited shots.push(4);

If you need to resize a dynamic array, you must do so in different ways depending on whether its data location is memory or storage. If the data location is storage, you can reset its length, as shown in the following snippet:

String

the string is, in fact, equivalent to bytes but with no length and push() members. You can initialize it with a string literal:

string name = "Roberto";

Struct

A struct is a user-defined type that contains a set of elements that in general are each of a different type. The following listing shows a contract declaring various structs that get referenced in its state variables. This example also shows how you can use enums in a struct.

Mapping

mapping is a special reference type that you can only use in the storage data location, which means you can declare it only as a state variable or a storage reference type. You might remember mapping is the Solidity implementation of a hash table, which stores values against keys. The hash table is strongly typed, which means you must declare the type of the key and the type of the value at its declaration:

mapping(address => int) public coin balance;
In general, you can declare the value of any type, including primitive types, arrays, structs, or mappings themselves.

 

3. Global namespace
The global namespace is a set of implicitly declared variables and functions that you can reference and use in your contract code directly.

Implicitly declared variables

The global namespace provides the following five variables:

the block holds information about the latest blockchain block.
msg provides data about the incoming message.
tx provides transaction data.
this is a reference to the current contract. You can use it to call internal functions as if they were defined external and therefore store the message call on the blockchain. (internal and external are function accessibility levels that I’ll explain later, in the functions section.) If you use it by itself, it’s implicitly converted to the address of the current contract.
now is the time associated with the creation of the latest block, expressed as a Unix epoch.

 

4. State variables
You already know state variables hold the contract state. What I haven’t covered so far is the access level that you can specify when declaring them. Table 5.4 summarizes the available options.

Table 5.4. Access levels of a state variable

Access level

Description

public    The compiler automatically generates a getter function for each public state variable. You can use public state variables directly from within the contract and access them through the related getter function from the external contractor client code.
internal    The contract and any inherited contract can access Internal state variables. This is the default level for state variables.
private    Only members of the same contract—not inherited contracts—can access private state variables.

 


5. Functions
You can specify function input and output parameters in various ways. Let’s see how.

Input parameters declaration

You declare input parameters in Solidity, as in other statically typed languages, by providing a list of typed parameter names, as shown in the following example:

    function process1(int _x, int _y, int _z, bool _flag) {
    ...
    }
If you don’t use some of the parameters in the implementation, you can leave them unnamed (or anonymous), like the second and third parameter in the following example:

    function process2(int _x, int, int, bool _flag) {
        if (_flag)
           stateVariable = _x;
    }

Access level
external    An external function is exposed in the contract interface, and you can only call it from external contracts or client code but not from within the contract.
public    A public function is exposed in the contract interface, and you can call it from within the contractor from external contracts or client code. This is the default access level for functions.
internal    An internal function isn’t part of the contract interface, and it’s only visible to contract members and inherited contracts.
private    A private function can only be called by members of the contract where it’s been declared, not by inherited contracts.

 

6. Events
An event allows a contract to notify another contract or a contract client, such as a Dapp user interface, that something of interest has occurred. You declare events like you do in C# and Java and publish them with the emit keyword, as you can see in the following code extract from SimpleCoin:

    event Transfer(address indexed from, 
          address indexed to, uint256 value);           
    function transfer(address _to, uint256 _amount) public {
        coinBalance[msg.sender] -= _amount;  
        coinBalance[_to] += _amount;   
        emit Transfer(msg.sender, _to, _amount);        
    }

Events in Ethereum haven’t only a real-time notification purpose, but also a long-term logging purpose. Events are logged on the transaction log of the blockchain, and you can retrieve them later for analysis. To allow quick retrieval, events are indexed against a key that you can define when you declare the event. The key can be composite and contain up to three of its input parameters, as you can see in the definition of Transfer shown previously:

event Transfer(address indexed from, address indexed to, uint256 value);

 

7. Conditional statements
Solidity supports all classic conditional statements available in C-like and Java-like languages:

if ... else
while
do ... while
for
Loops support both continue and break statements.

 

About Author

Author Image
Amit Kumar

Amit is an seasoned Backend Developer with expertise in Java, particularly the Spring framework as well as Javascript and related frameworks such as Node.js and Express. He possesses a solid understanding of blockchain fundamentals and has hands-on experience integrating major blockchains like Ethereum and Bitcoin into both centralized and decentralized applications. He is proficient in working with relational and non-relational databases, enabling him to handle various data storage requirements effectively. He has successfully contributed to a range of projects, including Belfrics, a cryptocurrency exchange platform, Daxxcoin, an Ethereum Proof-Of-Work fork, and Wethio blockchain, an Ethereum-based Proof-Of-Stake fork. His strong technical skills and practical experience make him a valuable asset, particularly in the blockchain domain. Amit's ability to integrate blockchain technologies into applications showcases his versatility and innovation.

Request for Proposal

Name is required

Comment is required

Sending message..