您现在的位置是:首页 >其他 >Solidity 小知识总结网站首页其他

Solidity 小知识总结

gridlayout 2024-10-25 12:01:04
简介Solidity 小知识总结

Solidity versions follow Semantic Versioning. In addition, patch level releases with major release 0 (i.e. 0.x.y) will not contain breaking changes. That means code that compiles with version 0.x.y can be expected to compile with 0.x.z where z > y.   solidity 的版本遵从语义版本法则。 另外,对针主要版本0.x.y 的path发布,不会有大的突破性变更。意味着0.x.y编译器能够编译的代码也能够被0.x.z编译器编译成功.  z >Y.

We currently use a 0.x version number to indicate this fast pace of change. 我们是用0.x数字版本号表示大版本的快速变化.

Pragmas

The pragma keyword is used to enable certain compiler features or checks. A pragma directive is always local to a source file, so you have to add the pragma to all your files if you want to enable it in your whole project.

pragma 关键字,用来开启确定的编译器特性或者检查。 pragma 作用范围仅限于本地的特定的文件, 所以,你需要添加pragma到所有的文件里面, 如果你想在整个项目里面开启某个特性。

Version Pragma

The version pragma is used as follows: 

pragma solidity ^0.5.2;

A source file with the line above does not compile with a compiler earlier than version 0.5.2, and it also does not work on a compiler starting from version 0.6.0 (this second condition is added by using ^). Because there will be no breaking changes until version 0.6.0, you can be sure that your code compiles the way you intended. The exact version of the compiler is not fixed, so that bugfix releases are still possible.

一个拥有上述版本声明的solidity源文件不会用早于版本0.5.2之前的编译器版本编译这个文件. 当然,版本0.6.0以上的编译器版本也无法编译这个文件.  因为直到0.6.0版本前,不会有突破性变化,所以你可以按照你的意图编译这个文件.

Using the version pragma does not change the version of the compiler. It also does not enable or disable features of the compiler. It just instructs the compiler to check whether its version matches the one required by the pragma. If it does not match, the compiler issues an error.

用版本语法pragma 不会改变编译器的版本。 也不能开启或关闭编译器特性功能。 它只是直到编译器去检查它的版本和要求的版本是否匹配. 如果不匹配,编译器会发出错误。

ABI Coder Pragma

By using pragma abicoder v1 or pragma abicoder v2 you can select between the two implementations of the ABI encoder and decoder. (这个pragma标识着,你可以使用encode,decoder方法)

The new ABI coder (v2) is able to encode and decode arbitrarily nested arrays and structs. Apart from supporting more types, it involves more extensive validation and safety checks, which may result in higher gas costs, but also heightened security. It is considered non-experimental as of Solidity 0.6.0 and it is enabled by default starting with Solidity 0.8.0. The old ABI coder can still be selected using pragma abicoder v1;.

新的ABI coder(v2) 可以编码或解码任意嵌套的数据和结构。除了支持更多的类型,它涉及到大量的校验和安全检查,可能会导致高gas费用,但是却加强了安全能力。 在0.6.0版本,它就被作为非实验性的特性,从solidity 0.8.0 开始正式启用.    仍然可以通过pragma abicoder v1 启用旧版本.

This pragma applies to all the code defined in the file where it is activated, regardless of where that code ends up eventually. This means that a contract whose source file is selected to compile with ABI coder v1 can still contain code that uses the new encoder by inheriting it from another contract. This is allowed if the new types are only used internally and not in external function signatures.   pragma 应用被激活的文件里面的所有代码,无论代码最终在哪里结束。 意思是用了coder v1的代码可以包含新版本encoder v2的(继承过来的)合约代码。

SMTChecker

安全检查.

Types

Solidity is a statically typed language, which means that the type of each variable (state and local) needs to be specified. Solidity provides several elementary types which can be combined to form complex types.

solidity 是一个静态的类型语言,以为着每一个变量需要指定类型。 solidity 提供多种类型可以组合成复杂的类型。

The concept of “undefined” or “null” values does not exist in Solidity, but newly declared variables always have a default value dependent on its type. 

在solidity中,不存在undefined和null的概念,每一个新声明的变量总是有一个跟它类型相对应的默认值。

值类型

The following are called value types because their variables will always be passed by value, i.e. they are always copied when they are used as function arguments or in assignments.

接下来的类型称为值类型,因为这些变量总是用值传递。 比如,当作为函数参数或赋值的时候,它们总是拷贝其中的值.

Booleans

Integers

Fixed Point Numbers (相对于floating point 来说的, 浮点数和固点数, fixed/ufixed = fixed128x8/ unfixed128x8.  ufixedMxN,fixedMxN, M代表类型所占用的位数, N 代表小数点位数. M必须能整除8,并且从8到256.  N的取值必须在0-80之间,包括80.   浮点数和固点数最大的区别是位数长度的变化和小数位数,浮点数的小数个数是灵活的 固点数小数位数受到N的严格约束).   

Address

Address的成员方法

The transfer function reverts on failure.  

address.transfer  如果失败合约停止运行,会产生fail方法,并且交易回滚。

address.send  是一个底层的跟transfer一样的功能,send如果失败,只会返回fasle, 合约不会停止运行。

  • calldelegatecall and staticcall

In order to interface with contracts that do not adhere to the ABI, or to get more direct control over the encoding, the functions call, delegatecall and staticcall are provided. 

为了不用ABI去连接合约,或者为了直接控制编码。 提供了函数call, delegatecall, staticcall.

They all take a single bytes memory parameter and return the success condition (as a bool) and the returned data (bytes memory). The functions abi.encodeabi.encodePackedabi.encodeWithSelector and abi.encodeWithSignature can be used to encode structured data.

它们总是以bytes memory 修饰的变量作为参数,返回成功条件(作为bool),返回byte memory 修饰的data.   函数abi.encodeabi.encodePackedabi.encodeWithSelector and abi.encodeWithSignature 被用于encode结构数据。

All these functions are low-level functions and should be used with care. Specifically, any unknown contract might be malicious and if you call it, you hand over control to that contract which could in turn call back into your contract, so be prepared for changes to your state variables when the call returns. The regular way to interact with other contracts is to call a function on a contract object (x.f()).  所有的这些底层函数,应该被小心使用。特别是,一些未知的恶意的合约,如果你调用它的参数, 你把控制权交给它,可能会导致它会回调你的合约, 因为为你的状态变量的变化做好准备,当调用结束后。调用其他合约函数的一般方式是x.f().  (x是合约的引用).

address(nameReg).call{gas: 1000000}(abi.encodeWithSignature("register(string)", "MyName"));
address(nameReg).call{value: 1 ether}(abi.encodeWithSignature("register(string)", "MyName"));
address(nameReg).call{gas: 1000000, value: 1 ether}(abi.encodeWithSignature("register(string)", "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.   

delegatecall 跟call的使用方法一样, 不同的是只有给出地址对应的合约代码被调用,其他方面数据(存储,余额)是从当前合约那过去的。调用的用户需要确保,两个合约的存储层结构是一致的。

Since byzantium staticcall can be used as well. This is basically the same as call, but will revert if the called function modifies the state in any way.

由于拜占庭式的staticcall 也可以使用。这基本上使用方法与call相同,但如果被调用的函数以任何方式修改状态,则会恢复。

The gas option is available on all three methods, while the value option is only available on call.

三个方式都需要gas参数,只有call方法有value参数,其他两个没有。

All contracts can be converted to address type, so it is possible to query the balance of the current contract using address(this).balance. 所有的合约可以被转换成address 类型,因此可以用address(this).balance查询函数余额. 

Contract Types

   地址类型: address 20字节, address payable地址支付类型.

  • address: Holds a 20 byte value (size of an Ethereum address).

  • address payable: Same as address, but with the additional members transfer and send. (基本和address相同, 但拥有额外的transfer 和 send函数)

如果合约拥有receive或payable回调函数,则可以显性的把address转换位address payable, 使用方法address(x).   如果合约没有提供receive或payable回调, 可以通过payable(address(x)) 把合约地址转换为address payable.

Fixed-size byte arrays (固定尺寸的字节数组)

the value types  byte1,byte2,byte3, ... byte32 持有从1-32的字节序列.

Dynamically-sized byte array  (动态尺寸的字节数组)

类型bytes 和 string 不是值类型,是引用类型。

Address Literals

地址字面字符串.

Rational and Integer Literals

有理数和整数数字.

String Literals and Types

字符串常量和类型.

Unicode Literals

unicode的常量

Hexadecimal Literals

16进制常量

Enums

枚举可以显式的和integer type类型进行互换, 但不能隐式转换。枚举至少需要一个成员,第一个成员将成为默认值,枚举的程序员数量不能超过256个.

User-defined Value Types

type C is V,    C是新引进的类型, V是内建的值类型(底层类型int,string...).  C.wrap 把底层类型转换为定制类型。   C.unwrap 把定制类型转换底层类型.

C类型没有任何操作符号和附加函数成员。 特别是,操作符号== 也没有定义。定制类型显式或隐式的转换为其他类型是不被允许的.

Function Type 

function (<parameter types>) {internal|external} [pure|view|payable] [returns (<return types>)]

Function types come in two flavours - internal and external functions (函数类型由两部分组成: 内部函数和外部函数.)

内部函数不能在当前合约上下文外执行, 因此只能在当前合约上下文内部调用。

By default, function types are internal, so the internal keyword can be omitted. Note that this only applies to function types. Visibility has to be specified explicitly for functions defined in contracts, they do not have a default. 默认情况下,function types 是内部的类型internal, 因此internal关键字可以省略. 注意,这只是适用于函数类型。 Visibility必须显性的指定,它没有默认值.

conversion(转变):

跟子类可以转变为父类, 父类不能转变为子类的原理一样。 这里描述的更科学, 受限更严格的类型不能转换为受限松散的类型。 受限松散的类型可以转换成受限严格的类型。

If external function types are used outside of the context of Solidity, they are treated as the function type, which encodes the address followed by the function identifier together in a single bytes24 type.

如果external 函数在solidity外部环境使用, 他们会被看成funciton的类型,以一个单独的byte24字节形式存在,编码  函数识别(identifier) + address格式存在。

Note that public functions of the current contract can be used both as an internal and as an external function. To use f as an internal function, just use f, if you want to use its external form, use this.f.

注意public 标识的函数可以被看作是内部或外部函数.  如果看作是内部函数,调用格式是: f, 如果看作是外部函数,调用格式是:  this.f

引用类型(Reference Type)

Currently, reference types comprise structs, arrays and mappings.   

目前,引用类型包括     结构struct, arrays,mappings.

If you use a reference type, you always have to explicitly provide the data area where the type is stored: memory (whose lifetime is limited to an external function call), storage (the location where the state variables are stored, where the lifetime is limited to the lifetime of a contract) or calldata (special data location that contains the function arguments).

如果你使用引用类型,你总得必须显式的提供类型数据存储区域: memory (生命周期仅限于外部函数调用的时间窗口), storage(是状态变量存储的地方,生命周期跟合约的生命周期一样长.) calldata (主要用于修饰函数参数的存储位置,不可修改,非持久化的数据区域. 用法跟memory相似.) 

calldata
官方文档对calldata的描述:

Calldata is a non-modifiable, non-persistent area where function arguments are stored, and behaves mostly like memory.

翻译:Calldata是一个不可修改的、非持久化的区域,函数参数存储在这里,其行为主要类似于内存。

它只能用于函数声明参数(而不是函数逻辑)

它是不可变的(不能被覆盖和更改),调用数据避免了数据拷贝,并确保数据不被修改

它必须用于external函数的动态参数

它是临时的(该值在事务完成后会销毁)

它是最便宜的存储位置,一般建议将函数参数声明为calldata,因为gas费会比较低。

是const

外部函数的参数(不包括返回参数)被强制指定为calldata

memory
简介:在合约中的本地内存变量。它的生命周期很短,当函数执行结束后就销毁了

内存是一个字节数组,内存槽为256位(32字节)
数据仅在函数执行期间存在,执行完毕后就被销毁,读或写一个内存槽都会消耗3gas
为了避免矿工的工作量过大,22个操作之后的单操作成本会上涨
storage
简介:在合约中可以被所有函数访问的全局变量。storage是永久的存储,意味着以太坊会把它保存到公链环境里的每一个节点

存储中的数据是永久存在的。存储是一个key/value库- 存储中的数据写入区块链,因此会修改状态,这也是存储使用成本高的原因。
占用一个256位的存储槽需要消耗20000 gas,
修改一个已经使用的存储槽的值,需要消耗5000 gas,当清零一个存储槽时,会返还一定数量的gas,
存储按256位的槽位分配,即使没有完全使用一个槽位,也需要支付其开销

 

Storage修饰的变量赋值给Storage修饰的变量,Memory修改是的变量赋值给Memory修饰的变量,会进行引用传递。  其他的都是值传递.

Data location  (数据位置)

Every reference type has an additional annotation, the “data location”, about where it is stored. There are three data locations: memorystorage and calldata. Calldata is a non-modifiable, non-persistent area where function arguments are stored, and behaves mostly like memory.

每一个引用类型都有一个额外的注解, "数据位置". 目前存在三种数据位置: memory, storage,calldata.   calldata 是一个不可修改,不持久化的区域,用来存储函数参数的数据位置,它的行为逻辑跟memory相似。

Now memory and calldata are allowed in all functions regardless of their visibility. 现在memory和calldata 可以使用在所有的函数中,不管它的可视属性。

Prior to version 0.5.0 the data location could be omitted, and would default to different locations depending on the kind of variable, function type, etc., but all complex types must now give an explicit data location.

在0.5.0版本之前,数据位置可以省略,默认为不同的位置,具体取决于变量的类型、函数类型等,但所有复杂类型现在都必须给出明确的数据位置。

Data location and assignment behaviour  (数据存储位置和赋值行为)

  • Assignments between storage and memory (or from calldata) always create an independent copy.  在storage和memory (或calldata)之间赋值,总是创建一个拷贝.

  • Assignments from memory to memory only create references. 在memory和memory之间进行赋值仅会创建一个引用。

  • Assignments from storage to a local storage variable also only assign a reference.  把storage变量赋值给local storage 变量,会赋值一个引用。

  • All other assignments to storage always copy.  除了storage,其他类型的变量赋值给storage都是一个值拷贝.

Arrays

Arrays can have a compile-time fixed size, or they can have a dynamic size.

数组可以具有编译时固定的大小,也可以具有动态大小。

Indices are zero-based, and access is in the opposite direction of the declaration. (索引基于0开始, 二维数组访问的时候跟二维数组声明的方向相反, 跟java,c是一样的.)

uint[][5] memory x 二维数组声明的方向和java,c的数组声明方向相反。表示5个元素的数组,每个元素是一个动态数组.

For example, if you have a variable uint[][5] memory x, you access the seventh uint in the third dynamic array using x[2][6], and to access the third dynamic array, use x[2].

二维数组声明的时候,顺序跟传统的顺序相反, 访问的时候,跟传统是一致的.

function selector , 由函数名和参数类型列表组合,然后进行keccak-256 hash完成唯一标识,获取字节数组前4个字节,作为唯一的标识.

Accessing an array past its end causes a failing assertion. Methods .push() and .push(value) can be used to append a new element at the end of the array, where .push() appends a zero-initialized element and returns a reference to it.

访问数组末尾会引起一个断言异常。  push(),push(value) 可以用于向数组结尾处填写新元素, push() 会添加一个默认初始化的元素,并且返回这个元素的引用变量。

You should use bytes over bytes1[] because it is cheaper, since bytes1[] adds 31 padding bytes between the elements. As a general rule, use bytes for arbitrary-length raw byte data and string for arbitrary-length string (UTF-8) data. If you can limit the length to a certain number of bytes, always use one of the value types bytes1 to bytes32 because they are much cheaper.

你可以是用bytes 替代byte1[], 因为bytes更便宜, 由于byte1[] 需要添加31个字节补位,在元素之间,消耗的存储槽位更多.   作为一个一般规则, 一般说来, 用bytes表达任意长度的字节数据,用string表达任意长度的字符串数据。 你可以限制长度到一个具体的长度字节,总是使用值类型bytes1到bytes32.  

string s = 'asdfadfadfsxsdf' ;

bytes(s)[7] = 'x' 代表的是底层的utf-8的accii码,并不是string里面的字符.

Allocating Memory Arrays(分配内存Arrays)

Memory arrays with dynamic length can be created using the new operator. As opposed to storage arrays, it is not possible to resize memory arrays (e.g. the .push member functions are not available). You either have to calculate the required size in advance or create a new memory array and copy every element.

可以使用new运算符创建具有动态长度的存储器阵列。与存储阵列相反,不可能调整存储阵列的大小(例如,.push成员功能不可用)。您必须提前计算所需的大小,或者创建一个新的内存阵列并复制每个元素。

Array Literals (数组常量)

([...]). For example [1, a, f(3)]

The base type of the array is the type of the first expression on the list such that all other expressions can be implicitly converted to it. It is a type error if this is not possible.

数组常量的基本类型取决于第一个表达式的类型, 并且其他表达式可以隐式的转换为第一个表达式的类型. 如果不可能,就会出现类型错误。

It is not enough that there is a type all the elements can be converted to. One of the elements has to be of that type.

所有的元素都可以转换为其中一个类型是不够的,必须其中一个元素就是那个类型。

Since fixed-size memory arrays of different type cannot be converted into each other (even if the base types can), you always have to specify a common base type explicitly if you want to use two-dimensional array literals: 由于不同类型的固定内存数组不可能互相转换,如果你使用二维数组常量,你应该显式的指定一个通用的基础类型。

pragma solidity >=0.4.16 <0.9.0;

contract C {
    function f() public pure returns (uint24[2][4] memory) {
        uint24[2][4] memory x = [[uint24(0x1), 1], [0xffffff, 2], [uint24(0xff), 3], [uint24(0xffff), 4]];
        // The following does not work, because some of the inner arrays are not of the right type.
        // uint[2][4] memory x = [[0x1, 1], [0xffffff, 2], [0xff, 3], [0xffff, 4]];
        return x;
    }
}

Fixed size memory arrays cannot be assigned to dynamically-sized memory arrays, i.e. the following is not possible: 固定尺寸的内存数组不能够赋值给动态的内存数组.

pragma solidity >=0.4.0 <0.9.0;

// This will not compile.
contract C {
    function f() public {
        // The next line creates a type error because uint[3] memory
        // cannot be converted to uint[] memory.
        uint[] memory x = [uint(1), 3, 4];
    }
}

Structs

It is not possible for a struct to contain a member of its own type.

结构里面不能包含它自己类型作为成员变量。相当于在死循环引用.

Mapping Types

key - value   key可以是内置的值类型, value是任意类型.

You can think of mappings as hash tables, which are virtually initialised such that every possible key exists and is mapped to a value whose byte-representation is all zeros, a type’s default value. The similarity ends there, the key data is not stored in a mapping, only its keccak256 hash is used to look up the value.

mapping 类似hashtable, 本质上初始化每一个可能存在的key和0字节值数据. key不存储在mapping中, 只有key的keccak256 hash, 用于寻找value.

Because of this, mappings do not have a length or a concept of a key or value being set, and therefore cannot be erased without extra information regarding the assigned keys (see Clearing Mappings). 因此,mapping没有长度的概念,也没有key set或value set的概念,因此在没有额外的关于key的准确信息下,无法擦除value的数据。

Mappings can only have a data location of storage and thus are allowed for state variables, as storage reference types in functions, or as parameters for library functions. They cannot be used as parameters or return parameters of contract functions that are publicly visible. These restrictions are also true for arrays and structs that contain mappings. 

mapping只能有一个storage 数据存储位置, 因此允许状态变量作为引用类型,用于函数或函数参数。mapping不能作为参数或者返回参数,用于public 可视的合约函数中。这个限制同样适用于那些持有mapping的array和struct.

You can mark state variables of mapping type as public and Solidity creates a getter for you. The _KeyType becomes a parameter for the getter. If _ValueType is a value type or a struct, the getter returns _ValueType. If _ValueType is an array or a mapping, the getter has one parameter for each _KeyType, recursively.  你可以用public修饰状态变量mapping,  solidity 创建一个getter方法,key的类型作为getter的方法参数, 如果value的类型是值类型或结构,则geter方法返回这个value类型。如果value类型是数组或mapping, 则getter为把每一个key类型作为参数,都声明出来。

Iterable Mappings 字面mappings

Operators Involving LValues

delete

delete a  相当于重置变量,结构,数组等,或初始化变量a.

delete has no effect on mappings (as the keys of mappings may be arbitrary and are generally unknown). So if you delete a struct, it will reset all members that are not mappings and also recurse into the members unless they are mappings. However, individual keys and what they map to can be deleted: If a is a mapping, then delete a[x] will delete the value stored at x.

delete 对mapping没有效果。(因为mapping的key是任意的,并且未知的)

delete struct,会重置所有非mapping的变量,并且递归子结构进行重置,除了mapping.

独立有效的key,是可以被delete的, delete a[x], x对应的value是可以被重置的。

It is important to note that delete a really behaves like an assignment to a, i.e. it stores a new object in a. This distinction is visible when a is reference variable: It will only reset a itself, not the value it referred to previously.

delete a 的行为跟初始化赋值a的行为非常相似。 当a是一个引用类型,它只会重置a本身,不会影响它之前的引用值.

Conversions between Elementary Types (元素类型间的互相转换)

In general, an implicit conversion between value-types is possible if it makes sense semantically and no information is lost. 隐式的转换发生在装换的过程中,被转换对象没有丢失信息的场景。

If an integer is explicitly converted to a smaller type, higher-order bits are cut off: 

大数字强制转换为小数字,高位字节(左侧字节)会被切断: 

uint32 a = 0x12345678;
uint16 b = uint16(a); // b will be 0x5678 now

If an integer is explicitly converted to a larger type, it is padded on the left (i.e., at the higher order end). The result of the conversion will compare equal to the original integer:

如果一个整数的小类型转成大类型, 高位补0, 左侧补0,  转换结果的值相等.

uint16 a = 0x1234;
uint32 b = uint32(a); // b will be 0x00001234 now
assert(a == b);

Fixed-size bytes types behave differently during conversions. They can be thought of as sequences of individual bytes and converting to a smaller type will cut off the sequence:

固定尺寸的bytes类型有一个不一样的行为,在转换过程中。 它们可以被看成独立字节的序列,如果转成小类型序列。 会切断序列后面的字节。

bytes2 a = 0x1234;
bytes1 b = bytes1(a); // b will be 0x12

If a fixed-size bytes type is explicitly converted to a larger type, it is padded on the right. 

如果固定尺寸的bytes被显示的转换成大尺寸bytes, 会在右边pad补位0.

bytes2 a = 0x1234;
bytes4 b = bytes4(a); // b will be 0x12340000, 2个16进制,占用一个字节.
assert(a[0] == b[0]);
assert(a[1] == b[1]);

Since integers and fixed-size byte arrays behave differently when truncating or padding, explicit conversions between integers and fixed-size byte arrays are only allowed, if both have the same size. If you want to convert between integers and fixed-size byte arrays of different size, you have to use intermediate conversions that make the desired truncation and padding rules explicit:

(数字大尺寸和小尺寸转换,是在左侧操作。  bytes大尺寸和小尺寸转换是在右侧操作.)

因为在截断和补位的行为中,数字和固定尺寸的byte类型不一样, 因此,如果两个类型的尺寸一样的前提下,在数字和byte之间,显式的转换是可以被允许的。不同的尺寸的数字和bytes之间的转换,需要中间状态转换,使的显式转换规则清晰。

bytes2 a = 0x1234;
uint32 b = uint16(a); // b will be 0x00001234
uint32 c = uint32(bytes4(a)); // c will be 0x12340000
uint8 d = uint8(uint16(a)); // d will be 0x34
uint8 e = uint8(bytes1(a)); // e will be 0x12

Conversions between Literals and Elementary Types (常量和元素类型之间的转换)

Integer Types

Decimal and hexadecimal number literals can be implicitly converted to any integer type that is large enough to represent it without truncation:  十进制和16进制常量,可以隐式的转换成任意数字类型,这个类型要足够大代表这个数字,不能被截断。

uint8 a = 12; // fine
uint32 b = 1234; // fine
uint16 c = 0x123456; // fails, since it would have to truncate to 0x3456

Fixed-Size Byte Arrays

Decimal number literals cannot be implicitly converted to fixed-size byte arrays. Hexadecimal number literals can be, but only if the number of hex digits exactly fits the size of the bytes type. As an exception both decimal and hexadecimal literals which have a value of zero can be converted to any fixed-size bytes type:

十进制常量不能被隐式的转换成固定尺寸的bytes数组。 16进制,在字节尺寸完全匹配的情况下,是可以直接隐式的转换成bytes数组。作为一个例外, 无论是十进制还是16进制, 0 是可以被转换成任意尺寸的字节数组的。bytes.

bytes2 a = 54321; // not allowed
bytes2 b = 0x12; // not allowed
bytes2 c = 0x123; // not allowed
bytes2 d = 0x1234; // fine
bytes2 e = 0x0012; // fine
bytes4 f = 0; // fine
bytes4 g = 0x0; // fine

String literals and hex string literals can be implicitly converted to fixed-size byte arrays, if their number of characters matches the size of the bytes type:  字串常量和16进制常量,在它们的字节尺寸一致的情况下,是可以隐式转成成固定尺寸的字节数组的.

bytes2 a = hex"1234"; // fine
bytes2 b = "xy"; // fine
bytes2 c = hex"12"; // not allowed
bytes2 d = hex"123"; // not allowed
bytes2 e = "x"; // not allowed
bytes2 f = "xyz"; // not allowed

Addresses

As described in Address Literals, hex literals of the correct size that pass the checksum test are of address type. No other literals can be implicitly converted to the address type.

正如Address Literals描述,可以通过checksum校验的正确尺寸的16进制常量是address 类型,可以隐式转换address 类型.

Explicit conversions from bytes20 or any integer type to address result in address payable.

显式的从byte20或其他整数类型转address 类型,会是address payable类型。

An address a can be converted to address payable via payable(a).

一个address a 可以通过payable(a) 转换成address payable类型.

Special Variables and Functions

When running sha256ripemd160 or ecrecover on a private blockchain, you might encounter Out-of-Gas. This is because these functions are implemented as “precompiled contracts” and only really exist after they receive the first message (although their contract code is hardcoded). Messages to non-existing contracts are more expensive and thus the execution might run into an Out-of-Gas error. A workaround for this problem is to first send Wei (1 for example) to each of the contracts before you use them in your actual contracts. This is not an issue on the main or test net.

当你再私链中使用sha256,ripemd160,ecrecover方法时, 你可能会遇到Out-of-Gas.这是因为这些功能作为预编译合约(precompiled)的函数,并且只有接收到第一个消息后才能真实存在。 给一个不存在的合约发送消息gas消耗非常大,因此执行过程可能会遇到Out-of-Gas 错误.

解决这个问题的方法是发送Wei给每一个合约,在你使用它们之前。

Members of Address Types

call, delegatecall 和 staticcall的区别: 

call所代表的上下文是被调用合约实例本身,delegatecall则是该方法调用的发起者.

staticcall,他与call十分相似,但这个方法会revert(恢复调用前状态)如果被调用方法修改了状态变量。staticcall 的上线文也是被调用合约本身。

You should avoid using .call() whenever possible when executing another contract function as it bypasses type checking, function existence check, and argument packing.

当执行另一个合约函数的时候, 无论何时,尽可能的躲避使用call 函数,因为它绕过了类型检查,函数存在检查和参数打包。

Prior to version 0.5.0, there was a member called callcode with similar but slightly different semantics than delegatecall. (内置变量msg的值不一样, deletecall msg不会被修改为调用者,callcode msg会修改为调用者)

0.5.0之前,address有一个成员callcode, 和delegatecall非常相似又有稍微的区别.

  • call: 最常用的调用方式,调用后内置变量 msg 的值会修改为调用者,执行环境为被调用者的运行环境(合约的 storage)。
  • delegatecall: 调用后内置变量 msg 的值不会修改为调用者,但执行环境为调用者的运行环境。
  • callcode: 调用后内置变量 msg 的值会修改为调用者,但执行环境为调用者的运行环境。

Salted contract creations / create2

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract D {
    uint public x;
    constructor(uint a) {
        x = a;
    }
}

contract C {
    function createDSalted(bytes32 salt, uint arg) public {
        // This complicated expression just tells you how the address
        // can be pre-computed. It is just there for illustration.
        // You actually only need ``new D{salt: salt}(arg)``.
        address predictedAddress = address(uint160(uint(keccak256(abi.encodePacked(
            bytes1(0xff),
            address(this),
            salt,
            keccak256(abi.encodePacked(
                type(D).creationCode,
                arg
            ))
        )))));

        D d = new D{salt: salt}(arg);
        require(address(d) == predictedAddress);
    }
}

Expressions and Control Structures (待细读补充)

位操作:

Bit operators: &, |, ^ (bitwise exclusive(按位异或) or), ~ (bitwise negation(按位求反))

The type bytes1[] is an array of bytes, but due to padding rules, it wastes 31 bytes of space for each element (except in storage). It is better to use the bytes type instead.

由于bytes1[] 是一个字节数组,根据补全规则, 它浪费了31个字节为每个元素(存储除外), 最好用bytes 类型去替换.

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。