Summary of Solidity knowledge

Solidity versions follow  Semantic Versioning . In addition, patch level releases with major release 0 (ie 0.xy) will not contain breaking changes. That means code that compiles with version 0.xy can be expected to compile with 0.xz where z > y. The version of solidity follows the semantic versioning rules. Also, there will be no major breaking changes for path releases targeting major version 0.xy. It means that the code that can be compiled by the 0.xy compiler can also be successfully compiled by the 0.xz compiler. z >Y.

We currently use a 0.x version number  to indicate this fast pace of change . We use a 0.x version number to indicate this fast pace of change.

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.

The pragma keyword, used to enable certain compiler features or checks. The scope of pragma is limited to specific local files, so you need to add pragma to all files if you want to enable a certain feature in the entire project.

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.

A solidity source file with the above version declaration will not compile this file with a compiler version earlier than version 0.5.2. Of course, a compiler version higher than version 0.6.0 will not compile this file. Because until version 0.6.0 Until now, there will be no breaking changes, so you can compile this file as you intended.

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.

Using the version syntax pragma does not change the compiler version. It is also not possible to turn on or off the compiler feature function. It's just up to the compiler to check that its version matches the required version. If it doesn't match, the compiler issues an error.

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. (This pragma indicates that you can use the encode and decoder methods)

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;.

The new ABI coder (v2) can encode or decode arbitrarily nested data and structures. In addition to supporting more types, it involves a lot of validation and safety checks, which may lead to high gas costs, but strengthens the security capabilities. In version 0.6.0, it is considered as a non-experimental feature, and it is officially enabled from solidity 0.8.0. Old versions can still be enabled through 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. The pragma applies to all code inside the activated file, no matter where the code ends up. It means that the code using encoder v1 can contain the (inherited) contract code of the new version encoder v2.

SMTChecker

Security check.

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 is a statically typed language, meaning that each variable needs to be typed. Solidity provides multiple types that can be combined into complex types.

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. 

In solidity, there is no concept of undefined or null, and each newly declared variable always has a default value corresponding to its type.

value type

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.

The next types are called value types because these variables are always passed by value. For example, when used as function parameters or assignments, they always copy the value inside.

Booleans

Integers

Fixed Point Numbers (relative to floating point, floating point numbers and fixed point numbers, fixed/ufixed = fixed128x8/ unfixed128x8. ufixedMxN, fixedMxN, M represents the number of digits occupied by the type, N represents the number of decimal points. M must be divisible by 8 , and from 8 to 256. The value of N must be between 0-80, including 80. The biggest difference between floating-point numbers and fixed-point numbers is the change in the length of digits and the number of decimal places. The number of decimal places in floating-point numbers is flexible The number of decimal places in fixed-point numbers is strictly constrained by N).   

Address

Member methods of Address

The transfer function reverts on failure.  

If address.transfer fails and the contract stops running, the fail method will be generated and the transaction will be rolled back

address.send is a low-level function similar to transfer. If send fails, it will only return fasle, and the contract will not stop running

  • 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. 

To link contracts without an ABI, or to directly control the encoding. Provides function 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.

They always take bytes memory-decorated variables as arguments, return the success condition (as a bool), and return byte memory-decorated data. Functions 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()). All these low-level functions should be used with care. In particular, some unknown malicious contracts, if you call its parameters and you give control to it, it may cause it to call back your contract, because it is ready for the change of your state variables, when the call ends back. The general way to call other contract functions is xf(). (x is a contract reference).

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 The method of use is the same as call, except that only the contract code corresponding to the given address is called, and other aspects of data (storage, balance) are passed from the current contract. The calling user needs to ensure that the storage layer structures of the two contracts are consistent.

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.

Since Byzantine staticcall can also be used. This is basically used in the same way as call, but if the called function modifies the state in any way, it will be reverted.

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

All three methods require a gas parameter, only the call method has a value parameter, and the other two do not.

All contracts can be converted to type, so it is possible to query  address the balance of the current contract using  .address(this).balanceaddress(this).balance查询函数余额. 

Contract Types

   Address type: address 20 bytes, address payable address payment type.

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

  • address payable: Same as  address, but with the additional members  transfer and  send. (Basically the same as address, but with additional transfer and send functions)

If the contract has a receive or payable callback function, you can explicitly convert address to address payable, using the method address(x). If the contract does not provide receive or payable callback, you can convert the contract address through payable(address(x)) for address payable.

Fixed-size byte arrays (fixed-size byte arrays)

the value types byte1,byte2,byte3, ... byte32 hold a sequence of bytes from 1-32.

Dynamically-sized byte array (dynamically sized byte array)

The types bytes and string are not value types, but reference types.

Address Literals

Address literal string.

Rational and Integer Literals

Rational and integer numbers.

String Literals and Types

String constants and types.

Unicode Literals

unicode constants

Hexadecimal Literals

Hexadecimal constant

Enums

Enumerations can be explicitly interchanged with integer types, but cannot be converted implicitly. An enum requires at least one member, the first member will be the default value, and the number of programmers for an enum cannot exceed 256.

User-defined Value Types

type C is V, C is a newly introduced type, V is a built-in value type (the underlying type int, string...). C.wrap converts the underlying type to a custom type. C.unwrap converts the custom type to the underlying type.

The C type does not have any operator symbols and additional function members. In particular, the operator == is also undefined. Explicit or implicit conversions of custom types to other types are not allowed.

Function Type 

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

two flavors -  internal  and  external  functions (the function type consists of two parts: internal function and external function.)

Internal functions cannot be executed outside the current contract context, so they can only be called inside the current contract context.

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. By default, function types are Internal is of type internal, so the internal keyword can be omitted. Note that this only applies to function types. Visibility must be specified explicitly, it has no default value.

conversion:

It is the same principle that a subclass can be transformed into a parent class, but a parent class cannot be transformed into a subclass. The more scientific, more strictly-restricted types described here cannot be converted to loosely-restricted types. A loosely constrained type can be converted to a strictly constrained type.

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.

If external functions are used in the external environment of solidity, they will be regarded as funciton types, exist in a single byte24 byte format, and exist in the format of encoding function identifier (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.

Note that the function identified by public can be regarded as an internal or external function. If it is regarded as an internal function, the calling format is: f, if it is regarded as an external function, the calling format is: this.f

Reference Type

Currently, reference types comprise structs, arrays and mappings.   

Currently, reference types include struct, arrays, and 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).

If you use reference types, you always have to explicitly provide type data storage areas: memory (lifetime is limited to the time window of external function calls), storage(是状态变量存储的地方,生命周期跟合约的生命周期一样长.) calldata (主要用于修饰函数参数的存储位置,不可修改,非持久化的数据区域. 用法跟memory相似.) 

Calldata
official documentation describes calldata:

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

Translation: Calldata is an unmodifiable, non-persistent area where function parameters are stored, and its behavior is mainly similar to memory.

It can only be used for function declaration parameters (not function logic)

It is immutable (cannot be overwritten and changed), calling data avoids data copying and ensures that data is not modified

It must be used for dynamic parameters of external functions

It is temporary (the value is destroyed after the transaction completes)

It is the cheapest storage location, and it is generally recommended to declare function parameters as calldata, because the gas fee will be relatively low.

is const

Parameters of external functions (excluding return parameters) are mandatory to be specified as calldata

Introduction to memory
: local memory variables in the contract. Its life cycle is very short, and it is destroyed when the function execution ends

The memory is a byte array, and the memory slot is 256 bits (32 bytes).
The data only exists during the execution of the function, and will be destroyed after execution. Reading or writing a memory slot will consume 3gas
. In order to avoid excessive workload of miners, After 22 operations, the cost of a single operation will increase.
Storage
Introduction: A global variable that can be accessed by all functions in the contract. Storage is permanent storage, which means that Ethereum will save it to every node in the public chain environment

Data in storage is permanent. Storage is a key/value store - data in storage is written to the blockchain, thus modifying state, which is why storage usage is expensive.
It takes 20,000 gas to occupy a 256-bit storage slot, and
5,000 gas to modify the value of an already-used storage slot. When a storage slot is cleared, a certain amount of gas will be returned, and the
storage is based on a 256-bit slot. Allocation, even if a slot is not fully used, its cost is paid

 

The variable modified by Storage is assigned to the variable modified by Storage, and the variable modified by Memory is assigned to the variable modified by Memory, which will be passed by reference. Everything else is passed by value.

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.

Each reference type has an additional annotation, "data location". Currently there are three data locations: memory, storage, calldata. calldata is an unmodifiable, non-persistent area, used to store the data location of function parameters, Its behavior logic is similar to that of memory.

Now  memory and  calldata are allowed in all functions regardless of their visibility.

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.

Prior to version 0.5.0, the data location could be omitted, defaulting to a different location depending on the type of variable, function type, etc., but all complex types must now be given an explicit data location.

Data location and assignment behavior (data storage location and assignment behavior)

  • Assignments between  storage and  memory (or from  calldata) always create an independent copy. Assignment between storage and memory (or calldata), always create a copy.

  • Assignments from  memory to  memory only create references. Assignments between memory and memory only create a reference.

  • Assignments from  storage to a  local  storage variable also only assign a reference. Assigning a storage variable to a local storage variable will assign a reference.

  • All other assignments to  storage always copy. Except for storage, other types of variable assignments to storage are a value copy.

Arrays

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

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

Indices are zero-based, and access is in the opposite direction of the declaration. (The index starts from 0, and the two-dimensional array access is opposite to the direction of the two-dimensional array declaration, which is the same as java and c.)

uint[][5] memory x The direction of two-dimensional array declaration is opposite to that of java and c. Represents an array of 5 elements, each element is a dynamic array.

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].

When a two-dimensional array is declared, the order is opposite to the traditional order, and when accessed, it is consistent with the traditional order.

The function selector is composed of the function name and parameter type list, and then performs keccak-256 hash to complete the unique identification, and obtains the first 4 bytes of the byte array as the unique identification.

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.

Accessing the end of the array raises an assertion exception. push(), push(value) can be used to fill in new elements at the end of the array, push() will add a default initialized element, and return the reference variable of this element.

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.

You can use bytes instead of byte1[], because bytes are cheaper, and since byte1[] needs to add 31 bytes of padding, it consumes more storage slots between elements. As a general rule, generally speaking, Use bytes to express byte data of arbitrary length, and use string to express string data of arbitrary length. You can limit the length to a specific length in bytes, always use the value types bytes1 to bytes32.  

string s = 'asdfadfadfsxsdf' ;

bytes(s)[7] = 'x' represents the accii code of the underlying utf-8, not the characters in the string.

Allocating Memory 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.

Memory arrays with dynamic length can be created using the new operator. In contrast to storage arrays, it is not possible to resize storage arrays (for example, the .push member function is not available). You have to calculate the required size ahead of time, or create a new memory array and copy each element.

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.

The underlying type of an array constant depends on the type of the first expression, and other expressions can be implicitly converted to the type of the first expression. If this is not possible, a TypeError occurs.

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.

It is not enough that all elements are convertible to one of the types, one of the elements must 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: It is impossible to convert memory arrays to each other. If you use two-dimensional array constants, you should explicitly specify a common base type.

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, ie the following is not possible: fixed-size memory arrays cannot be assigned to dynamic memory arrays.

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.

A structure cannot contain its own type as a member variable. It is equivalent to referencing in an infinite loop.

Mapping Types

key - value key can be a built-in value type, value can be any type.

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 is similar to hashtable, which essentially initializes every possible key and 0-byte value data. The key is not stored in the mapping, only the keccak256 hash of the key is used to find the 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 ). Therefore, mapping has no concept of length, nor key The concept of set or value set, so the value data cannot be erased without additional accurate information about the key.

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 can only have one storage data storage location, thus allowing state variables to be used as reference types for functions or function parameters. Mapping cannot be used as a parameter or return parameter in public visible contract functions. This restriction also applies to arrays and structs that hold mappings.

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. You can use public to modify the state variable mapping, solidity to create a getter method, the type of the key is used as the method parameter of the getter, if the type of the value is a value type or a structure, the getter method returns the value type. If the value type is an array or mapping, the getter declares each key type as a parameter.

Iterable Mappings Literal mappings

Operators Involving LValues

delete

delete a is equivalent to resetting variables, structures, arrays, etc., or initializing variable 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 has no effect on mapping. (Because the mapping key is arbitrary and unknown)

delete struct will reset all non-mapping variables, and recursive substructures will be reset, except for mapping.

An independent and valid key can be deleted, delete a[x], and the value corresponding to x can be reset.

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.

The behavior of delete a is very similar to the behavior of initial assignment a. When a is a reference type, it will only reset a itself, and will not affect its previous reference value.

Conversions between Elementary Types (mutual conversion between element 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: 

Large numbers are coerced to small numbers, with the high-order byte (left-hand byte) 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:

If the small type of an integer is converted to a large type, the high bit is filled with 0, the left side is filled with 0, and the values ​​of the conversion result are equal.

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:

Fixed-size bytes types have a different behavior during conversion. They can be seen as a sequence of independent bytes if converted to a sequence of small types. will cut off the bytes following the sequence.

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. 

If fixed-size bytes are converted to large-size bytes explicitly, 0 will be added to the right pad.

bytes2 a = 0x1234; 
bytes4 b = bytes4(a); // b will be 0x12340000, 2 hexadecimals, occupying one byte. 
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:

(Number large size and small size conversion are operated on the left side. Bytes large size and small size conversion are operated on the right side.)

Because numbers are not the same as fixed-size byte types in terms of truncation and padding, explicit conversions between numbers and bytes are allowed if the two types have the same size. Conversion between numbers and bytes of different sizes requires intermediate state transitions, making explicit conversion rules clear.

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 (conversion between constants and elemental 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: is truncated.

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:

Decimal constants cannot be implicitly converted to fixed-size bytes arrays. In hexadecimal, when the byte size matches exactly, it can be directly and implicitly converted into a bytes array. As an exception, 0 can be converted to a byte array of any size, whether in decimal or hexadecimal. 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: It can be implicitly converted into a fixed-size byte array.

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.

As described in Address Literals, the hexadecimal constant of the correct size that can be verified by checksum is the address type, and the address type can be implicitly converted.

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

Explicit conversion from byte20 or other integer types to address type will be address payable type.

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

An address a can be converted to address payable type by payable(a).

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.

When you use the sha256, ripemd160, ecrecover methods in the private chain, you may encounter Out-of-Gas. This is because these functions are functions of precompiled contracts (precompiled), and only after receiving the first message to truly exist. Sending a message to a non-existing contract consumes a lot of gas, so the execution process may encounter an Out-of-Gas error.

The solution to this problem is to send Wei to every contract before you spend them.

Members of Address Types

The difference between call, delegatecall and staticcall: 

The context represented by call is the called contract instance itself, and delegatecall is the initiator of the method call.

staticcall, he is very similar to call, but this method will revert (restore the state before the call) if the called method modifies the state variable. The launch context of staticcall is also the called contract itself.

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

Whenever possible, avoid using the call function when executing another contract function, because it bypasses type checking, function existence checking, and parameter packing.

Prior to version 0.5.0, there was a member called  callcode with similar but slightly different semantics than  delegatecall. (The value of the built-in variable msg is different, deletecall msg will not be modified to the caller, and callcode msg will be modified to the caller)

Before 0.5.0, address has a member callcode, which is very similar to delegatecall but slightly different.

  • call: The most commonly used calling method. After calling, the value of the built-in variable msg will be changed to the caller, and the execution environment will be the callee's running environment (storage of the contract).
  • delegatecall: After calling, the value of the built-in variable msg will not be changed to the caller, but the execution environment will be the caller's running environment.
  • callcode: After calling, the value of the built-in variable msg will be changed to the caller, but the execution environment is the caller's running environment.

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 (to be read and supplemented)

Bit manipulation:

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.

Since bytes1[] is a byte array, according to the completion rules, it wastes 31 bytes for each element (except storage), it is better to replace it with the bytes type.

Guess you like

Origin blog.csdn.net/gridlayout/article/details/130827812