Skip to content

SolidVM 3.4

SolidVM 3.4 builds on SolidVM 3.3 with even more features, language additions, improvements and bug fixes.

3.4 Features

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

  • Function Overloading
  • Support for Immutable variable modifiers
  • Support for Pure and View function modifiers
  • Support for revert built-in function
  • Added msg.sig and msg.data built-in properties
  • Support for ecrecover built-in function
  • .code Function for Contracts
  • Support for Hex String Literals
  • Add Unsigned Right Bit Shift operator
  • Add Base 10 integer parsing
  • Added duplicate declaration checks to the typechecker. This raises an error if a variable has been declared more than once in a given scope.

In STRATO 7.9, an API update allowed the following features to be available in SolidVM 3.4:

  • Free/File Level Functions
  • File Level Struct and Enum declarations
  • User Defined Errors

3.4 Bug Fixes

The following bug fixes are available in SolidVM ^3.4:

  • A list of named arguments passed to functions in an arbitrary order will now correctly call the appropriate function with the correct argument values.
  • Fixed iterated variable assignment in for loops.

Version Pragma

SolidVM 3.4 requires the following pragma at the top of all contracts:

pragma solidvm 3.4;

Functional Improvements

SolidVM 3.4 further expands upon expanding advanced contract functionality and control flows.

Function Overloading

SolidVM 3.4 introduces the ability to overload functions. An overloaded function is a duplicate function that has the same name as the original function, but the types of its parameters are different, while the length of the parameter list can be either the same or different. The function's return type has no impact on the validity of overloaded functions.

Example:

pragma solidvm 3.4;
contract A {

    constructor() {
        A();            // Returns 1
        A("foo");       // Returns 2
        A(3);           // Returns 3
    }

    function A() returns (int) {
        return 1;
    }

    function A(string aString) returns (int) {
        return 2;
    }

    function A(uint aNum) returns (int) {
        return 3;
    }

}

Free Functions/File Level Functions

This feature requires STRATO ^7.9.

Free functions, also known as file level functions, are functions that are not bound to any contract. Free functions have access to the this global variable that references the contract calling the function. Free functions are implicitly internal functions. However, free functions have no write/read access to contract storage elements unless passed in by reference as parameters.

Example:

function freeFunction() returns (uint) {
    return 3;
}

contract FreeFunctionExample {

    uint myNum;

    constructor() {
        myNum = freeFunction();       // myNum == 3
    }
}

File Level Declarations

This feature requires STRATO ^7.9.

Contracts may now include declarations of types and constants at the file level, outside of the scope of a specific contract. Types declared at this level are accessible to any contract in the Code Collection. Simple values must be declared as constant at the file level.

Example

pragma solidvm 3.4;

enum Decision { YES, NO, MAYBE }
struct Point {
    int x;
    int y;
}
int constant MY_CONST = 5;

contract FileLevelTypes {

    Decision myChoice;
    Point point;

    constructor(int _x, int _y) {
        myChoice = Decision.MAYBE;
        point = Point(_x, _y);
    }
}

Immutable State Variables

State variables can be marked immutable which causes them to be read-only, but assignable in the constructor. This is similiar to the constant keyword, but the difference is that the declaration is at contract level and the assignment can be in the constructor (or contract level). Once assigned, the value in the state variable is immutable.

Example:

pragma solidvm 3.4;

contract A {

    int immutable a = 3;
    int immutable b;

    constructor (int _b) {
        b = _b;
    }
}

Pure/View Function Modifiers

SolidVM 3.4 adds support for the built-in pure and view function modifiers. These modifiers allow developers to create functions that are guaranteed to meet certain requirements.

Pure Modifier

The pure modifier enforces that a function does not read from or write to any state variables, or variables from any other context. The output of the function is entirely dependent on the arguments given, therefore making the function 'pure'. Pure functions cannot read from built-in properties such as the msg or tx variables either. They cannot call other functions that are also not pure.

Example

pragma solidvm 3.4;

contract PureFunction {

    int x;

    constructor(int _x) {
        x = _x;
    }

    function pureAdd(int _a, int _b) pure returns (int) {
        return _a + _b;
    }

    // This function would raise a type error
    // function nonPureAdd(int _a, int _b) pure returns (int) {
    //     return _a + _b + x;
    // }

}

If the nonPureAdd function were uncommented, the VM would raise a type error since it attempts to access the state variable x in a pure function.

View Modifier

The view modifier enforces that a function only reads from a state or external data source, however it cannot write to state variables or create new values like contracts or events.

Example

pragma solidvm 3.4;

contract ViewFunction {

    int x;
    int y;
    int product;

    constructor(int _x, int _y) {
        x = _x;
        y = _y;
        product = x + y;
    }

    function viewDotProduct(int _a) view returns (int) {
        return (_a * x) + (_a * y);
    }

    // This function would raise a type error
    // function nonViewDotProduct(int _a) view returns (int) {
    //     product = (_a * x) + (_a * y);
    //     return product;
    // }

}

If the nonViewProduct function were uncommented, the VM would raise a type error since it attempts to assign the product variable to the calculated result and not just return it.

Custom User Error Types

This feature requires STRATO ^7.9.

Users can now define custom user error types that can be used for revert statements or for debugging their contracts in a traditional developer way through the use of throw statements.

error <name> (...args);

Custom user error types can be defined at the contract or file level.

Example:

pragma solidvm 3.4;

error fileLevelError();    // File Level Error

contract A {
    error myError(string message);      // Contract Level Error

    function throwError() {
        throw myError("CRITICAL FAILURE");
    }

    function revertError() {
        revert fileLevelError();
    }
}

Users can now also catch custom user error types in the SolidVM-style of try/catch statements introduced in SolidVM 3.3.

Example:

pragma solidvm 3.4;

error myError(string message);

contract A {

    constructor() {
        tryCatch();
    }

    function throwError() {
        throw myError("CRITICAL FAILURE");
    }

    function tryCatch() returns (bool) {
        try {
            throwError();
            return false;           // Does not execute
        }
        catch myError(myMsg) {
            return myMsg == "CRITICAL FAILURE";       // Returns True
        }
    }
}

Revert Statement

The revert function immediately rasies an exception in the SolidVM runtime, which can be optionally caught by the try/catch functionality introduced in SolidVM 3.3. If the revert is not caught, than it will cause the transaction to be invalid and revert any changes made in this transaction. This is commonly used to prevent the update or modification of state variables when certain criteria is met.

revert may also be used to throw custom user defined error types.

Important

The revert function operates differently in SolidVM than standard Solidity. In standard Solidity, revert throws out state changes from the current function scope and any sub-calls, as well as raises an exception. In SolidVM, revert only raises an exception and state changes will only be reverted if the error is uncaught.

Function Signature

revert();                   
//    invoke revert without any message/parameters passed

revert(args);
//    revert("error message") i.e. Ordered Args 
//    revert({x:"Message"}) i.e. Named Args

revert myCustomErrorType(args);
//    revert customError("error message") i.e. Ordered Args 
//    revert customError({x:"Message"}) i.e. Named Args

Arguments:

The revert function can take arbitrary arguments which will be thrown with the revert error.

Example:

1. Reverting without arguments

pragma solidvm 3.4;

contract RevertUsage {

    uint a;

    constructor() {
        a = 1;
        setA(9);
    }

    function setA(uint modified) {
        a = modified;
        revert(); // revert without arguments
    } 
}

After calling setA, a will still be 1 since revert was called and uncaught in the constructor.

2. Reverting Based on Named Arguments

pragma solidvm 3.4;

contract RevertNamedArgs {

    uint a;

    constructor() {
        a = 1;
        setA(9);
    }

    function setA(uint modified) {
        a = modified;
        revert({x:"Cannot modify 'a'"}); // revert based on named arguments
    }
}

3. Reverting with a User Defined Error

pragma solidvm 3.4;

contract RevertCustomError {

    uint a;
    error myErr(string message, uint attempt);

    constructor() {
        a = 1;
        setA(9);
    }

    function setA(uint modified) {
        a = modified;
        revert myErr("Cannot modify 'a'", modified); // revert with User-defined errors 
    } 
}

Additional Message Built-in Properties

The data and sig properties are now accessible on the global msg variable.

Data

The data property is a string of the comma separated argument values passed to the current function call. Arguments are wrapped in parentheses.

Example

pragma solidvm 3.4;

contract MessageData {

    int x;

    constructor() {
        x = 5;
    }

    function getCallData(int _a, int _b) returns (string) {
        x = _a + _b;
        return msg.data;
    }
}

When getCallData is called with arguments 1, and 2, the function will return the value (1, 2)

Signature

The sig property is the current function's call signature, which is the first 4 bytes of the Keccak256 hash of function's name and parameter list.

A function's signature will always be the same unless the contract's source code also changes.

Example

pragma solidvm 3.4;

contract MessageSig {

    int x;

    constructor() {
        x = 5;
    }

    function getCallSignature(int _a, int _b) returns (string) {
        x = _a + _b;
        return msg.sig; // first 4 bytes of keccak256("getCallSignature(int, int)")
    }
}

The getCallSignature function has the plain-text signature of getCallSignature(int, int) since its name is "getCallSignature" and its arguments are both of type int. Then a keccak256 hash is performed on that string. The final function signature is the first 4 bytes of the resulting hash.

Code Member Extension

SolidVM 3.4 adds the ability to access an account types code by using the code built in property. The property can also be used as a function to search for symbols inside the referenced contract, like functions or variables. The code member allows developers to navigate to a foreign contract and determine what is actually going on in the foriegn contract, and retrieve the foreign code. It also useful to check if the referenced account actually has a contract at the location since an non-exsitent contract will return an empty string.

Example:

pragma solidvm 3.4;

contract A {

    uint testy = 13;

    constructor () {
        testy = 26;
    }
}

contract B {
    string codeSnippet;

    constructor () {
        codeSnippet = account(this).code
    }
}

In the above example the variable: codeSnippet will contain the entire contents of the file, from the initial pragma, and including all of contract A and contract B. The variable this is used as an easy reference to the current contract, so for this purpose, it refers to contract B.

This is the most basic selection of code from a foreign file. While it is somewhat useful, it does not provide the user with a very useful mechanism to interact with the foreign code without a large amount of string parsing.

The .code member function may be used as a function that accepts zero or one arguments for more advanced functionality of selecting parts of contract. See below for the reference on the four ways to use this property:

  1. <account>.code will select the entire file contents at the referenced account. This is useful to see if a foreign contract actually exists, or is able to be accessed.

  2. <account>.code() will only select the entire given contract at the account. This is useful to see if a specific forgeign contract exists.

  3. <account>.code("") will also only select the entire given contract at the address. This is also useful to see if a foreign contract exists.

  4. <account>.code("<search_term>") will search a given contract to see if a particular symbol declaration in the contract exists and will return a string of the particular part that is searched. This is useful to see if specific parts of a contract exist.

Example

pragma solidvm 3.4;

contract A {

    uint testy = 13;

    constructor () {
        testy = 26;
    }

    function coolFunc(uint x) public view returns (uint){
        return x + 13;
    }
}

pragma solidvm 3.4;

contract B {
    string codeSnippet;

    constructor () {
        A a = new A();
        codeSnippet = account(a).code("coolFunc");
    }
}

In the above example codeSnippet will contain the following string:

function coolFunc (uint x) view public returns (uint ) {
    return x + 13;
}

While the above example might not look exactly the same as coolFunc as written in contract A, it is still perfectly correct.

This will work with all data-types currently supported in SolidVM, from strings to int, as well as being able to be used for variables, functions, contracts, enums, structs, events, custom modifiers, and overloaded functions (if a function is overloaded it will retrieve all of the overloaded functions).

Limitation

Please note that the code member function does not work when trying to access code across different private chains. For example if contract A on the main chain tries to get the information from contract B on a private chain there will be an error. However contract B may access to code of contract A since it is an accessible chain from contract B. In this way data on private chains stays private.

ECRecover Function

The ecrecover builtin helps recovering the the address associated with the public key from a recoverable elliptic curve signature. For improper inputs, this built-in returns the zero address.

Function Signature

ecrecover(string hash, uint v, string r, string s) returns (address)

Arguments:

  • hash : string
    • The (hash) of a message signed with ECDSA.
  • v : uint
    • The last byte of the signature or "recovery" byte, commonly known as v.
  • r : string
    • The first 32 bytes of the signature, commonly known as the r value of a signature.
  • s : string
    • The second 32 bytes of the signature, commonly known as the s value of a signature.

Example:

pragma solidvm 3.4;

contract RecoverAddress {

    address recoveredAddress;

    constructor(string _hash, int _v, string _r, string _s) {

        recoveredAddress = ecrecover(_hash, _v, _r, _s); //returns address of the message signer, otherwise 0 for invalid inputs

    }

}

Language Improvements

Support for Hexadecimal Literal Strings

Hexadecimal literals allow developers to encode specific byte values into variables by declaring them as hex values, rather than the explicit binary equivalents. This may be helpful when performing bitwise operations like XOR, AND, or bit-shifting and you need a specific byte value for that operation. Hexadecimal literals must be prefixed with the keyword "hex" and enclosed in double quotes (e.g hex"A9F2"). The content of the literal must be a valid hexadecimal string, the value of the literal will be the binary representation of the hexadecimal sequence. Please note, underscore separators between bytes are not currently supported.

Example:

pragma solidvm 3.4;

contract HexLiteralTest {

    bytes x;

    constructor() public {
        x = hex"AF32";
    }

}

Unsigned Right Bit Shift

Users can use the >>> operator to bit shift a number, treating the input (the number on the left) as an unsigned int (and the number of shifts as the number on the right of the operator). Note that ints are treated as 256 bit numbers. So that (-1 >>> 255) == 1 and (-1 >>> 256) == 0.

Example:

int a = 12  >>> 1;    // a  = 6
int b = 12  >>> 2;    // b  = 3
int c = 12  >>> 10;   // c  = 0
int d = -1 >>> 255;   // d  = 1
int e = -1 >>> 200;;  // e  = 72057594037927935

Base-10 Integer Parsing Support

SolidVM nows allows users to specify a base with which to parse a value to int. The base is provided in the optional second argument of this function. By default, it will use base 16.

Example:

pragma solidvm 3.4;

contract BaseExample {

    uint a;
    uint b;
    uint c;
    uint d;

    constructor() {
        a = uint("1237655", 10); // a = 1237655
        b = uint("188846", 16);  // b = 1607750 (expressed in decimal)
        c = uint("1f3479f6");    // c = 523532790 (expressed in decimal)
        // This next line will result in an error since "e" cannot be parsed to a base 10 digit
        // d = uint("e2245d", 10); 
    }
}