Miscellaneous¶

Layout of State Variables in Storage¶

Statically-sized variables (everything except mapping and dynamically-sized array types) are laid out contiguously in storage starting from position 0. Multiple items that need less than 32 bytes are packed into a single storage slot if possible, according to the following rules:

  • The first item in a storage slot is stored lower-order aligned.
  • Elementary types use only that many bytes that are necessary to store them.
  • If an elementary type does not fit the remaining part of a storage slot, it is moved to the next storage slot.
  • Structs and array data always start a new slot and occupy whole slots (but items inside a struct or array are packed tightly according to these rules).

Warning

When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher.This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smallerthan that, the EVM must use more operations in order to reduce the size of the element from 32bytes to the desired size.

It is only beneficial to use reduced-size arguments if you are dealing with storage valuesbecause the compiler will pack multiple elements into one storage slot, and thus, combinemultiple reads or writes into a single operation. When dealing with function arguments or memoryvalues, there is no inherent benefit because the compiler does not pack these values.

Finally, in order to allow the EVM to optimize for this, ensure that you try to order yourstorage variables and struct members such that they can be packed tightly. For example,declaring your storage variables in the order of uint128, uint128, uint256 instead ofuint128, uint256, uint128, as the former will only take up two slots of storage whereas thelatter will take up three.

The elements of structs and arrays are stored after each other, just as if they were given explicitly.

Due to their unpredictable size, mapping and dynamically-sized array types use a Keccak-256 hashcomputation to find the starting position of the value or the array data. These starting positions are always full stack slots.

The mapping or the dynamic array itselfoccupies an (unfilled) slot in storage at some position p according to the above rule (or byrecursively applying this rule for mappings to mappings or arrays of arrays). For a dynamic array, this slot stores the number of elements in the array (byte arrays and strings are an exception here, see below). For a mapping, the slot is unused (but it is needed so that two equal mappings after each other will use a different hash distribution).Array data is located at keccak256(p) and the value corresponding to a mapping keyk is located at keccak256(k . p) where . is concatenation. If the value is again anon-elementary type, the positions are found by adding an offset of keccak256(k . p).

bytes and string store their data in the same slot where also the length is stored if they are short. In particular: If the data is at most 31 bytes long, it is stored in the higher-order bytes (left aligned) and the lowest-order byte stores length 2. If it is longer, the main slot stores length 2 + 1 and the data is stored as usual in keccak256(slot).

So for the following contract snippet:

  1. pragma solidity ^0.4.0;
  2.  
  3. contract C {
  4. struct s { uint a; uint b; }
  5. uint x;
  6. mapping(uint => mapping(uint => s)) data;
  7. }

The position of data[4][9].b is at keccak256(uint256(9) . keccak256(uint256(4) . uint256(1))) + 1.

Layout in Memory¶

Solidity reserves three 256-bit slots:

  • 0 - 64: scratch space for hashing methods
  • 64 - 96: currently allocated memory size (aka. free memory pointer)
    Scratch space can be used between statements (ie. within inline assembly).

Solidity always places new objects at the free memory pointer and memory is never freed (this might change in the future).

Warning

There are some operations in Solidity that need a temporary memory area larger than 64 bytes and therefore will not fit into the scratch space. They will be placed where the free memory points to, but given their short lifecycle, the pointer is not updated. The memory may or may not be zeroed out. Because of this, one shouldn’t expect the free memory to be zeroed out.

Layout of Call Data¶

When a Solidity contract is deployed and when it is called from anaccount, the input data is assumed to be in the format in the ABIspecification. The ABI specification requires arguments to be padded to multiples of 32bytes. The internal function calls use a different convention.

Internals - Cleaning Up Variables¶

When a value is shorter than 256-bit, in some cases the remaining bitsmust be cleaned.The Solidity compiler is designed to clean such remaining bits before any operationsthat might be adversely affected by the potential garbage in the remaining bits.For example, before writing a value to the memory, the remaining bits needto be cleared because the memory contents can be used for computinghashes or sent as the data of a message call. Similarly, beforestoring a value in the storage, the remaining bits need to be cleanedbecause otherwise the garbled value can be observed.

On the other hand, we do not clean the bits if the immediatelyfollowing operation is not affected. For instance, since any non-zerovalue is considered true by JUMPI instruction, we do not cleanthe boolean values before they are used as the condition forJUMPI.

In addition to the design principle above, the Solidity compilercleans input data when it is loaded onto the stack.

Different types have different rules for cleaning up invalid values:

Type Valid Values Invalid Values Mean
enum of nmembers 0 until n - 1 exception
bool 0 or 1 1
signed integers sign-extendedword currently silentlywraps; in thefuture exceptionswill be thrown
unsignedintegers higher bitszeroed currently silentlywraps; in thefuture exceptionswill be thrown

Internals - The Optimizer¶

The Solidity optimizer operates on assembly, so it can be and also is used by other languages. It splits the sequence of instructions into basic blocks at JUMPs and JUMPDESTs. Inside these blocks, the instructions are analysed and every modification to the stack, to memory or storage is recorded as an expression which consists of an instruction and a list of arguments which are essentially pointers to other expressions. The main idea is now to find expressions that are always equal (on every input) and combine them into an expression class. The optimizer first tries to find each new expression in a list of already known expressions. If this does not work, the expression is simplified according to rules like constant + constant = sum_of_constants or X * 1 = X. Since this is done recursively, we can also apply the latter rule if the second factor is a more complex expression where we know that it will always evaluate to one. Modifications to storage and memory locations have to erase knowledge about storage and memory locations which are not known to be different: If we first write to location x and then to location y and both are input variables, the second could overwrite the first, so we actually do not know what is stored at x after we wrote to y. On the other hand, if a simplification of the expression x - y evaluates to a non-zero constant, we know that we can keep our knowledge about what is stored at x.

At the end of this process, we know which expressions have to be on the stack in the end and have a list of modifications to memory and storage. This information is stored together with the basic blocks and is used to link them. Furthermore, knowledge about the stack, storage and memory configuration is forwarded to the next block(s). If we know the targets of all JUMP and JUMPI instructions, we can build a complete control flow graph of the program. If there is only one target we do not know (this can happen as in principle, jump targets can be computed from inputs), we have to erase all knowledge about the input state of a block as it can be the target of the unknown JUMP. If a JUMPI is found whose condition evaluates to a constant, it is transformed to an unconditional jump.

As the last step, the code in each block is completely re-generated. A dependency graph is created from the expressions on the stack at the end of the block and every operation that is not part of this graph is essentially dropped. Now code is generated that applies the modifications to memory and storage in the order they were made in the original code (dropping modifications which were found not to be needed) and finally, generates all values that are required to be on the stack in the correct place.

These steps are applied to each basic block and the newly generated code is used as replacement if it is smaller. If a basic block is split at a JUMPI and during the analysis, the condition evaluates to a constant, the JUMPI is replaced depending on the value of the constant, and thus code like

  1. var x = 7;
  2. data[7] = 9;
  3. if (data[x] != x + 2)
  4. return 2;
  5. else
  6. return 1;

is simplified to code which can also be compiled from

  1. data[7] = 9;
  2. return 1;

even though the instructions contained a jump in the beginning.

Source Mappings¶

As part of the AST output, the compiler provides the range of the sourcecode that is represented by the respective node in the AST. This can beused for various purposes ranging from static analysis tools that reporterrors based on the AST and debugging tools that highlight local variablesand their uses.

Furthermore, the compiler can also generate a mapping from the bytecodeto the range in the source code that generated the instruction. This is againimportant for static analysis tools that operate on bytecode level andfor displaying the current position in the source code inside a debuggeror for breakpoint handling.

Both kinds of source mappings use integer indentifiers to refer to source files.These are regular array indices into a list of source files usually called"sourceList", which is part of the combined-json and the output ofthe json / npm compiler.

Note

In the case of instructions that are not associated with any particular source file,the source mapping assigns an integer identifier of -1. This may happen forbytecode sections stemming from compiler-generated inline assembly statements.

The source mappings inside the AST use the followingnotation:

s:l:f

Where s is the byte-offset to the start of the range in the source file,l is the length of the source range in bytes and f is the sourceindex mentioned above.

The encoding in the source mapping for the bytecode is more complicated:It is a list of s:l:f:j separated by ;. Each of theseelements corresponds to an instruction, i.e. you cannot use the byte offsetbut have to use the instruction offset (push instructions are longer than a single byte).The fields s, l and f are as above and j can be eitheri, o or - signifying whether a jump instruction goes into afunction, returns from a function or is a regular jump as part of e.g. a loop.

In order to compress these source mappings especially for bytecode, thefollowing rules are used:

- If a field is empty, the value of the preceding element is used.- If a : is missing, all following fields are considered empty.

This means the following source mappings represent the same information:

1:2:1;1:9:1;2:1:2;2:1:2;2:1:2

1:2:1;:9;2:1:2;;

Tips and Tricks¶

  • Use delete on arrays to delete all its elements.
  • Use shorter types for struct elements and sort them such that short types are grouped together. This can lower the gas costs as multiple SSTORE operations might be combined into a single (SSTORE costs 5000 or 20000 gas, so this is what you want to optimise). Use the gas price estimator (with optimiser enabled) to check!
  • Make your state variables public - the compiler will create getters for you automatically.
  • If you end up checking conditions on input or state a lot at the beginning of your functions, try using Function Modifiers.
  • If your contract has a function called send but you want to use the built-in send-function, use address(contractVariable).send(amount).
  • Initialize storage structs with a single assignment: x = MyStruct({a: 1, b: 2});

Note

If the storage struct has tightly packed properties, initialize it with separate assignments: x.a = 1; x.b = 2;. In this way it will be easier for the optimizer to update storage in one go, thus making assignment cheaper.

Cheatsheet¶

Order of Precedence of Operators¶

The following is the order of precedence for operators, listed in order of evaluation.

Precedence Description Operator
1 Postfix increment and decrement ++,
New expression new <typename>
Array subscripting <array>[<index>]
Member access <object>.<member>
Function-like call <func>(<args…>)
Parentheses (<statement>)
2 Prefix increment and decrement ++,
Unary plus and minus +, -
Unary operations delete
Logical NOT !
Bitwise NOT ~
3 Exponentiation **
4 Multiplication, division and modulo *, /, %
5 Addition and subtraction +, -
6 Bitwise shift operators <<, >>
7 Bitwise AND &
8 Bitwise XOR ^
9 Bitwise OR
10 Inequality operators <, >, <=, >=
11 Equality operators ==, !=
12 Logical AND &&
13 Logical OR
14 Ternary operator <conditional> ? <if-true> : <if-false>
15 Assignment operators =, =, ^=, &=, <<=,>>=, +=, -=, *=, /=,%=
16 Comma operator ,

Global Variables¶

  • block.blockhash(uint blockNumber) returns (bytes32): hash of the given block - only works for 256 most recent blocks
  • block.coinbase (address): current block miner’s address
  • block.difficulty (uint): current block difficulty
  • block.gaslimit (uint): current block gaslimit
  • block.number (uint): current block number
  • block.timestamp (uint): current block timestamp
  • gasleft() returns (uint256): remaining gas
  • msg.data (bytes): complete calldata
  • msg.gas (uint): remaining gas - deprecated in version 0.4.21 and to be replaced by gasleft()
  • msg.sender (address): sender of the message (current call)
  • msg.value (uint): number of wei sent with the message
  • now (uint): current block timestamp (alias for block.timestamp)
  • tx.gasprice (uint): gas price of the transaction
  • tx.origin (address): sender of the transaction (full call chain)
  • assert(bool condition): abort execution and revert state changes if condition is false (use for internal error)
  • require(bool condition): abort execution and revert state changes if condition is false (use for malformed input or error in external component)
  • revert(): abort execution and revert state changes
  • keccak256(…) returns (bytes32): compute the Ethereum-SHA-3 (Keccak-256) hash of the (tightly packed) arguments
  • sha3(…) returns (bytes32): an alias to keccak256
  • sha256(…) returns (bytes32): compute the SHA-256 hash of the (tightly packed) arguments
  • ripemd160(…) returns (bytes20): compute the RIPEMD-160 hash of the (tightly packed) arguments
  • ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address): recover address associated with the public key from elliptic curve signature, return zero on error
  • addmod(uint x, uint y, uint k) returns (uint): compute (x + y) % k where the addition is performed with arbitrary precision and does not wrap around at 2**256. Assert that k != 0 starting from version 0.5.0.
  • mulmod(uint x, uint y, uint k) returns (uint): compute (x y) % k where the multiplication is performed with arbitrary precision and does not wrap around at 2*256. Assert that k != 0 starting from version 0.5.0.
  • this (current contract’s type): the current contract, explicitly convertible to address
  • super: the contract one level higher in the inheritance hierarchy
  • selfdestruct(address recipient): destroy the current contract, sending its funds to the given address
  • suicide(address recipient): an alias to selfdestruct
  • <address>.balance (uint256): balance of the Address in Wei
  • <address>.send(uint256 amount) returns (bool): send given amount of Wei to Address, returns false on failure
  • <address>.transfer(uint256 amount): send given amount of Wei to Address, throws on failure

Function Visibility Specifiers¶

  1. function myFunction() <visibility specifier> returns (bool) {
  2. return true;
  3. }
  • public: visible externally and internally (creates a getter function for storage/state variables)
  • private: only visible in the current contract
  • external: only visible externally (only for functions) - i.e. can only be message-called (via this.func)
  • internal: only visible internally

Modifiers¶

  • pure for functions: Disallows modification or access of state - this is not enforced yet.
  • view for functions: Disallows modification of state - this is not enforced yet.
  • payable for functions: Allows them to receive Ether together with a call.
  • constant for state variables: Disallows assignment (except initialisation), does not occupy storage slot.
  • constant for functions: Same as view.
  • anonymous for events: Does not store event signature as topic.
  • indexed for event parameters: Stores the parameter as topic.

Reserved Keywords¶

These keywords are reserved in Solidity. They might become part of the syntax in the future:

abstract, after, case, catch, default, final, in, inline, let, match, null,of, relocatable, static, switch, try, type, typeof.

Language Grammar¶

  1. SourceUnit = (PragmaDirective | ImportDirective | ContractDefinition)*
  2.  
  3. // Pragma actually parses anything up to the trailing ';' to be fully forward-compatible.
  4. PragmaDirective = 'pragma' Identifier ([^;]+) ';'
  5.  
  6. ImportDirective = 'import' StringLiteral ('as' Identifier)? ';'
  7. | 'import' ('*' | Identifier) ('as' Identifier)? 'from' StringLiteral ';'
  8. | 'import' '{' Identifier ('as' Identifier)? ( ',' Identifier ('as' Identifier)? )* '}' 'from' StringLiteral ';'
  9.  
  10. ContractDefinition = ( 'contract' | 'library' | 'interface' ) Identifier
  11. ( 'is' InheritanceSpecifier (',' InheritanceSpecifier )* )?
  12. '{' ContractPart* '}'
  13.  
  14. ContractPart = StateVariableDeclaration | UsingForDeclaration
  15. | StructDefinition | ModifierDefinition | FunctionDefinition | EventDefinition | EnumDefinition
  16.  
  17. InheritanceSpecifier = UserDefinedTypeName ( '(' Expression ( ',' Expression )* ')' )?
  18.  
  19. StateVariableDeclaration = TypeName ( 'public' | 'internal' | 'private' | 'constant' )? Identifier ('=' Expression)? ';'
  20. UsingForDeclaration = 'using' Identifier 'for' ('*' | TypeName) ';'
  21. StructDefinition = 'struct' Identifier '{'
  22. ( VariableDeclaration ';' (VariableDeclaration ';')* )? '}'
  23.  
  24. ModifierDefinition = 'modifier' Identifier ParameterList? Block
  25. ModifierInvocation = Identifier ( '(' ExpressionList? ')' )?
  26.  
  27. FunctionDefinition = 'function' Identifier? ParameterList
  28. ( ModifierInvocation | StateMutability | 'external' | 'public' | 'internal' | 'private' )*
  29. ( 'returns' ParameterList )? ( ';' | Block )
  30. EventDefinition = 'event' Identifier EventParameterList 'anonymous'? ';'
  31.  
  32. EnumValue = Identifier
  33. EnumDefinition = 'enum' Identifier '{' EnumValue? (',' EnumValue)* '}'
  34.  
  35. ParameterList = '(' ( Parameter (',' Parameter)* )? ')'
  36. Parameter = TypeName StorageLocation? Identifier?
  37.  
  38. EventParameterList = '(' ( EventParameter (',' EventParameter )* )? ')'
  39. EventParameter = TypeName 'indexed'? Identifier?
  40.  
  41. FunctionTypeParameterList = '(' ( FunctionTypeParameter (',' FunctionTypeParameter )* )? ')'
  42. FunctionTypeParameter = TypeName StorageLocation?
  43.  
  44. // semantic restriction: mappings and structs (recursively) containing mappings
  45. // are not allowed in argument lists
  46. VariableDeclaration = TypeName StorageLocation? Identifier
  47.  
  48. TypeName = ElementaryTypeName
  49. | UserDefinedTypeName
  50. | Mapping
  51. | ArrayTypeName
  52. | FunctionTypeName
  53.  
  54. UserDefinedTypeName = Identifier ( '.' Identifier )*
  55.  
  56. Mapping = 'mapping' '(' ElementaryTypeName '=>' TypeName ')'
  57. ArrayTypeName = TypeName '[' Expression? ']'
  58. FunctionTypeName = 'function' FunctionTypeParameterList ( 'internal' | 'external' | StateMutability )*
  59. ( 'returns' FunctionTypeParameterList )?
  60. StorageLocation = 'memory' | 'storage'
  61. StateMutability = 'pure' | 'constant' | 'view' | 'payable'
  62.  
  63. Block = '{' Statement* '}'
  64. Statement = IfStatement | WhileStatement | ForStatement | Block | InlineAssemblyStatement |
  65. ( DoWhileStatement | PlaceholderStatement | Continue | Break | Return |
  66. Throw | EmitStatement | SimpleStatement ) ';'
  67.  
  68. ExpressionStatement = Expression
  69. IfStatement = 'if' '(' Expression ')' Statement ( 'else' Statement )?
  70. WhileStatement = 'while' '(' Expression ')' Statement
  71. PlaceholderStatement = '_'
  72. SimpleStatement = VariableDefinition | ExpressionStatement
  73. ForStatement = 'for' '(' (SimpleStatement)? ';' (Expression)? ';' (ExpressionStatement)? ')' Statement
  74. InlineAssemblyStatement = 'assembly' StringLiteral? InlineAssemblyBlock
  75. DoWhileStatement = 'do' Statement 'while' '(' Expression ')'
  76. Continue = 'continue'
  77. Break = 'break'
  78. Return = 'return' Expression?
  79. Throw = 'throw'
  80. EmitStatement = 'emit' FunctionCall
  81. VariableDefinition = ('var' IdentifierList | VariableDeclaration) ( '=' Expression )?
  82. IdentifierList = '(' ( Identifier? ',' )* Identifier? ')'
  83.  
  84. // Precedence by order (see github.com/ethereum/solidity/pull/732)
  85. Expression
  86. = Expression ('++' | '--')
  87. | NewExpression
  88. | IndexAccess
  89. | MemberAccess
  90. | FunctionCall
  91. | '(' Expression ')'
  92. | ('!' | '~' | 'delete' | '++' | '--' | '+' | '-') Expression
  93. | Expression '**' Expression
  94. | Expression ('*' | '/' | '%') Expression
  95. | Expression ('+' | '-') Expression
  96. | Expression ('<<' | '>>') Expression
  97. | Expression '&' Expression
  98. | Expression '^' Expression
  99. | Expression '|' Expression
  100. | Expression ('<' | '>' | '<=' | '>=') Expression
  101. | Expression ('==' | '!=') Expression
  102. | Expression '&&' Expression
  103. | Expression '||' Expression
  104. | Expression '?' Expression ':' Expression
  105. | Expression ('=' | '|=' | '^=' | '&=' | '<<=' | '>>=' | '+=' | '-=' | '*=' | '/=' | '%=') Expression
  106. | PrimaryExpression
  107.  
  108. PrimaryExpression = BooleanLiteral
  109. | NumberLiteral
  110. | HexLiteral
  111. | StringLiteral
  112. | TupleExpression
  113. | Identifier
  114. | ElementaryTypeNameExpression
  115.  
  116. ExpressionList = Expression ( ',' Expression )*
  117. NameValueList = Identifier ':' Expression ( ',' Identifier ':' Expression )*
  118.  
  119. FunctionCall = Expression '(' FunctionCallArguments ')'
  120. FunctionCallArguments = '{' NameValueList? '}'
  121. | ExpressionList?
  122.  
  123. NewExpression = 'new' TypeName
  124. MemberAccess = Expression '.' Identifier
  125. IndexAccess = Expression '[' Expression? ']'
  126.  
  127. BooleanLiteral = 'true' | 'false'
  128. NumberLiteral = ( HexNumber | DecimalNumber ) (' ' NumberUnit)?
  129. NumberUnit = 'wei' | 'szabo' | 'finney' | 'ether'
  130. | 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'years'
  131. HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'')
  132. StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"'
  133. Identifier = [a-zA-Z_$] [a-zA-Z_$0-9]*
  134.  
  135. HexNumber = '0x' [0-9a-fA-F]+
  136. DecimalNumber = [0-9]+ ( '.' [0-9]* )? ( [eE] [0-9]+ )?
  137.  
  138. TupleExpression = '(' ( Expression? ( ',' Expression? )* )? ')'
  139. | '[' ( Expression ( ',' Expression )* )? ']'
  140.  
  141. ElementaryTypeNameExpression = ElementaryTypeName
  142.  
  143. ElementaryTypeName = 'address' | 'bool' | 'string' | 'var'
  144. | Int | Uint | Byte | Fixed | Ufixed
  145.  
  146. Int = 'int' | 'int8' | 'int16' | 'int24' | 'int32' | 'int40' | 'int48' | 'int56' | 'int64' | 'int72' | 'int80' | 'int88' | 'int96' | 'int104' | 'int112' | 'int120' | 'int128' | 'int136' | 'int144' | 'int152' | 'int160' | 'int168' | 'int176' | 'int184' | 'int192' | 'int200' | 'int208' | 'int216' | 'int224' | 'int232' | 'int240' | 'int248' | 'int256'
  147.  
  148. Uint = 'uint' | 'uint8' | 'uint16' | 'uint24' | 'uint32' | 'uint40' | 'uint48' | 'uint56' | 'uint64' | 'uint72' | 'uint80' | 'uint88' | 'uint96' | 'uint104' | 'uint112' | 'uint120' | 'uint128' | 'uint136' | 'uint144' | 'uint152' | 'uint160' | 'uint168' | 'uint176' | 'uint184' | 'uint192' | 'uint200' | 'uint208' | 'uint216' | 'uint224' | 'uint232' | 'uint240' | 'uint248' | 'uint256'
  149.  
  150. Byte = 'byte' | 'bytes' | 'bytes1' | 'bytes2' | 'bytes3' | 'bytes4' | 'bytes5' | 'bytes6' | 'bytes7' | 'bytes8' | 'bytes9' | 'bytes10' | 'bytes11' | 'bytes12' | 'bytes13' | 'bytes14' | 'bytes15' | 'bytes16' | 'bytes17' | 'bytes18' | 'bytes19' | 'bytes20' | 'bytes21' | 'bytes22' | 'bytes23' | 'bytes24' | 'bytes25' | 'bytes26' | 'bytes27' | 'bytes28' | 'bytes29' | 'bytes30' | 'bytes31' | 'bytes32'
  151.  
  152. Fixed = 'fixed' | ( 'fixed' [0-9]+ 'x' [0-9]+ )
  153.  
  154. Ufixed = 'ufixed' | ( 'ufixed' [0-9]+ 'x' [0-9]+ )
  155.  
  156. InlineAssemblyBlock = '{' AssemblyItem* '}'
  157.  
  158. AssemblyItem = Identifier | FunctionalAssemblyExpression | InlineAssemblyBlock | AssemblyLocalBinding | AssemblyAssignment | AssemblyLabel | NumberLiteral | StringLiteral | HexLiteral
  159. AssemblyLocalBinding = 'let' Identifier ':=' FunctionalAssemblyExpression
  160. AssemblyAssignment = ( Identifier ':=' FunctionalAssemblyExpression ) | ( '=:' Identifier )
  161. AssemblyLabel = Identifier ':'
  162. FunctionalAssemblyExpression = Identifier '(' AssemblyItem? ( ',' AssemblyItem )* ')'

原文: http://solidity.apachecn.org/cn/doc/v0.4.21/miscellaneous.html