Development
May 18, 2018

Learning Solidity — Tips And Tricks From A Java Developer

Dominik Hys
Android Developer

During the last year alone all of the cryptocurrencies’ market cap boomed from $17B in January 2017 up to a whopping $830B in January 2018. It was a very sudden surge in interest and adoption taking into account that it took almost four years to reach $17B mark (May 2013 — January 2017). But cryptocurrencies are not only about coins and money.

There’s also a blockchain part which can be even more interesting in terms of technology. Seeing the growing interest in the community, I decided to take a closer look at it, learn how to develop decentralized applications, and see how does it differ from developing in Java and Kotlin in which I had been writing code every day.

Ethereum blockchain applications

With a launch of the Ethereum platform creating your own blockchain applications (so-called dapps) and writing smart contracts (in short: pieces of code residing on the blockchain) became much simpler. The most popular way of doing so became via Solidity — a new programming language created specifically for this purpose. After simplicity, adoption followed — people started creating hundreds of decentralized applications. The most popular of them all is CryptoKitties, which lets you buy and breed virtual kittens. At one point it was so widely used that it slowed down the whole Ethereum network. People were buying and selling their virtual pets so quickly that all of the transactions amassed to over $24M. The most expensive kitten was sold for over $100K. Seeing how fast this environment grows, I tried to learn Solidity. To do this, I dropped Android development for a few months and started writing smart contracts full time.

Object-oriented programming vs Design by contract

Solidity is a programming language created to follow the “design by contract” approach to creating software. It is not a new concept — it was first created by Bertrand Meyer in 1986 while working on a new programming language: Eiffel. It encourages developers to define formal preconditions, postconditions, and invariants for every software component. These formal specifications are called “contracts”. In Solidity, they are named that as well and are very similar to Java Classes. They both can have constructors, private and public methods, global and local variables, and can be instantiated. However, Solidity contracts also have public addresses in the blockchain (after being deployed) and can store and send value.

Similarities and differences between Solidity and Java

The biggest difference in how the code looks in these two languages are modifiers and events. Both are usually declared at the beginning of a contract.

Modifiers

Modifiers are used to restrict who can make modifications to the contract’s state or call the contract’s functions and are reusable (can be used in multiple functions). They are also a tool to enforce the function’s pre- and post-conditions.

contract NumberContract {
  address private contractIssuer;
  uint private number;
  
  modifier onlyContractIssuer {       
      require(contractIssuer == msg.sender);
      _;
  }
  
  function NumberContract() public {
      contractIssuer = msg.sender;    
  } 
  
  function setNumber(uint newNumber) onlyContractIssuer public {
      number = newNumber;
  }
}

Events

Events allow the usage of the Ethereum Virtual Machine (EVM) logging facilities and can be used to call callbacks that listen for them.

Events are inheritable members of contracts. When they are called, they cause the arguments to be stored in the transaction’s log — a special data structure in the blockchain. These logs are associated with the address of the contract and will be incorporated into the blockchain and stay there as long as a block is accessible. Log and event data is not accessible from within contracts (not even from the contract that created them).

contract NumberContract {
  address private contractIssuer;
  uint private number;
  
  event NumberSet(uint number);
  
  modifier onlyContractIssuer {       
      require(contractIssuer == msg.sender);
      _;
  }
  
  function NumberContract() public {
      contractIssuer = msg.sender;    
  } 
  
  function setNumber(uint newNumber) onlyContractIssuer public {
      number = newNumber;
      NumberSet(newNumber);
  }
}

Multiple inheritance

Solidity supports multiple inheritance by copying code including polymorphism. All function calls are virtual, which means that the most derived function is called, except when the contract name is explicitly given. When a contract inherits from multiple contracts, only a single contract is created on the blockchain, and the code from all the base contracts is copied into the created contract. The general inheritance system is very similar to Python’s, especially concerning multiple inheritance.

Function visibility: external

While in Solidity almost all types of function visibility (private, public, and internal) are intuitive and similar to these in Java, one is different. A function can be declared as external which means it can be called only from other contracts and by transactions. Calling it internally is impossible.

Function modifiers: pure, view, payable

When a function is declared as pure, it cannot modify or even access the state (variables, mappings, arrays, etc.). It’s the most restrictive modifier but it is the most secure and saves the most gas when applied.

The view is slightly more allowing modifier. It basically acts the same as pure but allows access to the state (but it still cannot modify it though).

When we want a function to be able to receive Ether together with a call, we declare it as payable. It allows for money transfers, deposits, and basically handling money in every way needed.

contract NumberContract {
  address private contractIssuer;
  uint private number;
  mapping(address => uint256) public availableWithdrawals;
  
  modifier onlyContractIssuer {       
      require(contractIssuer == msg.sender);
      _;
  }
  
  modifier hasPositiveBalance(address user) {
    require(availableWithdrawals[user] > 0);
    _;
  }
  
  function NumberContract() public {
      contractIssuer = msg.sender;
  } 
  
  function deposit() public payable {
      availableWithdrawals[msg.sender] = 
            safeAdd(availableWithdrawals[msg.sender], msg.value)
  }
  
  function withdraw() public payable hasPositiveBalance(msg.sender) {
    uint256 amount = availableWithdrawals[msg.sender];
    availableWithdrawals[msg.sender] = 0;
    msg.sender.transfer(amount);
  }
  
  function getAmountToWithdraw() public view returns (uint256) {
    return availableWithdrawals[msg.sender];
  }
  
  function safeAdd(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    assert(c >= a);
    return c;
  }
  
  function setNumber(uint newNumber) onlyContractIssuer public {
      number = newNumber;
  }
}

Additional data

When a function is called via a transaction, it can access additional information about the caller that is passed automatically, e.g. sender’s address (msg.sender), amount of Ether send (msg.value) or remaining gas (msg.gas) (see the code above for the usage examples).

Data structures

While Java has many complex and simple data structures built-in, in Solidity there are only two of them: mapping and array. The first one is a simplified version of Java map, meaning it can store key-value pairs and retrieve the value stored under a given key, but not much else. It cannot be iterated over and both key and value sets are not retrievable. The latter will be talked about in the next paragraph (see the code above for the example of a mapping).

Optimizations

Writing in Solidity requires paying attention to many more details than when writing in Java. Each transaction is an execution of a contract’s function which triggers operations. Each operation, in turn, costs gas which we need to pay in Ether (besides any Ether we want to send along). When the gas limit is exceeded, the whole function is being reverted and everything is restored to the state from before the function was called. In Java, however, even the code which is not optimized in any way will execute properly and the only side effect would be a longer runtime of the application.

Pitfalls to avoid

If you are used to writing code in Java, there are many different and unintuitive things in Solidity. Knowing about them is crucial if you want to avoid running into bugs that are hard to reproduce. I’ll list some of them below:

Overflow errors

In Solidity, all operations on integers can create an overflow error. What’s more, there are no overflow-safe operations which means you have to check for it manually every time using the require command (it is basically an assert which reverts the whole function when the condition is not fulfilled)

‘Byte’ and ‘Bytes’ confusion

Byte type is 256-bits wide which means that byte array (byte[]) takes 32x more space than expected (it’s very bad, considering the gas limit, and hard to debug keeping in mind that storage space is extremely limited). An actual byte array which takes as much space as expected should be declared as bytes.

Strings

Solidity doesn’t natively support any string manipulation. At the time of writing, even basic operations like concatenation of two strings have to be done manually after casting them to byte arrays. Length can be checked also only after conversion to a byte array. What’s more, there are no methods like indexOf() which are given in Java — they have to be written by hand, copied in from a different project, or reference a function from a library already on the blockchain.

Arrays

Array access syntax looks like in Java but declaration syntax is reversed — int8[][5] creates 5 dynamic arrays of bytes. Unfortunately only statically sized arrays can be returned by functions — there is no way to return a dynamically sized one. Also, multi-dimensional dynamic arrays cannot be created which means that declaring an array of strings (which are dynamically sized arrays of chars) is not possible.

‘For’ loops

Solidity was created to resemble JavaScript, but literal 0 type infers to byte, not int. That means writing:

for (var i = 0; i < a.length; i++) { a[i] = i; }

will enter an infinite loop if the "a" array is longer than 255 elements (the iterator will wrap around back to 0). This is despite the underlying VM using 256 bits to store this byte. You should just know about this and declare "i" as uint instead of var.

Operator’s semantics

Operators have different semantics depending on whether the operands are literals or not. For example, 1/2 is 0.5, but x/y for x==1 and y==2 is 0. Precision of the operation is also determined in this manner — literals are arbitrary-precision, other values are constrained by their types.

Mapping

Mappings, unlike maps in Java, don’t throw an exception on non-existing keys. They just return the default value depending on the key type (when keys are integers, 0 will be returned). What’s more, there is no way to check if an element exists (like contains() in Java) — when 0 is returned we don’t know if a key was added to the mapping with value 0 or is it the default value being returned because there is no such key in the mapping. There’s also no built-in method of extracting a key or value sets from a mapping which means iterating over a key set is not possible.

Summary

Solidity represents a unique and one of the first approaches to writing contracts on the blockchain. This language is still at very early stages and is nowhere near being complete and fully functioning. However, it evolves very quickly, so I’m sure that in the future many mistakes will be fixed and functionalities added.

Unfortunately during the designing phase, there have been some decisions made that I’m not sure are fully fixable. The biggest ones affect code security — this language should care and enforce security since all blockchain applications work with money. However, functions are public by default, integer operations can overflow, loops could never finish, updating code is problematic, etc.

The most fundamental problem is that it’s optimized to be user-friendly and look like a language people are used to and should be intuitively able to deduce what’s going on in EVM. It often does a completely different thing which can cause developer’s confusion, or worse yet memory leaks and security holes.

A new language that handles money transactions that are designed to be easy to learn should be intuitive. However, in its current shape it’s easy to learn how to do something wrong, but hard to do something right.

Design Sprint
From a bold idea to prototype
Learn more
Written by
Dominik Hys
Android Developer

You may also like

Like what you read?
Get monthly business and technology insights straight to your inbox.
Intent software house logo - home page
Contact
Email: growth@withintent.com
Location: Wilcza 46, 00-679 Warsaw
About us
.intent (formerly inFullMobile) is an international digital product design & development studio delivering software at the intersection of digital and physical.
Intent social profile icon - FacebookIntent profile icon - LinkedInIntent profile icon - InstagramIntent social profile icon - TwitterIntent social profile icon - BehanceIntent social profile icon -  Dribbble
.intent™ All rights reserved.
Terms and Privacy