1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
|
##################################
Expressions and Control Structures
##################################
.. index:: if, else, while, for, break, continue, return, switch, goto
Control Structures
===================
Most of the control structures from C/JavaScript are available in Solidity
except for `switch` and `goto`. So
there is: `if`, `else`, `while`, `for`, `break`, `continue`, `return`, `? :`, with
the usual semantics known from C / JavaScript.
Parentheses can *not* be omitted for conditionals, but curly brances can be omitted
around single-statement bodies.
Note that there is no type conversion from non-boolean to boolean types as
there is in C and JavaScript, so `if (1) { ... }` is *not* valid Solidity.
.. index:: ! function;call, function;internal, function;external
.. _function-calls:
Function Calls
==============
Internal Function Calls
-----------------------
Functions of the current contract can be called directly ("internally"), also recursively, as seen in
this nonsensical example::
contract c {
function g(uint a) returns (uint ret) { return f(); }
function f() returns (uint ret) { return g(7) + f(); }
}
These function calls are translated into simple jumps inside the EVM. This has
the effect that the current memory is not cleared, i.e. passing memory references
to internally-called functions is very efficient. Only functions of the same
contract can be called internally.
External Function Calls
-----------------------
The expression `this.g(8);` is also a valid function call, but this time, the function
will be called "externally", via a message call and not directly via jumps.
Functions of other contracts have to be called externally. For an external call,
all function arguments have to be copied to memory.
When calling functions
of other contracts, the amount of Wei sent with the call and the gas can be specified::
contract InfoFeed {
function info() returns (uint ret) { return 42; }
}
contract Consumer {
InfoFeed feed;
function setFeed(address addr) { feed = InfoFeed(addr); }
function callFeed() { feed.info.value(10).gas(800)(); }
}
Note that the expression `InfoFeed(addr)` performs an explicit type conversion stating
that "we know that the type of the contract at the given address is `InfoFeed`" and
this does not execute a constructor. We could also have used `function setFeed(InfoFeed _feed) { feed = _feed; }` directly. Be careful about the fact that `feed.info.value(10).gas(800)`
only (locally) sets the value and amount of gas sent with the function call and only the
parentheses at the end perform the actual call.
Named Calls and Anonymous Function Parameters
---------------------------------------------
Function call arguments can also be given by name, in any order, and the names
of unused parameters (especially return parameters) can be omitted.
::
contract c {
function f(uint key, uint value) { ... }
function g() {
// named arguments
f({value: 2, key: 3});
}
// omitted parameters
function func(uint k, uint) returns(uint) {
return k;
}
}
Order of Evaluation of Expressions
==================================
The evaluation order of expressions is not specified (more formally, the order
in which the children of one node in the expression tree are evaluated is not
specified, but they are of course evaluated before the node itself). It is only
guaranteed that statements are executed in order and short-circuiting for
boolean expressions is done.
.. index:: ! assignment
Assignment
==========
.. index:: ! assignment;destructuring
Destructuring Assignments and Returning Multiple Values
-------------------------------------------------------
Solidity internally allows tuple types, i.e. a list of objects of potentially different types whose size is a constant at compile-time. Those tuples can be used to return multiple values at the same time and also assign them to multiple variables (or LValues in general) at the same time::
contract C {
uint[] data;
function f() returns (uint, bool, uint) {
return (7, true, 2);
}
function g() {
// Declares and assigns the variables. Specifying the type explicitly is not possible.
var (x, b, y) = f();
// Assigns to a pre-existing variable.
(x, y) = (2, 7);
// Common trick to swap values -- does not work for non-value storage types.
(x, y) = (y, x);
// Components can be left out (also for variable declarations).
// If the tuple ends in an empty component,
// the rest of the values are discarded.
(data.length,) = f(); // Sets the length to 7
// The same can be done on the left side.
(,data[3]) = f(); // Sets data[3] to 2
// Components can only be left out at the left-hand-side of assignments, with
// one exception:
(x,) = (1,);
// (1,) is the only way to specify a 1-component tuple, because (1) is
// equivalent to 1.
}
}
Complications for Arrays and Structs
------------------------------------
The semantics of assignment are a bit more complicated for non-value types like arrays and structs.
Assigning *to* a state variable always creates an independent copy. On the other hand, assigning to a local variable creates an independent copy only for elementary types, i.e. static types that fit into 32 bytes. If structs or arrays (including `bytes` and `string`) are assigned from a state variable to a local variable, the local variable holds a reference to the original state variable. A second assignment to the local variable does not modify the state but only changes the reference. Assignments to members (or elements) of the local variable *do* change the state.
.. index:: ! exception, ! throw
Scoping and Declarations
========================
.. index:: ! scoping, ! declarations
In Solidity, a variable declared anywhere within a function will be in scope for the *entire function*, regardless of where it is declared.
This is in contrast to many languages where variables are only scoped where they are declared until the end of the semantic block.
As a result, the following code is illegal and cause the compiler to throw an error, `Identifier already declared.`::
contract ScopingErrors {
function scoping() {
uint i = 0;
while (i++ < 1) {
uint same1 = 0;
}
while (i++ < 2) {
uint same1 = 0;// Illegal, second declaration of same1
}
}
function minimalScoping() {
{
uint same2 = 0;
}
{
uint same2 = 0;// Illegal, second declaration of same2
}
}
function forLoopScoping() {
for (uint same3 = 0; same3 < 1; same3++) {
}
for (uint same3 = 0; same3 < 1; same3++) {// Illegal, second declaration of same3
}
}
}
In addition to this, if a variable is declared, it will be initialized at the beginning of the function to its default value.
As a result, the following code is legal, despite being poorly written.::
function foo() returns (uint) {
// baz is implicitly initialized as 0
uint bar = 5;
if (true) {
bar += baz;
}
else {
uint baz = 10;// never executes
}
return bar;// returns 5
}
Exceptions
==========
There are some cases where exceptions are thrown automatically (see below). You can use the `throw` instruction to throw an exception manually. The effect of an exception is that the currently executing call is stopped and reverted (i.e. all changes to the state and balances are undone) and the exception is also "bubbled up" through Solidity function calls (exceptions are `send` and the low-level functions `call`, `delegatecall` and `callcode`, those return `false` in case of an exception).
Catching exceptions is not yet possible.
In the following example, we show how `throw` can be used to easily revert an Ether transfer and also how to check the return value of `send`::
contract Sharer {
function sendHalf(address addr) returns (uint balance) {
if (!addr.send(msg.value/2))
throw; // also reverts the transfer to Sharer
return this.balance;
}
}
Currently, there are three situations, where exceptions happen automatically in Solidity:
1. If you access an array beyond its length (i.e. `x[i]` where `i >= x.length`)
2. If a function called via a message call does not finish properly (i.e. it runs out of gas or throws an exception itself).
3. If a non-existent function on a library is called or Ether is sent to a library.
Internally, Solidity performs an "invalid jump" when an exception is thrown and thus causes the EVM to revert all changes made to the state. The reason for this is that there is no safe way to continue execution, because an expected effect did not occur. Because we want to retain the atomicity of transactions, the safest thing to do is to revert all changes and make the whole transaction (or at least call) without effect.
.. index:: ! assembly, ! asm, ! evmasm
Inline Assembly
===============
For more fine-grained control especially in order to enhance the language by writing libraries,
it is possible to interleave Solidity statements with inline assembly in a language close
to the one of the virtual machine. Due to the fact that the EVM is a stack machine, it is
often hard to address the correct stack slot and provide arguments to opcodes at the correct
point on the stack. Solidity's inline assembly tries to facilitate that and other issues
arising when writing manual assembly by the following features:
* functional-style opcodes: `mul(1, add(2, 3))` instead of `push1 3 push1 2 add push1 1 mul`
* assembly-local variables: `let x := add(2, 3) let y := mload(0x40) x := add(x, y)`
* access to external variables: `function f(uint x) { assembly { x := sub(x, 1) } }`
* labels: `let x := 10 repeat: x := sub(x, 1) jumpi(repeat, eq(x, 0))`
We now want to describe the inline assembly language in detail.
.. warning::
Inline assembly is still a relatively new feature and might change if it does not prove useful,
so please try to keep up to date.
Example
-------
The following example provides library code to access the code of another contract and
load it into a `bytes` variable. This is not possible at all with "plain Solidity" and the
idea is that assembly libraries will be used to enhance the language in such ways.
.. code::
library GetCode {
function at(address _addr) returns (bytes o_code) {
assembly {
// retrieve the size of the code, this needs assembly
let size := extcodesize(_addr)
// allocate output byte array - this could also be done without assembly
// by using o_code = new bytes(size)
o_code := mload(0x40)
// new "memory end" including padding
mstore(0x40, add(o_code, and(add(add(size, 0x20), 0x1f), bnot(0x1f))))
// store length in memory
mstore(o_code, size)
// actually retrieve the code, this needs assembly
extcodecopy(_addr, add(o_code, 0x20), 0, size)
}
}
}
Inline assemmbly could also be beneficial in cases where the optimizer fails to produce
efficient code. Please be aware that assembly is much more difficult to write because
the compiler does not perform checks, so you should use it for complex things only if
you really know what you are doing.
.. code::
library VectorSum {
// This function is less efficient because the optimizer currently fails to
// remove the bounds checks in array access.
function sumSolidity(uint[] _data) returns (uint o_sum) {
for (uint i = 0; i < _data.length; ++i)
o_sum += _data[i];
}
// We know that we only access the array in bounds, so we can avoid the check.
// 0x20 needs to be added to an array because the first slot contains the
// array length.
function sumAsm(uint[] _data) returns (uint o_sum) {
for (uint i = 0; i < _data.length; ++i)
assembly { o_sum := mload(add(add(_data, 0x20), i)) }
}
}
Syntax
------
Inline assembly parses comments, literals and identifiers exactly as Solidity, so you can use the
usual `//` and `/* */` comments. Inline assembly is initiated by `assembly { ... }` and inside
these curly braces, the following can be used (see the later sections for more details)
- literals, i.e. `0x123`, `42` or `"abc"` (strings up to 32 characters)
- opcodes (in "instruction style"), e.g. `mload sload dup1 sstore`, for a list see below
- opcode in functional style, e.g. `add(1, mlod(0))`
- labels, e.g. `name:`
- variable declarations, e.g. `let x := 7` or `let x := add(y, 3)`
- identifiers (externals, labels or assembly-local variables), e.g. `jump(name)`, `3 x add`
- assignments (in "instruction style"), e.g. `3 =: x`
- assignments in functional style, e.g. `x := add(y, 3)`
- blocks where local variables are scoped inside, e.g. `{ let x := 3 { let y := add(x, 1) } }`
Opcodes
-------
This document does not want to be a full description of the Ethereum virtual machine, but the
following list can be used as a reference of its opcodes.
If an opcode takes arguments (always from the top of the stack), they are given in parentheses.
Note that the order of arguments can be seed to be reversed in non-functional style (explained below).
Opcodes marked with `-` do not push an item onto the stack, those marked with `*` are
special and all others push exactly one item onte the stack.
In the following, `mem[a...b)` signifies the bytes of memory starting at position `a` up to
(excluding) position `b` and `storage[p]` signifies the storage contents at position `p`.
The opcodes `pushi` and `jumpdest` cannot be used directly.
+-----------------------+------+---------------------------------------------------------------+
| stop + `-` | stop execution, identical to return(0,0) |
+-----------------------+------+---------------------------------------------------------------+
| add(x, y) | | x + y |
+-----------------------+------+---------------------------------------------------------------+
| sub(x, y) | | x - y |
+-----------------------+------+---------------------------------------------------------------+
| mul(x, y) | | x * y |
+-----------------------+------+---------------------------------------------------------------+
| div(x, y) | | x / y |
+-----------------------+------+---------------------------------------------------------------+
| sdiv(x, y) | | x / y, for signed numbers in two's complement |
+-----------------------+------+---------------------------------------------------------------+
| mod(x, y) | | x % y |
+-----------------------+------+---------------------------------------------------------------+
| smod(x, y) | | x % y, for signed numbers in two's complement |
+-----------------------+------+---------------------------------------------------------------+
| exp(x, y) | | x to the power of y |
+-----------------------+------+---------------------------------------------------------------+
| bnot(x) | | ~x, every bit of x is negated |
+-----------------------+------+---------------------------------------------------------------+
| lt(x, y) | | 1 if x < y, 0 otherwise |
+-----------------------+------+---------------------------------------------------------------+
| gt(x, y) | | 1 if x > y, 0 otherwise |
+-----------------------+------+---------------------------------------------------------------+
| slt(x, y) | |1 if x < y, 0 otherwise, for signed numbers in two's complement|
+-----------------------+------+---------------------------------------------------------------+
| sgt(x, y) | |1 if x > y, 0 otherwise, for signed numbers in two's complement|
+-----------------------+------+---------------------------------------------------------------+
| eq(x, y) | | 1 if x == y, 0 otherwise |
+-----------------------+------+---------------------------------------------------------------+
| not(x) | | 1 if x == 0, 0 otherwise |
+-----------------------+------+---------------------------------------------------------------+
| and(x, y) | | bitwise and of x and y |
+-----------------------+------+---------------------------------------------------------------+
| or(x, y) | | bitwise or of x and y |
+-----------------------+------+---------------------------------------------------------------+
| xor(x, y) | | bitwise xor of x and y |
+-----------------------+------+---------------------------------------------------------------+
| byte(n, x) | | nth byte of x, where the most significant byte is the 0th byte|
+-----------------------+------+---------------------------------------------------------------+
| addmod(x, y, m) | | (x + y) % m with arbitrary precision arithmetics |
+-----------------------+------+---------------------------------------------------------------+
| mulmod(x, y, m) | | (x * y) % m with arbitrary precision arithmetics |
+-----------------------+------+---------------------------------------------------------------+
| signextend(i, x) | | sign extend from (i*8+7)th bit counting from least significant|
+-----------------------+------+---------------------------------------------------------------+
| sha3(p, n) | | keccak(mem[p...(p+n))) |
+-----------------------+------+---------------------------------------------------------------+
| jump(label) | `-` | jump to label / code position |
+-----------------------+------+---------------------------------------------------------------+
| jumpi(label, cond) | `-` | jump to label if cond is nonzero |
+-----------------------+------+---------------------------------------------------------------+
| pc | | current position in code |
+-----------------------+------+---------------------------------------------------------------+
| pop | `*` | remove topmost stack slot |
+-----------------------+------+---------------------------------------------------------------+
| dup1 ... dup16 | | copy ith stack slot to the top (counting from top) |
+-----------------------+------+---------------------------------------------------------------+
| swap1 ... swap1 | `*` | swap topmost and ith stack slot below it |
+-----------------------+------+---------------------------------------------------------------+
| mload(p) | | mem[p..(p+32)) |
+-----------------------+------+---------------------------------------------------------------+
| mstore(p, v) | `-` | mem[p..(p+32)) := v |
+-----------------------+------+---------------------------------------------------------------+
| mstore8(p, v) | `-` | mem[p] := v & 0xff - only modifies a single byte |
+-----------------------+------+---------------------------------------------------------------+
| sload(p) | | storage[p] |
+-----------------------+------+---------------------------------------------------------------+
| sstore(p, v) | `-` | storage[p] := v |
+-----------------------+------+---------------------------------------------------------------+
| msize | | size of memory, i.e. largest accessed memory index |
+-----------------------+------+---------------------------------------------------------------+
| gas | | gas still available to execution |
+-----------------------+------+---------------------------------------------------------------+
| address | | address of the current contract / execution context |
+-----------------------+------+---------------------------------------------------------------+
| balance(a) | | wei balance at address a |
+-----------------------+------+---------------------------------------------------------------+
| caller | | call sender (excluding delegatecall) |
+-----------------------+------+---------------------------------------------------------------+
| callvalue | | wei sent together with the current call |
+-----------------------+------+---------------------------------------------------------------+
| calldataload(p) | | call data starting from position p (32 bytes) |
+-----------------------+------+---------------------------------------------------------------+
| calldatasize | | size of call data in bytes |
+-----------------------+------+---------------------------------------------------------------+
| calldatacopy(t, f, s) | `-` | copy s bytes from calldata at position f to mem at position t |
+-----------------------+------+---------------------------------------------------------------+
| codesize | | size of the code of the current contract / execution context |
+-----------------------+------+---------------------------------------------------------------+
| codecopy(t, f, s) | `-` | copy s bytes from code at position f to mem at position t |
+-----------------------+------+---------------------------------------------------------------+
| extcodesize(a) | | size of the code at address a |
+-----------------------+------+---------------------------------------------------------------+
|extcodecopy(a, t, f, s)| `-` | like codecopy(t, f, s) but take code at address a |
+-----------------------+------+---------------------------------------------------------------+
| create(v, p, s) | | create new contract with code mem[p..(p+s)) and send v wei |
| | | and return the new address |
+-----------------------+------+---------------------------------------------------------------+
| call(g, a, v, in, | | call contract at address a with input mem[in..(in+insize)] |
| insize, out, outsize) | | providing g gas and v wei and output area |
| | | mem[out..(out+outsize)] returting 1 on error (out of gas) |
+-----------------------+------+---------------------------------------------------------------+
| callcode(g, a, v, in, | | identical to call but only use the code from a and stay |
| insize, out, outsize) | | in the context of the current contract otherwise |
+-----------------------+------+---------------------------------------------------------------+
| delegatecall(g, a, in,| | identical to callcode but also keep `caller` and `callvalue` |
| insize, out, outsize) | | |
+-----------------------+------+---------------------------------------------------------------+
| return(p, s) | `*` | end execution, return data mem[p..(p+s)) |
+-----------------------+------+---------------------------------------------------------------+
| selfdestruct(a) | `*` | end execution, destroy current contract and send funds to a |
+-----------------------+------+---------------------------------------------------------------+
| log0(p, s) | `-` | log without topics and data mem[p..(p+s)) |
+-----------------------+------+---------------------------------------------------------------+
| log1(p, s, t1) | `-` | log with topic t1 and data mem[p..(p+s)) |
+-----------------------+------+---------------------------------------------------------------+
| log2(p, s, t1, t2) | `-` | log with topics t1, t2 and data mem[p..(p+s)) |
+-----------------------+------+---------------------------------------------------------------+
| log3(p, s, t1, t2, t3)| `-` | log with topics t1, t2, t3 and data mem[p..(p+s)) |
+-----------------------+------+---------------------------------------------------------------+
| log4(p, s, t1, t2, t3,| `-` | log with topics t1, t2, t3, t4 and data mem[p..(p+s)) |
| t4) | | |
+-----------------------+------+---------------------------------------------------------------+
| origin | | transaction sender |
+-----------------------+------+---------------------------------------------------------------+
| gasprice | | gas price of the transaction |
+-----------------------+------+---------------------------------------------------------------+
| blockhash(b) | |hash of block nr b - only for last 256 blocks excluding current|
+-----------------------+------+---------------------------------------------------------------+
| coinbase | | current mining beneficiary |
+-----------------------+------+---------------------------------------------------------------+
| timestamp | | timestamp of the current block in seconds since the epoch |
+-----------------------+------+---------------------------------------------------------------+
| number | | current block number |
+-----------------------+------+---------------------------------------------------------------+
| difficulty | | difficulty of the current block |
+-----------------------+------+---------------------------------------------------------------+
| gaslimit | | block gas limit of the current block |
+-----------------------+------+---------------------------------------------------------------+
Literals
--------
You can use integer constants by typing them in decimal or hexadecimal notation and an
appropriate `PUSHi` instruction will automatically be generated. The following creates code
to add 2 and 3 resulting in 5 and then computes the bitwise and with the string "abc".
Strings are stored left-aligned and cannot be longer than 32 bytes.
.. code::
assembly { 2 3 add "abc" and }
Functional Style
-----------------
You can type opcode after opcode in the same way they will end up in bytecode. For example
adding `3` to the contents in memory at position `0x80` would be
.. code::
3 0x80 mload add 0x80 mstore
As it is often hard to see what the actual arguments for certain opcodes are,
Solidity inline assembly also provides a "functional style" notation where the same code
would be written as follows
.. code::
mstore(0x80, add(mload(0x80), 3))
Functional style and instructional style can be mixed, but any opcode inside a
functional style expression has to return exactly one stack slot (most of the opcodes do).
Note that the order of arguments is reversed in functional-style as opposed to the instruction-style
way. If you use functional-style, the first argument will end up on the stack top.
Access to External Variables
----------------------------
Solidity variables and other identifiers can be accessed by simply using their name.
For storage and memory variables, this will push the address and not the value onto the
stack. Also note that non-struct and non-array storage variable addresses occupy two slots
on the stack: One for the address and one for the byte offset inside the storage slot.
In assignments (see below), we can even use local Solidity variables to assign to.
.. code::
contract c {
uint b;
function f(uint x) returns (uint r) {
assembly {
b pop // remove the offset, we know it is zero
sload
x
mul
=: r // assign to return variable r
}
}
}
Labels
------
Another problem in EVM assembly is that `jump` and `jumpi` use absolute addresses
which can change easily. Solidity inline assembly provides labels to make the use of
jumps easier. The following code computes an element in the Fibonacci series.
.. code::
{
let n := calldataload(4)
let a := 1
let b := a
loop:
jumpi(loopend, eq(n, 0))
a add swap1
n := sub(n, 1)
jump(loop)
loopend:
mstore(0, a)
return(0, 0x20)
}
Please note that automatically accessing stack variables can only work if the
assembler knows the current stack height. This fails to work if the jump source
and target have different stack heights. It is still fine to use such jumps,
you should just not access any stack variables (even assembly variables) in that case.
Furthermore, the stack height analyser goes through the code opcode by opcode
(and not according to control flow), so in the following case, the assembler
will have a wrong impression about the stack height at label `two`:
.. code::
{
jump(two)
one:
// Here the stack height is 1 (because we pushed 7),
// but the assembler thinks it is 0 because it reads
// from top to bottom.
// Accessing stack variables here will lead to errors.
jump(three)
two:
7 // push something onto the stack
jump(one)
three:
}
Declaring Assembly-Local Variables
----------------------------------
You can use the `let` keyword to declare variables that are only visible in
inline assembly and actually only in the current `{...}`-block. What happens
is that the `let` instruction will create a new stack slot that is reserved
for the variable and automatically removed again when the end of the block
is reached. You need to provide an initial value for the variable which can
be just `0`, but it can also be a complex functional-style expression.
.. code::
contract c {
function f(uint x) returns (uint b) {
assembly {
let v := add(x, 1)
mstore(0x80, v)
{
let y := add(sload(v), 1)
b := y
} // y is "deallocated" here
b := add(b, v)
} // v is "deallocated" here
}
}
Assignments
-----------
Assignments are possible to assembly-local variables and to function-local
variables. Take care that when you assign to variables that point to
memory or storage, you will only change the pointer and not the data.
There are two kinds of assignments: Functional-style and instruction-style.
For functionaly-style assignments (`variable := value`), you need to provide a value in a
functional-style expression that results in exactly one stack value
and for instruction-style (`=: variable`), the value is just taken from the stack top.
For both ways, the colon points to the name of the variable.
.. code::
assembly {
let v := 0 // functional-style assignment as part of variable declaration
let g := add(v, 2)
sload(10)
=: v // instruction style assignment, puts the result of sload(10) into v
}
Things to Avoid
---------------
Inline assembly might have a quite high-level look, but it actually is extremely
low-level. The only thing the assembler does for you is re-arranging
functional-style opcodes, managing jump labels, counting stack height for
variable access and removing stack slots for assembly-local variables when the end
of their block is reached. Especially for those two last cases, it is important
to know that the assembler only counts stack height from top to bottom, not
necessarily following control flow. Furthermore, operations like swap will only
swap the contents of the stack but not the location of variables.
Conventions in Solidity
-----------------------
In contrast to EVM assembly, Solidity knows types which are narrower than 256 bits,
e.g. `uint24`. In order to make them more efficient, most arithmetic operations just
treat them as 256 bit numbers and the higher-order bits are only cleaned at the
point where it is necessary, i.e. just shortly before they are written to memory
or before comparisons are performed. This means that if you access such a variable
from within inline assembly, you might have to manually clean the higher order bits
first.
Solidity manages memory in a very simple way: There is a "free memory pointer"
at position `0x40` in memory. If you want to allocate memory, just use the memory
from that point on and update the pointer accordingly.
Elements in memory arrays in Solidity always occupy multiples of 32 bytes (yes, this is
even true for `byte[]`, but not for `bytes` and `string`). Multi-dimensional memory
arrays are pointers to memory arrays. The length of a dynamic array is stored at the
first slot of the array and then only the array elements follow.
.. warning::
Statically-sized memory arrays do not have a length field, but it will be added soon
to allow better convertibility between statically- and dynamically-sized arrays, so
please do not rely on that.
|