Skip to content

Core SolidVM

As described in the STRATO pluggable VM section, SolidVM is the STRATO custom implementation and extension of Solidity as an interpreted language. It provides faster execution speeds, no gas limits, and allows for the usage of many STRATO specific features such as addresses on private chains, X.509 identity features, and more.

Pragma Version

No pragma version is needed to use the base SolidVM interpreter. Only use a version pragma to target a higher version.

Features

By using SolidVM, you can unlock many useful features in your Solidity smart contracts. Below are some of the differences and extended features that SolidVM offers on top of standard Solidity.

Data Type Lengths

There are no fixed-length data types such as int8. SolidVM removes the necessity to specify the bit-length of a numeric data type, such as int8 or bytes16. Instead all these data types allow for arbitrary length. This prevents data overflow conditions when numbers become very large.

Equivalent SolidVM Numeric Types

EVM SolidVM
int<length> int
uint<length> uint
bytes<length> bytes
fixed<M>x<N> Unsupported

Tip

SolidVM will recognize numeric types - even if they have a specified length. SolidVM will ignore this specified length and treat it as the corresponding unfixed-length data type. This means that types like int8 or bytes16 will be treated as just int or bytes in SolidVM. This is useful when porting Contracts written for the EVM to SolidVM.

Account Data Type

The account data type is similar to the address data type. The account type takes the form of <address>:<chainId?>. With the introduction of Private Chains on STRATO, it is necessary to specify which chain an account exists on if it is on a Private Chain. To declare an account type, use the account constructor, which takes either an address and chain ID, or just an address. If the chain ID is not specified, the account chain ID used will be the current chain of the contract. See below on how to specify different chains relative to the current chain.

function account(address addr, uint chainId) returns (account);

function account(address addr) returns (account);

Tip

In SolidVM 3.2, the account constructor can now take a string directly as a chainId argument.

See the documentation here.

Example (from within SolidVM)

contract AccountExample {

  account myAccount;

  constructor() {
    address myAddress = address(0x1a2b3c4d5e7f1a2b3c4d5e7f1a2b3c4d5e7f1a2b);
    int myChainId = 0xad23ad6df23ad23ad23ad6df23ad23;
    myAccount = account(myAddress, myChainId); // Create variable for this account on the correct private chain 
  }
}

Example (from an API call)

contract AccountExample {

  account myAccount;

  constructor(address _addr, int _chainId) {
    myAccount = account(_addr, _chainId); // Create variable for this account on the correct private chain 
  }
}
Transaction Parameters:

{ 
  "_addr": "1a2b3c4d5e7f1a2b3c4d5e7f1a2b3c4d5e7f1a2b", // address does not need 0x prefix when being passed to the API
  "_chainId": "0xad23ad6df23ad23ad23ad6df23ad23" //chainId does need 0x prefix when being passed to the API
}

Alternatively, the chainId could be constructed from a string argument type and cast as an int from within the contract as follows:

contract AccountExample {

  account myAccount;

  constructor(address _addr, string _chainId) {
    int myChainId = int(_chainId);
    myAccount = account(_addr, myChainId); 
  }
}

Transaction Parameters:

{ 
  "_addr": "1a2b3c4d5e7f1a2b3c4d5e7f1a2b3c4d5e7f1a2b", // address does not need 0x prefix when being passed to the API
  "_chainId": "ad23ad6df23ad23ad23ad6df23ad23" //since the chainId is being parsed as an int, it does not need 0x prefix when being passed to the API
}

Tip

Because most languages have a maximum integer size, it is not recommended to convert the chain ID to a base 10 int before sending it in a transaction API call. This is because the hexadecimal chain ID will translate to an extremely large base 10 number, usually resulting in an overflow or loss of precision in the sender's environment.

Warning

Account values cannot be passed as arguments to functions or constructors through direct API calls. However functions may call other functions or constructors with account values as parameters.

Example:

contract AccountTest {
  function f(account acc) returns (account) {
    return acc;
  }
  function g(address addr, uint chainId) returns (account) {
    account acc = account(addr, chainId);
    return f(acc);
  }
}

The g() function would be able to be called from the API by passing in the address and chainId arguments, however an API call to f() with an argument of <address>:<chainId> would fail.

Because of this limitation, it is always recommended to construct account types from separate address and chainId arguments using the account constructor.

As a convenience, several keywords are available that allow for a specific chain to be referenced in the account type constructor. All references are relative to the current contract invoking the account constructor.

self

  • References the chain ID of the current chain.
    address myAddress = 0x1a2b3c4d5e7f1a2b3c4d5e7f1a2b3c4d5e7f1a2b;
    account myAccount = account(myAddress, "self");
    

parent

  • References the chain ID of the parent chain of the current chain.
    address myAddress = 0x1a2b3c4d5e7f1a2b3c4d5e7f1a2b3c4d5e7f1a2b;
    account myAccount = account(myAddress, "parent");
    

grandparent

  • References the chain ID of the grandparent of the current chain.
    address myAddress = 0x1a2b3c4d5e7f1a2b3c4d5e7f1a2b3c4d5e7f1a2b;
    account myAccount = account(myAddress, "grandparent");
    

ancestor N

  • References the Nth ancestor up a tree of chains. The current chain is the 0th ancestor of the current chain, and it's parent chain is it's 1st ancestor. Generally any chain's parent chain's level is 1 + the current level.
  • The level of the ancestor chain that you wish to reference is given as the third argument
  • This example shows how to extract the chain ID of the contract's grandparent chain, since a grandparent chain would be the current chain's second ancestor.
    address myAddress = 0x1a2b3c4d5e7f1a2b3c4d5e7f1a2b3c4d5e7f1a2b;
    account myAccount = account(myAddress, "ancestor", 2);
    

main

  • References the Main Chain of the network.
    address myAddress = 0x1a2b3c4d5e7f1a2b3c4d5e7f1a2b3c4d5e7f1a2b;
    account myAccount = account(myAddress, "main");
    

Warning

In STRATO < v7.6, any keyword referencing an ancestor of the current chain (parent, grandparent or ancestor) cannot be used in the constructor of a Governance contract of a private chain. This is because the chain creation and constructor run as an atomic process, therefore the chain will not have any relations until after the constructor has been completed and the chain is created. Thus referencing the related chain will throw an error.

Contract Types

A contract type is a type that references a specific type of contract at a given address. (The account type can also be used, however it is recommended to use the name of a contract directly.) A Contract type behaves similarly to an Object in an OOP language - it can be used to reference another contract to access its methods and members. While objects exist in memory, contract data exists on the blockchain.

Declare contract variables that reference other contracts by using the <contractName>(args...) constructor. The constructor takes either an address, or account as a single parameter. If a contract does not actually exist at the provided account/address, then any attempts to access its functions/members will result in an error.

Instantiate new contracts on the blockchain by using the new <contractName>(args...) keyword. This creates a whole new contract instance with a new address and state variables on the current chain. It uses the contract's constructor to create the contract. This is the parallel to creating a new object in an OOP language, except the new object is permantently stored on the blockchain, rather than in the program's memory.

Any referenced contract name must in the current contract's scope, so its code must be uploaded with the original contract's code bundle.

Example (create new contracts)

contract Foo {
  uint x;
  constructor(uint _x) {
    x = _x;
  }
  function doubleX() returns (uint) {
    return x * 2;
  }
}

contract Bar {
  Foo myFoo;
  uint y;
  constructor(uint _x) {
    y = _x;
    myFoo = new Foo(_x + 1);
  }
  function useFoo(uint _y) returns (uint) {
    return myFoo.doubleX() + y; // y = (foo.x * 2) + y
  }
}

Example (reference existing contracts from arguments)

In this example, the accessFoo function would be called with the account and chainId of an existing Foo contract instance.

contract Foo {
  uint x;
  constructor(uint _x) {
    x = _x;
  }
  function doubleX() returns (uint) {
    return x * 2;
  }
}

contract Bar {
  uint y;
  constructor(uint _y) {
    y = _y;
  }
  function accessFoo(address fooAddress, uint fooChain) returns (uint) {
    Foo tempFoo = Foo(account(fooAddress, fooChain)); // use address + chainId to generate an account type
    return tempFoo.doubleX() + y; // y = (foo.x * 2) + y
  }
}

Example (without specifying a Chain ID)

In the case when the referenced contract is on the same chain as the current contract, then the chainId may be omitted from the account constructor or an address type may be used.

contract Foo {
  uint x;
  constructor(uint _x) {
    x = _x;
  }
  function doubleX() returns (uint) {
    return x * 2;
  }
}

contract Bar {
  uint y;
  constructor(uint _y) {
    y = _y;
  }
  function useFoo(address fooAddress) returns (uint) {
    Foo tempFoo = Foo(fooAddress);
    return tempFoo.doubleX() + y; // y = (foo.x * 2) + y
  }
}

SolidVM enforces the following rule to enable data integrity on-chain:

  • Contracts cannot modify the state of other chains (such as modifying contract state variables or creating new contracts on other chains.)

Furthermore, it is impossible to create new private chains from within SolidVM.

Joining Contract Info

When a "host" contract needs to reference the data of another external contract, SolidVM contracts work in conjunction with the STRATO Cirrus feature to easily allow data from related contracts to be retrieved in a single query. This is not a direct feature of SolidVM, but of STRATO, so contracts of any version may use this functionality. This feature requires STRATO >= v7.5.

See Joining Multiple Contract Tables for more detailed instructions on this topic.

No Data Location Required for Variables

Standard Solidity allows for variables to have their data location set by the user using keywords like memory or storage as a decorator on the variable after its type. However within SolidVM, there is no need to declare the storage location of a variable since the language is interpreted. All local variables assigned to the value of a global variable will automatically create memory copies of the global value. This is allowed because there is no concept of gas in SolidVM, so programmers do not have to worry about the slight overhead of copying storage values into memory before modifying them.

Extended Type Casting/Constructors

SolidVM allows basic types to be casted between each other more easily than standard Solidity. This can be achieved by using the syntax:

typeA oldValue = 5
typeB myType = typeB(oldValue);

Below is a list of supported type conversions outside of standard Solidity:

From To Notes Example
string int/uint The string literal value is interpreted as a base-16 number. string s = "12";
int x = int(s);
// x = 18 (base-10)
string address The string literal value is interpreted as a base-16 blockchain address. string s = "12";
address a = address(s);
// a = 0x000...12
int/uint string Converts a number to its base-10 string representation. int x = 12;
string s = int(x);
// s = "12"
int/uint address Interprets a number as a base-16 value. See account/address types. int x = 12;
address a = address(x);
// a = 0x000...0c

String Concatenation

Two strings may be joined together like many other programming languages:

string s = "Block" + "Apps" // "BlockApps"

Human-readable Event Logs

SolidVM allows Solidity Events to be logged with the exact data with which they were emitted. This is in great contrast to EVM, where events are not human readable and hard to find, due to the nature of how they are implemented. Recording events and their arguments in SolidVM allows them to be used as lightweight pseudo-contracts. They can be easily accessed from Cirrus and do not affect the state of the blockchain. They are useful for recording read-only data.

Info

Events are stored in Cirrus with the naming format of <ContractName>.<EventName>.

As an example, below is a SolidVM contract that emits an event upon contract creation and then the event information is retrieved from Cirrus.

contract EmitEvent {
  event MyEvent(string memo, uint timestamp, bool myBool);
  constructor(string m, uint t, bool b) {
    emit MyEvent(m, t, b);
  }
}

To query for an event in Cirrus, make the following API call:

Request

curl -X GET \
  -H "Authorization: Bearer <token>" \
  -H "Accept: application/json" \
  "https://<strato_address>/cirrus/search/EmitEvent.MyEvent"
Returns
[
  {
    "id": 1,
    "address": "a1b2c3d4e5f67a1b2c3d4e5f67a1b2c3d4e5f67a",
    "memo": "Hello World",
    "timestamp": "12345",
    "myBool": "True"
  }
]

Info

Cirrus always stores Data from Solidity Events as strings, so remember to convert them to their proper types when working with event data through the API.

Selecting SolidVM as a Contract's VM

To select which VM is used to process a contract, you will need to include a VM option in the metadata parameter of a transaction payload. A VM can be set to EVM or SolidVM. The EVM will be used by default if the VM is not specified.

We use a basic SimpleStorage contract:

contract SimpleStorage {
  string myString;
  uint myNumber;
  constructor(string _myString, uint _myNumber) {
    myString = _myString;
    myNumber = _myNumber;
  }
  function setString(string _s) {
    myString = _s;
  }
  function setNumber(uint _n) {
    myNumber = _n;
  }
}

Request

curl -X POST \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "txs": [
        {
          "payload": {
            "contract": "SimpleStorage",
            "src": "<contract-src>",
            "args": {
              "_storedData": 3
            }, 
            "metadata": {
              "VM": "SolidVM"
            }
          },
          "type": "CONTRACT"
        }
      ],
      "txParams": {
        "gasLimit": 32100000000,
        "gasPrice": 1
      }
    }' \
  "https://<strato_address>/strato/v2.3/transaction?resolve=true"

Info

When posting multiple contracts in one API call, STRATO will use the VM defined in the payload of the first contract.

To enable SolidVM on an application that uses the blockapps-rest JS SDK, include the VM property in the options parameter when making a call to create a contract.

Example:

For this example we assume the options object has already been configured, and the stratoUser has also already been created and a key has been obtained.

// read Solidity source
const simpleStorageSrc = fsUtil.get("SimpleStorage.sol");

const contractArgs = {
  name: 'SimpleStorage',
  source: simpleStorageSrc,
  args: {
    _myString: "Hello World",
    _myNumber: 10,
  },
}

const solidVMOptions = {
  config: {
    ...options,
    VM: 'SolidVM'
  }
}

// Use your STRATO identity to upload the contract
const contract = await rest.createContract(stratoUser, contractArgs, solidVMOptions)
Learn more about BlockApps Rest

Debugging SolidVM

BlockApps offers several resources that allows developers to easily create and debug their SolidVM smart contract applications on STRATO.

VM Logs

If you are developer making contracts with direct access to an active STRATO node, you can enable VM logs within the STRATO container. Logs are enabled using an environment variable at boot time of a STRATO node.

By enabling logs, it may cause a slight performance hit to the VM, therefore it is recommended that this feature only be enabled during the development process of an application, and disabled during its active deployment. Once enabled, logs are enabled for both VMs on STRATO. See the below sections for info on each VM.

Once logs are enabled, you may access them using the following command on the STRATO host machine:

sudo docker exec -it strato_strato_1 cat logs/vm-runner

You may use similar file inspection commands with the vm-runner logs to inspect them more in-depth, such as grep or tail -f to examine logs as a contract is being executed.

SolidVM Logs

SolidVM offers verbose logs for each expression and statement within a contract, allowing for easy debugging.

Environment variable:

svmTrace=true
Example

Below is a simple contract that stores a value, y, and has a function f which sets y and returns a value depending on the value of its single parameter, x.

contract LogsContract {
    uint y;
    constructor(uint _y) {
        y = _y;
    }
    function f(uint x) returns (uint) {
        if (x % 2 == 0) {
            y = y + x;
            return (x * 2) + 1;
        }
        else {
            y = y - x;
            return x + 3;
        }
    }
}

When running the constructor with the value of _y = 5, we get the following logs:

Creating Contract: 69de75a9d810e139f14cb87d7dcd7fda620fa359 of type LogsContract
setCreator/versioning ---> getting creator org of 058c7ab489998e5b148cd7af1d4b84e236a8e193 for new contract 69de75a9d810e139f14cb87d7dcd7fda620fa359
setCreator/versioning ---> no org found for this creator....
╔══════════════════════════════════════════╗
║ running constructor: LogsContract(_y) ║
╚══════════════════════════════════════════╝

LogsContract constructor> y = _y;
(line 4, column 9):     Setting: y = 5
Done Creating Contract: 69de75a9d810e139f14cb87d7dcd7fda620fa359 of type LogsContract
setCreator/versioning ---> getting creator org of 058c7ab489998e5b148cd7af1d4b84e236a8e193 for new contract 69de75a9d810e139f14cb87d7dcd7fda620fa359
setCreator/versioning ---> no org found for this creator....
create'/versioning --->  we created "LogsContract" in app "" of org ""
[1111-11-11 01:01:01.4792820 UTC]  INFO | ThreadId 6     | printTx/ok                          | ==============================================================================
[1111-11-11 01:01:01.4793648 UTC]  INFO | ThreadId 6     | printTx/ok                          | | Adding transaction signed by: 058c7ab489998e5b148cd7af1d4b84e236a8e193     |
[1111-11-11 01:01:01.4793981 UTC]  INFO | ThreadId 6     | printTx/ok                          | | Tx hash:  6dc7261e9c1020a1cade551a50dddbfad59439918dfe44b172e7b76d10e54395 |
[1111-11-11 01:01:01.4794236 UTC]  INFO | ThreadId 6     | printTx/ok                          | | Tx nonce: 2                                                                |
[1111-11-11 01:01:01.4794457 UTC]  INFO | ThreadId 6     | printTx/ok                          | | Chain Id: <main chain>                                                     |
[1111-11-11 01:01:01.4794669 UTC]  INFO | ThreadId 6     | printTx/ok                          | | Create Contract LogsContract(5) 69de75a9d810e139f14cb87d7dcd7fda620fa359   |
[1111-11-11 01:01:01.4795601 UTC]  INFO | ThreadId 6     | printTx/ok                          | | t = 0.00080s                                                               |
[1111-11-11 01:01:01.4795893 UTC]  INFO | ThreadId 6     | printTx/ok                          | ==============================================================================

We can see in these logs what contract constructor the VM is taking, as well as what values it is assigning to state variables. Notice the setCreator/versioning lines, these are useful when integrating X.509 or contract versioning features into your contracts, and org information or version numbers will show here. We can also see the address of the new contract being created, and of the contract creator.

Following the constructor, we can see that the transaction was successful and its relevant information. In this scenario, the transaction was created on the main chain and the transaction time was 0.00080s.

Suppose we call the function f with the argument of x = 3:

[1111-11-11 01:01:01.4121601 UTC]  INFO | ThreadId 6     | addTx                               | gas is off, so I'm giving the account enough balance for this TX
----------------- caller address: Nothing
----------------- callee address: 69de75a9d810e139f14cb87d7dcd7fda620fa359
callWraper/versioning --->  we are calling LogsContract in app "LogsContract" of org ""
╔═════════════════════════════════════════════════════════════════╗
║ calling function: 69de75a9d810e139f14cb87d7dcd7fda620fa359 ║
║ LogsContract/f(3)                                          ║
╚═════════════════════════════════════════════════════════════════╝

            args: ["x"]
f> if (x % 2 == 0) {
y = y + x;
    return x * 2 + 1;

} else {
y = y - x;
    return x + 3;

}
            %% val1 = SInteger 1
            %% val2 = SInteger 0
(line 7, column 9):        if condition failed, skipping internal code
f> y = y - x;
(line 12, column 13):     Setting: y = 2
f> return x + 3;
╔════════════════════╗
║ returning from f: ║
║ 6                 ║
╚════════════════════╝

[1111-11-11 01:01:01.4126984 UTC]  INFO | ThreadId 6     | printTx/ok                          | ==============================================================================
[1111-11-11 01:01:01.4127614 UTC]  INFO | ThreadId 6     | printTx/ok                          | | Adding transaction signed by: 058c7ab489998e5b148cd7af1d4b84e236a8e193     |
[1111-11-11 01:01:01.4127893 UTC]  INFO | ThreadId 6     | printTx/ok                          | | Tx hash:  46351a0ebf5dd34d06f7e6a56a1b5206f347b2de7b7538cc792a153bb0a0c43b |
[1111-11-11 01:01:01.4128138 UTC]  INFO | ThreadId 6     | printTx/ok                          | | Tx nonce: 3                                                                |
[1111-11-11 01:01:01.4128355 UTC]  INFO | ThreadId 6     | printTx/ok                          | | Chain Id: <main chain>                                                     |
[1111-11-11 01:01:01.4128998 UTC]  INFO | ThreadId 6     | printTx/ok                          | | calling 69de75a9d810e139f14cb87d7dcd7fda620fa359/f(3)                      |
[1111-11-11 01:01:01.4129256 UTC]  INFO | ThreadId 6     | printTx/ok                          | | t = 0.00055s                                                               |
[1111-11-11 01:01:01.4129473 UTC]  INFO | ThreadId 6     | printTx/ok                          | ==============================================================================

Like the contract constructor, it also easy to see the beginning of a call and its initial parameters it was called with. After the first box showing call information of the function, each step of the function is shown, with actual src code prefaced by the name of the function f and right caret. When the VM encounters an if-statement, we can see its comparison below the source code. Each argument in the boolean expression is evaluated and displayed as %%val1 and %%val2 respectively. Since 3 % 2 = 1, val1 = 1. Since the expression evaluated to false, the internal code is skipped. It then follows each line of code, setting y = 2 since 5 - 3 = 2. Lastly it boldly shows the return value in a box. At this point the function execution has terminated and the transaction is complete, and the same transaction information is displayed as before.

Please note due to your display or current window size, logs may format incorrectly or unpredictably. It is best to view tables and wide console outputs on a large display/window.

Enable Contract Debugging Tools

STRATO has several built-in SolidVM smart contract debugging tools. These tools must be enabled manually at STRATO boot time to be used. Currently they are available for easy use through the BlockApps IDE.

Enable SolidVM Debugging by setting the vmDebug environment variable to true:

vmDebug=true
when starting STRATO.

IDE

BlockApps has an IDE through its VS Code extension on the VS Code Marketplace. The IDE provides access to many useful tools for developing applications with STRATO. This allows for rapid development since it brings STRATO features into an accessible and integrated environment.

Features:

  • Static Analysis and type-checking for SolidVM contracts (.sol files)
  • Code Fuzzing Tools (checking for unexpected results)
  • Debugger
  • STRATO Project Management
  • Node information

View the STRATO IDE Documentation for the full list features, and how to setup the extension within your workspace.

Limitations

As an evolving language based on Solidity 0.4.25 , Core SolidVM does not currently support the entire modern Solidity standard. Below is a list of known limitations:

  • Unsupported:
    • break and continue statements in loops.*
    • do-while loops.*
    • fixed data type.
    • Libraries and the using keyword.
    • Inline assembly (since assembly instructions are not used in SolidVM.)
    • Custom (user-defined) function modifiers.**
    • Visibility modifiers on functions and variables.
    • Transfer and other token-related functions.*

*This feature is supported in SolidVM ^3.2

**This feature is supported in SolidVM ^3.3