Language Reference¶
This page lists what SolidVM supports in the current release. Where behavior differs from Solidity, the entry says so and links to Differences from Solidity.
Source files¶
A source file can contain:
contract,abstract contract,interfaceandlibrarydefinitions- free functions
- file-level
constants,structs,enums anderrors using L for T;directives- user-defined value types (
type Price is uint;)
Events and non-constant variables must be declared inside a contract.
import "../abstract/ERC20/IERC20.sol";
On chain, every imported file must be included in the same upload (the JSON list of [filename, source] pairs). solid-vm-cli reads imports from disk, relative to the directory you run it in.
Types¶
| Type | Notes |
|---|---|
int, uint, int8 … int256, uint8 … uint256 |
Arbitrary precision. The size is accepted but not enforced: values never wrap and never overflow. See Integer range checks. |
decimal |
Fixed-point decimal. Supports literals such as 1.5, + - * / %, comparisons, and d.truncate(places). There is no fixed or ufixed. |
bool |
|
string |
Concatenate with + or string.concat(a, b, ...). .length is available. |
bytes, bytes1 … bytes32, byte |
+ concatenates bytes. |
address, address payable |
|
T[], T[n] |
.push(x) also works on memory arrays. There is no .pop(). |
mapping(K => V) |
Missing keys read as the zero value. |
struct, enum |
Declare in a contract or at file level. |
| Contract types | Token t = Token(addr); |
variadic |
Holds any number of values of any type. Used by fallback(variadic args) and by calls by name. |
type T is V; |
User-defined value type, replaced by V at compile time. |
Unset storage and missing mapping keys read as the zero value: 0, false, an empty string or bytes, address(0), an empty array, or the first enum member.
Integer range checks¶
SolidVM integers never wrap. The VM checks one case: storing a negative value in a uint variable, with plain = or in the variable's declaration. That raises ArithmeticException ("integer out of bounds: underflow").
Nothing else is checked. -=, --, unchecked { }, writes to array elements, mapping values or struct fields, and intermediate results can all hold negative numbers. See Integers.
Data location¶
memory, storage and calldata are accepted. How locals and arguments share data:
- A local assigned from a storage array refers to storage, so writing through it changes contract state.
- Assigning a memory array to another local copies it.
- Arrays and structs in memory are passed by reference to internal functions, so the caller sees the callee's changes. Parameters declared
storagerefer to storage. - Other contracts receive copies of arguments, including storage arrays.
Conversions¶
| Expression | Result |
|---|---|
uint(x), int(x), uint256(x), … |
Converts an integer, decimal (rounded), enum (its index), bytes (big-endian), address or string. |
uint("ff") |
Strings are parsed as hexadecimal by default, so this is 255. uint("255", 10) parses base 10. |
uint8(300) |
300. Sized casts don't truncate. |
string(x) |
Converts an integer (base 10), bool ("true"/"false"), address (40 hex digits without 0x) or bytes (decoded as UTF-8). |
string(i, 16), string(i, 16, n) |
Hex with a 0x prefix, e.g. string(255, 16) is "0xff". With n, zero-padded to n bytes. |
bytes(x) |
Converts a string (UTF-8), an integer or an address (20 bytes). |
bytes32(x), bytesN(x) |
An integer is masked to N bytes and left-padded. A string or bytes value is cut to N bytes. |
address(x) |
Converts an integer, hex string, bytes or contract. |
payable(a) |
address payable |
bool("true"), bool("false") |
bool |
decimal(x) |
Converts an integer or a string such as "1.25". |
MyContract(a) |
Contract reference at address a |
Literals and operators¶
- Solidity's operators are supported, including
**,<<,>>,>>>and the compound assignments. Upquark and helium still use an older precedence table; see Operator precedence. - Number literals accept scientific notation (
1e18) and the unitswei,szabo,finneyandether. - Hex literals (
hex"00ff"), and strings in single or double quotes. new C(args)deploys a contract.new C{salt: "s"}(args)deploys it to an address derived from the salt.
Contracts and functions¶
Supported:
- multiple inheritance, with
super - interfaces, abstract contracts, libraries and
using L for T - free functions
- modifiers with arguments
virtualandoverride- overloading, and named arguments (
f({to: a, amount: 1})) constantandimmutable- getters for
publicstate variables receive,fallback,viewandpure
Calling a private or internal function from another contract fails.
When a call names a function the contract doesn't define, fallback(variadic args) external returns (variadic) runs, and msg.sig holds the requested function's name.
Calling other contracts¶
Target t = new Target();
string memory a = t.greet("x"); // typed call
string memory b = address(t).call("greet", "strato"); // call by function name
a.call(name, args...)calls a function by name and returns its return value directly. It does not return a(bool, bytes)pair, and errors in the callee propagate to the caller.a.delegatecall(name, args...)runsa's code against the calling contract's storage, withmsg.senderunchanged.a.staticcall(name, args...)runs the call read-only.create(name, source, args...)andcreate2(salt, name, source, args...)deploy a contract from source text at run time and return its address.
Address members¶
| Member | Returns |
|---|---|
a.balance |
Account balance |
a.transfer(amount) |
Sends balance to an address payable. Raises PaymentError on failure. |
a.send(amount) |
Like transfer, but returns bool |
a.code |
The contract's source text (not bytecode) |
a.codehash |
Hash of the contract's code, as a hex string |
a.nonce |
Account nonce |
Global variables¶
| Name | Value |
|---|---|
block.number |
Block number |
block.timestamp, now |
Block time in seconds |
block.proposer |
Address of the block's proposer (same as block.coinbase) |
block.chainid |
The network ID: 33056204878082667 on upquark, 114784819836269 on helium. This is not the EIP-155 chain ID returned by eth_chainId. |
block.prevProposer, block.prevIntendedProposer, block.prevRound |
The parent block's proposer, intended proposer and consensus round. A zero intended proposer means no information, as for blocks before stake-weighted proposer selection. |
block.difficulty, block.gaslimit |
Block header fields |
blockhash(n) |
Hash of block n |
msg.sender |
Immediate caller |
msg.data |
The current function's arguments, as a variadic value |
msg.sig |
The current function's name, for example "transfer" |
tx.origin |
Transaction signer |
this |
Current contract |
msg.value, gasleft(), tx.gasprice and type(T).max are not available.
Builtin functions¶
Hashing, signatures and encoding¶
| Function | Notes |
|---|---|
keccak256(b), sha256(b), ripemd160(b) |
Given one bytes argument, returns the standard digest as bytes. Given anything else, hashes an RLP encoding of the arguments and returns a hex string, which doesn't match Ethereum. For standard results use keccak256(bytes(s)) or keccak256(abi.encodePacked(...)). |
ecrecover(hash, v, r, s) |
Signer address, or address(0) if recovery fails |
verifyP256(hash, r, s, x, y), verifyP256(hash, r, s, publicKey) |
Verifies a P-256 (secp256r1) signature over a 32-byte SHA-256 digest. publicKey is 64 or 65 bytes. Returns bool. |
abi.encode(...), abi.encodePacked(...) |
ABI-encoded bytes. encodePacked encodes every integer as 32 bytes. |
base64encode(x), base64urlencode(x) |
Base64 of a string or bytes. The URL-safe variant has no padding. |
addmod(a, b, m), mulmod(a, b, m), modExp(base, exponent, modulus) |
Modular arithmetic. modExp follows EIP-198: a zero modulus returns 0. |
Cryptography¶
These builtins run natively and are charged gas before they run, based on input size. Inputs that must be field elements have to be canonical (less than the field modulus), or the call fails.
| Function | Purpose | Gas |
|---|---|---|
ecAdd(x1, y1, x2, y2), ecMul(x, y, s) |
BN254 G1 addition and scalar multiplication, with EIP-196 input checks. Both return (x, y). |
50 and 300 |
ecPairing(input) |
BN254 pairing check over 6 integers per pair in EIP-197 order. Rejects G2 points outside the subgroup. Returns bool. |
45,000 + 17,000 per pair |
poseidon(...) |
Poseidon hash of 1 to 8 BN254 field elements | 100 + 100 per input |
poseidon2(...), poseidon2Compress(l, r) |
Poseidon2 over BN254 with gnark-crypto parameters | 100 + 100 per input |
poseidon2Permute, poseidon2Hash, poseidon2HashBytes |
Parametrized Poseidon2. The first argument is a 38-byte parameter block selecting a registered instance: BN254 (t=2) or Goldilocks (t=12). | Per permutation |
poseidon2gl(inputs), poseidon2glBytes(data) |
Goldilocks Poseidon2, returning 4 elements | Per permutation |
bls12381G1Add, bls12381G2Add |
BLS12-381 point addition | 4,000 and 9,000 |
bls12381G1Msm, bls12381G2Msm |
Multi-scalar multiplication | 2,500 and 9,000 per term |
bls12381Pairing |
Pairing check. Returns bool. |
50,000 + 20,000 per pair |
bls12381MapFpToG1, bls12381MapFp2ToG2 |
Map a field element to the curve | 5,000 and 20,000 |
bls12381HashToCurveG1(msg, dst), bls12381HashToCurveG2(msg, dst) |
Hash to curve | 10,000 and 40,000, + 100 per 32 message bytes |
bls12381DecompressG1, bls12381DecompressG2 |
Point decompression | 5,000 and 20,000 |
The BLS12-381 functions take EIP-2537-encoded bytes. The addition, MSM and pairing functions also accept flat integer coordinates.
A Groth16 verification is one four-pair ecPairing check (113,000 gas), which fits in a transaction's 400,000 budget. app/contracts/tests/General/main.groth16.sol is a Groth16 verifier built on these builtins, and app/contracts/tests/General/cryptoGas.test.sol checks the gas ceilings.
Other builtins¶
| Function | Notes |
|---|---|
require, assert, revert |
See Error Handling. |
log(values...) |
Prints values to the VM's standard output: the terminal under solid-vm-cli, vm-runner's output on a node. No on-chain effect. |
selfdestruct(recipient) |
Clears the contract's code and storage, and sends its balance to recipient. |
Events¶
event Transfer(address indexed from, address indexed to, uint256 value);
function transfer(address to, uint256 value) public {
// ...
emit Transfer(msg.sender, to, value);
}
- The event must be declared in the emitting contract or a parent.
emitmust pass one value per parameter; argument types are not checked atemit. - Parameters marked
indexedbecome Ethereum-style log topics, after the event-signature topic unless the event isanonymous. Block log blooms and JSON-RPC log topics use them; see JSON-RPC. - Cirrus records every event with all its arguments,
indexedor not, in a table named<creator>-<Contract>-<Event>. For example, transfers from the platformTokencontract are inBlockApps-Token-Transfer. Array arguments get their own table. Current nodes do not create separateindexed@tables. See Cirrus.