Introducing Parasol 2.0: Featuring Psol, the 1st Ever Solidity Preprocessor

A month ago, we released Parasol. An alternative full-featured Ethereum smart contract development environment optimized for freedom and agile development.

Today, one day before Devcon4, we're releasing Parasol 2.0. The new release features a bunch of bug fixes, improvements and one new major feature: The 1st ever Solidity lexical preprocessor.

NEW WEBSITE!

We've launched an official website for Parasol, check out getparasol.io.
getparasol

Psol: The Solidity Preprocessor

Psol is a simple Solidity lexical preprocessor made possible thanks to Underscore.js templates. It is lexical, as opposed to syntactic, in the sense that the preprocessor does not know or read Solidity. It only processes .psol files and follows instructions wrapped in special syntax in order to output a new valid .sol Solidity file.

Get started at the official documentation page.

The Psol Rationale

Natively, Solidity features no preprocessing features whatsoever. Therefore, an independent Solidity preprocessor is imminent to reach the full potential of the language. But Psol is a very special preprocessor, it is a Javascript-powered Solidity preprocessor. It allows the execution of Javascript code and the interpolation of Javascript variables in Solidity files on compile time. This opens a range of possibilities for developer experience improvements, as well as a range of possible new design patterns for smart contract development.

Vue-Style Single File Components

Similar to Truffle, Parasol separates unit tests from smart contract source code at the tests/ directory. But what if you could (if you want), create a single file component for each of your smart contracts (inspired by .vue components), where the Solidity source code, mocha unit tests, Natspec documentation and even post-compilation/deployment hooks all live at the same place. Parasol will preprocess, compile, generate documentation, deploy, test and allow you to interact with, your contracts. Here's an example:

First, we set our custom preprocessor context variables at the parasol.js file in the root directory:

module.exports = {
    preprocessor: {
        context: {
            title: "A minimal token contract",
            tokenSupply: "1*10**28",
            tokenName: "Token",
            tokenDecimals:18,
            tokenSymbol: "TOK"
        },
        strict: false // If true, strict mode will abort deployment on warnings as well as errors
    }
}

Then, at contracts/Token.psol:

pragma solidity ^0.4.24;

{{ strict = true }} // Enables strict mode. Both compilations warnings and errors will abort deployment
{{ // abort("just for fun") }} // If called, will abort all contract deployment for a specified reason
{{ // ignore(this) }} // If called, it ignores the deployment of this specific contract

/// @title {{= title }}
contract Token {

    string public name = "{{= tokenName}}";
    string public symbol = "{{= tokenSymbol}}";
    uint8 public decimals = {{= tokenDecimals}};
    uint256 public totalSupply = {{= tokenSupply}};
    
    mapping (address => uint256) public _balances;

    event Transfer(address indexed _from, address indexed _to, uint256 _value);

    constructor() public {
        _balances[msg.sender] = totalSupply;
    }

    function transfer(address _to, uint256 _value) public returns (bool success) {
        require(_balances[msg.sender] >= _value);
        _balances[msg.sender] -= _value;
        _balances[_to] += _value;
        emit Transfer(msg.sender, _to, _value);
        return true;
    }
    
    {{
            test(this, 'Balance should change balance after transfer()', async function() {
        var preBalance = await contracts["Token.psol:Token"].methods.balanceOf(accounts[0]).call()
        await contracts["Token.psol:Token"].methods.transfer(accounts[1], 1).send()
        var postBalance = await contracts["Token.psol:Token"].methods.balanceOf(accounts[0]).call()
        assert.notEqual(preBalance, postBalance)
    })
    }}
    
    function balanceOf(address _owner) constant public returns (uint256 balance) {
        return _balances[_owner];
    }
}

From the above example, notice three things:

  1. Javasript expressions inside the {{ }} tags are executed on precompile time.
  2. Javascript variables inside the {{= }} tags are interpolated into the Solidity file on precompile time.
  3. There are built-in context variables that are defined by Parasol by default. The list includes ignore(), abort(), strict, test(), web3, accounts, network and many others.

Remote Contract Inclusion

Psol has a neat little feature that extends a limitation of the Solc compiler: automatic remote contract inclusion. If you call the get() Javascript function, psol will automatically fetch any remote smart contract code on the web, import it in your smart contract file, and pass it to the Solidity compiler. Additionally, if the imported file is a .psol file, it will also preprocess it before compilation.
Here's a simple example of importing a full OpenZeppelin ERC20 token implementation without writing a single line of Solidity (aside from pragma):

pragma solidity ^0.4.24;
{{
    get("https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-solidity/release-v2.0.0/contracts/math/SafeMath.sol");
    get("https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-solidity/release-v2.0.0/contracts/token/ERC20/IERC20.sol");
    get("https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-solidity/release-v2.0.0/contracts/token/ERC20/ERC20.sol");
}}

The above code, is written in a .psol will compile and deploy an ERC20 token based on OpenZeppelin's standard. You could even create a new contract under it and inherit the imported ERC20 contract to customize your own instance.

Inline Mocha Unit Tests

The Javascript test() function in the example above is a built-in Mocha unit test tool. It allows for mocha unit tests to be written inside of .psol files instead of in a separate file in the tests/ directory.
This can be used to either:

  1. Write vue-style components, where unit tests are written under the code in the same file.
  2. Write inline mocha unit tests, where the tests are embedded between Solidity functions in order to test as well as describe the purpose of the neighboring code using the mocha tests.

More Power; Same Freedom

Because Parasol appreciates freedom, you are free to use whichever design pattern you prefer in your code. You are also free not to ever use any of the features given to you, including the Psol preprocessor. Parasol will only preprocess files that end with the .psol extension in the contracts/ directory, leaving your traditional .sol files alone.

Further Reading

There is far more to Parasol 2.0 and Psol than we can describe in this article. So go ahead and dig deeper by reading the Psol documentation page. Or check out Psol on Github.

Meet us at Devcon4

Email us at contact@lamarkaz.com if you're hanging in Prague and would like to sit and discuss Parasol over coffee.

Again, make sure you visit the new website getparasol.io and maybe share it around.