Types
Solidity is a statically typed language, which means that the type of each variable (state and local) needs to be specified. Solidity provides several elementary types which can be combined to form complex types.
In addition, types can interact with each other in expressions containing operators. For a quick reference of the various operators, see Order of Precedence of Operators.
The concept of "undefined" or "null" values does not exist in Solidity, but newly declared variables always have a default value dependent on its type. To handle any unexpected values, you should use the revert function to revert the whole transaction, or return a tuple with a second bool value denoting success.
💡 NeoVM Difference
While Solidity types carry specific constraints (e.g., uint8 vs uint256), NeoVM relies heavily on its own fundamental types (BigInteger, ByteArray, Boolean, Array, Map). The Neo DevPack for Solidity compiler enforces Solidity's type rules during compilation and automatically lowers them to the appropriate NeoVM representations.
For a shorter reader guide to the same mapping rules, see Types and Values and Storage and Mappings.
Value Types
The following types are also called value types because variables of these types will always be passed by value, i.e. they are always copied when they are used as function arguments or in assignments.
Booleans
bool: The possible values are constants true and false.
Operators:
!(logical negation)&&(logical conjunction, "and")||(logical disjunction, "or")==(equality)!=(inequality)
The operators || and && apply the common short-circuiting rules. This means that in the expression f(x) || g(y), if f(x) evaluates to true, g(y) will not be evaluated even if it may have side-effects.
- NeoVM Mapping: Maps directly to the NeoVM
Booleanstack item.
Integers
int / uint: Signed and unsigned integers of various sizes. Keywords uint8 to uint256 in steps of 8 (unsigned of 8 up to 256 bits) and int8 to int256. uint and int are aliases for uint256 and int256, respectively.
Operators:
- Comparisons:
<=,<,==,!=,>=,>(evaluate tobool) - Bit operators:
&,|,^(bitwise exclusive or),~(bitwise negation) - Shift operators:
<<(left shift),>>(right shift) - Arithmetic operators:
+,-, unary-(only for signed integers),*,/,%(modulo),**(exponentiation)
💡 NeoVM Difference: Checked Fixed-Width Semantics
NeoVM represents integers as arbitrary-precision BigInteger values internally, but Neo DevPack for Solidity enforces Solidity 0.8 fixed-width arithmetic semantics at the compiler/runtime boundary:
- Checked arithmetic: Outside
unchecked, operations such asuint8(255) + 1revert withPanic(0x11). - Unchecked arithmetic: Inside
unchecked, supported fixed-width arithmetic suppresses overflow guards and wraps, souint8(255) + 1becomes0. - Other panics still apply:
uncheckeddoes not suppress division-by-zero or modulo-by-zero checks.
Fixed Point Numbers
fixed / ufixed: Signed and unsigned fixed point numbers of various sizes.
🚫 Unsupported Feature
Fixed point numbers are not fully supported by mainline Solidity and are completely unsupported by Neo DevPack for Solidity. Use scaled integer arithmetic instead (e.g., multiply by 10^18).
Address
The address type comes in two largely identical flavors:
address: Holds a 20 byte value (size of an Ethereum address or Neo script hash).address payable: Same asaddress, but with the additional memberstransferandsend.
Operators:
<=,<,==,!=,>=,>
💡 NeoVM Difference: The Address Type
On NeoVM, address maps to a UInt160 (the 20-byte script hash of an account or contract in little-endian format).
While address payable is parsed by the compiler, direct value transfers via .transfer() and .send() emit a warning and are mapped to GAS.transfer(). Neo does not have a native "Ether" concept attached to addresses; all value transfers use the NEP-17 token standard.
Members of Addresses
balanceandtransfer: On Ethereum, you can query the balance of an address using the propertybalanceand send Ether to a payable address using thetransferfunction. On NeoVM,address.balanceis auto-mapped toGAS.balanceOf(address).address.transferis auto-mapped toGAS.transfer(this, address, amount, data). Both emit warnings because they are approximations.call,staticcall,delegatecall, andcallcode:address.callmaps directly toSystem.Contract.Calland is the standard way to do cross-contract invocations.address.staticcallmaps toSystem.Contract.Callwith read-only flags.address.delegatecallandcallcodeare blocked. EVM uses these operations to execute code in the caller's storage context. NeoVM does not support that storage model. UseContractManagement.updatefor contract upgrades.
Contract Types
Every contract defines its own type. You can implicitly convert contracts to contracts they inherit from. Contracts can be explicitly converted to and from the address type.
Token t = Token(0x123...);Fixed-size byte arrays
The value types bytes1, bytes2, bytes3, ..., bytes32 hold a sequence of bytes from one to up to 32. byte is an alias for bytes1.
Operators:
Comparisons:
<=,<,==,!=,>=,>(evaluate tobool)Bit operators:
&,|,^(bitwise exclusive or),~(bitwise negation)Shift operators:
<<(left shift),>>(right shift)Index access: If
xis of typebytesI, thenx[k]for0 <= k < Ireturns thekth byte (read-only).NeoVM Mapping: Maps to a fixed-length NeoVM
ByteArray.bytes32translates specifically to the Neo ABI typeHash256and is often used for NEP-11 NFT token IDs.
Literals
Address Literals
Hexadecimal literals that pass the address checksum test, for example 0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF are of address payable type.
Rational and Integer Literals
Integer literals are formed from a sequence of numbers in the range 0-9. They are evaluated to arbitrary precision in the compiler. Fractional literals are not supported on NeoVM.
String Literals and Hex Literals
String literals are written with either double or single-quotes ("foo" or 'bar'). Hex literals are prefixed with the keyword hex and enclosed in double or single-quotes (hex"001122FF").
Enums
Enums are one way to create a user-defined type in Solidity. They are explicitly convertible to and from all integer types but implicit conversion is not allowed.
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
ActionChoices choice = ActionChoices.GoStraight;- NeoVM Mapping: Enums are backed by
uint8and are represented asBigIntegerat runtime on NeoVM.
User-Defined Value Types
A user-defined value type allows creating a zero cost abstraction over an elementary value type. This is similar to an alias, but with stricter type requirements.
type UFixed256x18 is uint256;These map to the underlying type (uint256 -> BigInteger) on NeoVM.
Reference Types
Values of reference type can be modified through multiple different names. Contrast this with value types where you get an independent copy whenever a variable of value type is used. Because of that, reference types have to be handled more carefully.
Data location
Every reference type has an additional annotation, the "data location", about where it is stored. There are three data locations: memory, storage and calldata.
storage- The location where state variables are stored. Lifetime is limited to the contract's existence.memory- Lifetime is limited to an external function call.calldata- Special data location that contains the function arguments.
💡 NeoVM Difference: calldata vs memory
On Ethereum, calldata is a non-modifiable, non-persistent area where function arguments are stored, and behaves differently than memory.
On NeoVM, calldata is treated identically to memory. The compiler accepts the calldata keyword to maintain strict source compatibility with Ethereum contracts, but internally, both map to items on the NeoVM execution stack.
Arrays
Arrays can have a compile-time fixed size or a dynamic size.
uint256[] dynamicArray;
uint256[5] fixedArray;Array Members
length: Arrays have alengthmember that contains their number of elements.push()/push(x): Dynamic storage arrays andbytes(notstring) have apushmember that you can use to append a zero-initialized element at the end of the array, or append a given element.pop(): Dynamic storage arrays andbytes(notstring) have apopmember that you can use to remove an element from the end of the array.NeoVM Mapping: Memory arrays map to the NeoVM
Arraytype. Storage arrays map to deterministic key-value prefix operations in NeoStorage.
Structs
Solidity provides a way to define new types in the form of structs.
struct Funder {
address addr;
uint amount;
}💡 NeoVM Difference: Struct Storage
On NeoVM, structs map to an Array containing the struct's fields in memory.
When written to storage, the entire struct is serialized as a single binary blob via StdLib.serialize(). Because struct storage is atomic on NeoVM, reading a single field from storage requires deserializing the entire struct. If you only ever access individual fields, consider using separate state variables to save GAS.
Mapping Types
Mapping types use the syntax mapping(_KeyType => _ValueType) and variables of mapping type are allowed in state variable arrays, structs, or as state variables themselves.
_KeyTypecan be any built-in value type,bytes,string, or any contract or enum type. Other user-defined or complex types, such as mappings, structs or array types are not allowed._ValueTypecan be any type, including mappings, arrays and structs.
mapping(address => uint256) public balances;- NeoVM Mapping: Mappings do not have a length or a concept of a key or value being "set". On Neo, mappings compile to iterative
SHA256hashing operations.balances[addr]computesSHA256(serialize(addr) || SHA256("balances"))and looks up the resulting key in Neo's globalSystem.Storage.Gettrie.
Iterable Mappings
You cannot iterate over mappings, i.e. you cannot enumerate their keys. It is possible, though, to implement a data structure on top of them and iterate over that. On NeoVM, this is often done by pairing a mapping with an array that tracks the inserted keys.
