Skip to content

SolidVM 3.3

SolidVM 3.3 builds on SolidVM 3.2 and makes more language additions and bug fixes.

SolidVM 3.3 also includes more improvements and additions to the X.509 Identity registration and security features already built-in to SolidVM.

3.3 Features

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

  • Try/Catch Statements
  • Custom Function Modifiers
  • RIPEMD-160 & SHA-256 Built-in Hashing Functions
  • Selfdestruct Built-in Function
  • Scientific Notation and Ether-Unit number suffixes
  • Salted Contract creation

3.3 Bug Fixes

The following bug fixes are available in SolidVM ^3.3

  • "account" is now properly parsed as a reserved word. Contracts may no longer have symbols named "account".
  • Added the following reserved words to prevent errors when indexing contract data in Cirrus:
    • block_number
    • block_timestamp
    • block_hash
    • record_id
    • transaction_hash
    • transaction_sender
    • salt

Version Pragma

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

pragma solidvm 3.3;

Language Improvements

Ether Number Unit Suffixes

Users can use ether units to express address balances that are too big to be written in decimal notation. The unit suffix is placed after the number value.

X wei == X
X szabo == X * 10 ^ 12
X finney == X * 10 ^ 15
X ether == X * 10 ^ 18

Example:

int paymentAmount = 3 finney; // paymentAmount = 3 * (10^15)

Scientific Notation

Users can use the e scientific notation to express numbers that are too big to be written in decimal notation. The e is placed in between a coefficient X and the exponent Y.

XeY == x * 10 ^ Y

Example:

int a = 3e5; // a = 3 * (10^5)

Mappings

SolidVM no longer throws a runtime error when accessing non-existent keys in a mapping. Mappings now return the default value (0 or "" or false) of the map, if the key does not exist. This functionality is now in parity with Solidity. The default values are based on the value type of the mapping. Please reference the Default Storage Values.

This functionality is useful when using mappings to store a large collection of key-value pairs, and you need to query for a possibly non-existent key - user's can than check if the key was present or not by checking the returned type of the mapping access.

Example:

pragma solidvm 3.3;
contract DefaultMappingValues {
  mapping(uint => bool) boolMap;
  mapping(address => uint) intMap;
  mapping(uint => string) stringMap;
  bool x;
  uint y;
  string z;
  constructor() {
    boolMap[1] = true;
    intMap[msg.sender] = 1;
    stringMap[1] = "Cartography";
    x = boolMap[9]; // x == false;
    y = intMap[address(0)]; // y == 0;
    z = stringMap[9];  // z == "";
  }
}

After initialising the mappings created in the constructor, the state variables x,y,z are given values for a key that is non-existent in a mapping. The values that x, y, and z have are false,0 and "" respectively.

Custom Function Modifiers

SolidVM 3.3 adds support for custom function modifiers.

Custom function modifiers are a way to add customized code execution before and after a function is executed. They allow developers to write common function patterns once and reuse the code across a contract. Common use cases include requiring certain addresses to call a function or setting a control variable before or after execution. Modifiers typically use the require function to throw errors so that the main function body is never invoked. If the modifier does not revert (i.e. throw an error), than the main function body will be called.

Modifiers are defined using the modifier keyword, followed by any parameters and the modifiers body. Modifiers are used by inserting its name between a function's parameters and return type declarations.

Custom modifiers can be run before or after the main function's execution by the location of the "_" symbol in the modifier's body. The "_" acts as the place where code execution is handed over from the modifier back to the main function. Therefore any code before the "_" is run before the function, and any code after the "_" is run after the function.

Example:

pragma solidvm 3.3;
contract FuncModifier {
    // We will use these variables to demonstrate how to use
    // modifiers.
    address public  host;
    uint    public  x = 10;
    bool    public  locked;

    constructor() public {
        // Set the transaction sender as the host of the contract.
        host = msg.sender;
    }

    modifier onlyHost() {
        require(msg.sender == host, "Not host");
        _;
    }

    //Inputs can be passed to a modifier
    modifier validAddress(address _addr) {
        require(_addr != address(0), "Not a valid address");
        _;
    }

    function changeHost(address _newHost) public onlyHost validAddress(_newHost) {
        host = _newHost;
    }

    // Modifiers can be called before and / or after a function.
    // This modifier prevents a function from being called while
    // it is still executing.
    modifier noReentrancy() {
        require(!locked, "No reentrancy");

        locked = true;
        _; // function execution occurs here
        locked = false;
    }

    function decrement(uint _i) public noReentrancy {
        x -= _i;

        if (_i > 1) {
            decrement(_i - 1);
        }
    }
}

The noReentrancy modifier for example would be able to be called with the parameter 1, however not 2, since the function is recursive.

Try/Catch Statements

SolidVM 3.3 allows contracts to catch errors that may potentially throw errors and handle them with grace. Previously any code that threw an error immediately caused a runtime error, halting and reverting the transaction without any way to handle errors.

SolidVM presents two ways to handle errors: a method more paradigmatic with traditional Solidity, as well as a custom implementation just for SolidVM that allows for more granular error catching.

The SolidVM Way

The SolidVM-style of error catching enables developers to catch errors using SolidVM-defined error types. It also allows an arbitrary block of code to be run inside the try block, rather than being limited to a single expression. A try/catch block can be used to catch any number of error types by chaining multiple catch blocks with different error types after each other.

Example

pragma solidvm 3.3;
contract SolidVMCatch {

    uint public myNum = 5;

    constructor() public {
        try {
            myNum = 1 / 0;
            //... can put as many statements as you want here
        } catch DivideByZero {
            myNum = 3;
        }
    }
}

The Solidity Way

Standard Solidity provides generic error handling. The Solitdity try/catch behavior can be found on the Solidity Docs.

SolidVM 3.3 will catch errors based on the same logic, like catching division by zero as a Panic error, or calling revert as generic Error. See the Error Type Appendix for a full list of error types and their codes.

Try/catch statements are defined by placing the code that might throw an error right after the try keyword. A code block can then be place afterwards to define what should occur in the event of successful code execution. catch blocks are placed after this to define the behavior based on the type of error thrown.

Example

pragma solidvm 3.3;
contract Divisor {
function doTheDivide() public returns (uint) {
    return (1 / 0);
}
}
contract DoTheDivide {

Divisor public d;
uint public errCount = 0;

constructor() public {
    d = new Divisor();
}

function tryTheDivide() returns (uint, bool) {
    try d.doTheDivide() returns (uint v) {
        return (v, true);
    } catch Error(string memory itsamessage) { 
        // This is executed in case
        // revert was called inside doTheDivide()
        // and a reason string was provided.
        errCount++;
        return (0, false);
    } catch Panic(uint errCode) {
        // This is executed in case of a panic,
        // i.e. a serious error like division by zero
        // or overflow. The error code can be used
        // to determine the kind of error.
        errCount++;
        return (errCode, false);
    } catch (bytes bigTest) {
        // This is executed in case revert() was used.
        errCount++;
        return (0, false);
    }
}
}

In the above example, a call to tryTheDivide would catch the Panic error and return the error code of 12.

Error type Appendix

As a reference, these are error types for SolidVM:

  • Require
    • Error code: none
    • Thrown when a require function's condition is not satisfied.
    • Error classification: Error
  • Assert
    • Error code: none
    • Thrown when a assert function's condition is not satisfied.
    • Error classification: Error
  • TypeError
    • Error code: 1
    • Reason: Thrown when a type error occurs, such as assigning a value of the wrong type to a variable. These errors typically happen at contract upload time.
    • Error classification: Panic
  • InternalError
    • Error code: 2
    • Reason: Thrown when an internal error occurs in the VM execution.
    • Error classification: Panic
  • InvalidArguments
    • Error code: 3
    • Thrown when an invalid number of arguments are given to a function.
    • Error classification: Panic
  • IndexOutOfBounds
    • Error code: 4
    • Thrown when an invalid index of an array is accessed.
    • Error classification: Panic
  • TODO
    • Error code: 5
    • Thrown when a feature/operation is unimplemented in SolidVM.
    • Error classification: Panic
  • MissingField
    • Error code: 6
    • Thrown when a symbol or element is missing from a statement or expression.
    • Error classification: Panic
  • MissingType
    • Error code: 7
    • Thrown when a symbol is declared as a non-existent type.
    • Error classification: Panic
  • DuplicateDefinition - 8
    • Thrown when a symbol is defined/declared multiple times
    • Error classification: Panic
  • ArityMismatch
    • Error code: 9
    • Thrown when instantiated a new array using the new keyword and the declared length mismatches the array literal's length.
    • Error classification: Panic
  • UnknownFunction
    • Error code: 10
    • Thrown when a function is called but not defined.
    • Error classification: Panic
  • UnknownVariable
    • Error code: 11
    • Thrown when a variable is referenced but not defined in the current scope.
    • Error classification: Panic
  • DivideByZero
    • Error code: 12
    • Thrown when dividing by zero.
    • Error classification: Panic
  • MissingCodeCollection
    • Error code: 13
    • Thrown when a contract's code collection is non-existent at a provided address, or is not SolidVM code.
    • Error classification: Panic
  • InaccessibleChain
    • Error code: 14
    • Thrown when attempting to access an invalid chain.
    • Error classification: Panic
  • InvalidWrite
    • Error code: 15
    • Thrown when attempting to write data to a state variable on another chain.
    • Error classification: Panic
  • InvalidCertificate
    • Error code: 16
    • Thrown when attempting to register an invalid certificate.
    • Error classification: Panic
  • MalformedData
    • Error code: 17
    • Thrown when a message hash, public key or EC signature could not be properly parsed by the built-in verifyCert, verifyCertSignedBy, and verifySignature functions.
    • Error classification: Panic
  • TooMuchGas
    • Error code: 18
    • Not thrown in SolidVM.
    • Error classification: Panic
  • PaymentError
    • Error code: 19
    • Thrown when attempting to pay a non-payable account.
    • Error classification: Panic
  • ParseError
    • Error code: 20
    • Thrown when a contract or its arguments cannot be properly parsed.
    • Error classification: Panic
  • UnknownConstant
    • Error code: 21
    • Thrown when attempting to access an unknown constant of a contract.
    • Error classification: Panic
  • UnknownStatement
    • Error code: 22
    • Thrown when attempting to access a feature not supported by the contract's current SolidVM version.
    • Error classification: Panic

Extended Contract Creation

Salted Contracts

SolidVM 3.3 introduces the creation of salted contracts. Users can provide a 32-byte word as a modifier to a new contract creation expression to create a salted contract.

The address of a salted contract is calculated using:

hash(0xFF, sender, salt, contract_name, codecollection_hash)

This allows users to deploy contracts at a predetermined address by providing a specific salt value.

A salted contract is created as follows:

new <contract name>{salt: <bytes32>}(...args);

Example:

pragma solidvm 3.3;
contract A {
    // Contract A Implementation
}

contract B {

    A public a;

    constructor() {
        a = new A{salt: "hello, world"}(...args);
    }

}

Additional Built-In Functions

Selfdestruct

The selfdestruct built-in function allows a user to send the remaining tokens of a contract to a valid payable address before deleting the contract's code and rendering the contract unserviceable. However, do note that users can still send tokens to the self-destructed contract but retrieving the tokens would be impossible.

Function Signature

function selfdestruct(address payable _addr); 

Arguments:

  • _addr : address payable
    • The payable address to self-destruct.

Example:

pragma solidvm 3.3;
contract SelfDestructTest {

    account owner;
    uint _x;

    constructor() {
        owner = account(this);
        selfDestructContract();
    }

    function setX (uint x) {
        _x = x;
    }

    function selfDestructContract() {
        require(msg.sender == owner);
        selfdestruct(payable(owner));
    }
}

After running function selfDestructContract in the constructor and subsequently calling the selfdestruct built-in, users will receive an error when subsequently trying to call the setX function due to the contract's code being deleted after self-destructing.

Additional Hashing Functions

SolidVM 3.3 adds support for the sha256() and ripemd160() hash functions.

Like the keccak256 hashing function, these functions are also multivariate, meaning that can take any number of arguments.

SHA-256 Hash

function sha256(string arg_0, ...args) returns (bytes);

Arguments:

  • This function takes an arbitrary number of strings

Returns:

  • The SHA-256 hash of the arguments concatenated together.

RIPEMD-160 Hash

function ripemd160(string arg_0, ...args) returns (bytes);

Arguments:

  • This function takes an arbitrary number of strings

Returns:

  • The RIPEMD-160 hash of the arguments concatenated together.

Example:

pragma solidvm 3.3;
contract MultivariateTest {

    constructor() {}

    function f() public pure returns (bytes) {
        return sha256("Hello", " ", "World");
    }

    function g() public pure returns (bytes) {
        return ripemd160("Hello", " ", "World");
    }
}

Blockhash

The blockhash built-in function allows a user to lookup the hash of any given block number. Unlike in Ethereum Solidity, the SolidVM blockhash function is not restricted to the last 256 blocks on the chain.

Only one argument is given to the blockhash function, which is of type integer.

Function Signature

blockhash(int _blockNumber) returns bytes32; 

Arguments:

  • _blockNumber : int
    • The block number for which to look up the blockhash.

Example:

@@ -293,4 +295,4 @@ contract BlockhashTest {

} ```

The blockhash method can be invoked at any point of time. If the provided blocknumber is negative or does not exist, an InvalidArguments error will be thrown.