Before we start, I have to mention that I refer to this post a lot.
https://blog.qtum.org/diving-into-the-ethereum-vm-6e8d5d2f3c30

For ethereum vm, the storage access is expensive. sstore costs 20000 gas and sload costs 200 gas. So we have to learn to optimize the code to save real money here.

We don’t pay for variable declaration

I got this code here.

1
2
3
4
5
6
7
8
9
10
sam@hero:~/Documents/test$ cat c1.sol 
pragma solidity ^0.4.11;
contract C {
uint256 a;
uint256 b;
uint256 c;
function C() {
a = 7;
}
}

And let’s compile it. (solc –bin –asm –optimize c1.sol ) If we take a look at the output assembly code, we will see this.

1
2
3
0x7
0x0
sstore

It only stores a. And that’s what we are going to pay.

Read from an uninitialized position returns 0

Let’s change our code here.

1
2
3
4
5
6
7
8
9
10
sam@hero:~/Documents/test$ cat c1.sol 
pragma solidity ^0.4.11;
contract C {
uint256 a;
uint256 b;
uint256 c;
function C() {
a = a + 1;
}
}

Let’s compile it.

1
2
3
4
5
6
7
8
pop
0x0
dup1
sload
0x1
add
swap1
sstore

So here we see that it’s wasting gas with sload.

struct

Let’s assign a value in a struct.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sam@hero:~/Documents/test$ cat c1.sol 
pragma solidity ^0.4.11;
contract C {
struct Tuple{
uint256 a;
uint256 b;
}

Tuple t;

function C() {
t.b = 7;
}
}

Compile it.

1
2
3
4
5
tag_1:
pop
0x7
0x1
sstore

We see that we only pay for the uint256 b.

Fixed length array
Our code here.

1
2
3
4
5
6
7
8
9
sam@hero:~/Documents/test$ cat c1.sol 
pragma solidity ^0.4.11;
contract C {
uint256[6] numbers;

function C() {
numbers[5] = 7;
}
}

Compile it.

1
2
3
4
pop
0x7
0x5
sstore

The same as above cases. But if we turn off the compile optimization, we can see the bound-checking code.

1
2
3
4
5
6
7
8
9
10
11
12
pop
0x7
0x0
0x5
0x6
dup2
lt
iszero
iszero
tag_4
jumpi
invalid

Store 4 8 bytes number in one 32 bytes slot

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sam@hero:~/Documents/test$ cat c1.sol 
pragma solidity ^0.4.11;
contract C {
uint64 a;
uint64 b;
uint64 c;
uint64 d;

function C() {
a = 1;
b = 2;
c = 3;
d = 4;
}
}

Compile it with optimization.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
0x0
dup1
sload
0x1
not(0xffffffffffffffff)
swap1
swap2
and
or
not(sub(exp(0x2, 0x80), exp(0x2, 0x40)))
and
0x20000000000000000
or
not(sub(exp(0x2, 0xc0), exp(0x2, 0x80)))
and
0x300000000000000000000000000000000
or
sub(exp(0x2, 0xc0), 0x1)
and
0x4000000000000000000000000000000000000000000000000
or
dup2
sstore

See what? We only have one sstore here.

Mapping

Let’s try mapping in solidity here!

1
2
3
4
5
6
7
8
9
sam@hero:~/Documents/test$ cat c1.sol 
pragma solidity ^0.4.11;
contract C {
mapping(uint256 => uint256) items;

function C() {
items[0xbeef] = 7;
}
}

Compile it.

1
2
3
4
5
6
7
8
9
10
11
pop
0xbeef
0x0
swap1
dup2
mstore
0x20
mstore
0x7
0xf795696b84ec505a06e455ed35745d482b1c95debff7502f2dfa10a8a8820138
sstore

We see that EVM doesn’t use 0xbeef we provided as the key. Instead it used a hash number. And that’s keccak256 result.

So let’s make some changes and use much large values.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pragma solidity ^0.4.11;
contract C {
mapping(uint256 => Tuple) tuples;
struct Tuple {
uint256 a;
uint256 b;
uint256 c;
}
function C() {
tuples[0x1].a = 0x1A;
tuples[0x1].b = 0x1B;
tuples[0x1].c = 0x1C;
}
}

So it uses 3 sstore now.

1
2
3
4
5
6
7
8
9
0x1a
0xada5013122d395ba3c54772283fb069b10426056ef8ca54750cb9bb552a59e7d
sstore
0x1b
0xada5013122d395ba3c54772283fb069b10426056ef8ca54750cb9bb552a59e7e
sstore
0x1c
0xada5013122d395ba3c54772283fb069b10426056ef8ca54750cb9bb552a59e7f
sstore

Array
Array is just like mapping. Let’s consider a simple array here.

1
2
3
4
5
6
7
8
9
pragma solidity ^0.4.11;
contract C {
uint256[] chunks;
function C() {
chunks.push(0xAA);
chunks.push(0xBB);
chunks.push(0xCC);
}
}

The 4 storage slots are like this. while the first one is the length.

1
2
3
4
5
6
7
8
key: 0x0000000000000000000000000000000000000000000000000000000000000000
value: 0x0000000000000000000000000000000000000000000000000000000000000003
key: 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563
value: 0x00000000000000000000000000000000000000000000000000000000000000aa
key: 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e564
value: 0x00000000000000000000000000000000000000000000000000000000000000bb
key: 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e565
value: 0x00000000000000000000000000000000000000000000000000000000000000cc

For a dynamic array.

1
2
3
4
5
6
7
8
9
10
11
pragma solidity ^0.4.11;
contract C {
uint128[] s;
function C() {
s.length = 4;
s[0] = 0xAA;
s[1] = 0xBB;
s[2] = 0xCC;
s[3] = 0xDD;
}
}

It will fit into 2 slots.

1
2
3
4
5
6
key: 0x0000000000000000000000000000000000000000000000000000000000000000
value: 0x0000000000000000000000000000000000000000000000000000000000000004
key: 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563
value: 0x000000000000000000000000000000bb000000000000000000000000000000aa
key: 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e564
value: 0x000000000000000000000000000000dd000000000000000000000000000000cc

How a function in smart contract gets called

So for a function like this:

1
2
3
4
5
6
7
8
9
10
pragma solidity ^0.4.11;
contract C {
uint256 a;
function setA(uint256 _a) {
a = _a;
}
function getA() returns(uint256) {
return a;
}
}

If you deploy it on the network, you will get a contract address.

0x62650ae5c5777d1660cc17fcd4f48f6a66b9a4c2

When you want to make a call to setA, you are actually making a transaction. The destination is

0x62650ae5c5777d1660cc17fcd4f48f6a66b9a4c2

And the input data in the transaction is

0xee919d500000000000000000000000000000000000000000000000000000000000000001

Here the first 4 bytes are method selector and the last one is the argument.

And if you want to use getA(), you don’t need to pay since it won’t change anything. The input data should be

1
2
>>> sha3("getA()")[0:8].hex()
'd46300fd'