Layout of a Solidity Source File
Version Pragma
1 | pragma solidity ^0.4.11; |
Importing other Source File
1 | import "filename"; |
In the above, filename
is always treated as a path with /
as directory seperator.
All path names are treated as absolute paths unless thay start with .
or ..
.
Remapping
For example, remap github.com/ethereum/dapp-bin/library
to /usr/local/dapp-bin/library
solc
1 | import "github.com/ethereum/dapp-bin/library/iterable_mapping.sol` as it_mapping; |
1 | solc github.com/ethereum/dapp-bin/=/usr/local/dapp-bin/ source.sol |
Comments
Single Line Comments: //
Multi-line Comments: /* ... */
Natspec Comments: ///
or /** ... */
Natspec should be used directly above function declarations or statements.
1 | pragma solidity ^0.4.11; |
Structure of Contract
Contracts in Solidity are similar to classes in object-oriented languages. Each contract can contain declarations of State Variables, Functions, Function Modifiers, Events, Struct Types and Enum Types.
State Variables
State variables are values which are permanently stored in contract storage.
1 | pragma solidity ^0.4.11; |
Functions
Functions are executable units of code within a contract.
1 | pragma solidity ^0.4.11; |
Function Modifiers
Function modifiers can be used to amend the semantics of functions in a declarative way.
1 | pragma solidity ^0.4.11; |
Events
Events are convenience interfaces with the EVM logging facilities.
1 | pragma solidity ^0.4.11; |
Struct Types
Structs are custom defined types that can group several variables.
1 | pragma solidity ^0.4.11; |
Enum Types
1 | pragma solidity ^0.4.0; |
Types
bool
:true
orfalse
!
logical negation&&
logical and||
logical or==
logical equality!=
logical inequality
int/uint
:uint8
touint256
,int8
toint256
<=
,>=
,==
,!=
,<
,>
&
,|
,^
,~
, bit operators+
,-
,*
,/
,%
,**
,<<
,>>
address
: holds a 20 bytes value(size of an Ethereum address)<=
,=>
,==
,!=
,<
,>
Starting with version 0.5.0 contracts do not derive from the
address
type, but can still be explicitly converted to address.
Members of Address
balance
transfer
, address.transfer(value), send value Wei to the address.send
: Send is the low-level counterpart oftransfer
. If the execution fails, the current contract will not stop with an exception, butsend
will returnfalse
There are some dangers in using
send
: The transfer fails if the call stack depth is 1024 and it also fails if the recipient runs out of gas. So in order to make safe Ether transfers, always check the return value ofsend
, usetransfer
or even better.
call
,callcode
, anddelegatecall
Furthermore, to interface with contracts that do not adhere to the ABI, the function call
is provided which takes an arbitrary number of arguments of any type. The arguments are padding to 32 bytes and concatenated. One exception is the case where the first argument is encoded to exactly four bytes. In this case, it is not padding to allow the use of function signatures here.
1 | address nameReg = '0x72ba7d8e73fe8eb666ea66babc8116a41bfb10e2'; |
call
returns a boolean indicating whether the invoked function terminated (true) or caused an EVM exception (false). It is not possible to access the actual data returned.
It is possible to adjust the supplied gas with the .gas()
modifier:
1 | nameReg.call.gas(1000000000)('register', 'myName'); |
and supplied Ether value can be controlled too:
1 | nameReg.call.value(1 ether)('regiter', 'myName'); |
Chained
1 | nameReg.call.value(1 ether).gas(100000000)('register', 'myName'); |
In a similar way, the function delegatecall
can be used: the difference is that only the code of the given address is used, all other aspects(storage, balance, …) are taken from the current contract. The purpose of delegatecall
is to use library code which is stored in another contract. The user has to ensure that the layout of storage in both contracts is suitable for delegatecall to be used. Prior to homestead, only a limited variant called callcode
was available that did not provide access to the original msg.sender
and msg.value
.
All three functions call
, delegatecall
, and callcode
are very low-level functions and should only be used as a last resort as they break the type-safety of Solidity.
The .gas()
option is available on all three methods, while the .value()
option is not supported for delegatecall
.
All Contracts inherit the members of address, so it is possible to query the balance of the current contract using
this.balance
.
The use of
callcode
is discouraged and will be removed in the future.
Fixed-size byte array
.length
yields the fixed length of the byte array(read-only)
It is possible to use an array of bytes as
byte[]
, but it is wasting a lot of space, 21 bytes every element, to be exact, when passing in calls. It is better to usebytes
.
Dynamically-sized byte array
bytes
: dynamically-sized array, see Array, not a value-type.
string
: dynamically-sized UTF-8-encoded string, see Array, not a value-type.
Address Liberals
Hexadecimal literals that pass the address checksum test, for example 0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF
are of address
type. Hexadecimal literals that are between 39 and 41 digits long and do not pass the checksum test produce a warning and are treated as regular rational number literals
Rational and Integer Literals
Integer literal are formed form a sequence of numbers in the range 0-9. They are interpreted as decimals, for example, 69
means sixty nine. Octal literals do not exist in Solidity and leading zeros are invalid.
Decimal fraction literals are formed by a .
with at least one number on one side(1.
, .2
, 1.3
)
Scientific notation is also supported, where the base can have fractions, white the exponent cannot(2e10, -2e10, 2e-10, 2.5e10)
Number literal expressions retains arbitrary precision until they are converted to a non-literal type.
For example (2**800 + 1) - 2**800
result in the constant 1
, .5 * 8
results in the integer 4
Number literal expressions are converted into a non-literal type as soon as they are used with non-literal expressions. Even though we know that the value of the expression assigned to
b
in the following example evaluates to an integer, but the partial expression2.5 + a
does not type check so the code does not compile.
1 | uint128 a = 1; |
String Literals
String literals are written with either double or sinlge-quotes. They do not imply trailing zeros as in C, "foo"
represents three bytes not four.
String Literal supports escape characters, such as \n
, \xNN
and \uNNNN
.
Hexadecimal Literals
hex"001122FF"
Enums
Enums are one way to create a user-defined type in Solidity. They are explicitly convertable to and from all integer types but implicit conversion is not allowed. The explicit conversions check the value ranges at runtime, and a failure causes an exception.
1 | pragma solidity ^0.4.11; |
Function Types
Function types are notated as follows:
1 | function (<parameter types>) {internal|external} [pure|constant|view|payable] [returns (<return types>)] |
In contrast to the parameter types, the return types cannot be empty - if the function type should not return anything, the whole returns (<return type>)
part has to be omitted.
Public (or external) functions have a special member called selector
, which returns the ABI function selector
:
Reference Types
Complex types, i.e. types which do not always fit into 256 bits have to be handled more carefully than than the value-types we have already seen. Since copying them can be quite expensive, we have to think about whether we want them to be store in memory(which is not persisting) or storage(where the state variables are held).
Data location
Every complex type, i.e. arrays and structs has an additional annotation, the “data location” about where it is stored in memory or in storage. Depending on the context, there is always a default, but it can be overrideen by appeding either storage
or memory
to the type. The default for function parameters(including return parameters) is memory the default for local variable is storage and the location is forced to storage for state variables
There is also a third data location, calldata, which is a non-modificable, non-persistent area where function arguments are stored. Function parameter(not return parameters) of external functions are forced to calldata and behave mostly like memory.
Data locations are imortant because they change how assignment behave: assignments between storage and memory and also to a state variable always create an independent copy. Assignments to local storage variables only assign a reference though, and this reference always points to the state variable even if the latter is changed in the meantime. On the other hand, assignments from a memory stoted reference type to another memory-stored reference type do not create a copy.
1 | pragma solidity ^0.4.11; |
Summary
Forced data location:
parameters(not return) of external functions: calldata
state variables: storage
Default data location:
parameters(also return) of functions: memory
all other local variables: storage
Arrays
An array of fixed size k
and element type T
is written as T[k]
An array of dynamic size as T[]
An array of 5 dynamic arrays of uint
is uint[][5]
. (note that the notation is reversed when compared to some other language). To access the second uint in the third dynamic array, you use x[2][1]
Variables of type bytes
and string
are special arrays. A bytes
is similar to byte[]
, but it is packed tightly in calldata. string
is equal to bytes
but does not allow length or index access(for now).
So bytes
should always be preferred over byte[]
because it is cheaper.
Structs
Solidity provides a way to define new types in the form of structs, which is shown in the following example:
1 | pragma solidity ^0.4.11; |
It is not possible for a struct to contain a member of its own type, although the struct itself can be the value type of a mapping member. This restriction is necessary, as the size of the struct has to be finite.
Mappings
Mapping types are declared as mapping(_keyType => _valueType)
. Here _keyType
can be almost any type except for a mapping, a dynamically sized array, a contract, an enum, and a struct. _valueType
can actually be any type, including mappings.
Mappings can be seen as hash tables which are virtually initialized such that every possible key exists and is mapped to a value whose byte-representation is all zeros: a type’s default value.
Mappings are only allowed for state variables( or as storage reference types in internal function).
It is possible to mark mapping public
and have Solidity create a getter. The _keyType
will become a required parameter for the getter and it will return _valueType
.
Mappings are not iterable, but it is possible to implement a data structure on top of them.
Operators Involving LValues
LValue, i.e. a variable or something that can be assigned to.
delete
delete a
assigns the initial value for the type to a. I.e., for integers, it is equivalent to a = 0
.
delete
has no effect on whole mappints. So if you delete a struct, it will reset all members that are not mappings and also recurse into the memebers unless they are mappings. However, individual keys and what they map to can be deleted.
It’s really important to note that delete a
really behaves like an assignment to a
, it stores a new object in a
.
Conversions between Elementary Types.
Implicit Conversions
In general, an implicit conversion between value-types is possible if it makes sense semantically and no information is lost: uint8
is convertible to uint16
, but uint16
is not convertible to uint8
. And int8
is not convertible to uint256
(because uint256
cannot hold hold e.g. -1
).
Explicit Conversions
If the compiler does not allow implicit conversion but you know what you are doing, an explicit type conversion is sometimes possible.
1 | int8 y = -3; |
Type Deduction
For convenience, it is not always necessary to explicitly specify the type of a variable, the compiler automatically infers it from the type of the first expression that is assigned to the variable:
1 | uint24 x = 0x123; |
Units and Globally Available Variables
Ether Units
A literal number can take a suffix of wei
, finney
, szabo
, or ether
to convert between the subdenominations of Ether, where Ether currency numbers without a postfix are assumed to be Wei.
2 ether === 2000 finney
evaluates to true
Time Units
Suffixes like seconds
, minutes
, hours
, days
, weeks
and years
after literal numbers can be used to convert between units of time where seconds are the base unit.
Take care if you perform calendar calculations using these units, because not every year equals 365 days and not even every day has 24 hours because of leap seconds. Due to the fact that leap seconds cannot be predicted, an exact calendar library has to be updated by an external oracle.
1 | function f(unit start, unit dayAfter) { |
Other Global Variables and Functions: