diff --git a/docs/zh/part2/smart-contract-development.md b/docs/zh/part2/smart-contract-development.md index a2ac7d78..ac9a3e34 100644 --- a/docs/zh/part2/smart-contract-development.md +++ b/docs/zh/part2/smart-contract-development.md @@ -1154,7 +1154,50 @@ Solidity 是一种面向合约的高级编程语言,专门用于在以太坊 receive() external payable {} } ``` +#### Solidity 存储 +Solidity合约数据存储采用的为合约的每项数据指定一个可计算的可存储位置,数据存储在一个容量为2^256^超大数组中,其中数组的每个元素的初始值都为0。而2^256^是一个超级大的数字,足够容量合约需要任意大小的存储。Solidity是插槽式数据存储,每个插槽可存储32字节的数据,当某个数据超过了32字节,则需要占用多个存储插槽(占用插槽数量由数据长度决定,`data.length`/32)。 + +对于数据长度是否已知,solidity有不同的存储方式: + ++ 当数据长度已知时,则存储位置将在编译时指定存储位置; + + + **定长数据存储**:Solidity编译器在编译合约时,当数据类型是值类型(固定大小的值)时,编译时将严格根据字段排序顺序,给每个要存储的值类型数据预分配存储位置。 相当于已提前指定了固定不变的数据指针。 + + + **紧凑存储**:一大部分值类型实际上不需要用到 32 字节,如`bool`、`uint1` 到 `uint256`。 为了节约存储量,编译器在发现所用存储不超过 32 字节时,将会将其和后面字段尽可能的存储在一个存储中。 + + + 即使是`bool`类型,虽然取值只有true/false(1/0),但是仍然占据1字节。 + + + 出于节省gas的目的,在编写合约代码时,即使声明的状态变量的数量及其类型发生不变,而变量的顺序发生变化有时也会有gas不同的差别。 + + ```solidity + pragma solidity >0.8.0; + contract Storage1 { + uint256 a = 11; // slot0 + uint8 b = 12; // slot1,1 字节 + uint128 c = 13; // slot1,16 字节 + bool d = true; // slot1,1 字节 + uint128 e = 14;// slot2 + } + + contract Storage2 { + uint256 a = 11; // slot0 + uint8 b = 12; // slot1,1 字节 + uint128 c = 13; // slot1,16 字节 + uint128 e = 14;// slot2 + bool d = true; // slot3,1 字节 + } + ``` + + 如上述合约所示,只将变量e和d的顺序调换,占用插槽的数量也会不一样。**鉴于这种紧凑存储原则,有效降低了存储占用。而以太坊存储是昂贵的,因此为了降低存储占用, 在编写合约时,记得注意状态变量的声明顺序。** + ++ 而对于未知长度的数据时(比如动态数组,映射等)则按照一定的规则计算存储位置。 + + + `string` 和 `bytes` 实际是一个特殊的 `array` ,编译器对这类数据有进行优化。如果 `string` 和 `bytes` 的数据很短。那么它们的长度也会和数据一起存储到同一个插槽。 具体为: + + 如果数据长度小于等于 31 字节, 则它存储在高位字节(左对齐),最低位字节存储 `length * 2`。 + + 如果数据长度超出 31 字节,则在主插槽存储 `length * 2 + 1`, 数据照常存储在 `keccak256(slot)` 中。 + + 动态数组 `T[]` 由两部分组成,数组长度和元素值。在 `Solidity` 中定义动态数组后,将在定义的插槽位置存储数组元素数量, 元素数据存储的起始位置是:`keccak256(slot)+偏移量`,每个元素需要根据下标和元素大小来读取数据。 + + 映射的存储布局是直接存储 `Key` 对应的 `value`,每个 `Key` 对应一份存储。一个 `Key` 的对应存储位置是 `keccak256(abi.encode(key, slot))` , 可直接获得 `value` 的存储。 ::: ## 五、智能合约实战项目