Skip to content

Commit

Permalink
Merge branch 'main' into 40_ERC1155
Browse files Browse the repository at this point in the history
  • Loading branch information
eddiehsu66 authored Oct 22, 2024
2 parents f6e473f + ef95cdd commit 7f758aa
Show file tree
Hide file tree
Showing 82 changed files with 493 additions and 201 deletions.
12 changes: 6 additions & 6 deletions 02_ValueTypes/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,14 @@ bool public _bool5 = _bool != _bool1; // 不相等
```solidity
// 整型
int public _int = -1; // 整数,包括负数
uint public _uint = 1; // 正整数
uint256 public _number = 20220330; // 256位正整数
uint public _uint = 1; // 无符号整数
uint256 public _number = 20220330; // 256位无符号整数
```

常用的整型运算符包括:

- 比较运算符(返回布尔值): `<=``<``==``!=``>=``>`
- 算数运算符`+``-``*``/``%`(取余),`**`(幂)
- 算术运算符`+``-``*``/``%`(取余),`**`(幂)

```solidity
// 整数运算
Expand Down Expand Up @@ -117,7 +117,7 @@ bytes32 public _byte32 = "MiniSolidity";
bytes1 public _byte = _byte32[0];
```

在上述代码中,`MiniSolidity` 变量以字节的方式存储进变量 `_byte32`。如果把它转换成 `16 进制`,就是:`0x4d696e69536f6c69646974790000000000000000000000000000000000000000`
在上述代码中,字符串 `MiniSolidity` 以字节的方式存储进变量 `_byte32`。如果把它转换成 `16 进制`,就是:`0x4d696e69536f6c69646974790000000000000000000000000000000000000000`

`_byte` 变量的值为 `_byte32` 的第一个字节,即 `0x4d`

Expand All @@ -132,7 +132,7 @@ enum ActionSet { Buy, Hold, Sell }
ActionSet action = ActionSet.Buy;
```

枚举可以显式地和 `uint` 相互转换,并会检查转换的正整数是否在枚举的长度内,否则会报错:
枚举可以显式地和 `uint` 相互转换,并会检查转换的无符号整数是否在枚举的长度内,否则会报错:

```solidity
// enum可以和uint显式的转换
Expand All @@ -141,7 +141,7 @@ function enumToUint() external view returns(uint){
}
```

`enum` 是一个比较冷门的变量,几乎没什么人用。
`enum` 是一个比较冷门的数据类型,几乎没什么人用。

## 在 Remix 上运行

Expand Down
15 changes: 11 additions & 4 deletions 03_Function/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ Solidity语言的函数非常灵活,可以进行各种复杂操作。在本教
我们先看一下 Solidity 中函数的形式:

```solidity
function <function name>(<parameter types>) {internal|external|public|private} [pure|view|payable] [returns (<return types>)]
function <function name>([parameter types[, ...]]) {internal|external|public|private} [pure|view|payable] [virtual|override] [<modifiers>]
[returns (<return types>)]{ <function body> }
```

看着有一些复杂,让我们从前往后逐个解释(方括号中的是可写可不
Expand All @@ -35,7 +36,7 @@ function <function name>(<parameter types>) {internal|external|public|private} [

2. `<function name>`:函数名。

3. `(<parameter types>)`:圆括号内写入函数的参数,即输入到函数的变量类型和名称。
3. `([parameter types[, ...]])`:圆括号内写入函数的参数,即输入到函数的变量类型和名称。

4. `{internal|external|public|private}`:函数可见性说明符,共有4种。

Expand All @@ -46,11 +47,17 @@ function <function name>(<parameter types>) {internal|external|public|private} [

**注意 1**:合约中定义的函数需要明确指定可见性,它们没有默认值。

**注意 2**`public|private|internal` 也可用于修饰状态变量。`public`变量会自动生成同名的`getter`函数,用于查询数值。未标明可见性类型的状态变量,默认为`internal`
**注意 2**`public|private|internal` 也可用于修饰状态变量(定义可参考[WTF Solidity 第5讲的相关内容]([../05_DataStorage/readme.md#1-状态变量](https://github.com/AmazingAng/WTF-Solidity/tree/main/05_DataStorage#1-%E7%8A%B6%E6%80%81%E5%8F%98%E9%87%8F)))`public`变量会自动生成同名的`getter`函数,用于查询数值。未标明可见性类型的状态变量,默认为`internal`

5. `[pure|view|payable]`:决定函数权限/功能的关键字。`payable`(可支付的)很好理解,带着它的函数,运行的时候可以给合约转入 ETH。`pure``view` 的介绍见下一节。

6. `[returns ()]`:函数返回的变量类型和名称。
6. `[virtual|override]`: 方法是否可以被重写,或者是否是重写方法。`virtual`用在父合约上,标识的方法可以被子合约重写。`override`用在自合约上,表名方法重写了父合约的方法。

7. `<modifiers>`: 自定义的修饰器,可以有0个或多个修饰器。

8. `[returns ()]`:函数返回的变量类型和名称。

9. `<function body>`: 函数体。

## 到底什么是 `Pure``View`

Expand Down
2 changes: 1 addition & 1 deletion 04_Return/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ function returnMultiple() public pure returns(uint256, bool, uint256[3] memory){

## 命名式返回

我们可以在 `returns` 中标明返回变量的名称。Solidity 会初始化这些变量,并且自动返回这些函数的值,无需使用 `return`
我们可以在 `returns` 中标明返回变量的名称。Solidity 会初始化这些变量,并且自动返回这些变量的值,无需使用 `return`

```solidity
// 命名式返回
Expand Down
6 changes: 3 additions & 3 deletions 05_DataStorage/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ tags:

## 数据位置

Solidity数据存储位置有三类:`storage``memory``calldata`。不同存储位置的`gas`成本不同。`storage`类型的数据存在链上,类似计算机的硬盘,消耗`gas`多;`memory``calldata`类型的临时存在内存里,消耗`gas`少。大致用法:
Solidity数据存储位置有三类:`storage``memory``calldata`。不同存储位置的`gas`成本不同。`storage`类型的数据存在链上,类似计算机的硬盘,消耗`gas`多;`memory``calldata`类型的临时存在内存里,消耗`gas`少。整体消耗`gas`从多到少依次为:`storage` > `memory` > `calldata`大致用法:

1. `storage`:合约里的状态变量默认都是`storage`,存储在链上。

Expand Down Expand Up @@ -68,7 +68,7 @@ function fCalldata(uint[] calldata _x) public pure returns(uint[] calldata){
![5-2.png](./img/5-2.png)
- `memory`赋值给`memory`,会创建引用,改变新变量会影响原变量。

- 其他情况下,赋值创建的是本体的副本,即对二者之一的修改,并不会同步到另一方
- 其他情况下,赋值创建的是本体的副本,即对二者之一的修改,并不会同步到另一方。这有时会涉及到开发中的问题,比如从`storage`中读取数据,赋值给`memory`,然后修改`memory`的数据,但如果没有将`memory`的数据赋值回`storage`,那么`storage`的数据是不会改变的。

## 变量的作用域

Expand Down Expand Up @@ -125,7 +125,7 @@ function global() external view returns(address, uint, bytes memory){

在上面例子里,我们使用了3个常用的全局变量:`msg.sender``block.number``msg.data`,他们分别代表请求发起地址,当前区块高度,和请求数据。下面是一些常用的全局变量,更完整的列表请看这个[链接](https://learnblockchain.cn/docs/solidity/units-and-global-variables.html#special-variables-and-functions)

- `blockhash(uint blockNumber)`: (`bytes32`) 给定区块的哈希值 – 只适用于256最近区块, 不包含当前区块。
- `blockhash(uint blockNumber)`: (`bytes32`) 给定区块的哈希值 – 只适用于最近的256个区块, 不包含当前区块。
- `block.coinbase`: (`address payable`) 当前区块矿工的地址
- `block.gaslimit`: (`uint`) 当前区块的gaslimit
- `block.number`: (`uint`) 当前区块的number
Expand Down
2 changes: 1 addition & 1 deletion 07_Mapping/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ mapping(address => address) public swapPair; // 币对的映射,地址到地

- **原理1**: 映射不储存任何键(`Key`)的资讯,也没有length的资讯。

- **原理2**: 映射使用`keccak256(abi.encodePacked(key, slot))`当成offset存取value,其中`slot`是映射变量定义所在的插槽位置。
- **原理2**: 对于映射使用`keccak256(h(key) . slot)`计算存取value的位置。感兴趣的可以去阅读 [WTF Solidity 内部规则: 映射存储布局](https://github.com/WTFAcademy/WTF-Solidity-Internals/tree/master/tutorials/02_MappingStorage)

- **原理3**: 因为Ethereum会定义所有未使用的空间为0,所以未赋值(`Value`)的键(`Key`)初始值都是各个type的默认值,如uint的默认值是0。

Expand Down
2 changes: 1 addition & 1 deletion 09_Constant/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ address constant CONSTANT_ADDRESS = 0x0000000000000000000000000000000000000000;

### immutable

`immutable`变量可以在声明时或构造函数中初始化,因此更加灵活。在`Solidity v8.0.21`以后,`immutable`变量不需要显式初始化,未显式初始化的`immutable`变量将使用数值类型的初始值(见 [8. 变量初始值](https://github.com/AmazingAng/WTF-Solidity/blob/main/08_InitialValue/readme.md#%E5%8F%98%E9%87%8F%E5%88%9D%E5%A7%8B%E5%80%BC))。反之,则需要显式初始化。
`immutable`变量可以在声明时或构造函数中初始化,因此更加灵活。在`Solidity v0.8.21`以后,`immutable`变量不需要显式初始化,未显式初始化的`immutable`变量将使用数值类型的初始值(见 [8. 变量初始值](https://github.com/AmazingAng/WTF-Solidity/blob/main/08_InitialValue/readme.md#%E5%8F%98%E9%87%8F%E5%88%9D%E5%A7%8B%E5%80%BC))。反之,则需要显式初始化。
`immutable`变量既在声明时初始化,又在constructor中初始化,会使用constructor初始化的值。

``` solidity
Expand Down
2 changes: 1 addition & 1 deletion 10_InsertionSort/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ Remix decoded output 出现错误内容

### 正确的Solidity插入排序

花了几个小时,在`Dapp-Learning`社群一个朋友的帮助下,终于找到了`bug`所在。`Solidity`中最常用的变量类型是`uint`也就是正整数,取到负值的话,会报`underflow`错误。而在插入算法中,变量`j`有可能会取到`-1`,引起报错。
花了几个小时,在`Dapp-Learning`社群一个朋友的帮助下,终于找到了`bug`所在。`Solidity`中最常用的变量类型是`uint`也就是无符号整数,取到负值的话,会报`underflow`错误。而在插入算法中,变量`j`有可能会取到`-1`,引起报错。

这里,我们需要把`j`加1,让它无法取到负值。正确代码:

Expand Down
2 changes: 2 additions & 0 deletions 12_Event/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ keccak256("Transfer(address,address,uint256)")

`indexed`标记的参数可以理解为检索事件的索引“键”,方便之后搜索。每个 `indexed` 参数的大小为固定的256比特,如果参数太大了(比如字符串),就会自动计算哈希存储在主题中。

这里其实会引入一个新的问题,根据Solidity的[官方文档](https://docs.soliditylang.org/en/v0.8.27/abi-spec.html#encoding-of-indexed-event-parameters), 对于非值类型的参数(如arrays, bytes, strings), Solidity不会直接存储,而是会将`Keccak-256`哈希存储在主题中,从而导致数据信息的丢失。这对于某些依赖于链上事件的DAPP(跨链,用户注册等等)来说,可能会导致事件检索困难,需要解析哈希值。

### 数据 `data`

事件中不带 `indexed`的参数会被存储在 `data` 部分中,可以理解为事件的“值”。`data` 部分的变量不能被直接检索,但可以存储任意大小的数据。因此一般 `data` 部分可以用来存储复杂的数据结构,例如数组和字符串等等,因为这些数据超过了256比特,即使存储在事件的 `topics` 部分中,也是以哈希的方式存储。另外,`data` 部分的变量在存储上消耗的gas相比于 `topics` 更少。
Expand Down
2 changes: 1 addition & 1 deletion 13_Inheritance/Inheritance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pragma solidity ^0.8.21;
contract Yeye {
event Log(string msg);

// 定义3个function: hip(), pop(), man(),Log值为Yeye。
// 定义3个function: hip(), pop(), yeye(),Log值为Yeye。
function hip() public virtual{
emit Log("Yeye");
}
Expand Down
2 changes: 1 addition & 1 deletion 13_Inheritance/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ mapping(address => uint256) public override balanceOf;
contract Yeye {
event Log(string msg);
// 定义3个function: hip(), pop(), man(),Log值为Yeye。
// 定义3个function: hip(), pop(), yeye(),Log值为Yeye。
function hip() public virtual{
emit Log("Yeye");
}
Expand Down
4 changes: 2 additions & 2 deletions 17_Library/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ tags:
3. 不能接收以太币
4. 不可以被销毁

需要注意的是,库合约重的函数可见性如果被设置为`public`或者`external`,则在调用函数时会触发一次`delegatecall`。而如果被设置为`internal`,则不会引起。对于设置为`private`可见性的函数来说,其仅能在库合约中可见,在其他合约中不可用。
需要注意的是,库合约中的函数可见性如果被设置为`public`或者`external`,则在调用函数时会触发一次`delegatecall`。而如果被设置为`internal`,则不会引起。对于设置为`private`可见性的函数来说,其仅能在库合约中可见,在其他合约中不可用。


## Strings库合约
Expand Down Expand Up @@ -103,7 +103,7 @@ library Strings {
}
```

他主要包含两个函数`toString()``uint256`转为`string``toHexString()``uint256`转换为`16进制`,在转换为`string`
它主要包含两个函数`toString()``uint256`转换为10进制的`string``toHexString()``uint256`转换为16进制的`string`

### 如何使用库合约

Expand Down
3 changes: 2 additions & 1 deletion 23_Delegatecall/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ contract C {

### 发起调用的合约B

首先,合约`B`必须和目标合约`C`的变量存储布局必须相同,两个变量,并且顺序为`num``sender`
首先,合约`B`必须和目标合约`C`的变量存储布局必须相同 —— 即存在两个 `public` 变量且变量类型顺序为 `uint256``address`
> **注意:** 变量名称可以不同
```solidity
contract B {
Expand Down
Binary file added 24_Create/img/24-4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 10 additions & 4 deletions 24_Create/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ contract PairFactory{
}
```

工厂合约(`PairFactory`)有两个状态变量`getPair`是两个代币地址到币对地址的`map`,方便根据代币找到币对地址;`allPairs`是币对地址的数组,存储了所有代币地址
工厂合约(`PairFactory`)有两个状态变量`getPair`是两个代币地址到币对地址的`map`,方便根据代币找到币对地址;`allPairs`是币对地址的数组,存储了所有币对地址

`PairFactory`合约只有一个`createPair`函数,根据输入的两个代币地址`tokenA``tokenB`来创建新的`Pair`合约。其中

Expand All @@ -112,13 +112,19 @@ BSC链上的PEOPLE地址: 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c
1. 使用`WBNB``PEOPLE`的地址作为参数调用`createPair`,得到`Pair`合约地址:0xD3e2008b4Da2cD6DEAF73471590fF30C86778A48

![24-1](./img/24-1.png)
2. 查看`Pair`合约变量

2. 将 Contract 改为 `Pair`,然后在 At Address 输入框输入 `Pair` 合约地址,创建一个前端接口用于调用已部署的合约。

![24-4](./img/24-4.png)

3. 查看`Pair`合约变量

![24-2](./img/24-2.png)
3. Debug查看`create`操作码

4. Debug查看`create`操作码

![24-3](./img/24-3.png)

## 总结

这一讲,我们用极简`Uniswap`的例子介绍了如何使用`create`方法再合约里创建合约,下一讲我们将介绍如何使用`create2`方法来实现极简`Uniswap`
这一讲,我们用极简`Uniswap`的例子介绍了如何使用`create`方法在合约里创建合约,下一讲我们将介绍如何使用`create2`方法来实现极简`Uniswap`
2 changes: 1 addition & 1 deletion 26_DeleteContract/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ tags:
`selfdestruct`使用起来非常简单:

```solidity
selfdestruct(_addr)
selfdestruct(_addr);
```

其中`_addr`是接收合约中剩余`ETH`的地址。`_addr` 地址不需要有`receive()``fallback()`也能接收`ETH`
Expand Down
16 changes: 14 additions & 2 deletions 27_ABIEncode/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,23 @@ function encode() public view returns(bytes memory result) {
}
```

编码的结果为`0x000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000007a58c0be72be218b41c608b7fe7c5bb630736c7100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000043078414100000000000000000000000000000000000000000000000000000000`,由于`abi.encode`将每个数据都填充为32字节,中间有很多`0`
编码的结果为`0x000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000007a58c0be72be218b41c608b7fe7c5bb630736c7100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000043078414100000000000000000000000000000000000000000000000000000000`,详细解释下编码的细节:

```
000000000000000000000000000000000000000000000000000000000000000a // x
0000000000000000000000007a58c0be72be218b41c608b7fe7c5bb630736c71 // addr
00000000000000000000000000000000000000000000000000000000000000a0 // name 参数的偏移量
0000000000000000000000000000000000000000000000000000000000000005 // array[0]
0000000000000000000000000000000000000000000000000000000000000006 // array[1]
0000000000000000000000000000000000000000000000000000000000000004 // name 参数的长度为4字节
3078414100000000000000000000000000000000000000000000000000000000 // name
```

其中 `name` 参数被转换为UTF-8的字节值 `0x30784141`,在 abi 编码规范中,string 属于动态类型 ,动态类型的参数需要借助偏移量进行编码,可以参考[动态类型的使用](https://learnblockchain.cn/docs/solidity/abi-spec.html#id9)。由于 abi.encode 会将每个参与编码的参数元素(包括偏移量,长度)都填充为32字节(evm字长为32字节),所以可以看到编码后的数据中有很多填充的 0 。

### `abi.encodePacked`

将给定参数根据其所需最低空间编码。它类似 `abi.encode`,但是会把其中填充的很多`0`省略。比如,只用1字节来编码`uint8`类型。当你想省空间,并且不与合约交互的时候,可以使用`abi.encodePacked`,例如算一些数据的`hash`时。
将给定参数根据其所需最低空间编码。它类似 `abi.encode`,但是会把其中填充的很多`0`省略。比如,只用1字节来编码`uint8`类型。当你想省空间,并且不与合约交互的时候,可以使用`abi.encodePacked`,例如算一些数据的`hash`时。需要注意,`abi.encodePacked`因为不会做填充,所以不同的输入在拼接后可能会产生相同的编码结果,导致冲突,这也带来了潜在的安全风险。

```solidity
function encodePacked() public view returns(bytes memory result) {
Expand Down
4 changes: 2 additions & 2 deletions 34_ERC721/ERC721.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import "./IERC721Metadata.sol";
import "./String.sol";

contract ERC721 is IERC721, IERC721Metadata{
using Strings for uint256; // 使用String库
using Strings for uint256; // 使用Strings库

// Token名称
string public override name;
Expand All @@ -21,7 +21,7 @@ contract ERC721 is IERC721, IERC721Metadata{
mapping(address => uint) private _balances;
// tokenID 到 授权地址 的授权映射
mapping(uint => address) private _tokenApprovals;
// owner地址。到operator地址 的批量授权映射
// owner地址 到 operator地址 的批量授权映射
mapping(address => mapping(address => bool)) private _operatorApprovals;

// 错误 无效的接收者
Expand Down
Loading

0 comments on commit 7f758aa

Please sign in to comment.