Michal Zalecki
Michal Zalecki
software development, testing, JavaScript,
TypeScript, Node.js, React, and other stuff

An Intro to Nebulas for Ethereum Developers

Nebulas is yet another platform on which you can develop smart contracts. It offers a means of using JavaScript to develop Smart Contracts — an intriguing alternative to more established solutions, such as Ethereum.

I read about it for the first time on Reddit when they announced the Nebulas Incentive Program, which rewards developers for successfully submitting a dApp (decentralised application). From Nebulas’ whitepaper, we can learn about the team’s motivation and their goal to come up with a search engine and ranking algorithm for dApps. Sounds familiar? Let me google that. Oh, that sounds like Google.

By skimming through the whitepaper, you learn that Nebulas recognizes the problem of “measurement of value for applications on the blockchain” and the difficulties of platforms that operate on blockchain to upgrade themselves and evolve.

This is not a review, and I neither want to nor feel knowledgeable enough to assess whether such the problems that this project solves, as mentioned above, are worth investing your time or money. I am interested in the developer experience, the quality of the provided tooling from an engineering perspective and seeing how it compares to the well-established Ethereum. If our goals are inline, then this is a post worth reading.

Ethereum Virtual Machine and Nebulas Virtual Machine

In general, learning about the Nebulas Virtual Machine (NVM) and how the platform works is a breeze if you are familiar with how Ethereum works. Supplied gas intrinsically binds computation on both the Ethereum Virtual Machine (EVM) and NVM. The transaction fee is the the gas used, multiplied by the gas price.

There are two types of accounts: external/non-contract account and smart contracts (denoted accordingly by type 87 and 88).

curl -X POST \
  http://localhost:8685/v1/user/accountstate \
  -H 'content-type: application/json' \
  -d '{ "address": "n1Vg9Ngvi3vXo5f59diW4MK8XXger36weUm" }'

{"result":{"balance":"1000000000000000000","nonce":"0","type":87}}

Calls running locally on a currently connected node are free, immediately return values and do not change the blockchain state.

curl -X POST \
  http://localhost:8685/v1/user/call \
  -H 'content-type: application/json' \
  -d '{
  "from": "n1QA4usgq7sJbcM5LEkJWpgyNBcKtVEULFf",
  "to": "n1mQoB6HneRuu7c15Sy79CPHv8rhkNQinJe",
  "value": "0",
  "gasPrice": "1000000",
  "gasLimit": "2000000",
  "contract": { "function": "myView", "args": "[100]" }
}
'

{
  "result": {
    "result": "{\"key\":\"value\"}",
    "execute_err": "",
    "estimate_gas": "20126"
  }
}

Each transaction costs gas and changes the blockchain state (it’s dirt cheap and a small fraction of a penny at the time of writing).

curl -X POST \
  http://localhost:8685/v1/admin/transactionWithPassphrase \
  -H 'content-type: application/json' \
  -d '{
  "transaction": {
    "from": "n1Vg9Ngvi3vXo5f59diW4MK8XXger36weUm",
    "to": "n1gQgDb72yL1vrRcUEP3219ytcZGxEmcc9u",
    "value": "0",
    "nonce": 59,
    "gasPrice": "1000000",
    "gasLimit": "2000000",
    "contract": { "function": "myMethod", "args": "" }
  },
  "passphrase": "passphrase"
}
'

{
    "result": {
        "txhash": "36a61c6413e71387f34b0b442e73d2a8b54646917c58338166b0473292c0b26d",
        "contract_address": ""
    }
}

The most noticeable difference is the programming language used to develop smart contracts. On EVM, Solidity is the de facto standard language to write a smart contract. In its current form, NVM supports JavaScript (v8 6.2, from what I have found out) and quasi-TypeScript though compilation to JS. There are no typings available for storage, transaction or other globally available objects.

Due to plans for supporting LLVM, we might see the broader range of supported languages, such as C/C++, Go, Solidity or Haskell. This would be quite a feature if the Nebulas team can deliver on this promise and a big disappointment otherwise.

Smart Contracts

Let’s take a deep dive into how the same constructs are implemented in Ethereum (Solidity) and Nebulas (JavaScript).

Transfering value

Both Ethereum and Nebulas have the smallest nominal value called Wei. 1 ETH or 1 NAS is 10¹⁸ Wei. The value accepted by the transfer function should be the number of Wei. In the case of Ethereum, it is uint256, and for Nebulas, it should be an instance of BigNumber.

// Solidity

address(address).transfer(value);

// Nebulas (JavaScript)

Blockchain.transfer(address, value);

Transaction properties

Transaction properties exist in the global namespace and provide a set of information about the height of the chain, block timestamp, supplied gas and much more.

// Solidity

msg.sender          // sender address (address)
msg.value           // number of Wei sent (uint256)
block.timestamp     // current block timestamp (uint256)

// Nebulas (JavaScript)

Blockchain.transaction.from    // sender address (string)
Blockchain.transaction.value   // number of Wie sent (string)
Blockchain.block.timestamp     // number of Wie sent (string)

Ethereum address validation is possible using mixed-case checksum encoding, as described in EIP55, the proposal by Vitalik Buterin. Wallet software has widely adopted this improvement. Due to using a 20-bytes address type, it is impossible to validate it using this method on-chain. Nebulas address is a little different; you can calculate the checksum from the public key. It also contains the information regarding whether the address is a regular account or a smart contract.

Blockchain.verifyAddress(address);

Preventing overflows

The maximum safe integer in JavaScript is 2⁵³ — 1. The maximum safe integer (unsigned) in Solidity is even bigger: 2²⁵⁶ — 1. In some particular use cases, it is possible to overflow (or underflow) these values. To mitigate the severity of any issues that may originate from an overflow, you can use third-party libraries.

uint max = 2**256 - 1; // 1157920892373161950...57584007913129639935
max + 1; // 0

In Solidity, you can use the popular SafeMath library, which throws error, consuming all the gas left in case of the underflow or overflow.

import "openzeppelin-solidity/contracts/math/SafeMath.sol";

using SafeMath for uint256;

uint max = 2**256 - 1;
max.add(1); // VM error

Our JavaScript-powered smart contract running on Nebulas can use bignumber.js without any additional imports.

const BigNumber = require("bignumber.js");

Number.MAX_SAFE_INTEGER;     // 9007199254740991
Number.MAX_SAFE_INTEGER + 1; // 9007199254740992
Number.MAX_SAFE_INTEGER + 2; // 9007199254740992

const number = new BigNumber(Number.MAX_SAFE_INTEGER);
number.plus(2).toString();   // "9007199254740993"

Contract structure

Solidity is a contact-oriented language. This means that it is an object-oriented language with a contract keyword that defines a class-like type, able to store state and provide behavior for this state via functions.

contract Crowdsale is MintedCrowdsale, CappedCrowdsale {
  constructor(uint256 _rate, address _wallet, ERC20 _token) public {
    // ...
  }

  function () public payable {
    buyTokens(msg.sender);
  }

  function buyTokens(address _beneficiary) public payable {
    // ...
  }
}

Furthermore, Solidity also supports interfaces and Python-like multiple inheritance (through C3 superclass linearization).

On Nebulas, a contract is a class (or a function) with methods available on its prototype. One function, init, is required and executed during the contract initialization; it accepts arguments passed during the contract creation.

class StandardToken {
  init() {
    // ...
  }
}

module.exports = StandardToken;

State variables

Solidity has three types of storage if you do not count the events log as a fourth: Storage, Memory, and Stack. Storage keeps contract state variables.

contract Ownable {
  address owner;

  constructor() public {
    owner = msg.owner;
  }
}

Nebulas provides the abstraction on top of its storage capabilities. Using LocalContractStorage, we have to indicate which variables should persist explicitly.

class Ownable {
  constructor() {
    LocalContractStorage.defineProperty(this, "storage");
  }

  init() {
    this.owner = Blockchain.transaction.from;
  }
}

Visibility

Solidity has four visibility specifiers: public, private, external and internal. The public specifier allows for external and internal calls, while private functions can be called only from the contract that defines them. Internal functions work like private, but extending contracts can call them too. External functions are callable by other contracts and transactions, as well as internally using “this” keyword.

contract Visibility {
    function visible() pure public {}

    function hiddenFromOthers() pure private {}

    function visibleOnlyForFunctions() pure external {}

    function visibleForChildContracts() pure internal {}
}

On Nebulas, private functions are achieved via naming convention and not forced by the language. All functions that start with an underscore are not a part of the interface and cannot be called via transaction.

class Visibility {
  function visible() {
   this._hidden();
  }

  function _hidden() {}
}

Client-side applications

Ethereum dApps use an injected web3 instance with the embedded provider that asks users for confirmation each time the dApp tries to sign the transaction. MetaMask, which is an Ethereum extension for your browser, supports this flow, as well as Mist and Status (wallet apps with dApps browsers for desktop and mobile, accordingly).

When using Nebulas dApps, you can install WebExtensionWallet. It lacks convenient web3 abstraction but it is good enough for PoCs and simple use cases. The API for sending transactions is very similar to using RPC directly. In fact, using RPC directly is the easiest way to make a call that does not require signing.

Deployment

There are multiple ways to deploy contracts in Ethereum. The most developer-friendly are Remix and Truffle migrations scripts. Nebulas provides Mist-like experience within its web-wallet. You copy and paste the contract source code, specify constructor arguments and you are all set to go.

Alternatively, you can change the contract source to an escaped string and send the transaction, which creates a new contract using RPC.

Testing

Testing is one of the most crucial parts of smart contracts development, due to their immutability and costly mistakes. The Nebulas ecosystem is in its infancy and has no tooling to make such testing possible. Although clumsy, it is not impossible to test the Nebulas smart contract. It requires mocking internal APIs but, once you set your mind to do it, you will more or less reliably test the contract.

It is much easier to test smart contracts in Solidity. Thanks to the efforts of the Truffle’s team, you can almost reliably test contracts in isolation in both Solidity and JavaScript.

pragma solidity 0.4.24;

import "truffle/Assert.sol";
import "../contracts/Ownable.sol";


contract OwnableTest {
    Ownable ownable;

    function beforeEach() public {
        ownable = new Ownable();
    }

    function testConstructor() public {
        Assert.equal(ownable.owner(), address(this), "owner address is invalid");
    }

    // ...
}

Conclusion

Frankly, since mid-2017, I thought that Lisk was going to be the first platform for JavaScript smart contracts. Nebulas took me by surprise.

Nebulas, of course, cannot match the more mature Ethereum ecosystem yet but the comparison to come up with what is better is not the goal here. I think that new projects should be a little more modest and be upfront about their shortcomings. My initial disappointment was only due to the huge claims made at the time.

When I take a step back, it is clear that the Nebulas team have made the considerable progress in the last few months. I believe that this is a project worth observing and I hope that releasing mainnet was not premature and is not going to slow down the development as it tends to with other, similar projects.


This article has been originaly posted on Tooploox's blog: Nebulas: JavaScript Meets Smart Contracts

Photo by Thom Schneider on Unsplash.