0%

Doc-SolidityByExample_1_Basics

Pre:

Solidity by Example 学习记录基础语法的QuickView🧐🧐🧐

Hello World:

pragma specifies the compiler version of Solidity.

1
2
3
4
5
6
7
8
// SPDX-License-Identifier: MIT
// compiler version must be greater than or equal to 0.8.10 and less than 0.9.0
// 规定编译器的版本范围
pragma solidity ^0.8.10;

contract HelloWorld {
string public greet = "Hello World!";
}

就像python2或Python3,你最好声明出来,但solidity需要你强制声明,有规矩😅
在线evm/在线运行环境支持多版本solidity,还挺好的,不像py2.x、py3.x还要自己运维server上的py版本。

First App:

Here is a simple contract that you can get, increment and decrement the count store in this contract.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract Counter {
uint public count;

// Function to get the current count
function get() public view returns (uint) {
return count;
}

// Function to increment count by 1
function inc() public {
count += 1;
}

// Function to decrement count by 1
function dec() public {
count -= 1;
}
}

Primitive Data Types:

Here we introduce you to some primitive(基本) data types available in Solidity.

  • boolean

  • uint/int

  • address

基础知识:

1
2
Bit = Binary digIT = 0 or 1
Byte = a sequence of 8 bits = 00000000, 00000001, ..., or 11111111
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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract Primitives {
bool public boo = true;

/*
uint stands for unsigned integer, meaning non negative integers
different sizes are available
uint8 ranges from 0 to 2 ** 8 - 1
uint16 ranges from 0 to 2 ** 16 - 1
...
uint256 ranges from 0 to 2 ** 256 - 1
*/

// 无符号uint,无负数
// uintX X代表Bit 位,8bit= 1byte,uint256(32byte)默认就是一个Storage slot的长度
// uint 是uint256的别名

uint8 public u8 = 1;
uint public u256 = 456;
uint public u = 123; // uint is an alias for uint256 u

/*
Negative numbers are allowed for int types.
Like uint, different ranges are available from int8 to int256

int256 ranges from -2 ** 255 to 2 ** 255 - 1
int128 ranges from -2 ** 127 to 2 ** 127 - 1
*/

// int可代表负数
// 可以定义的范围是:int8~int256,8为步长,也就是一个byte为步长

int8 public i8 = -1;
int public i256 = 456;
int public i = -123; // int is same as int256

// minimum and maximum of int
int public minInt = type(int).min;
int public maxInt = type(int).max;

address public addr = 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c;

/*
In Solidity, the data type byte represent a sequence of bytes.
Solidity presents two type of bytes types :

- fixed-sized byte arrays 固定长度字节数组 byteX
- dynamically-sized byte arrays. 动态长度字节数组 bytes、byte[]

The term bytes in Solidity represents a dynamic array of bytes.
It’s a shorthand for byte[] .
*/

//
bytes1 a = 0xb5; // [10110101]
bytes1 b = 0x56; // [01010110]

// 只声明,未赋值的变量都有默认值
// Default values
// Unassigned variables have a default value
bool public defaultBoo; // false
uint public defaultUint; // 0
int public defaultInt; // 0
address public defaultAddr; // 0x0000000000000000000000000000000000000000
}

在EVM中,32是一个重要的数字,要记住!20220710095620
每个存储槽Storage slot可以存32byte的数据

20220712105238

整数值、布尔值、地址值这些固定长度的基本类型,在定义长度的时候都和32byte 息息相关。

  • 整数范围 uint8(Bit)uint256(Bit):最小1byte最大32byte

  • uint = uint256:32byte

地址值蛮特别,别的编程语言里没有。

Variables

There are 3 types of variables in Solidity
从作用域、存储位置去区分

  • local 局部变量

  • declared inside a function

  • not stored on the blockchain

  • state 状态变量

  • declared outside a function

  • stored on the blockchain

  • global (provides information about the blockchain) 全局变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract Variables {
// State variables are stored on the blockchain.
string public text = "Hello";
uint public num = 123;

function doSomething() public {
// Local variables are not saved to the blockchain.
uint i = 456;

// Here are some global variables
uint timestamp = block.timestamp; // Current block timestamp
address sender = msg.sender; // address of the caller
}
}

State Variable声明的位置有点像py里的全局变量/class变量,但特点在于它是存储在blockchain上的,一旦存储在blockchain,那全世界都可以访问了,应该叫World State Variable😂

Constants

Constants are variables that cannot be modified.
Their value is hard coded and using constants can save gas cost.

不可修改、省gas

1
2
3
4
5
6
7
8
9
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract Constants {
// 格式约定:对常量大写
// coding convention to uppercase constant variables
address public constant MY_ADDRESS = 0x777788889999AaAAbBbbCcccddDdeeeEfFFfCcCc;
uint public constant MY_UINT = 123;
}

常量/不可变量似乎跟别的编程语言没有啥区别,但考虑到存储位置的不同:别的编程语言一个常量可能就存储在单机server上,但由于是状态变量存在world State/blockchain上的,分布式存储的,存储消耗是要算一个乘法的,就要好好计价了(gas敏感),能省则省。

Immutable

Immutable variables are like constants. Values of immutable variables can be set inside the constructor but cannot be modified afterwards.
constant的区别在于赋值的位置不一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract Immutable {
// 格式约定:对常量大写
// coding convention to uppercase constant variables
address public immutable MY_ADDRESS;
uint public immutable MY_UINT;

constructor(uint _myUint) {
MY_ADDRESS = msg.sender;
MY_UINT = _myUint;
}
}

Reading and Writing to a State Variable

To write or update a state variable you need to send a transaction.
On the other hand, you can read state variables, for free, without any transaction fee.

对状态变量的读写,读免费, 写/改变其状态的话,要发交易/要耗gas。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract SimpleStorage {
// State variable to store a number
uint public num;

// You need to send a transaction to write to a state variable.
function set(uint _num) public {
num = _num;
}

// You can read from a state variable without sending a transaction.
function get() public view returns (uint) {
return num;
}
}

同样要考虑到blockchain分布式多节点的问题,表面看似只改一个状态变量的值,但改了世界各地节点的存储。

Ether and Wei

Transactions are paid with ether. 交易以ether单位计价
Similar to how one dollar is equal to 100 cent, one ether is equal to 1018 wei.
1美元=100美分,1Ether=1e18Wei,solidity自定义了自己的单位

1
2
3
4
5
6
7
8
9
10
11
12
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract EtherUnits {
uint public oneWei = 1 wei;
// 1 wei is equal to 1
bool public isOneWei = 1 wei == 1;

uint public oneEther = 1 ether;
// 1 ether is equal to 10^18 wei
bool public isOneEther = 1 ether == 1e18;
}

Gas and Gas Price:

How much ether do you need to pay for a transaction?
You pay gas spent * gas price(数量*愿意出的单价) amount of ether, where

  • gas is a unit of computation 计算单位

  • gas spent is the total amount of gas used in a transaction

  • gas price is how much ether you are willing to pay per gas

| keyword | 含义 |
| — | — |— |
| gas | 计价单位 |
| gas spent | 交易里花费了多少份gas | |
| gas price | gas的单价 | |

Transactions with higher gas price have higher priority to be included in a block.
Unspent gas will be refunded.

有点像在拍卖东西,你愿意出的拍卖价(筹码数量 x 筹码价格)

  • A: 筹码数量10 x 筹码价格10=100

  • B: 筹码数量10 x 筹码价格20=200

  • C: 筹码数量20 x 筹码价格10=200

然后价高者得,优先处理你的交易。拍卖价的计算公式:gas spent * gas price ,为了加快交易,你可以提高筹码数量或每个筹码的价格,最终拍卖价变高就行。

以太坊经常被描述为“世界计算机”,以太坊是一个开源的,全球分散的计算基础设施,执行称为智能合约的程序。它使用区块链来同步和存储系统的状态变化,以及称为以太网的加密货币来计量和约束执行资源成本。——《mastering-ethereum》

20220712113140

Gas Limit

There are 2 upper bounds to the amount of gas you can spend

  • gas limit (max amount of gas you’re willing to use for your transaction, set by you)

  • block gas limit (max amount of gas allowed in a block, set by the network)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract Gas {
uint public i = 0;

// Using up all of the gas that you send causes your transaction to fail.
// State changes are undone.
// Gas spent are not refunded.
function forever() public {
// Here we run a loop until all of the gas are spent
// and the transaction fails
while (true) {
i += 1;
}
}
}

If / Else:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract IfElse {
function foo(uint x) public pure returns (uint) {
if (x < 10) {
return 0;
} else if (x < 20) {
return 1;
} else {
return 2;
}
}

function ternary(uint _x) public pure returns (uint) {
// if (_x < 10) {
// return 1;
// }
// return 2;

// shorthand way to write if / else statement
return _x < 10 ? 1 : 2;
}
}

For and While Loop:

Solidity supports for, while, and do while loops.
Don’t write loops that are unbounded as this can hit the gas limit, causing your transaction to fail.
For the reason above, while and do while loops are rarely used.

很少用whiledo while每个opcode都有其gas定价,过多循环,只会累加gas,很可能达到block的gas limit导致交易失败。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract Loop {
function loop() public {
// for loop
for (uint i = 0; i < 10; i++) {
if (i == 3) {
// Skip to next iteration with continue
continue;
}
if (i == 5) {
// Exit loop with break
break;
}
}

// while loop
uint j;
while (j < 10) {
j++;
}
}
}

Mapping

Maps are created with the syntax mapping(keyType => valueType).
The keyType can be any built-in value type, bytes, string, or any contract.

TODO:哪些是类型可以做Key?常用用什么做key?

valueType can be any type including another mapping or an array.
Mappings are not iterable.

Array Vs 哈希表:

  • Array:没有目录的字典

  • 哈希表:有目录的字典

在数组中如果要找一个单词,由于不知道我们在哪个位置,所以只能从头开始查询(又称线性查找),数据量越大越难查。想想不用手写字母作为索引,直接去字典里翻找字典多麻烦。。。在哈希表中,有Key作为标识符,按图索骥,找东西就快很多。在数据存储上的灵活性和数据查询上的高效性,适合用来存用于余额等数据。

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract Mapping {
// Mapping from address to uint
mapping(address => uint) public myMap;

function get(address _addr) public view returns (uint) {
// Mapping always returns a value.
// If the value was never set, it will return the default value.
return myMap[_addr];
}

function set(address _addr, uint _i) public {
// Update the value at this address
myMap[_addr] = _i;
}

function remove(address _addr) public {
// Reset the value to the default value.
delete myMap[_addr];
}
}

contract NestedMapping {
// Nested mapping (mapping from address to another mapping)
mapping(address => mapping(uint => bool)) public nested;

function get(address _addr1, uint _i) public view returns (bool) {
// You can get values from a nested mapping
// even when it is not initialized
return nested[_addr1][_i];
}

function set(
address _addr1,
uint _i,
bool _boo
) public {
nested[_addr1][_i] = _boo;
}

function remove(address _addr1, uint _i) public {
delete nested[_addr1][_i];
}
}

Array:

Array can have a compile-time fixed size or a dynamic size.
固定长度或动态长度。

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract Array {
// Several ways to initialize an array
// 多种方式初始化
uint[] public arr;
uint[] public arr2 = [1, 2, 3];
// Fixed sized array, all elements initialize to 0
uint[10] public myFixedSizeArr;

function get(uint i) public view returns (uint) {
return arr[i]; // 通过下标去找
}

// Solidity can return the entire array. 能返回整个数组,但避免能无限长的数组。
// But this function should be avoided for
// arrays that can grow indefinitely in length.
function getArr() public view returns (uint[] memory) {
return arr;
}

function push(uint i) public {
// Append to array
// This will increase the array length by 1. // 长度+1
arr.push(i); // 在尾端加入,追加
}

function pop() public {
// Remove last element from array
// This will decrease the array length by 1 // 长度-1
arr.pop(); // 在尾端去除
}

function getLength() public view returns (uint) {
return arr.length;
}

function remove(uint index) public {
// Delete does not change the array length. 不改变数组长度,仅设为默认值
// It resets the value at index to it's default value,
// in this case 0
delete arr[index]; //
}

function examples() external {
// create array in memory, only fixed size can be created
uint[] memory a = new uint[](5);
}
}

Examples of removing array element

Remove array element by shifting elements from right to left

自带的delete arr[index];不改变数组长度,仅把要删除的位置的 值 设为默认值,会有“脏数据”。

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
// 在要删除索引的位置,之后的值依次向前覆盖,再pop掉最后一个元素
contract ArrayRemoveByShifting {
// [1, 2, 3] -- remove(1) --> [1, 3, 3] --> [1, 3]
// [1, 2, 3, 4, 5, 6] -- remove(2) --> [1, 2, 4, 5, 6, 6] --> [1, 2, 4, 5, 6]
// [1, 2, 3, 4, 5, 6] -- remove(0) --> [2, 3, 4, 5, 6, 6] --> [2, 3, 4, 5, 6]
// [1] -- remove(0) --> [1] --> []

uint[] public arr;

function remove(uint _index) public {
require(_index < arr.length, "index out of bound");

for (uint i = _index; i < arr.length - 1; i++) {
arr[i] = arr[i + 1]; // 在要删除索引的位置,之后的值依次向前覆盖
}
arr.pop();//
}

function test() external {
arr = [1, 2, 3, 4, 5];
remove(2); // 删除值为3的元素
// [1, 2, 4, 5]
assert(arr[0] == 1);
assert(arr[1] == 2);
assert(arr[2] == 4);
assert(arr[3] == 5);
assert(arr.length == 4);

arr = [1];
remove(0);
// []
assert(arr.length == 0);
}
}

Remove array element by copying last element into to the place to remove

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
// 用最后一个元素 覆盖 要函数的元素
contract ArrayReplaceFromEnd {
uint[] public arr;

// Deleting an element creates a gap in the array.
// One trick to keep the array compact is to
// move the last element into the place to delete.
function remove(uint index) public {
// Move the last element into the place to delete
arr[index] = arr[arr.length - 1];
// Remove the last element
arr.pop();
}

function test() public {
arr = [1, 2, 3, 4];

remove(1); // 删除值为2的元素
// [1, 4, 3]
assert(arr.length == 3);
assert(arr[0] == 1);
assert(arr[1] == 4);
assert(arr[2] == 3);

remove(2);
// [1, 4]
assert(arr.length == 2);
assert(arr[0] == 1);
assert(arr[1] == 4);
}
}

Enum:

Solidity supports enumerables and they are useful to model choice and keep track of state.
单选项,适合来表示状态,主要用于为uint分配名称,使程序易于阅读和维护
Enums can be declared outside of a contract.枚举类型可以在合约外定义

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract Enum {
// Enum representing shipping status
enum Status {
Pending,
Shipped,
Accepted,
Rejected,
Canceled
}

// Default value is the first element listed in
// definition of the type, in this case "Pending"
Status public status;

// Returns uint
// Pending - 0
// Shipped - 1
// Accepted - 2
// Rejected - 3
// Canceled - 4
function get() public view returns (Status) {
return status;
}

// Update status by passing uint into input
function set(Status _status) public {
status = _status;
}

// You can update to a specific enum like this
function cancel() public {
status = Status.Canceled;
}

// delete resets the enum to its first value, 0
function reset() public {
delete status;
}
}

20220713193040
设置和获取的都是无符号整数型

合约外定义:

Declaring and importing Enum File that the enum is declared in

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
// This is saved 'EnumDeclaration.sol'

enum Status {
Pending,
Shipped,
Accepted,
Rejected,
Canceled
}
File that imports the enum above


// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

import "./EnumDeclaration.sol";

contract Enum {
Status public status;
}

Structs:

You can define your own type by creating a struct.自定义类型,形式上与enum相似,但可以比enum定义更多的类型
They are useful for grouping together related data. 组织数据
Structs can be declared outside of a contract and imported in another contract.

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract Todos {
struct Todo {
string text;
bool completed;
}

// An array of 'Todo' structs
Todo[] public todos;

function create(string memory _text) public {
// 3 ways to initialize a struct
// - calling it like a function
todos.push(Todo(_text, false));

// key value mapping
todos.push(Todo({text: _text, completed: false}));

// initialize an empty struct and then update it
Todo memory todo;
todo.text = _text;
// todo.completed initialized to false

todos.push(todo);
}

// Solidity automatically created a getter for 'todos' so
// you don't actually need this function.
function get(uint _index) public view returns (string memory text, bool completed) {
Todo storage todo = todos[_index];
return (todo.text, todo.completed);
}

// update text
function update(uint _index, string memory _text) public {
Todo storage todo = todos[_index];
todo.text = _text;
}

// update completed
function toggleCompleted(uint _index) public {
Todo storage todo = todos[_index];
todo.completed = !todo.completed;
}
}

20220713194303
用index取值

导入struct:

Declaring and importing Struct File that the struct is declared in

1
2
3
4
5
6
7
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

struct Todo {
string text;
bool completed;
}

File that imports the struct above

1
2
3
4
5
6
7
8
9
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

import "./StructDeclaration.sol";

contract Todos {
// An array of 'Todo' structs
Todo[] public todos;
}

Data Locations - Storage, Memory and Calldata:

Variables are declared as either storage, memory or calldata to explicitly specify the location of the data.
定义数据存储的位置

  • storage - variable is a state variable (store on blockchain) World State变量😆,存在链上

  • memory - variable is in memory and it exists while a function is being called 函数里的临时变量

  • calldata - special data location that contains function arguments 用于函数参数

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract DataLocations {
uint[] public arr;
mapping(uint => address) map;
struct MyStruct {
uint foo;
}
mapping(uint => MyStruct) myStructs;

function f() public {
// call _f with state variables
_f(arr, map, myStructs[1]);

// get a struct from a mapping
// {Type} {Location} {VariableName} 😤😤😤
MyStruct storage myStruct = myStructs[1];
// create a struct in memory
MyStruct memory myMemStruct = MyStruct(0);
}

function _f(
uint[] storage _arr,
mapping(uint => address) storage _map,
MyStruct storage _myStruct
) internal {
// do something with storage variables
}

// You can return memory variables
function g(uint[] memory _arr) public returns (uint[] memory) {
// do something with memory array
}

function h(uint[] calldata _arr) external {
// do something with calldata array
}
}

没有解释得很清楚😕 ,需要例子不是很清楚什么时候用calldata

Function:

There are several ways to return outputs from a function. return的多种情况
Public functions cannot accept certain data types as inputs or outputs

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract Function {
// Functions can return multiple values.
// 一次能返回多个数据
function returnMany()
public
pure
returns (
uint,
bool,
uint
)
{
return (1, true, 2);
}

// Return values can be named.
// 返回值可以命名
function named()
public
pure
returns (
uint x,
bool b,
uint y
)
{
return (1, true, 2);
}

// Return values can be assigned to their name.
// In this case the return statement can be omitted.
// 提前声明返回值,可省略返回语句,隐式返回
function assigned()
public
pure
returns (
uint x,
bool b,
uint y
)
{
x = 1;
b = true;
y = 2;
}

// Use destructuring assignment when calling another
// function that returns multiple values.
// 非结构化赋值
function destructuringAssignments()
public
pure
returns (
uint,
bool,
uint,
uint,
uint
)
{
(uint i, bool b, uint j) = returnMany();

// Values can be left out.
// 非结构化赋值
// 空开
(uint x, , uint y) = (4, 5, 6);

return (i, b, j, x, y);
}

// Cannot use map for either input or output
// map不能作为输入或输出

// Can use array for input
// 数组可作为输入
function arrayInput(uint[] memory _arr) public {}

// Can use array for output
// 数组可作为输出
uint[] public arr;

function arrayOutput() public view returns (uint[] memory) {
return arr;
}
}

View and Pure Functions:

Getter functions can be declared view or pure.
View function declares that no state will be changed. 没有world State变量会改变
Pure function declares that no state variable will be changed or read. 没有world State变量会改变或读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract ViewAndPure {
uint public x = 1;

// Promise not to modify the state.
// 保证不改变状态变量,就是不改Storage的东西
// 视图函数
function addToX(uint y) public view returns (uint) {
return x + y;
}

// Promise not to modify or read from the state.
// 读都不读,纯纯的函数了
// 纯函数
function add(uint i, uint j) public pure returns (uint) {
return i + j;
}
}

Error:

An error will undo all changes made to the state during a transaction.
You can throw an error by calling require, revert or assert.

  • require is used to validate inputs and conditions before execution. 在执行代码前,验证输入

  • revert is similar to require. See the code below for details.

  • assert is used to check for code that should never be false. Failing assertion probably means that there is a bug.
    Use custom error to save gas.

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract Error {
function testRequire(uint _i) public pure {
// Require should be used to validate conditions such as:
// - inputs
// - conditions before execution
// - return values from calls to other functions
// 三种情况:外部输入、执行前的一些判断条件、其它函数的返回值
require(_i > 10, "Input must be greater than 10");
}

function testRevert(uint _i) public pure {
// Revert is useful when the condition to check is complex.
// This code does the exact same thing as the example above
// 复杂的检验条件
// 与上面的代码相同
if (_i <= 10) {
revert("Input must be greater than 10");
}
}

uint public num;

function testAssert() public view {
// Assert should only be used to test for internal errors,
// and to check invariants.
// 仅用于内部错误检查 或 去检查一些不变量

// Here we assert that num is always equal to 0
// since it is impossible to update the value of num
assert(num == 0);
}

// custom error
error InsufficientBalance(uint balance, uint withdrawAmount);

function testCustomError(uint _withdrawAmount) public view {
uint bal = address(this).balance;
if (bal < _withdrawAmount) {
revert InsufficientBalance({balance: bal, withdrawAmount: _withdrawAmount});
}
}
}

Here is another example

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract Account {
uint public balance;
uint public constant MAX_UINT = 2**256 - 1;

function deposit(uint _amount) public {
uint oldBalance = balance;
uint newBalance = balance + _amount;

// balance + _amount does not overflow if balance + _amount >= balance
require(newBalance >= oldBalance, "Overflow"); // 检查外部输入

balance = newBalance;

assert(balance >= oldBalance); // 检查内部错误
}

function withdraw(uint _amount) public {
uint oldBalance = balance;

// balance - _amount does not underflow if balance >= _amount
require(balance >= _amount, "Underflow");

if (balance < _amount) {
revert("Underflow");
}

balance -= _amount;

assert(balance <= oldBalance);
}
}

Function Modifier:

Modifiers are code that can be run before and / or after a function call.🤔🤔🤔

Modifiers can be used to:

  • Restrict access

  • Validate inputs

  • Guard against reentrancy hack

类似python的修饰器,其实本质是一个函数,可以定义任何功能的修饰器。一般修饰器的功能用于做 pre_check,相当于把require的代码块抽离出去了。

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract FunctionModifier {
// We will use these variables to demonstrate how to use
// modifiers.
address public owner;
uint public x = 10;
bool public locked;

constructor() {
// Set the transaction sender as the owner of the contract.
owner = msg.sender; // 创建合约//发起一个创建合约的交易/创建人是 owner
}

// Modifier to check that the caller is the owner of
// the contract.
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
// Underscore is a special character only used inside
// a function modifier and it tells Solidity to
// execute the rest of the code.
// 下划线,类似预留位置😆
_;
}

// Modifiers can take inputs. This modifier checks that the
// address passed in is not the zero address.
// 检测入参地址 非0
modifier validAddress(address _addr) {
require(_addr != address(0), "Not valid address");
_;
}

function changeOwner(address _newOwner) public onlyOwner validAddress(_newOwner) {
owner = _newOwner;
}

// Modifiers can be called before and / or after a function.
// This modifier prevents a function from being called while
// it is still executing.
// 防重入修饰器:当函数在执行时不能被调用
modifier noReentrancy() {
require(!locked, "No reentrancy");

locked = true;
_;
locked = false;
}

function decrement(uint i) public noReentrancy {
x -= i;

if (i > 1) {
decrement(i - 1);
}
}
}

Events:

Events allow logging to the Ethereum blockchain. Some use cases for events are:

  • Listening for events and updating user interface 可用作通知,前端可以监听并更新用户界面

  • A cheap form of storage 非必要存储的,不记录在storage里,记录在event里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract Event {
// Event declaration
// Up to 3 parameters can be indexed. 最多3个indexed参数
// Indexed parameters helps you filter the logs by the indexed parameter 相当于标识符,索引,可以在日志里过滤出来
event Log(address indexed sender, string message);
event AnotherLog();

function test() public {
emit Log(msg.sender, "Hello World!");
emit Log(msg.sender, "Hello EVM!");
emit AnotherLog();
}
}

Constructor:

A constructor is an optional function that is executed upon contract creation. 合约创建时,构造函数是可选的
Here are examples of how to pass arguments to constructors.

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

// Base contract X
contract X {
string public name;

constructor(string memory _name) {
name = _name;
}
}

// Base contract Y
contract Y {
string public text;

constructor(string memory _text) {
text = _text;
}
}

// There are 2 ways to initialize parent contract with parameters.

// Pass the parameters here in the inheritance list.
// 1.继承列表式
contract B is X("Input to X"), Y("Input to Y") {

}

contract C is X, Y {
// Pass the parameters here in the constructor,
// similar to function modifiers.

// 2.类似函数修饰器的写法
constructor(string memory _name, string memory _text) X(_name) Y(_text) {}
}

// Parent constructors are always called in the order of inheritance
// regardless of the order of parent contracts listed in the
// constructor of the child contract.

// Order of constructors called:
// 1. X
// 2. Y
// 3. D
contract D is X, Y {
constructor() X("X was called") Y("Y was called") {}
}

// Order of constructors called:
// 1. X
// 2. Y
// 3. E
contract E is X, Y {
constructor() Y("Y was called") X("X was called") {}
}
// 始终按照继承顺序调用父构造函数

Inheritance:

Solidity supports multiple inheritance. 支持多重继承
Contracts can inherit other contract by using the is keyword.
Function that is going to be overridden by a child contract must be declared as virtual. 父类声明虚函数,可被重写
Function that is going to override a parent function must use the keyword override. 子类声明重载函数

Order of inheritance is important. 继承的顺序很重要
You have to list the parent contracts in the order from “most base-like” to “most derived”.

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

/* Graph of inheritance
A
/ \
B C
/ \ /
F D,E

*/

contract A {
function foo() public pure virtual returns (string memory) { // virtual
return "A";
}
}

// Contracts inherit other contracts by using the keyword 'is'.
contract B is A {
// Override A.foo()
function foo() public pure virtual override returns (string memory) { // virtual override
return "B";
}
}

contract C is A {
// Override A.foo()
function foo() public pure virtual override returns (string memory) { // virtual override
return "C";
}
}

// Contracts can inherit from multiple parent contracts.
// When a function is called that is defined multiple times in
// different contracts, parent contracts are searched from
// right to left, and in depth-first manner.
// 当有同名函数时,深度优先,从右到左 😳😳😳
contract D is B, C {
// D.foo() returns "C"
// since C is the right most parent contract with function foo()
function foo() public pure override(B, C) returns (string memory) {
return super.foo();
}
}

contract E is C, B {
// E.foo() returns "B"
// since B is the right most parent contract with function foo()
function foo() public pure override(C, B) returns (string memory) {
return super.foo();
}
}

/* Graph of inheritance
A
/ \
B C
/ \ /
F D,E

*/
// Inheritance must be ordered from “most base-like” to “most derived”.
// Swapping the order of A and B will throw a compilation error.
// 编译报错 TypeError: Linearization of inheritance graph impossible
// 线性继承,声明顺序,类似: 钢笔is 物品,文具 大到小 总分结构
// 同名函数执行顺序,具体到抽象 文具foo()->物品foo() 小到大 分总结构
contract F is A, B {
function foo() public pure override(A, B) returns (string memory) {
return super.foo();
}
}

Shadowing Inherited State Variables:

Unlike functions, state variables cannot be overridden by re-declaring it in the child contract.
与函数不同,状态变量不能通过在子合约中重新声明来重写。
Let’s learn how to correctly override inherited state variables.

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract A {
string public name = "Contract A";

function getName() public view returns (string memory) {
return name;
}
}

// Shadowing is disallowed in Solidity 0.6
// This will not compile
// contract B is A {
// string public name = "Contract B";
// }

contract C is A {
// This is the correct way to override inherited state variables.
constructor() {
name = "Contract C"; // TODO: 只能在构造函数里声明吗?😨😨😨
}

// C.getName returns "Contract C"
}

Calling Parent Contracts:

Parent contracts can be called directly, or by using the keyword super.
使用super关键词直接调用父类方法
By using the keyword super, all of the immediate parent contracts will be called.

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

/* Inheritance tree
A
/ \
B C
\ /
D
*/

contract A {
// This is called an event. You can emit events from your function
// and they are logged into the transaction log.
// In our case, this will be useful for tracing function calls.
// 事件记录在交易收据里,便于跟踪
event Log(string message);

function foo() public virtual {
emit Log("A.foo called");
}

function bar() public virtual {
emit Log("A.bar called");
}
}

contract B is A {
function foo() public virtual override {
emit Log("B.foo called");
A.foo();
}

function bar() public virtual override {
emit Log("B.bar called");
super.bar();
}
}

contract C is A {
function foo() public virtual override {
emit Log("C.foo called");
A.foo();
}

function bar() public virtual override {
emit Log("C.bar called");
super.bar();
}
}

contract D is B, C {
// Try:
// - Call D.foo and check the transaction logs.
// Although D inherits A, B and C, it only called C and then A.
// - Call D.bar and check the transaction logs
// D called C, then B, and finally A.
// Although super was called twice (by B and C) it only called A once.

function foo() public override(B, C) {
super.foo();
}

function bar() public override(B, C) {
super.bar();
}
}

调用foo:
20220713225842

调用bar:
20220713225804

Visibility

Functions and state variables have to declare whether they are accessible by other contracts.
函数和状态变量必须声明它们是否可以被其他合约访问。

Functions can be declared as

  • public - any contract and account can call 当前合约、子合约、外部合约和账户

  • private - only inside the contract that defines the function 当前合约

  • internal - only inside contract that inherits an internal function 当前合约和子合约

  • external - only other contracts and accounts can call 除了当前合约不能访问,子合约、外部合约和账户可以访问

可见性,看不见、访问不到就没法子调用了。

public vs external ???

State variables can be declared as public, private, or internal but not external.

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract Base {
// Private function can only be called
// - inside this contract
// Contracts that inherit this contract cannot call this function.
function privateFunc() private pure returns (string memory) {
return "private function called";
}

function testPrivateFunc() public pure returns (string memory) {
return privateFunc();
}

// Internal function can be called
// - inside this contract
// - inside contracts that inherit this contract
function internalFunc() internal pure returns (string memory) {
return "internal function called";
}

function testInternalFunc() public pure virtual returns (string memory) {
return internalFunc();
}

// Public functions can be called
// - inside this contract
// - inside contracts that inherit this contract
// - by other contracts and accounts
function publicFunc() public pure returns (string memory) {
return "public function called";
}

// External functions can only be called
// - by other contracts and accounts
function externalFunc() external pure returns (string memory) {
return "external function called";
}

// This function will not compile since we're trying to call
// an external function here.
// function testExternalFunc() public pure returns (string memory) {
// return externalFunc();
// }

// State variables
string private privateVar = "my private variable";
string internal internalVar = "my internal variable";
string public publicVar = "my public variable";
// State variables cannot be external so this code won't compile.
// string external externalVar = "my external variable";
}

contract Child is Base {
// Inherited contracts do not have access to private functions
// and state variables.
// function testPrivateFunc() public pure returns (string memory) {
// return privateFunc();
// }

// Internal function call be called inside child contracts.
function testInternalFunc() public pure override returns (string memory) {
return internalFunc();
}
}

Interface:

You can interact with other contracts by declaring an Interface.

Interface

  • cannot have any functions implemented

  • can inherit from other interfaces

  • all declared functions must be external

  • cannot declare a constructor

  • cannot declare state variables

您可以通过声明一个Interface与其他合约进行交互。

  • 接口不能有任何实现的函数

  • 可以从其他接口继承

  • 所有声明的函数必须是外部的

  • 不能声明构造函数

  • 不能声明状态变量

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract Counter {
uint public count;

function increment() external {
count += 1;
}
}

interface ICounter {
function count() external view returns (uint);

function increment() external;
}

contract MyContract {
function incrementCounter(address _counter) external {
ICounter(_counter).increment();
}

function getCount(address _counter) external view returns (uint) {
return ICounter(_counter).count();
}
}

// Uniswap example
interface UniswapV2Factory {
function getPair(address tokenA, address tokenB)
external
view
returns (address pair);
}

interface UniswapV2Pair {
function getReserves()
external
view
returns (
uint112 reserve0,
uint112 reserve1,
uint32 blockTimestampLast
);
}

contract UniswapExample {
address private factory = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;
address private dai = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
address private weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;

function getTokenReserves() external view returns (uint, uint) {
address pair = UniswapV2Factory(factory).getPair(dai, weth);
(uint reserve0, uint reserve1, ) = UniswapV2Pair(pair).getReserves();
return (reserve0, reserve1);
}
}

Payable:

Functions and addresses declared payable can receive ether into the contract.
接受ether,不是其他Token

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract Payable {
// Payable address can receive Ether
address payable public owner;

// Payable constructor can receive Ether
constructor() payable {
owner = payable(msg.sender);
}

// Function to deposit Ether into this contract.
// Call this function along with some Ether.
// The balance of this contract will be automatically updated.
function deposit() public payable {}

// Call this function along with some Ether.
// The function will throw an error since this function is not payable.
function notPayable() public {}

// Function to withdraw all Ether from this contract.
function withdraw() public {
// get the amount of Ether stored in this contract
uint amount = address(this).balance;

// send all Ether to owner
// Owner can receive Ether since the address of owner is payable
(bool success, ) = owner.call{value: amount}("");
require(success, "Failed to send Ether");
}

// Function to transfer Ether from this contract to address from input
function transfer(address payable _to, uint _amount) public {
// Note that "to" is declared as payable
(bool success, ) = _to.call{value: _amount}("");
require(success, "Failed to send Ether");
}
}

Sending Ether - Transfer, Send, and Call:

How to send Ether?

You can send Ether to other contracts by

  • transfer (2300 gas, throws error)

  • send (2300 gas, returns bool)

  • call (forward all gas or set gas, returns bool)

How to receive Ether?

A contract receiving Ether must have at least one of the functions below

  • receive() external payable

  • fallback() external payable

receive() is called if msg.data is empty, otherwise fallback() is called.

Which method should you use?

call in combination with re-entrancy guard is the recommended method to use after December 2019.注意防重入

Guard against re-entrancy by

  • making all state changes before calling other contracts 在调用其他合约之前,执行完所有的状态变量更改

  • using re-entrancy guard modifier

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract ReceiveEther {
/*
Which function is called, fallback() or receive()?

send Ether
|
msg.data is empty?
/ \
yes no
/ \
receive() exists? fallback()
/ \
yes no
/ \
receive() fallback()
*/

// Function to receive Ether. msg.data must be empty
receive() external payable {}

// Fallback function is called when msg.data is not empty
fallback() external payable {}

function getBalance() public view returns (uint) {
return address(this).balance;
}
}

contract SendEther {
function sendViaTransfer(address payable _to) public payable {
// This function is no longer recommended for sending Ether.
// 不推荐
_to.transfer(msg.value);
}

function sendViaSend(address payable _to) public payable {
// Send returns a boolean value indicating success or failure.
// This function is not recommended for sending Ether.
// 不推荐
bool sent = _to.send(msg.value);
require(sent, "Failed to send Ether");
}

function sendViaCall(address payable _to) public payable {
// Call returns a boolean value indicating success or failure.
// This is the current recommended method to use.
// 推荐
(bool sent, bytes memory data) = _to.call{value: msg.value}("");
require(sent, "Failed to send Ether");
}
}

Fallback:

fallback is a function that does not take any arguments and does not return anything.
一个不接受任何参数且不返回任何内容的函数
It is executed either when 在何时执行

  • a function that does not exist is called or 调用不存在的函数

  • Ether is sent directly to a contract but receive() does not exist or msg.data is not empty 以太币直接发送到合约但receive()不存在或msg.data不为空

fallback has a 2300 gas limit when called by transfer or send.

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract Fallback {
event Log(uint gas);

// Fallback function must be declared as external. 回退函数的可见性必须声明为external

fallback() external payable {
// send / transfer (forwards 2300 gas to this fallback function)
// call (forwards all of the gas)
emit Log(gasleft());
}

// Helper function to check the balance of this contract
function getBalance() public view returns (uint) {
return address(this).balance;
}
}

contract SendToFallback {
function transferToFallback(address payable _to) public payable {
_to.transfer(msg.value);
}

function callFallback(address payable _to) public payable {
(bool sent, ) = _to.call{value: msg.value}("");
require(sent, "Failed to send Ether");
}
}

Call:

call is a low level function to interact with other contracts.
This is the recommended method to use when you’re just sending Ether via calling the fallback function.
However it is not the recommend way to call existing functions. 不是调用现有函数的推荐方法

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract Receiver {
event Received(address caller, uint amount, string message);

fallback() external payable {
emit Received(msg.sender, msg.value, "Fallback was called");
}

function foo(string memory _message, uint _x) public payable returns (uint) {
emit Received(msg.sender, msg.value, _message);

return _x + 1;
}
}

contract Caller {
event Response(bool success, bytes data);

// Let's imagine that contract B does not have the source code for
// B合约未开源
// contract A, but we do know the address of A and the function to call.
function testCallFoo(address payable _addr) public payable {
// You can send ether and specify a custom gas amount
(bool success, bytes memory data) = _addr.call{value: msg.value, gas: 5000}(
abi.encodeWithSignature("foo(string,uint256)", "call foo", 123)
);

emit Response(success, data);
}

// Calling a function that does not exist triggers the fallback function.
// 调用不存在的函数会触发回退函数。
function testCallDoesNotExist(address _addr) public {
(bool success, bytes memory data) = _addr.call(
abi.encodeWithSignature("doesNotExist()")
);

emit Response(success, data);
}
}

Delegatecall:

delegatecall is a low level function similar to call.
When contract A executes delegatecall to contract B, B’s code is excutedwith contractA's storage, msg.senderandmsg.value`.

好好理解一下委托的含义

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

// NOTE: Deploy this contract first
contract B {
// NOTE: storage layout must be the same as contract A
uint public num;
address public sender;
uint public value;

function setVars(uint _num) public payable {
num = _num;
sender = msg.sender;
value = msg.value;
}
}

contract A {
uint public num;
address public sender;
uint public value;

function setVars(address _contract, uint _num) public payable {
// A's storage is set, B is not modified.
(bool success, bytes memory data) = _contract.delegatecall(
abi.encodeWithSignature("setVars(uint256)", _num)
);
}
}

20220714005053

Function Selector:

When a function is called, the first 4 bytes of calldata specifies which function to call.
This 4 bytes is called a function selector.
调用函数时,前 4 个字节calldata指定调用哪个函数。这 4 个字节称为函数选择器。像路由。🤨🤨🤨

Take for example, this code below. It uses call to execute transfer on a contract at the address addr.

1
addr.call(abi.encodeWithSignature("transfer(address,uint256)", 0xSomeAddress, 123))

The first 4 bytes returned from abi.encodeWithSignature(....) is the function selector.
Perhaps you can save a tiny amount of gas if you precompute and inline the function selector in your code?
如果您在代码中预先计算并内联函数选择器,也许您可​​以节省少量gas?
Here is how the function selector is computed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract FunctionSelector {
/*
"transfer(address,uint256)"
0xa9059cbb
"transferFrom(address,address,uint256)"
0x23b872dd
*/
function getSelector(string calldata _func) external pure returns (bytes4) {
return bytes4(keccak256(bytes(_func)));
}
}

Calling Other Contract:

Contract can call other contracts in 2 ways. 可以通过 2 种方式调用其他合约。(不是4种吗?)
The easiest way to is to just call it, like A.foo(x, y, z).
Another way to call other contracts is to use the low-level call. 不推荐
This method is not recommended.

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract Callee {
uint public x;
uint public value;

function setX(uint _x) public returns (uint) {
x = _x;
return x;
}

function setXandSendEther(uint _x) public payable returns (uint, uint) {
x = _x;
value = msg.value;

return (x, value);
}
}

contract Caller {
function setX(Callee _callee, uint _x) public {
uint x = _callee.setX(_x);
}

function setXFromAddress(address _addr, uint _x) public {
Callee callee = Callee(_addr);
callee.setX(_x);
}

function setXandSendEther(Callee _callee, uint _x) public payable {
(uint x, uint value) = _callee.setXandSendEther{value: msg.value}(_x);
}
}

Creating Contracts from a Contract:

Contract that Creates other Contracts
Contracts can be created by other contracts using the new keyword. Since 0.8.0, new keyword supports create2 feature by specifying salt options.

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract Car {
address public owner;
string public model;
address public carAddr;

constructor(address _owner, string memory _model) payable {
owner = _owner;
model = _model;
carAddr = address(this);
}
}

contract CarFactory {
Car[] public cars;

function create(address _owner, string memory _model) public {
Car car = new Car(_owner, _model);
cars.push(car);
}

function createAndSendEther(address _owner, string memory _model) public payable {
Car car = (new Car){value: msg.value}(_owner, _model);
cars.push(car);
}

function create2(
address _owner,
string memory _model,
bytes32 _salt
) public {
Car car = (new Car){salt: _salt}(_owner, _model);
cars.push(car);
}

function create2AndSendEther(
address _owner,
string memory _model,
bytes32 _salt
) public payable {
Car car = (new Car){value: msg.value, salt: _salt}(_owner, _model);
cars.push(car);
}

function getCar(uint _index)
public
view
returns (
address owner,
string memory model,
address carAddr,
uint balance
)
{
Car car = cars[_index];

return (car.owner(), car.model(), car.carAddr(), address(car).balance);
}
}

Try / Catch:

try / catch can only catch errors from external function calls and contract creation. 只能从外部函数调用和合约创建中捕获错误。

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

// External contract used for try / catch examples
contract Foo {
address public owner;

constructor(address _owner) {
require(_owner != address(0), "invalid address");
assert(_owner != 0x0000000000000000000000000000000000000001);
owner = _owner;
}

function myFunc(uint x) public pure returns (string memory) {
require(x != 0, "require failed");
return "my func was called";
}
}

contract Bar {
event Log(string message);
event LogBytes(bytes data);

Foo public foo;

constructor() {
// This Foo contract is used for example of try catch with external call
foo = new Foo(msg.sender);
}

// Example of try / catch with external call
// tryCatchExternalCall(0) => Log("external call failed")
// tryCatchExternalCall(1) => Log("my func was called")
function tryCatchExternalCall(uint _i) public {
try foo.myFunc(_i) returns (string memory result) {
emit Log(result);
} catch {
emit Log("external call failed");
}
}

// Example of try / catch with contract creation
// tryCatchNewContract(0x0000000000000000000000000000000000000000) => Log("invalid address")
// tryCatchNewContract(0x0000000000000000000000000000000000000001) => LogBytes("")
// tryCatchNewContract(0x0000000000000000000000000000000000000002) => Log("Foo created")
function tryCatchNewContract(address _owner) public {
try new Foo(_owner) returns (Foo foo) {
// you can use variable foo here
emit Log("Foo created");
} catch Error(string memory reason) {
// catch failing revert() and require()
emit Log(reason);
} catch (bytes memory reason) {
// catch failing assert()
emit LogBytes(reason);
}
}
}

Import:

You can import local and external files in Solidity.

Local

Here is our folder structure.

1
2
├── Import.sol
└── Foo.sol

Foo.sol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

struct Point {
uint x;
uint y;
}

error Unauthorized(address caller);

function add(uint x, uint y) pure returns (uint) {
return x + y;
}

contract Foo {
string public name = "Foo";
}

Import.sol:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

// import Foo.sol from current directory
import "./Foo.sol";

// import {symbol1 as alias, symbol2} from "filename";
import {Unauthorized, add as func, Point} from "./Foo.sol";

contract Import {
// Initialize Foo.sol
Foo public foo = new Foo();

// Test Foo.sol by getting it's name.
function getFooName() public view returns (string memory) {
return foo.name();
}
}

External:

You can also import from GitHub by simply copying the url

1
2
3
4
5
6
// https://github.com/owner/repo/blob/branch/path/to/Contract.sol
import "https://github.com/owner/repo/blob/branch/path/to/Contract.sol";

// Example import ECDSA.sol from openzeppelin-contract repo, release-v4.5 branch
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.5/contracts/utils/cryptography/ECDSA.sol
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.5/contracts/utils/cryptography/ECDSA.sol";

Library:

Libraries are similar to contracts, but you can’t declare any state variable and you can’t send ether.
A library is embedded into the contract if all library functions are internal.
Otherwise the library must be deployed and then linked before the contract is deployed.

库类似于合约,但你不能声明任何状态变量,也不能发送以太币。如果所有库函数都是内部的,则将库嵌入到合约中。否则,必须先部署库,然后在部署合约之前进行链接。

抽象一层,抽出代码块。

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

library SafeMath {
function add(uint x, uint y) internal pure returns (uint) {
uint z = x + y;
require(z >= x, "uint overflow");

return z;
}
}

library Math {
function sqrt(uint y) internal pure returns (uint z) {
if (y > 3) {
z = y;
uint x = y / 2 + 1;
while (x < z) {
z = x;
x = (y / x + x) / 2;
}
} else if (y != 0) {
z = 1;
}
// else z = 0 (default value)
}
}

contract TestSafeMath {
using SafeMath for uint;

uint public MAX_UINT = 2**256 - 1;

function testAdd(uint x, uint y) public pure returns (uint) {
return x.add(y);
}

function testSquareRoot(uint x) public pure returns (uint) {
return Math.sqrt(x);
}
}

// Array function to delete element at index and re-organize the array
// so that their are no gaps between the elements.
library Array {
function remove(uint[] storage arr, uint index) public {
// Move the last element into the place to delete
require(arr.length > 0, "Can't remove from empty array");
arr[index] = arr[arr.length - 1];
arr.pop();
}
}

contract TestArray {
using Array for uint[];

uint[] public arr;

function testArrayRemove() public {
for (uint i = 0; i < 3; i++) {
arr.push(i);
}

arr.remove(1);

assert(arr.length == 2);
assert(arr[0] == 0);
assert(arr[1] == 2);
}
}

ABI Decode:

abi.encode encodes data into bytes.
abi.decode decodes bytes back into data.

类似base64编码

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract AbiDecode {
struct MyStruct {
string name;
uint[2] nums;
}

function encode(
uint x,
address addr,
uint[] calldata arr,
MyStruct calldata myStruct
) external pure returns (bytes memory) {
return abi.encode(x, addr, arr, myStruct);
}

function decode(bytes calldata data)
external
pure
returns (
uint x,
address addr,
uint[] memory arr,
MyStruct memory myStruct
)
{
// (uint x, address addr, uint[] memory arr, MyStruct myStruct) = ...
(x, addr, arr, myStruct) = abi.decode(data, (uint, address, uint[], MyStruct));
}
}

Hashing with Keccak256:

keccak256 computes the Keccak-256 hash of the input. 一个哈希算法罢了。

Some use cases are:

  • Creating a deterministic unique ID from a input 从输入创建确定性唯一ID,例如订单id

  • Commit-Reveal scheme 应该是提交-验证方案,类似登录密码md5哈希加密

  • Compact cryptographic signature (by signing the hash instead of a larger input) 紧凑的加密签名(通过签名散列,而不是更大的输入)

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract HashFunction {
function hash(
string memory _text,
uint _num,
address _addr
) public pure returns (bytes32) {
return keccak256(abi.encodePacked(_text, _num, _addr));
}

// Example of hash collision
// Hash collision can occur when you pass more than one dynamic data type
// to abi.encodePacked. In such case, you should use abi.encode instead.
// abi.encodePacked 紧打包可能会有哈希碰撞问题
// 在这种情况下,建议用abi.encode
function collision(string memory _text, string memory _anotherText)
public
pure
returns (bytes32)
{
// encodePacked(AAA, BBB) -> AAABBB
// encodePacked(AA, ABBB) -> AAABBB
// Oh,哈希碰撞
return keccak256(abi.encodePacked(_text, _anotherText));
}
}

contract GuessTheMagicWord {
bytes32 public answer = 0x60298f78cc0b47170ba79c10aa3851d7648bd96f2f8e46a19dbc777c36fb0c00;

// Magic word is "Solidity"
function guess(string memory _word) public view returns (bool) {
return keccak256(abi.encodePacked(_word)) == answer;
}
}

Verifying Signature:

Messages can be signed off chain and then verified on chain using a smart contract.
消息可以在链下签名,然后使用智能合约在链上进行验证。

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

/* Signature Verification

How to Sign and Verify
# Signing
1. Create message to sign
2. Hash the message
3. Sign the hash (off chain, keep your private key secret)

# Verify
1. Recreate hash from the original message
2. Recover signer from signature and hash
3. Compare recovered signer to claimed signer
*/
contract VerifySignature {
/* 1. Unlock MetaMask account
ethereum.enable()
*/

/* 2. Get message hash to sign
getMessageHash(
0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C,
123,
"coffee and donuts",
1
)

hash = "0xcf36ac4f97dc10d91fc2cbb20d718e94a8cbfe0f82eaedc6a4aa38946fb797cd"
*/
function getMessageHash(
address _to,
uint _amount,
string memory _message,
uint _nonce
) public pure returns (bytes32) {
return keccak256(abi.encodePacked(_to, _amount, _message, _nonce));
}

/* 3. Sign message hash
# using browser
account = "copy paste account of signer here"
ethereum.request({ method: "personal_sign", params: [account, hash]}).then(console.log)

# using web3
web3.personal.sign(hash, web3.eth.defaultAccount, console.log)

Signature will be different for different accounts
0x993dab3dd91f5c6dc28e17439be475478f5635c92a56e17e82349d3fb2f166196f466c0b4e0c146f285204f0dcb13e5ae67bc33f4b888ec32dfe0a063e8f3f781b
*/
function getEthSignedMessageHash(bytes32 _messageHash)
public
pure
returns (bytes32)
{
/*
Signature is produced by signing a keccak256 hash with the following format:
"\x19Ethereum Signed Message\n" + len(msg) + msg
*/
return
keccak256(
abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash)
);
}

/* 4. Verify signature
signer = 0xB273216C05A8c0D4F0a4Dd0d7Bae1D2EfFE636dd
to = 0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C
amount = 123
message = "coffee and donuts"
nonce = 1
signature =
0x993dab3dd91f5c6dc28e17439be475478f5635c92a56e17e82349d3fb2f166196f466c0b4e0c146f285204f0dcb13e5ae67bc33f4b888ec32dfe0a063e8f3f781b
*/
function verify(
address _signer,
address _to,
uint _amount,
string memory _message,
uint _nonce,
bytes memory signature
) public pure returns (bool) {
bytes32 messageHash = getMessageHash(_to, _amount, _message, _nonce);
bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);

return recoverSigner(ethSignedMessageHash, signature) == _signer;
}

function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature)
public
pure
returns (address)
{
(bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature);

return ecrecover(_ethSignedMessageHash, v, r, s);
}

function splitSignature(bytes memory sig)
public
pure
returns (
bytes32 r,
bytes32 s,
uint8 v
)
{
require(sig.length == 65, "invalid signature length");

assembly {
/*
First 32 bytes stores the length of the signature

add(sig, 32) = pointer of sig + 32
effectively, skips first 32 bytes of signature

mload(p) loads next 32 bytes starting at the memory address p into memory
*/

// first 32 bytes, after the length prefix
r := mload(add(sig, 32))
// second 32 bytes
s := mload(add(sig, 64))
// final byte (first byte of the next 32 bytes)
v := byte(0, mload(add(sig, 96)))
}

// implicitly return (r, s, v)
}
}