Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions changes.txt

Large diffs are not rendered by default.

138 changes: 76 additions & 62 deletions docs/ethers-102/23_Frontrun/frontrun.js
Original file line number Diff line number Diff line change
@@ -1,81 +1,95 @@
//1.连接到foundry本地网络
import { ethers } from "ethers";
// V6 版本 const provider = new ethers.JsonRpcProvider('http://127.0.0.1:8545')
const provider = new ethers.providers.WebSocketProvider('http://127.0.0.1:8545')
let network = provider.getNetwork()
network.then(res => console.log(`[${(new Date).toLocaleTimeString()}]链接到网络${res.chainId}`))
const provider = new ethers.JsonRpcProvider("http://127.0.0.1:8545");
let network = provider.getNetwork();
network.then(res =>
console.log(`[${new Date().toLocaleTimeString()}]链接到网络${res.chainId}`),
);

//2.构建contract实例
const contractABI = [
"function mint() public",
"function ownerOf(uint256) public view returns (address) ",
"function totalSupply() view returns (uint256)"
]
const contractAddress = '0xC76A71C4492c11bbaDC841342C4Cb470b5d12193'
const contractFM = new ethers.Contract(contractAddress, contractABI, provider)
"function mint() public",
"function ownerOf(uint256) public view returns (address) ",
"function totalSupply() view returns (uint256)",
];
const contractAddress = "0xC76A71C4492c11bbaDC841342C4Cb470b5d12193";
const contractFM = new ethers.Contract(contractAddress, contractABI, provider);

//3.创建Interface对象,用于检索mint函数。
const iface = new ethers.utils.Interface(contractABI)
const iface = new ethers.Interface(contractABI);
function getSignature(fn) {
// V6 版本 return iface.getFunction("mint").selector
return iface.getSighash(fn)
return iface.getFunction(fn).selector;
}

//4. 创建测试钱包,用于发送抢跑交易,私钥是foundry测试网提供
const privateKey = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'
const wallet = new ethers.Wallet(privateKey, provider)
const privateKey =
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
const wallet = new ethers.Wallet(privateKey, provider);

//5. 构建正常mint函数,检验mint结果,显示正常。
const normaltx = async () => {
provider.on('pending', async (txHash) => {
provider.getTransaction(txHash).then(
async (tx) => {
if (tx.data.indexOf(getSignature("mint")) !== -1) {
console.log(`[${(new Date).toLocaleTimeString()}]监听到交易:${txHash}`)
console.log(`铸造发起的地址是:${tx.from}`)
await tx.wait()
const tokenId = await contractFM.totalSupply()
console.log(`mint的NFT编号:${tokenId}`)
console.log(`编号${tokenId}NFT的持有者是${await contractFM.ownerOf(tokenId)}`)
console.log(`铸造发起的地址是不是对应NFT的持有者:${tx.from === await contractFM.ownerOf(tokenId)}`)
}
}
)
})
}
provider.on("pending", async txHash => {
provider.getTransaction(txHash).then(async tx => {
if (tx.data.indexOf(getSignature("mint")) !== -1) {
console.log(`[${new Date().toLocaleTimeString()}]监听到交易:${txHash}`);
console.log(`铸造发起的地址是:${tx.from}`);
await tx.wait();
const tokenId = await contractFM.totalSupply;
console.log(`mint的NFT编号:${tokenId}`);
console.log(
`编号${tokenId}NFT的持有者是${await contractFM.ownerOf(tokenId)}`,
);
console.log(
`铸造发起的地址是不是对应NFT的持有者:${tx.from === (await contractFM.ownerOf(tokenId))}`,
);
}
});
});
};

//6.构建抢跑交易,检验mint结果,抢跑成功!
const frontRun = async () => {
provider.on('pending', async (txHash) => {
const tx = await provider.getTransaction(txHash)
if (tx.data.indexOf(getSignature("mint")) !== -1 && tx.from !== wallet.address) {
console.log(`[${(new Date).toLocaleTimeString()}]监听到交易:${txHash}\n准备抢先交易`)
const frontRunTx = {
to: tx.to,
value: tx.value,
// V6版本 maxPriorityFeePerGas: tx.maxPriorityFeePerGas * 2n, 其他运算同理。参考https://docs.ethers.org/v6/migrating/#migrate-bigint
maxPriorityFeePerGas: tx.maxPriorityFeePerGas.mul(2),
maxFeePerGas: tx.maxFeePerGas.mul(2),
gasLimit: tx.gasLimit.mul(2),
data: tx.data
}
const aimTokenId = (await contractFM.totalSupply()).add(1)
console.log(`即将被mint的NFT编号是:${aimTokenId}`)//打印应该被mint的nft编号
const sentFR = await wallet.sendTransaction(frontRunTx)
console.log(`正在frontrun交易`)
const receipt = await sentFR.wait()
console.log(`frontrun 交易成功,交易hash是:${receipt.transactionHash}`)
console.log(`铸造发起的地址是:${tx.from}`)
console.log(`编号${aimTokenId}NFT的持有者是${await contractFM.ownerOf(aimTokenId)}`)//刚刚mint的nft持有者并不是tx.from
console.log(`编号${aimTokenId.add(1)}的NFT的持有者是:${await contractFM.ownerOf(aimTokenId.add(1))}`)//tx.from被wallet.address抢跑,mint了下一个nft
console.log(`铸造发起的地址是不是对应NFT的持有者:${tx.from === await contractFM.ownerOf(aimTokenId)}`)//比对地址,tx.from被抢跑
//检验区块内数据结果
const block = await provider.getBlock(tx.blockNumber)
console.log(`区块内交易数据明细:${block.transactions}`)//在区块内,后发交易排在先发交易前,抢跑成功。
}
})
}

provider.on("pending", async txHash => {
const tx = await provider.getTransaction(txHash);
if (
tx.data.indexOf(getSignature("mint")) !== -1 &&
tx.from !== wallet.address
) {
console.log(
`[${new Date().toLocaleTimeString()}]监听到交易:${txHash}\n准备抢先交易`,
);
const frontRunTx = {
to: tx.to,
value: tx.value,
// V6版本 maxPriorityFeePerGas: tx.maxPriorityFeePerGas * 2n, 其他运算同理。参考https://docs.ethers.org/v6/migrating/#migrate-bigint
maxPriorityFeePerGas: tx.maxPriorityFeePerGas.mul(2),
maxFeePerGas: tx.maxFeePerGas.mul(2),
gasLimit: tx.gasLimit.mul(2),
data: tx.data,
};
const aimTokenId = (await contractFM.totalSupply()).add(1);
console.log(`即将被mint的NFT编号是:${aimTokenId}`); //打印应该被mint的nft编号
const sentFR = await wallet.sendTransaction(frontRunTx);
console.log(`正在frontrun交易`);
const receipt = await sentFR.wait();
console.log(`frontrun 交易成功,交易hash是:${receipt.transactionHash}`);
console.log(`铸造发起的地址是:${tx.from}`);
console.log(
`编号${aimTokenId}NFT的持有者是${await contractFM.ownerOf(aimTokenId)}`,
); //刚刚mint的nft持有者并不是tx.from
console.log(
`编号${aimTokenId.add(1)}的NFT的持有者是:${await contractFM.ownerOf(aimTokenId.add(1))}`,
); //tx.from被wallet.address抢跑,mint了下一个nft
console.log(
`铸造发起的地址是不是对应NFT的持有者:${tx.from === (await contractFM.ownerOf(aimTokenId))}`,
); //比对地址,tx.from被抢跑
//检验区块内数据结果
const block = await provider.getBlock(tx.blockNumber);
console.log(`区块内交易数据明细:${block.transactions}`); //在区块内,后发交易排在先发交易前,抢跑成功。
}
});
};

frontRun()
//normaltx()
// frontRun()
normaltx();
12 changes: 6 additions & 6 deletions docs/solidity-101/02_ValueTypes/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,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 @@ -119,7 +119,7 @@ bytes32 public _byte32 = "MiniSolidity";
bytes1 public _byte = _byte32[0];
```

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

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

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

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

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

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

## 在 Remix 上运行

Expand Down
15 changes: 11 additions & 4 deletions docs/solidity-101/03_Function/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,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 @@ -37,7 +38,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 @@ -48,11 +49,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 docs/solidity-101/04_Return/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,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 docs/solidity-101/05_DataStorage/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,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 @@ -70,7 +70,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 @@ -127,7 +127,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
3 changes: 2 additions & 1 deletion docs/solidity-101/07_Mapping/Mapping.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ contract Mapping {
mapping(uint => address) public idToAddress; // id映射到地址
mapping(address => address) public swapPair; // 币对的映射,地址到地址

// 规则1. _KeyType不能是自定义的 下面这个例子会报错
// 规则1. _KeyType 不能是自定义的, 下面这个例子会报错
// _KeyType 可以是 内置值类型,string, bytes, 合约,枚举类型
// 我们定义一个结构体 Struct
// struct Student{
// uint256 id;
Expand Down
2 changes: 1 addition & 1 deletion docs/solidity-101/07_Mapping/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,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
5 changes: 3 additions & 2 deletions docs/solidity-101/09_Constant/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,14 @@ address constant CONSTANT_ADDRESS = 0x0000000000000000000000000000000000000000;

### immutable

`immutable`变量可以在声明时或构造函数中初始化,因此更加灵活。在`Solidity v8.0.21`以后,`immutable`变量不需要显式初始化。反之,则需要显式初始化。
`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
// immutable变量可以在constructor里初始化,之后不能改变
uint256 public immutable IMMUTABLE_NUM = 9999999999;
address public immutable IMMUTABLE_ADDRESS;
// 在`Solidity v8.0.21`以后,下列变量数值暂为初始值
address public immutable IMMUTABLE_ADDRESS;
uint256 public immutable IMMUTABLE_BLOCK;
uint256 public immutable IMMUTABLE_TEST;
```
Expand Down
2 changes: 1 addition & 1 deletion docs/solidity-101/10_InsertionSort/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,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 docs/solidity-101/12_Event/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,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 docs/solidity-101/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
Loading