Skip to content

STRATO's Pluggable VM

What is a Virtual Machine?

A virtual machine (VM) is an internal, software processing component, or "brain" of a larger system that emulates the processes of normal computer system. Rather than instructions being sent to the physical hardware of a machine like the CPU, instructions are sent to a "virtual CPU" that has been written in software. VMs allow for complete custom implementation of a system that can be tailor fit to meet the needs of the use-case. It also provides a runtime-environment that is completely isolated from the rest of the system that the virtual machine is running on. Any operation that is done on a VM happens completely within the VM and does not expose the actual components or operating system of the native host machine to the running process.

For more information about Virtual Machines, see Red Hat's explanation of the topic.

The STRATO VM

The STRATO platform processes all transactions within its own virtual machine. The platform ships with two different VMs that users can choose from - the Ethereum Virtual Machine or EVM, as described in the Ethereum Yellow Paper, or BlockApps' custom SolidVM. The VM that each transaction uses to process data can be specified by the user - this functionality is what makes STRATO have a "Pluggable VM". The EVM is used by default, however it is strongly recommended to use SolidVM for its extended features and improved performance.

More information on the Ethereum Virtual Machine.

Solidity Smart Contracts

STRATO's VMs both use Solidity, the de-facto programming language used to create blockchain smart contracts. Solidity is very similar to other curly-braced languages such as C++, Java, or JavaScript. Much of the functionality will be familiar to many programmers, however there are several concepts and features unique to Solidity that allow it to harness and interact with the full capabilities of the blockchain. Because Solidity is such a large language with many different features, we will not go into detail about how to program with Solidity or all of it's features. However, here are some highlights of what makes Solidity powerful on the blockchain:

  • Address/Contract Types
    • Contracts can use the address of another contract as a reference to it's functions and state.
    • Contracts can be used like objects with their own methods and variables.
  • New Contract Creation
    • Contracts can create new contracts of a given type much like one would create a new Object in an Object-Oriented language.
  • Protected Data Access
    • Contracts can not directly modify the state of another contract. Contracts must implement their own functions to modify their own state variables.
  • Atomicity
    • If a function fails at any point in a transaction, any state changes in the transaction are reverted.
  • Payable Functions
    • In networks that use tokens like Ethereum, functions may only be called after an amount of tokens have been paid to that function. The contract then acts as a repository or wallet for tokens.
    • In STRATO, all parties are working together to provide accountability and verifiability, rather than competing to complete create a block, so this feature is rarely used or needed.

Furthermore, through the properties of a blockchain, a smart contract's code can never be modified. So whenever a smart contract is created, its internal logic will always be the same over time. This allows all parties to know exactly what a contract does and will do.

The supported versions of Solidity within the EVM for STRATO are:

EVM vs. SolidVM

While both VMs are capable of processing instructions through Solidity, each VM accomplishes that task in different ways, and with different features.

EVM (Ethereum Virtual Machine)

The EVM for STRATO is a Solidity compiler, meaning it takes the Solidity input source code, and translates that directly to instructions similar to the RISC instruction set. These instructions can than be executed by the EVM and produce an output. Because of the specification of the Ethereum Virtual Machine, there are limitations on what contracts can achieve using it. Most importantly this includes limits on the maximum level of recursion in a function, or the number of symbols (parameters or local variables) a function can use. Because the source code is compiled down to an instruction set, the original Solidity is lost and unable to be directly stored on the blockchain. Rather only the final instruction set is stored on the blockchain. This makes it difficult to inspect the contract's functionality after it is created.

SolidVM

SolidVM is BlockApps' in-house Solidity interpreter, meaning it parses the input source code into a logical structure that it can understand, and executes each statement in sequential order. A key difference here is that SolidVM does not transform the source code into a different instruction set for the VM to execute, rather it executes the instructions in the Solidity directly - there is no middleman process between Solidity statements and the actual execution of instructions. SolidVM also introduces necessary features in Smart Contracts to interact with key STRATO features like Private Chains and X.509 Identity Certificates. Because SolidVM adds features not included the EVM, there is some added or changed syntax over Solidity, however largely it remains the same. SolidVM is based on Solidity 0.4.24.

Advantages

  1. All data types are of variable length (e.g. int, string, bytes, etc. can all take values of arbitrary size. They are not limited to a fixed byte length upon declaration). This prohibits dangerous overflow conditions by deliberately not supporting fixed length types like int8, etc.
  2. Function arguments and variables are not limited to 16 in quantity, as there is no maximum stack size. This also allows for arbitrary levels of recursion.
  3. Solidity contracts are stored and accessible in their original format since the source code is not compiled into low-level instructions.
  4. Solidity is processed faster due to a more efficient, custom-built execution machine, leading to faster transaction times overall.
  5. Solidity Events are logged in the Cirrus database. Each unique Solidity Event in a contract has a corresponding table in the database, and every time an instance of that event is emitted, a row is added to the table. EVM events only store the hashed data of the event payload, making it difficult to extract the data from the event.
  6. SolidVM enables contracts to communicate with and access contracts on Private Chains. This would be impossible in the EVM since the EVM has no concept of a Private Chain.
  7. SolidVM enables X.509 Identity Integration with smart contracts. This allows you to associate a signed identity certificate with a STRATO user account.

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);
Example
uint myChainId = 0xad23ad6df23ad23ad23ad6df23ad23; // Chain ID
address myAddress = address(0x1a2b3c4d5e7f1a2b3c4d5e7f1a2b3c4d5e7f1a2b); // Just the address
account myAccount = account(myAddress, chainId); // Create variable for this account on the correct private chain 

Tip

To pass a chain ID to a function parameter from the API, it must be a base 10 uint value, or the hexadecimal string of the chain ID prefixed with 0x. SolidVM will automatically parse the function parameter as the correct numeric type.

function createAccountUint(address addr, uint chainId) {
  return account(addr, temp);
}

And to call this function, use the the following args:

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

Or using the base 10 value of the chain ID:

{
  "addr": "1a2b3c4d5e7f1a2b3c4d5e7f1a2b3c4d5e7f1a2b",
  "chainId": 898990981361471850115147265411886371 // Base 10 conversion of the chain ID
}

Because many languages have a maximum size for number types, it is not recommended to convert the chain ID to base 10 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.

Alternatively, the chain ID can be parsed from string to uint within the contract itself. Below is an example of how this can be accomplished within a contract.

function createAccountString(address addr, string chainId) returns (account) {
  uint temp = uint(chainId); // cast chainId string as a uint
  return account(addr, temp);
} 

This function would be called with the following args:

{
  "addr": "1a2b3c4d5e7f1a2b3c4d5e7f1a2b3c4d5e7f1a2b",
  "chainId": "ad23ad6df23ad23ad23ad6df23ad23" // Omit 0x from string
}

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.

As an example, lets say there is the following contract on the chain:

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);
  }
}

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

The account type is used to reference another contract that exists so that you can access its methods and members. You can declare a variable to reference another contract using either the account or <contractName> (contract) types. The <contractName>() constructor takes either an address, or account as a single parameter (note only when referencing existing contracts, if you are creating a new contract, use the new keyword.) While a contract-type and account-type variable provides the same functionality, it is often useful to declare variables of a contract-type rather than the generic account-type so it is easier to determine the available functions of that contract by immediately knowing what type of contract it is.

Example

// posted on a private chain
contract Foo {
  uint x;
  constructor(uint _x) {
    x = _x;
  }
  function doubleX() returns (uint) {
    return x * 2;
  }
}
// posted on the same chain as 'Foo'
contract Bar {
  uint y;
  constructor(uint _y) {
    y = _y;
  }
  function useFoo(address fooAddress, uint fooChain) returns (uint) {
    Foo tempFoo = Foo(account(fooAddress, tempChainId)); // use address + chainId to generate an account type, cast account-type -> Foo-type
    return tempFoo.doubleX() + y; // y = (foo.x * 2) + y
  }
}

Example without specifying a Chain ID

// posted on any chain
contract Foo {
  uint x;
  constructor(uint _x) {
    x = _x;
  }
  function doubleX() returns (uint) {
    return x * 2;
  }
}
// posted on the same chain as 'Foo'
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
  }
}

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);
    

Warning

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.

main

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

Joining Contract Info

When a contract needs to reference the data of another contract, SolidVM contracts work in conjunction with the STRATO Cirrus feature to easily allow data from related contracts to retrieved in a single query.

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

Passing Complex Data Structures to the API

STRATO version ^7.6 added support for passing complex data structures in API calls. This allows users to use these data types as arguments in contract creation and function upload API calls. Below are added types supported and how to send them in a API request:

Multidimensional Arrays

Multidimensional arrays can be sent as normal multidimensional JSON arrays.

Example

...transaction arguments,
args: {
  multiArr: [
    [1, 2, 3],
    [4, 5, 6],
  ]
}
Structs

Structs can be sent to the API as JSON objects, where each key of the object represents a data-point in the Solidity struct.

Please note is up to the user to ensure the shape of the struct passed to the API matches the definition of the struct. Doing otherwise will raise an exception in VM.

Example

contract StructExample {
  struct MyStruct {
    int x;
    int y;
  }
  int z;
  constructor(MyStruct _myStruct) {
    z = _myStruct.x + myStruct.y;
  }
}

```javascript
...transaction arguments,
args: {
  _myStruct: {
    x: 1,
    y: 2,
  }
}

Mappings

Mappings can be sent to the API as JSON objects, where each key-value pair of the object represents a key-value pair in the Solidity mapping.

String-type keys must be wrapped in single quotes.

Address-like keys/values are unsupported.

Please note is up to the user to ensure the types of the keys and values passed to the API match the definition of the mapping. Doing otherwise will raise an exception in VM.

Example

contract MappingExample {
  int z;
  constructor(mapping(string => int) _myMapping, string _k) {
    z = _myMapping[_k];
  }
}

```javascript
...transaction arguments,
args: {
  _myMapping: {
    "'foo'": 47,
    "'bar'": 82,
  },
  _k: "foo"
}

Pragmas

Because SolidVM uses a custom syntax of Solidity, there is no need for the standard Solidity version pragma at the start of the contract such as:

pragma solidity ^0.4.25;
Instead, use a pragma to define your SolidVM version to ensure you are running the correct version for your Smart Contract.
pragma solidvm x.y;
where x.y is the version number of SolidVM required.

SolidVM 3.0

The following SolidVM features are only available in versions ^3.0:

  • X.509 Certificate Registration.
  • Built-in Transaction properties of org and the hidden property creator (used to set the contract's creator's organization).
  • Default values for unset storage variables.
  • Contract-type variable equality operator.

SolidVM 3.2

The following SolidVM features are only available in versions ^3.2:

  • SolidVM Typechecker runs automatically on contract creation, returning an SolidVM error if the contract fails this step. Transactions that contain faulty contracts will be stored in the blockchain in an error state, just as if it were a transaction that failed for other reasons, like an index out-of-bounds or non-existent function call. See the Typechecker Documentation in the IDE page for more information.
  • break and continue statements in loops.
  • Support for do-while loops.
  • Support for pushing to memory arrays (Such as arrays that are passed as arguments to other functions).
  • Unitary negative number declaration (e.g. x = -1;).
  • Add ability to access the chain ID of an account variable using the built-in property of <account-variable>.chainId
  • Fixed a bug for using getter functions on string-type state variables.
  • Improved support for accessing and returning variables from complex structures like arrays or structs.
  • Support for returning array values in transaction results.

Info

SolidVM versions are independent of STRATO versions.

The latest SolidVM feature updates are also available in the STRATO Release Notes.

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 it's type. However within SolidVM, there is no need to declare the storage location of a variable. All local variables using the values of global variables will create memory copies of the value. Because there is no need to use gas sparingly, 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, 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 since their values are inherently immutable without having functions or state variables.

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.

X.509 Integration

As of SolidVM 3.0, there are several built-in functions which allow you to directly interface with an X.509 associated with a given address. To use these features, include the following pragma at the top of your smart contract:

pragma solidvm 3.0;

Built-in Functions:

  • registerCert(address addr, string certificate);
    • registers a PEM/DER encoded X.509 certificate with a given address.
  • getUserCert(address addr) returns (mapping(string => string));
    • Gets the X.509 certificate registered to this address
    • The returned data type is a solidity mapping(string => string) where each key-value pair in the mapping is the respective key-value pair of the certificate.
  • parseCert(string certificate) returns (mapping(string => string));
    • Takes a PEM/DER encoded certificate and parses it as a mapping(string => string). Does not register the certificate with any address or require that the certificate be registered with an address.

If the account creating a transaction has a certificate registered to its address, than the global tx variable will have 3 additional properties corresponding to the certificate's properties.

Transaction Properties:

  • tx.username : string
  • tx.organization : string
  • tx.group : string

For the full details about using X.509 certificates in smart contracts, see the X.509 Documentation.

Known Limitations

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

  • Unsupported:
    • fixed data type.
    • Libraries and the using keyword.
    • import keyword*
    • Inline assembly (since assembly instructions are not used in SolidVM.)
    • Custom (user-defined) function modifiers.
    • Visibility modifiers on functions and variables.
    • Contracts modifying the state of other chains (such as modifying contract state variables or creating new contracts on other chains.)
    • Contracts creating new Private Chains.

* You may use the import keyword when using the BlockApps Rest SDK to upload contracts by using the importer.combine(src) method. This will combine all the contract source code from the specified file paths in any import statements in the src contract into one contract payload to upload to STRATO. Please see the BlockApps Rest Documentation for full details.

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.

Raw Request Example

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.

Selecting the VM in BlockApps Rest SDK

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 easier 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 causes a slight performance hit on the platform, therefore it is recommended that this feature only be enabled during the development process of an application, and disabled during it's active deployment.

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 of each instruction 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.

EVM Logs

Environment variable:

evmDebugMode=true

Limitation

Contracts using the EVM will only output the compiled low-level instructions the EVM will execute. This makes it rather difficult to trace each step that the VM is taking. This is another benefit of using SolidVM on the STRATO platform over EVM.

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.

EVM Compatibility Mode

As of STRATO 7.1, the platform has the capability of running in "EVM Compatibility Mode". This enforces all contracts uploaded to use the EVM. If a user attempts to upload a contract using SolidVM as the VM, an error will be thrown. This is an environment variable specified at STRATO boot time and a network initialized with this feature turned on cannot revert this choice. The benefit of this feature is that it allows contracts to be ported from the main Ethereum network to the BlockApps STRATO platform with little to no code alteration.

To turn on EVM compatibility mode, use:

EVM_COMPATIBLE=true
when starting STRATO. This will also automatically turn on EVM indexing in Cirrus, which has been turned off by default as of STRATO v7.5.

Info

If a contract written for SolidVM with SolidVM-specific syntax is uploaded as an EVM contract, STRATO will throw an error for unsupported syntax since the normal solidity compiler will not recognize some of the syntax that was written for SolidVM.

Smart Contracts Resources

If you are new to developing with Solidity, don't be afraid. Solidity is easy to pick up if you have experience with other popular languages that use Object Oriented principles. Once you understand the motivation behind Smart Contracts it should hopefully make programming them easier as well.

For more resources around Solidity and Smart Contract development, we recommend: