diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 000000000..bb4686766 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,46 @@ +name: Bug report (程序Bug报告) +title: "[Bug] [Class (章节)] Title(标题)" +description: Problems and issues with code of WTF-Solidity (报告WTF-Solidity中的程序Bug) +body: + - type: markdown + attributes: + value: > + Please make sure what you are reporting is indeed a bug with reproducible steps(请确保您所报告的是一个具有可重现步骤的错误), if you want to ask questions + or share ideas, you can head to our (如果你是想问一个问题或者分享一个主意,你可以通过以下方式) + [Discussions](https://github.com/AmazingAng/WTF-Solidity/discussions) tab, you can also + [join our discord](https://discord.gg/5akcruXrsk) + + + - type: textarea + attributes: + label: Details (细节) + description: Describe what happened.(描述一下发生了什么) + placeholder: > + Please provide the context in which the problem occurred and explain what happened(请提供问题发生的背景并解释发生的情况) + validations: + required: true + + + - type: textarea + attributes: + label: Environment (环境) + description: Tell us about your environment(告诉我们你的环境信息) + placeholder: > + Tell us what you're using it: Hardhat, Remix, etc. (告诉我们你使用的环境:Hardhat,Remix等等。) + + + - type: checkboxes + attributes: + label: Are you willing to submit PR?(你愿意提交PR吗?) + description: > + This is absolutely not required, but we are happy to guide you in the contribution process + especially if you already have a good understanding of how to implement the fix. + (这不是必须的,但是我们很高兴您能够提交PR) + options: + - label: Yes I am willing to submit a PR!(是的我愿意) + + + + - type: markdown + attributes: + value: "Thanks for completing our form!(感谢填写表单!)" diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..4fcbaa800 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Ask a question or get support(提个问题或者获得支持) + url: https://github.com/AmazingAng/WTF-Solidity/discussions + about: Ask a question or request support for using WTF-Solidity (在使用WTF-Solidity时,遇到的问题或者需要获得帮助) diff --git a/.github/ISSUE_TEMPLATE/document.yml b/.github/ISSUE_TEMPLATE/document.yml new file mode 100644 index 000000000..e3dec71ea --- /dev/null +++ b/.github/ISSUE_TEMPLATE/document.yml @@ -0,0 +1,31 @@ +name: Typo report(勘误报告) +description: Report a typo in the WTF-Solidity(报告WTF-Solidity中的勘误) +title: "[Doc][Module Name(模块)] Title(标题)" +body: + - type: markdown + attributes: + value: | + For better global communication, Please write in English. + + If you feel the description in English is not clear, then you can use Chinese, thanks! + (为了更好的全球化的交流,请使用英文,如果觉得用英文无法清楚描述,可以使用中文) + + - type: textarea + attributes: + label: Details(细节) + description: A short description what your find in our document.(简短的描述一下在文档中发现了什么错误) + + + - type: checkboxes + attributes: + label: Are you willing to submit a PR?(你愿意提交PR吗?) + description: > + This is absolutely not required, but we are happy to guide you in the contribution process + especially if you already have a good understanding of how to implement the fix. + (这不是必须的,但是我们很高兴您能够提交PR) + options: + - label: Yes I am willing to submit a PR!(是的我愿意) + + - type: markdown + attributes: + value: "Thanks for completing our form!(感谢填写表单!)" diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml new file mode 100644 index 000000000..f0ffa96f5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -0,0 +1,44 @@ + +name: Feature request(对此项目提出一个意见) +description: Suggest an idea for this project(对此项目提出一个意见) +title: "[Feature][Module Name(模块名称)] Title(标题)" +body: + - type: markdown + attributes: + value: | + For better global communication, Please write in English. + + If you feel the description in English is not clear, then you can use Chinese, thanks! + (为了更好的全球化的交流,请使用英文,如果觉得用英文无法清楚描述,可以使用中文) + + + + - type: textarea + attributes: + label: Details(细节) + description: Please describe your feature request in detail. (请详细描述您的功能请求。) + + - type: textarea + attributes: + label: Motivation(动机) + description: Please describe your motivation in the feature request. (请描述您在功能请求中的动机。) + placeholder: > + Please describe your motivation in the feature request. (请描述您在功能请求中的动机。) + + + + - type: checkboxes + attributes: + label: Are you willing to submit a PR?(你愿意提交PR吗?) + description: > + This is absolutely not required, but we are happy to guide you in the contribution process + especially if you already have a good understanding of how to implement the fix. + (这不是必须的,但是我们很高兴您能够提交PR) + options: + - label: Yes I am willing to submit a PR!(是的我愿意) + + + + - type: markdown + attributes: + value: "Thanks for completing our form!(感谢填写表单!)" diff --git a/.github/ISSUE_TEMPLATE/improvement-report.yml b/.github/ISSUE_TEMPLATE/improvement-report.yml new file mode 100644 index 000000000..e9b0a8b56 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/improvement-report.yml @@ -0,0 +1,31 @@ +name: Improvement request (提升请求) +description: Suggest an improvement for this project (给WTF-Solidity提一个改进提升的请求) +title: "[Improvement][Module Name(模块名称)] Title(标题)" +body: + - type: markdown + attributes: + value: | + For better global communication, Please write in English. + + If you feel the description in English is not clear, then you can use Chinese, thanks! + (为了更好的全球化的交流,请使用英文,如果觉得用英文无法清楚描述,可以使用中文) + + - type: textarea + attributes: + label: Description + description: A short description why your find in our document.(简短的描述一下) + + + - type: checkboxes + attributes: + label: Are you willing to submit a PR?(你愿意提交PR吗?) + description: > + This is absolutely not required, but we are happy to guide you in the contribution process + especially if you already have a good understanding of how to implement the fix. + (这不是必须的,但是我们很高兴您能够提交PR) + options: + - label: Yes I am willing to submit a PR!(是的我愿意) + + - type: markdown + attributes: + value: "Thanks for completing our form!(感谢填写表单!)" diff --git a/11_Modifier/img/11-1.jpg b/11_Modifier/img/11-1.jpg index 4a3c39de3..086880235 100644 Binary files a/11_Modifier/img/11-1.jpg and b/11_Modifier/img/11-1.jpg differ diff --git a/11_Modifier/img/11-2.jpg b/11_Modifier/img/11-2.jpg index d5078886f..7b1e27905 100644 Binary files a/11_Modifier/img/11-2.jpg and b/11_Modifier/img/11-2.jpg differ diff --git a/11_Modifier/img/11-3.jpg b/11_Modifier/img/11-3.jpg index 62ebbaf01..04d7a1864 100644 Binary files a/11_Modifier/img/11-3.jpg and b/11_Modifier/img/11-3.jpg differ diff --git a/11_Modifier/img/11-4.jpg b/11_Modifier/img/11-4.jpg new file mode 100644 index 000000000..593159011 Binary files /dev/null and b/11_Modifier/img/11-4.jpg differ diff --git a/11_Modifier/readme.md b/11_Modifier/readme.md index dc5093a3e..46c872967 100644 --- a/11_Modifier/readme.md +++ b/11_Modifier/readme.md @@ -83,17 +83,19 @@ function changeOwner(address _newOwner) external onlyOwner{ 以 `Owner.sol` 为例。 -1. 在 Remix 上编译部署代码。 +1. 在 Remix 上编译并部署代码,在合约部署时传入 initialOwner 变量。 + + ![11-1](./img/11-1.jpg) 2. 点击 `owner` 按钮查看当前 owner 变量。 - ![11-1](img/11-1.jpg) + ![11-2](./img/11-2.jpg) 3. 以 owner 地址的用户身份,调用 `changeOwner` 函数,交易成功。 - ![11-2](img/11-2.jpg) + ![11-3](./img/11-3.jpg) 4. 以非 owner 地址的用户身份,调用 `changeOwner` 函数,交易失败,因为modifier onlyOwner 的检查语句不满足。 - ![11-3](img/11-3.jpg) + ![11-4](./img/11-4.jpg) ## 总结 -这一讲,我们介绍了`Solidity`中的构造函数和修饰符,并举了一个控制合约权限的`Ownable`合约。 +这一讲,我们介绍了`Solidity`中的构造函数和修饰符,并写了一个控制合约权限的`Ownable`合约。 diff --git a/12_Event/img/12-4.png b/12_Event/img/12-4.png new file mode 100644 index 000000000..b8dd27140 Binary files /dev/null and b/12_Event/img/12-4.png differ diff --git a/12_Event/readme.md b/12_Event/readme.md index e18f001a7..5316c8729 100644 --- a/12_Event/readme.md +++ b/12_Event/readme.md @@ -98,11 +98,11 @@ keccak256("Transfer(address,address,uint256)") ### 在Etherscan上查询事件 -我们尝试用`_transfer()`函数在`Rinkeby`测试网络上转账100代币,可以在`Etherscan`上查询到相应的`tx`:[网址](https://rinkeby.etherscan.io/tx/0x8cf87215b23055896d93004112bbd8ab754f081b4491cb48c37592ca8f8a36c7)。 +我们尝试用`_transfer()`函数在`Sepolia`测试网络上转账100代币,可以在`Etherscan`上查询到相应的`tx`:[网址](https://sepolia.etherscan.io/tx/0xb07dcd9943662e2e8b17c7add370f046401962ce24d0690a61bb249a385dc8c9#eventlog)。 点击`Logs`按钮,就能看到事件明细: -![Event明细](https://images.mirror-media.xyz/publication-images/gx6_wDMYEl8_Gc_JkTIKn.png?height=980&width=1772) +![Event明细](img/12-4.png) `Topics`里面有三个元素,`[0]`是这个事件的哈希,`[1]`和`[2]`是我们定义的两个`indexed`变量的信息,即转账的转出地址和接收地址。`Data`里面是剩下的不带`indexed`的变量,也就是转账数量。 diff --git a/17_Library/readme.md b/17_Library/readme.md index da37f9368..3abdb4d7b 100644 --- a/17_Library/readme.md +++ b/17_Library/readme.md @@ -19,7 +19,7 @@ tags: 所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) --- -这一讲,我们用`ERC721`的引用的库合约`String`为例介绍`Solidity`中的库合约(`Library`),并总结了常用的库合约。 +这一讲,我们用`ERC721`的引用的库合约`Strings`为例介绍`Solidity`中的库合约(`Library`),并总结了常用的库合约。 ## 库合约 @@ -34,9 +34,12 @@ tags: 3. 不能接收以太币 4. 不可以被销毁 -## String库合约 +需要注意的是,库合约重的函数可见性如果被设置为`public`或者`external`,则在调用函数时会触发一次`delegatecall`。而如果被设置为`internal`,则不会引起。对于设置为`private`可见性的函数来说,其仅能在库合约中可见,在其他合约中不可用。 -`String库合约`是将`uint256`类型转换为相应的`string`类型的代码库,样例代码如下: + +## Strings库合约 + +`Strings库合约`是将`uint256`类型转换为相应的`string`类型的代码库,样例代码如下: ```solidity library Strings { @@ -104,7 +107,7 @@ library Strings { ### 如何使用库合约 -我们用`String`库合约的`toHexString()`来演示两种使用库合约中函数的办法。 +我们用`Strings`库合约的`toHexString()`来演示两种使用库合约中函数的办法。 1. 利用using for指令 @@ -134,9 +137,9 @@ library Strings { ## 总结 -这一讲,我们用`ERC721`的引用的库合约`String`为例介绍`Solidity`中的库合约(`Library`)。99%的开发者都不需要自己去写库合约,会用大神写的就可以了。我们只需要知道什么情况该用什么库合约。常用的有: +这一讲,我们用`ERC721`的引用的库合约`Strings`为例介绍`Solidity`中的库合约(`Library`)。99%的开发者都不需要自己去写库合约,会用大神写的就可以了。我们只需要知道什么情况该用什么库合约。常用的有: -1. [String](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/4a9cc8b4918ef3736229a5cc5a310bdc17bf759f/contracts/utils/Strings.sol):将`uint256`转换为`String` +1. [Strings](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/4a9cc8b4918ef3736229a5cc5a310bdc17bf759f/contracts/utils/Strings.sol):将`uint256`转换为`String` 2. [Address](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/4a9cc8b4918ef3736229a5cc5a310bdc17bf759f/contracts/utils/Address.sol):判断某个地址是否为合约地址 3. [Create2](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/4a9cc8b4918ef3736229a5cc5a310bdc17bf759f/contracts/utils/Create2.sol):更安全的使用`Create2 EVM opcode` 4. [Arrays](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/4a9cc8b4918ef3736229a5cc5a310bdc17bf759f/contracts/utils/Arrays.sol):跟数组相关的库合约 diff --git a/26_DeleteContract/DeployContract.sol b/26_DeleteContract/DeployContract.sol new file mode 100644 index 000000000..0ac94eb1d --- /dev/null +++ b/26_DeleteContract/DeployContract.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import "./DeleteContract.sol"; + +contract DeployContract { + + struct DemoResult { + address addr; + uint balance; + uint value; + } + + constructor() payable {} + + function getBalance() external view returns(uint balance){ + balance = address(this).balance; + } + + function demo() public payable returns (DemoResult memory){ + DeleteContract del = new DeleteContract{value:msg.value}(); + DemoResult memory res = DemoResult({ + addr: address(del), + balance: del.getBalance(), + value: del.value() + }); + del.deleteContract(); + return res; + } +} \ No newline at end of file diff --git a/26_DeleteContract/img/26-3.png b/26_DeleteContract/img/26-3.png new file mode 100644 index 000000000..de69e075c Binary files /dev/null and b/26_DeleteContract/img/26-3.png differ diff --git a/26_DeleteContract/img/26-4.png b/26_DeleteContract/img/26-4.png new file mode 100644 index 000000000..8d500a91c Binary files /dev/null and b/26_DeleteContract/img/26-4.png differ diff --git a/26_DeleteContract/img/26-5.png b/26_DeleteContract/img/26-5.png new file mode 100644 index 000000000..02f89bef0 Binary files /dev/null and b/26_DeleteContract/img/26-5.png differ diff --git a/26_DeleteContract/img/26-6.png b/26_DeleteContract/img/26-6.png new file mode 100644 index 000000000..730f13df6 Binary files /dev/null and b/26_DeleteContract/img/26-6.png differ diff --git a/26_DeleteContract/readme.md b/26_DeleteContract/readme.md index cdd4dcd99..d66e15824 100644 --- a/26_DeleteContract/readme.md +++ b/26_DeleteContract/readme.md @@ -24,6 +24,11 @@ tags: `selfdestruct`命令可以用来删除智能合约,并将该合约剩余`ETH`转到指定地址。`selfdestruct`是为了应对合约出错的极端情况而设计的。它最早被命名为`suicide`(自杀),但是这个词太敏感。为了保护抑郁的程序员,改名为`selfdestruct`;在 [v0.8.18](https://blog.soliditylang.org/2023/02/01/solidity-0.8.18-release-announcement/) 版本中,`selfdestruct` 关键字被标记为「不再建议使用」,在一些情况下它会导致预期之外的合约语义,但由于目前还没有代替方案,目前只是对开发者做了编译阶段的警告,相关内容可以查看 [EIP-6049](https://eips.ethereum.org/EIPS/eip-6049)。 +然而,在以太坊坎昆(Cancun)升级中,[EIP-6780](https://eips.ethereum.org/EIPS/eip-6780)被纳入升级以实现对`Verkle Tree`更好的支持。EIP-6780减少了`SELFDESTRUCT`操作码的功能。根据提案描述,当前`SELFDESTRUCT`仅会被用来将合约中的ETH转移到指定地址,而原先的删除功能只有在`合约创建-自毁`这两个操作处在同一笔交易时才能生效。所以目前来说: + +1. 已经部署的合约无法被`SELFDESTRUCT`了。 +2. 如果要使用原先的`SELFDESTRUCT`功能,必须在同一笔交易中创建并`SELFDESTRUCT`。 + ### 如何使用`selfdestruct` `selfdestruct`使用起来非常简单: @@ -34,7 +39,9 @@ selfdestruct(_addr); 其中`_addr`是接收合约中剩余`ETH`的地址。`_addr` 地址不需要有`receive()`或`fallback()`也能接收`ETH`。 -### 例子 +### Demo-转移ETH功能 + +以下合约在坎昆升级前可以完成合约的自毁,在坎昆升级后仅能实现内部ETH余额的转移。 ```solidity contract DeleteContract { @@ -60,16 +67,11 @@ contract DeleteContract { 部署好合约后,我们向`DeleteContract`合约转入1 `ETH`。这时,`getBalance()`会返回1 `ETH`,`value`变量是10。 -当我们调用`deleteContract()`函数,合约将自毁,此时再次调用合约函数交互会失败。 - -### 注意事项 - -1. 对外提供合约销毁接口时,最好设置为只有合约所有者可以调用,可以使用函数修饰符`onlyOwner`进行函数声明。 -2. 当合约被销毁后再次与合约函数交互会报error。 -3. 当合约中有`selfdestruct`功能时常常会带来安全问题和信任问题,合约中的selfdestruct功能会为攻击者打开攻击向量(例如使用`selfdestruct`向一个合约频繁转入token进行攻击,这将大大节省了GAS的费用,虽然很少人这么做),此外,此功能还会降低用户对合约的信心。 +当我们调用`deleteContract()`函数,合约将触发`selfdestruct`操作。**在坎昆升级前,合约会被自毁。但是在升级后,合约依然存在,只是将合约包含的ETH转移到指定地址,而合约依然能够调用。** -### 在remix上验证 +#### 在remix上验证 +##### 坎昆升级之前 1. 部署合约并且转入1ETH,查看合约状态 ![deployContract.png](./img/26-1.png) @@ -78,6 +80,64 @@ contract DeleteContract { ![deleteContract.png](./img/26-2.png) 从测试中观察合约状态可以发现合约销毁后的ETH返回给了指定的地址,在合约销毁后再次调用合约函数进行交互则会失败。 +##### 坎昆升级之后 +1. 部署合约并且转入1ETH,查看合约状态 + + ![deployContract2.png](./img/26-3.png) +2. 销毁合约,查看合约状态 + + ![deleteContract2.png](./img/26-4.png) +从测试中观察合约状态可以发现合约包含的ETH已经清零(返回给了指定的地址),再次调用合约函数进行交互依然可以成功。 + + + + +### Demo-同笔交易内实现合约创建-自毁 + +根据提案,原先的删除功能只有在`合约创建-自毁`这两个操作处在同一笔交易时才能生效。所以我们需要通过另一个合约进行控制。 + +```solidity +contract DeployContract { + + struct DemoResult { + address addr; + uint balance; + uint value; + } + + constructor() payable {} + + function getBalance() external view returns(uint balance){ + balance = address(this).balance; + } + + function demo() public payable returns (DemoResult memory){ + DeleteContract del = new DeleteContract{value:msg.value}(); + DemoResult memory res = DemoResult({ + addr: address(del), + balance: del.getBalance(), + value: del.value() + }); + del.deleteContract(); + return res; + } +} +``` +#### 在remix上验证 +1. 部署`DeployContract`合约并且转入1ETH调用`demo`方法,查看合约状态,显示`DeleteContract`已被正确部署,且在`selfdestruct`后ETH已转移到`DeployContract`。 + + ![deployContract3.png](./img/26-5.png) +2. 选择导入返回值中的地址为`DeleteContract`。显示该地址不存有ETH,且调用合约函数进行交互均失败。 + + ![deleteContract3.png](./img/26-6.png) + + +### 注意事项 + +1. 对外提供合约销毁接口时,最好设置为只有合约所有者可以调用,可以使用函数修饰符`onlyOwner`进行函数声明。 +2. 当合约中有`selfdestruct`功能时常常会带来安全问题和信任问题,合约中的selfdestruct功能会为攻击者打开攻击向量(例如使用`selfdestruct`向一个合约频繁转入token进行攻击,这将大大节省了GAS的费用,虽然很少人这么做),此外,此功能还会降低用户对合约的信心。 + + ## 总结 -`selfdestruct`是智能合约的紧急按钮,销毁合约并将剩余`ETH`转移到指定账户。当著名的`The DAO`攻击发生时,以太坊的创始人们一定后悔过没有在合约里加入`selfdestruct`来停止黑客的攻击吧。 +`selfdestruct`是智能合约的紧急按钮,销毁合约并将剩余`ETH`转移到指定账户。当著名的`The DAO`攻击发生时,以太坊的创始人们一定后悔过没有在合约里加入`selfdestruct`来停止黑客的攻击吧。在坎昆升级后,`selfdestruct`的作用也逐渐发生了改变,什么都不是一成不变的,还是要保持学习。 diff --git a/29_Selector/Selector.sol b/29_Selector/Selector.sol index 79f28c086..c5aaaf16d 100644 --- a/29_Selector/Selector.sol +++ b/29_Selector/Selector.sol @@ -1,9 +1,23 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.21; +contract DemoContract { + // empty contract +} + contract Selector{ // event 返回msg.data event Log(bytes data); + event SelectorEvent(bytes4); + + // Struct User + struct User { + uint256 uid; + bytes name; + } + + // Enum School + enum School { SCHOOL1, SCHOOL2, SCHOOL3 } // 输入参数 to: 0x2c44b726ADF1963cA47Af88B284C06f30380fC78 function mint(address /*to*/) external{ @@ -16,10 +30,70 @@ contract Selector{ return bytes4(keccak256("mint(address)")); } + // 无参数selector + // 输入: 无 + // nonParamSelector() : 0x03817936 + function nonParamSelector() external returns(bytes4 selectorWithNonParam){ + emit SelectorEvent(this.nonParamSelector.selector); + return bytes4(keccak256("nonParamSelector()")); + } + + // elementary(基础)类型参数selector + // 输入:param1: 1,param2: 0 + // elementaryParamSelector(uint256,bool) : 0x3ec37834 + function elementaryParamSelector(uint256 param1, bool param2) external returns(bytes4 selectorWithElementaryParam){ + emit SelectorEvent(this.elementaryParamSelector.selector); + return bytes4(keccak256("elementaryParamSelector(uint256,bool)")); + } + + // fixed size(固定长度)类型参数selector + // 输入: param1: [1,2,3] + // fixedSizeParamSelector(uint256[3]) : 0xead6b8bd + function fixedSizeParamSelector(uint256[3] memory param1) external returns(bytes4 selectorWithFixedSizeParam){ + emit SelectorEvent(this.fixedSizeParamSelector.selector); + return bytes4(keccak256("fixedSizeParamSelector(uint256[3])")); + } + + // non-fixed size(可变长度)类型参数selector + // 输入: param1: [1,2,3], param2: "abc" + // nonFixedSizeParamSelector(uint256[],string) : 0xf0ca01de + function nonFixedSizeParamSelector(uint256[] memory param1,string memory param2) external returns(bytes4 selectorWithNonFixedSizeParam){ + emit SelectorEvent(this.nonFixedSizeParamSelector.selector); + return bytes4(keccak256("nonFixedSizeParamSelector(uint256[],string)")); + } + + // mapping(映射)类型参数selector + // 输入:demo: 0x9D7f74d0C41E726EC95884E0e97Fa6129e3b5E99, user: [1, "0xa0b1"], count: [1,2,3], mySchool: 1 + // mappingParamSelector(address,(uint256,bytes),uint256[],uint8) : 0xe355b0ce + function mappingParamSelector(DemoContract demo, User memory user, uint256[] memory count, School mySchool) external returns(bytes4 selectorWithMappingParam){ + emit SelectorEvent(this.mappingParamSelector.selector); + return bytes4(keccak256("mappingParamSelector(address,(uint256,bytes),uint256[],uint8)")); + } + // 使用selector来调用函数 - function callWithSignature() external returns(bool, bytes memory){ - // 只需要利用`abi.encodeWithSelector`将`mint`函数的`selector`和参数打包编码 - (bool success, bytes memory data) = address(this).call(abi.encodeWithSelector(0x6a627842, 0x2c44b726ADF1963cA47Af88B284C06f30380fC78)); - return(success, data); + function callWithSignature() external{ + // 初始化uint256数组 + uint256[] memory param1 = new uint256[](3); + param1[0] = 1; + param1[1] = 2; + param1[2] = 3; + + // 初始化struct + User memory user; + user.uid = 1; + user.name = "0xa0b1"; + + // 利用abi.encodeWithSelector将函数的selector和参数打包编码 + // 调用nonParamSelector函数 + (bool success0, bytes memory data0) = address(this).call(abi.encodeWithSelector(0x03817936)); + // 调用elementaryParamSelector函数 + (bool success1, bytes memory data1) = address(this).call(abi.encodeWithSelector(0x3ec37834, 1, 0)); + // 调用fixedSizeParamSelector函数 + (bool success2, bytes memory data2) = address(this).call(abi.encodeWithSelector(0xead6b8bd, [1,2,3])); + // 调用nonFixedSizeParamSelector函数 + (bool success3, bytes memory data3) = address(this).call(abi.encodeWithSelector(0xf0ca01de, param1, "abc")); + // 调用mappingParamSelector函数 + (bool success4, bytes memory data4) = address(this).call(abi.encodeWithSelector(0xe355b0ce, 0x9D7f74d0C41E726EC95884E0e97Fa6129e3b5E99, user, param1, 1)); + require(success0 && success1 && success2 && success3 && success4); } -} +} \ No newline at end of file diff --git a/29_Selector/img/29-2.png b/29_Selector/img/29-2.png index 3202f7372..5fbad0c3b 100644 Binary files a/29_Selector/img/29-2.png and b/29_Selector/img/29-2.png differ diff --git a/29_Selector/img/29-3.png b/29_Selector/img/29-3.png index 014832282..2b7155e76 100644 Binary files a/29_Selector/img/29-3.png and b/29_Selector/img/29-3.png differ diff --git a/29_Selector/readme.md b/29_Selector/readme.md index 88ec17dda..bb5d0b941 100644 --- a/29_Selector/readme.md +++ b/29_Selector/readme.md @@ -80,18 +80,91 @@ function mintSelector() external pure returns(bytes4 mSelector){ ![method id in remix](./img/29-2.png) -### 使用selector +由于计算`method id`时,需要通过函数名和函数的参数类型来计算。在`Solidity`中,函数的参数类型主要分为:基础类型参数,固定长度类型参数,可变长度类型参数和映射类型参数。 + +##### 基础类型参数 +`solidity`中,基础类型的参数有:`uint256`(`uint8`, ... , `uint256`)、`bool`, `address`等。在计算`method id`时,只需要计算`bytes4(keccak256("函数名(参数类型1,参数类型2,...)"))`。例如,如下函数,函数名为`elementaryParamSelector`,参数类型分别为`uint256`和`bool`。所以,只需要计算`bytes4(keccak256("elementaryParamSelector(uint256,bool)"))`便可得到此函数的`method id`。 +```solidity +    // elementary(基础)类型参数selector +    // 输入:param1: 1,param2: 0 +    // elementaryParamSelector(uint256,bool) : 0x3ec37834 +    function elementaryParamSelector(uint256 param1, bool param2) external returns(bytes4 selectorWithElementaryParam){ +        emit SelectorEvent(this.elementaryParamSelector.selector); +        return bytes4(keccak256("elementaryParamSelector(uint256,bool)")); +    } +``` -我们可以利用`selector`来调用目标函数。例如我想调用`mint`函数,我只需要利用`abi.encodeWithSelector`将`mint`函数的`method id`作为`selector`和参数打包编码,传给`call`函数: +##### 固定长度类型参数 +固定长度的参数类型通常为固定长度的数组,例如:`uint256[5]`等。例如,如下函数`fixedSizeParamSelector`的参数为`uint256[3]`。因此,在计算该函数的`method id`时,只需要通过`bytes4(keccak256("fixedSizeParamSelector(uint256[3])"))`即可。 ```solidity -function callWithSignature() external returns(bool, bytes memory){ - (bool success, bytes memory data) = address(this).call(abi.encodeWithSelector(0x6a627842, 0x2c44b726ADF1963cA47Af88B284C06f30380fC78)); - return(success, data); +    // fixed size(固定长度)类型参数selector +    // 输入: param1: [1,2,3] +    // fixedSizeParamSelector(uint256[3]) : 0xead6b8bd +    function fixedSizeParamSelector(uint256[3] memory param1) external returns(bytes4 selectorWithFixedSizeParam){ +        emit SelectorEvent(this.fixedSizeParamSelector.selector); +        return bytes4(keccak256("fixedSizeParamSelector(uint256[3])")); +    } +``` + +##### 可变长度类型参数 +可变长度参数类型通常为可变长的数组,例如:`address[]`、`uint8[]`、`string`等。例如,如下函数`nonFixedSizeParamSelector`的参数为`uint256[]`和`string`。因此,在计算该函数的`method id`时,只需要通过`bytes4(keccak256("nonFixedSizeParamSelector(uint256[],string)"))`即可。 + +```solidity +    // non-fixed size(可变长度)类型参数selector +    // 输入: param1: [1,2,3], param2: "abc" +    // nonFixedSizeParamSelector(uint256[],string) : 0xf0ca01de +    function nonFixedSizeParamSelector(uint256[] memory param1,string memory param2) external returns(bytes4 selectorWithNonFixedSizeParam){ +        emit SelectorEvent(this.nonFixedSizeParamSelector.selector); +        return bytes4(keccak256("nonFixedSizeParamSelector(uint256[],string)")); +    } +``` + +##### 映射类型参数 +映射类型参数通常有:`contract`、`enum`、`struct`等。在计算`method id`时,需要将该类型转化成为`ABI`类型。 + +例如,如下函数`mappingParamSelector`中`DemoContract`需要转化为`address`,结构体`User`需要转化为`tuple`类型`(uint256,bytes)`,枚举类型`School`需要转化为`uint8`。因此,计算该函数的`method id`的代码为`bytes4(keccak256("mappingParamSelector(address,(uint256,bytes),uint256[],uint8)"))`。 + +```solidity +contract DemoContract { +    // empty contract } + +contract Selector{ +    // Struct User +    struct User { +        uint256 uid; +        bytes name; +    } +    // Enum School +    enum School { SCHOOL1, SCHOOL2, SCHOOL3 } +    ... +    // mapping(映射)类型参数selector +    // 输入:demo: 0x9D7f74d0C41E726EC95884E0e97Fa6129e3b5E99, user: [1, "0xa0b1"], count: [1,2,3], mySchool: 1 +    // mappingParamSelector(address,(uint256,bytes),uint256[],uint8) : 0xe355b0ce +    function mappingParamSelector(DemoContract demo, User memory user, uint256[] memory count, School mySchool) external returns(bytes4 selectorWithMappingParam){ +        emit SelectorEvent(this.mappingParamSelector.selector); +        return bytes4(keccak256("mappingParamSelector(address,(uint256,bytes),uint256[],uint8)")); +    } +    ... +} +``` + +### 使用selector + +我们可以利用`selector`来调用目标函数。例如我想调用`elementaryParamSelector`函数,我只需要利用`abi.encodeWithSelector`将`elementaryParamSelector`函数的`method id`作为`selector`和参数打包编码,传给`call`函数: + +```solidity + // 使用selector来调用函数 + function callWithSignature() external{ + ... + // 调用elementaryParamSelector函数 + (bool success1, bytes memory data1) = address(this).call(abi.encodeWithSelector(0x3ec37834, 1, 0)); + ... + } ``` -在日志中,我们可以看到`mint`函数被成功调用,并输出`Log`事件。 +在日志中,我们可以看到`elementaryParamSelector`函数被成功调用,并输出`Log`事件。 ![logs in remix](./img/29-3.png) diff --git a/34_ERC721/Address.sol b/34_ERC721/Address.sol deleted file mode 100644 index 0861a44c8..000000000 --- a/34_ERC721/Address.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.1; - -// Address库 -library Address { - // 利用extcodesize判断一个地址是否为合约地址 - function isContract(address account) internal view returns (bool) { - uint size; - assembly { - size := extcodesize(account) - } - return size > 0; - } -} diff --git a/34_ERC721/ERC721.sol b/34_ERC721/ERC721.sol index 824928191..d43b33517 100644 --- a/34_ERC721/ERC721.sol +++ b/34_ERC721/ERC721.sol @@ -6,11 +6,9 @@ import "./IERC165.sol"; import "./IERC721.sol"; import "./IERC721Receiver.sol"; import "./IERC721Metadata.sol"; -import "./Address.sol"; import "./String.sol"; contract ERC721 is IERC721, IERC721Metadata{ - using Address for address; // 使用Address库,用isContract来判断地址是否为合约 using Strings for uint256; // 使用String库, // Token名称 @@ -26,6 +24,9 @@ contract ERC721 is IERC721, IERC721Metadata{ // owner地址。到operator地址 的批量授权映射 mapping(address => mapping(address => bool)) private _operatorApprovals; + // 错误 无效的接收者 + error ERC721InvalidReceiver(address receiver); + /** * 构造函数,初始化`name` 和`symbol` . */ @@ -165,7 +166,7 @@ contract ERC721 is IERC721, IERC721Metadata{ bytes memory _data ) private { _transfer(owner, from, to, tokenId); - require(_checkOnERC721Received(from, to, tokenId, _data), "not ERC721Receiver"); + _checkOnERC721Received(from, to, tokenId, _data); } /** @@ -225,22 +226,22 @@ contract ERC721 is IERC721, IERC721Metadata{ } // _checkOnERC721Received:函数,用于在 to 为合约的时候调用IERC721Receiver-onERC721Received, 以防 tokenId 被不小心转入黑洞。 - function _checkOnERC721Received( - address from, - address to, - uint tokenId, - bytes memory _data - ) private returns (bool) { - if (to.isContract()) { - return - IERC721Receiver(to).onERC721Received( - msg.sender, - from, - tokenId, - _data - ) == IERC721Receiver.onERC721Received.selector; - } else { - return true; + function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory data) private { + if (to.code.length > 0) { + try IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, data) returns (bytes4 retval) { + if (retval != IERC721Receiver.onERC721Received.selector) { + revert ERC721InvalidReceiver(to); + } + } catch (bytes memory reason) { + if (reason.length == 0) { + revert ERC721InvalidReceiver(to); + } else { + /// @solidity memory-safe-assembly + assembly { + revert(add(32, reason), mload(reason)) + } + } + } } } diff --git a/34_ERC721/readme.md b/34_ERC721/readme.md index 54b960ddd..1eb080c0a 100644 --- a/34_ERC721/readme.md +++ b/34_ERC721/readme.md @@ -150,24 +150,32 @@ interface IERC721Receiver { 我们看下`ERC721`利用`_checkOnERC721Received`来确保目标合约实现了`onERC721Received()`函数(返回`onERC721Received`的`selector`): ```solidity - function _checkOnERC721Received( - address from, - address to, - uint tokenId, - bytes memory _data - ) private returns (bool) { - if (to.isContract()) { - return - IERC721Receiver(to).onERC721Received( - msg.sender, - from, - tokenId, - _data - ) == IERC721Receiver.onERC721Received.selector; - } else { - return true; +function _checkOnERC721Received( + address operator, + address from, + address to, + uint256 tokenId, + bytes memory data +) internal { + if (to.code.length > 0) { + try IERC721Receiver(to).onERC721Received(operator, from, tokenId, data) returns (bytes4 retval) { + if (retval != IERC721Receiver.onERC721Received.selector) { + // Token rejected + revert IERC721Errors.ERC721InvalidReceiver(to); + } + } catch (bytes memory reason) { + if (reason.length == 0) { + // non-IERC721Receiver implementer + revert IERC721Errors.ERC721InvalidReceiver(to); + } else { + /// @solidity memory-safe-assembly + assembly { + revert(add(32, reason), mload(reason)) + } + } } } +} ``` ## IERC721Metadata @@ -199,11 +207,9 @@ import "./IERC165.sol"; import "./IERC721.sol"; import "./IERC721Receiver.sol"; import "./IERC721Metadata.sol"; -import "./Address.sol"; import "./String.sol"; contract ERC721 is IERC721, IERC721Metadata{ - using Address for address; // 使用Address库,用isContract来判断地址是否为合约 using Strings for uint256; // 使用String库, // Token名称 @@ -219,6 +225,9 @@ contract ERC721 is IERC721, IERC721Metadata{ // owner地址。到operator地址 的批量授权映射 mapping(address => mapping(address => bool)) private _operatorApprovals; + // 错误 无效的接收者 + error ERC721InvalidReceiver(address receiver); + /** * 构造函数,初始化`name` 和`symbol` . */ @@ -294,7 +303,7 @@ contract ERC721 is IERC721, IERC721Metadata{ _approve(owner, to, tokenId); } - // 查询 spender地址是否可以使用tokenId(他是owner或被授权地址)。 + // 查询 spender地址是否可以使用tokenId(需要是owner或被授权地址) function _isApprovedOrOwner( address owner, address spender, @@ -358,7 +367,7 @@ contract ERC721 is IERC721, IERC721Metadata{ bytes memory _data ) private { _transfer(owner, from, to, tokenId); - require(_checkOnERC721Received(from, to, tokenId, _data), "not ERC721Receiver"); + _checkOnERC721Received(from, to, tokenId, _data); } /** @@ -418,22 +427,22 @@ contract ERC721 is IERC721, IERC721Metadata{ } // _checkOnERC721Received:函数,用于在 to 为合约的时候调用IERC721Receiver-onERC721Received, 以防 tokenId 被不小心转入黑洞。 - function _checkOnERC721Received( - address from, - address to, - uint tokenId, - bytes memory _data - ) private returns (bool) { - if (to.isContract()) { - return - IERC721Receiver(to).onERC721Received( - msg.sender, - from, - tokenId, - _data - ) == IERC721Receiver.onERC721Received.selector; - } else { - return true; + function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory data) private { + if (to.code.length > 0) { + try IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, data) returns (bytes4 retval) { + if (retval != IERC721Receiver.onERC721Received.selector) { + revert ERC721InvalidReceiver(to); + } + } catch (bytes memory reason) { + if (reason.length == 0) { + revert ERC721InvalidReceiver(to); + } else { + /// @solidity memory-safe-assembly + assembly { + revert(add(32, reason), mload(reason)) + } + } + } } } @@ -455,6 +464,7 @@ contract ERC721 is IERC721, IERC721Metadata{ return ""; } } + ``` ## 写一个免费铸造的APE diff --git a/36_MerkleTree/readme.md b/36_MerkleTree/readme.md index fdec082af..7f10a9e6a 100644 --- a/36_MerkleTree/readme.md +++ b/36_MerkleTree/readme.md @@ -27,7 +27,7 @@ tags: ![Merkle Tree](./img/36-1.png) -`Merkle Tree`允许对大型数据结构的内容进行有效和安全的验证(`Merkle Proof`)。对于有`N`个叶子结点的`Merkle Tree`,在已知`root`根值的情况下,验证某个数据是否有效(属于`Merkle Tree`叶子结点)只需要`log(N)`个数据(也叫`proof`),非常高效。如果数据有误,或者给的`proof`错误,则无法还原出`root`根植。 +`Merkle Tree`允许对大型数据结构的内容进行有效和安全的验证(`Merkle Proof`)。对于有`N`个叶子结点的`Merkle Tree`,在已知`root`根值的情况下,验证某个数据是否有效(属于`Merkle Tree`叶子结点)只需要`ceil(log₂N)`个数据(也叫`proof`),非常高效。如果数据有误,或者给的`proof`错误,则无法还原出`root`根植。 下面的例子中,叶子`L1`的`Merkle proof`为`Hash 0-1`和`Hash 1`:知道这两个值,就能验证`L1`的值是不是在`Merkle Tree`的叶子中。为什么呢? 因为通过叶子`L1`我们就可以算出`Hash 0-0`,我们又知道了`Hash 0-1`,那么`Hash 0-0`和`Hash 0-1`就可以联合算出`Hash 0`,然后我们又知道`Hash 1`,`Hash 0`和`Hash 1`就可以联合算出`Top Hash`,也就是root节点的hash。 @@ -114,7 +114,7 @@ library MerkleProof { 3. `_hashPair()`函数:用`keccak256()`函数计算非根节点对应的两个子节点的哈希(排序后)。 -我们将`地址0`,`root`和对应的`proof`输入到`verify()`函数,将返回`true`。因为`地址0`在根为`root`的`Merkle Tree`中,且`proof`正确。如果改变了其中任意一个值,都将返回`false`。 +我们将`地址0`的`Hash`,`root`和对应的`proof`输入到`verify()`函数,将返回`true`。因为`地址0`的`Hash`在根为`root`的`Merkle Tree`中,且`proof`正确。如果改变了其中任意一个值,都将返回`false`。 ## 利用`Merkle Tree`发放`NFT`白名单 diff --git a/51_ERC4626/readme.md b/51_ERC4626/readme.md index 1a6d0d284..56d8842d0 100644 --- a/51_ERC4626/readme.md +++ b/51_ERC4626/readme.md @@ -29,7 +29,7 @@ tags: - 收益农场: 在 Yearn Finance 中,你可以质押 `USDT` 获取利息。 - 借贷: 在 AAVE 中,你可以出借 `ETH` 获取存款利息和贷款。 -- 质押: 在 Lido 中,你可以质押 `ETH` 参与 ETH 2.0 质押,得到可以升息的 `stETH`。 +- 质押: 在 Lido 中,你可以质押 `ETH` 参与 ETH 2.0 质押,得到可以生息的 `stETH`。 ## ERC4626 diff --git a/53_ERC20Permit/readme.md b/53_ERC20Permit/readme.md index ede498bc1..901975a98 100644 --- a/53_ERC20Permit/readme.md +++ b/53_ERC20Permit/readme.md @@ -198,7 +198,7 @@ contract ERC20Permit is ERC20, IERC20Permit, EIP712 { 3. 调用合约的 `permit()` 方法,输入相应参数,进行授权。 -4. 调用合约的 `allance()` 方法,输入相应的 `owner` 和 `spender`,可以看到授权成功。 +4. 调用合约的 `allowance()` 方法,输入相应的 `owner` 和 `spender`,可以看到授权成功。 ## 安全注意 @@ -206,6 +206,8 @@ ERC20Permit 利用链下签名进行授权给用户带来了便利,同时带 **签名时,一定要谨慎的阅读签名内容!** +同时,一些合约在集成`permit`时,也会带来DoS(拒绝服务)的风险。因为`permit`在执行时会用掉当前的`nonce`值,如果合约的函数中包含`permit`操作,则攻击者可以通过抢跑执行`permit`从而使得目标交易因为`nonce`被占用而回滚。 + ## 总结 这一讲,我们介绍了 ERC20Permit,一个 ERC20 代币标准的拓展,支持用户使用链下签名进行授权操作,改善了用户体验,被很多项目采用。但同时,它也带来了更大的风险,一个签名就能将你的资产卷走。大家在签名时一定要更加谨慎。 diff --git a/56_DEX/readme.md b/56_DEX/readme.md index e004dbb52..554e31c00 100644 --- a/56_DEX/readme.md +++ b/56_DEX/readme.md @@ -26,7 +26,7 @@ tags: ![](./img/56-1.png) -接下来,我们以可乐($COLA)和美元($USD)的市场为例,给大家介绍 AMM。为了方便,我们规定一下符号: $x$ 和 $y$ 分别表示市场中可乐和美元的总量,$\Delta x$ 和 $\Delta y$ 分别表示一笔交易中可乐和美元的变化量,$L$ 和 $\Delta L$ 表示总流动性和流动性的变化量。 +接下来,我们以可乐($COLA)和美元($USD)的市场为例,给大家介绍 AMM。为了方便,我们规定一下符号: $x$ 和 $y$ 分别表示市场中可乐和美元的总量, $\Delta x$ 和 $\Delta y$ 分别表示一笔交易中可乐和美元的变化量,$L$ 和 $\Delta L$ 表示总流动性和流动性的变化量。 ### 恒定总和自动做市商 @@ -442,4 +442,4 @@ contract SimpleSwap is ERC20 { ## 总结 -这一讲,我们介绍了恒定乘积自动做市商,并写了一个极简的去中心化交易所。在极简Swap合约中,我们有很多没有考虑的部分,例如交易费用以及治理部分。如果你对去中心化交易所感兴趣,推荐你阅读 [Programming DeFi: Uniswap V2](https://jeiwan.net/posts/programming-defi-uniswapv2-1/) 和 [Uniswap v3 book](https://y1cunhui.github.io/uniswapV3-book-zh-cn/) ,更深入的学习。 \ No newline at end of file +这一讲,我们介绍了恒定乘积自动做市商,并写了一个极简的去中心化交易所。在极简Swap合约中,我们有很多没有考虑的部分,例如交易费用以及治理部分。如果你对去中心化交易所感兴趣,推荐你阅读 [Programming DeFi: Uniswap V2](https://jeiwan.net/posts/programming-defi-uniswapv2-1/) 和 [Uniswap v3 book](https://y1cunhui.github.io/uniswapV3-book-zh-cn/) ,更深入的学习。 diff --git a/Languages/en/S01_ReentrancyAttack_en/readme.md b/Languages/en/S01_ReentrancyAttack_en/readme.md index f0cfd1e45..5e7e3276a 100644 --- a/Languages/en/S01_ReentrancyAttack_en/readme.md +++ b/Languages/en/S01_ReentrancyAttack_en/readme.md @@ -21,7 +21,7 @@ English translations by: [@to_22X](https://twitter.com/to_22X) --- -In this lesson, we will introduce the most common type of smart contract attack - reentrancy attack, which has led to the Ethereum fork into ETH and ETC (Ethereum Classic), and discuss how to prevent it. +In this lesson, we will introduce the most common type of smart contract attack - a reentrancy attack, which has led to the Ethereum fork into ETH and ETC (Ethereum Classic), and discuss how to prevent it. ## Reentrancy Attack @@ -58,7 +58,7 @@ One day, the hacker `0xAA` came to the bank and had the following conversation w - 0xAA: Wait, I want to withdraw `1 ETH`. - ... -In the end, `0xAA` emptied the bank's assets through the vulnerability of reentrancy attack, and the bank collapsed. +In the end, `0xAA` emptied the bank's assets through the vulnerability of a reentrancy attack, and the bank collapsed. ![](./img/S01-1.png) @@ -68,8 +68,8 @@ In the end, `0xAA` emptied the bank's assets through the vulnerability of reentr The bank contract is very simple and includes `1` state variable `balanceOf` to record the Ethereum balance of all users. It also includes `3` functions: -- `deposit()`: Deposit function that allows users to deposit `ETH` into the bank contract and updates their balances. -- `withdraw()`: Withdraw function that transfers the caller's balance to them. The steps are the same as in the story above: check balance, transfer funds, update balance. **Note: This function has a reentrancy vulnerability!** +- `deposit()`: Deposit function that allows users to deposit `ETH` into the bank contract and update their balances. +- `withdraw()`: Withdraw function that transfers the caller's balance to them. The steps are the same as in the story above: check balance, transfer funds, and update balance. **Note: This function has a reentrancy vulnerability!** - `getBalance()`: Get the `ETH` balance in the bank contract. ```solidity @@ -101,7 +101,7 @@ contract Bank { ### Attack Contract -One vulnerability point of reentrancy attack is the transfer of `ETH` in the contract: if the target address of the transfer is a contract, it will trigger the fallback function of the contract, potentially causing a loop. If you are not familiar with fallback functions, you can read [WTF Solidity: 19: Receive ETH](https://github.com/AmazingAng/WTF-Solidity/blob/main/Languages/en/19_Fallback_en/readme.md). The `Bank` contract has an `ETH` transfer in the `withdraw()` function: +One vulnerability point of a reentrancy attack is the transfer of `ETH` in the contract: if the target address of the transfer is a contract, it will trigger the fallback function of the contract, potentially causing a loop. If you are not familiar with fallback functions, you can read [WTF Solidity: 19: Receive ETH](https://github.com/AmazingAng/WTF-Solidity/blob/main/Languages/en/19_Fallback_en/readme.md). The `Bank` contract has an `ETH` transfer in the `withdraw()` function: ``` (bool success, ) = msg.sender.call{value: balance}(""); @@ -216,4 +216,4 @@ function withdraw() external nonReentrant{ ## Summary -In this lesson, we introduced the most common attack in Ethereum - the reentrancy attack, and made a story of robbing a bank with `0xAA` to help understand it. Finally, we discussed two methods to prevent reentrancy attacks: the checks-effect-interaction pattern and the reentrant lock. In the example, the hacker exploited the fallback function to perform a reentrancy attack during `ETH` transfer in the target contract. In real-world scenarios, the `safeTransfer()` and `safeTransferFrom()` functions of `ERC721` and `ERC1155`, as well as the fallback function of `ERC777`, can also potentially trigger reentrancy attacks. For beginners, my suggestion is to use a reentrant lock to protect all `external` functions that can change the contract state. Although it may consume more `gas`, it can prevent greater losses. +In this lesson, we introduced the most common attack in Ethereum - the reentrancy attack and made a story of robbing a bank with `0xAA` to help understand it. Finally, we discussed two methods to prevent reentrancy attacks: the checks-effect-interaction pattern and the reentrant lock. In the example, the hacker exploited the fallback function to perform a reentrancy attack during the `ETH` transfer in the target contract. In real-world scenarios, the `safeTransfer()` and `safeTransferFrom()` functions of `ERC721` and `ERC1155`, as well as the fallback function of `ERC777`, can also potentially trigger reentrancy attacks. For beginners, my suggestion is to use a reentrant lock to protect all `external` functions that can change the contract state. Although it may consume more `gas`, it can prevent greater losses. diff --git a/Languages/en/S02_SelectorClash_en/readme.md b/Languages/en/S02_SelectorClash_en/readme.md index 71883cdd0..dac72e30f 100644 --- a/Languages/en/S02_SelectorClash_en/readme.md +++ b/Languages/en/S02_SelectorClash_en/readme.md @@ -21,7 +21,7 @@ English translations by: [@to_22X](https://twitter.com/to_22X) --- -In this lesson, we will introduce the selector clash attack, which is one of the reasons behind the hack of the cross-chain bridge Poly Network. In August 2021, the cross-chain bridge contracts of Poly Network on ETH, BSC, and Polygon were hacked, resulting in a loss of up to $611 million ([summary](https://rekt.news/zh/polynetwork-rekt/)). This is the largest blockchain hack of 2021 and the second-largest in history, second only to the Ronin bridge hack. +In this lesson, we will introduce the selector clash attack, which is one of the reasons behind the hack of the cross-chain bridge Poly Network. In August 2021, the cross-chain bridge contracts of Poly Network on ETH, BSC, and Polygon were hacked, resulting in a loss of up to $611 million ([summary](https://rekt.news/zh/polynetwork-rekt/)). This is the largest blockchain hack of 2021 and the second-largest in history, second only to the Ronin Bridge hack. ## Selector Clash @@ -46,9 +46,9 @@ In contrast, the public key of a wallet is `64` bytes long and the probability o The people of Ethereum have angered the gods, and the gods are furious. In order to punish the people of Ethereum, the goddess Hera sends down a Sphinx, a creature with the head of a human and the body of a lion, to the cliffs of Ethereum. The Sphinx presents a riddle to every Ethereum user who passes by the cliff: "What walks on four legs in the morning, two legs at noon, and three legs in the evening? It is the only creature that walks on different numbers of legs throughout its life. When it has the most legs, it is at its slowest and weakest." Those who solve this enigmatic riddle will be spared, while those who fail to solve it will be devoured. The Sphinx uses the selector `0x10cd2dc7` to verify the correct answer. -One morning, Oedipus passes by and encounters the Sphinx. He solves the mysterious riddle and says, "It is `function man()`. In the morning of life, he is a child who crawls on two legs and two hands. At noon, he becomes an adult who walks on two legs. In the evening, he grows old and weak, and needs a cane to walk, hence he is called three-legged." After guessing the riddle correctly, Oedipus is allowed to live. +One morning, Oedipus passes by and encounters the Sphinx. He solves the mysterious riddle and says, "It is `function man()`. In the morning of life, he is a child who crawls on two legs and two hands. At noon, he becomes an adult who walks on two legs. In the evening, he grows old and weak and needs a cane to walk, hence he is called three-legged." After guessing the riddle correctly, Oedipus is allowed to live. -Later that afternoon, `0xAA` passes by and encounters the Sphinx. He also solves the mysterious riddle and says, "It is `function peopleLduohW(uint256)`. In the morning of life, he is a child who crawls on two legs and two hands. At noon, he becomes an adult who walks on two legs. In the evening, he grows old and weak, and needs a cane to walk, hence he is called three-legged." Once again, the riddle is guessed correctly, and the Sphinx becomes furious. In a fit of anger, the Sphinx slips and falls from the towering cliff to its death. +Later that afternoon, `0xAA` passes by and encounters the Sphinx. He also solves the mysterious riddle and says, "It is `function peopleLduohW(uint256)`. In the morning of life, he is a child who crawls on two legs and two hands. At noon, he becomes an adult who walks on two legs. In the evening, he grows old and weak and needs a cane to walk, hence he is called three-legged." Once again, the riddle is guessed correctly, and the Sphinx becomes furious. In a fit of anger, the Sphinx slips and falls from the towering cliff to its death. ![](./img/S02-2.png) @@ -88,7 +88,7 @@ In the Poly Network hack, the hacker collided the `_method` as `f1121318093`, wh ## Reproduce on `Remix` 1. Deploy the `SelectorClash` contract. -2. Call `executeCrossChainTx()` with the parameters `0x6631313231333138303933`, `0x`, `0x`, `0`, to initiate the attack. +2. Call `executeCrossChainTx()` with the parameters `0x6631313231333138303933`, `0x`, `0x`, and `0`, to initiate the attack. 3. Check the value of the `solved` variable, which should be modified to `true`, indicating a successful attack. ## Summary diff --git a/Languages/en/S04_AccessControlExploit_en/readme.md b/Languages/en/S04_AccessControlExploit_en/readme.md index 8e1ec3ff3..2a93c5a60 100644 --- a/Languages/en/S04_AccessControlExploit_en/readme.md +++ b/Languages/en/S04_AccessControlExploit_en/readme.md @@ -82,4 +82,4 @@ function goodBurn(address account, uint amount) public { ## Summary -In this lesson, we discussed access control vulnerabilities in smart contracts. There are two main forms: incorrect access configuration and authorization check errors. To avoid these vulnerabilities, we should use a access control library to configure appropriate accesses for special functions and ensure that the caller of the function has sufficient authorization within the function's logic. +In this lesson, we discussed access control vulnerabilities in smart contracts. There are two main forms: incorrect access configuration and authorization check errors. To avoid these vulnerabilities, we should use an access control library to configure appropriate accesses for special functions and ensure that the caller of the function has sufficient authorization within the function's logic. diff --git a/Languages/en/S06_SignatureReplay_en/readme.md b/Languages/en/S06_SignatureReplay_en/readme.md index 1934303fd..01809eced 100644 --- a/Languages/en/S06_SignatureReplay_en/readme.md +++ b/Languages/en/S06_SignatureReplay_en/readme.md @@ -20,7 +20,7 @@ English translations by: [@to_22X](https://twitter.com/to_22X) --- -In this lesson, we will introduce the Signature Replay attack and how to prevent in smart contracts, which indirectly led to the theft of 20 million $OP tokens from the famous market maker Wintermute. +In this lesson, we will introduce the Signature Replay attack and how to prevent it in smart contracts, which indirectly led to the theft of 20 million $OP tokens from the famous market maker Wintermute. ## Signature Replay @@ -37,7 +37,7 @@ There are generally two common types of replay attacks on digital signatures: ## Vulnerable Contract Example -The `SigReplay` contract below is an `ERC20` token contract that has a signature replay vulnerability in its minting function. It uses off-chain signatures to allow whitelisted address `to` to mint a corresponding amount `amount` of tokens. The contract stores the `signer` address to verify the validity of the signature. +The `SigReplay` contract below is an `ERC20` token contract that has a signature replay vulnerability in its minting function. It uses off-chain signatures to allow whitelisted addresses `to` mint a corresponding amount `amount` of tokens. The contract stores the `signer` address to verify the validity of the signature. ```solidity // SPDX-License-Identifier: MIT @@ -162,7 +162,7 @@ There are two main methods to prevent signature replay attacks: ## Summary -In this lesson, we discussed the signature replay vulnerability in smart contracts and introduced two methods to prevent: +In this lesson, we discussed the signature replay vulnerability in smart contracts and introduced two methods to prevent it: 1. Keep a record of used signatures to prevent their reuse. diff --git a/Languages/en/S07_BadRandomness_en/readme.md b/Languages/en/S07_BadRandomness_en/readme.md index 2493c19c6..816250d57 100644 --- a/Languages/en/S07_BadRandomness_en/readme.md +++ b/Languages/en/S07_BadRandomness_en/readme.md @@ -85,9 +85,9 @@ Since the Remix VM does not support the `blockhash` function, you need to deploy ## How to Prevent -To prevent such vulnerabilities, we often use off-chain random numbers provided by oracle projects, such as Chainlink VRF. These random numbers are generated off-chain and then uploaded to the blockchain, ensuring that the numbers are unpredictable. For more information, you can read [WTF Solidity 39: Pseudo-random Numbers](https://github.com/AmazingAng/WTF-Solidity/tree/main/39_Random). +To prevent such vulnerabilities, we often use off-chain random numbers provided by Oracle projects, such as Chainlink VRF. These random numbers are generated off-chain and then uploaded to the blockchain, ensuring that the numbers are unpredictable. For more information, you can read [WTF Solidity 39: Pseudo-random Numbers](https://github.com/AmazingAng/WTF-Solidity/tree/main/39_Random). ## Summary -In this lesson, we introduced the Bad Randomness vulnerability and discussed a simple method to prevent it: using off-chain random numbers provided by oracle projects. NFT and GameFi projects should avoid using on-chain pseudorandom numbers for lotteries to prevent exploitation by hackers. +In this lesson, we introduced the Bad Randomness vulnerability and discussed a simple method to prevent it: using off-chain random numbers provided by Oracle projects. NFT and GameFi projects should avoid using on-chain pseudorandom numbers for lotteries to prevent exploitation by hackers. diff --git a/Languages/en/S09_DoS_en/readme.md b/Languages/en/S09_DoS_en/readme.md index 27ff8d1ef..9ed5f146c 100644 --- a/Languages/en/S09_DoS_en/readme.md +++ b/Languages/en/S09_DoS_en/readme.md @@ -26,7 +26,7 @@ In this lesson, we will introduce the Denial of Service (DoS) vulnerability in s In Web2, a Denial of Service (DoS) attack refers to the phenomenon of overwhelming a server with a large amount of junk or disruptive information, rendering it unable to serve legitimate users. In Web3, it refers to exploiting vulnerabilities that prevent a smart contract from functioning properly. -In April 2022, a popular NFT project called Akutar raised 11,539.5 ETH through a Dutch auction for its public launch, achieving great success. Participants who held their community Pass were supposed to receive a refund of 0.5 ETH. However, when they attempted to process the refunds, they discovered that the smart contract was unable to function correctly, resulting in all funds being permanently locked in the contract. Their smart contract had a DoS vulnerability. +In April 2022, a popular NFT project called Akutar raised 11,539.5 ETH through a Dutch auction for its public launch, achieving great success. Participants who held their community Passes were supposed to receive a refund of 0.5 ETH. However, when they attempted to process the refunds, they discovered that the smart contract was unable to function correctly, resulting in all funds being permanently locked in the contract. Their smart contract had a DoS vulnerability. ![](./img/S09-1.png) diff --git a/Languages/en/S10_Honeypot_en/readme.md b/Languages/en/S10_Honeypot_en/readme.md index 9977f3c1a..878fc99c7 100644 --- a/Languages/en/S10_Honeypot_en/readme.md +++ b/Languages/en/S10_Honeypot_en/readme.md @@ -31,8 +31,8 @@ In this lesson, we will introduce the Pixiu contract and stay away from Pixiu to Typically, a Pixiu scam follows the following lifecycle: -1. Malicious project owner deploys the Pixiu token contract. -2. Promote the Pixiu token to retail investors, and due to the inability to sell, the token price keeps rising. +1. A malicious project owner deploys the Pixiu token contract. +2. They promote the Pixiu token to retail investors, and due to the inability to sell, the token price keeps rising. 3. The project owner performs a "rug pull" and runs away with the funds. ![](./img/S10-1.png) diff --git a/Languages/en/S11_Frontrun_en/readme.md b/Languages/en/S11_Frontrun_en/readme.md index 2d85aeb51..486b88c88 100644 --- a/Languages/en/S11_Frontrun_en/readme.md +++ b/Languages/en/S11_Frontrun_en/readme.md @@ -166,7 +166,7 @@ main(); **5. Call the `mint()` function:** Call the `mint()` function of the Freemint contract on the deployment page of Remix to mint an NFT. -**6. Script detects and frontruns the transaction:** We can see in the terminal that the `frontrun.js` script successfully detects the transaction and frontruns it. If you call the `ownerOf()` function of the NFT contract to check the owner of `tokenId` 0, and it matches the wallet address in the frontrun script, it proves that the frontrun was successful!. +**6. The script detects and frontruns the transaction:** We can see in the terminal that the `frontrun.js` script successfully detects the transaction and frontruns it. If you call the `ownerOf()` function of the NFT contract to check the owner of `tokenId` 0, and it matches the wallet address in the frontrun script, it proves that the frontrun was successful! ![](./img/S11-4.png) ## How to Prevent @@ -174,7 +174,7 @@ main(); Frontrunning is a common issue on Ethereum and other public blockchains. While we cannot eliminate it entirely, we can reduce the profitability of frontrunning by minimizing the importance of transaction order or time: - Use a commit-reveal scheme. -- Use dark pools, where user transactions bypass the public mempool and go directly to miners. Examples include flashbots and TaiChi. +- Use dark pools, where user transactions bypass the public mempool and go directly to miners. Examples include Flashbots and TaiChi. ## Summary diff --git a/Languages/en/S12_TxOrigin_en/img/S12-4.jpg b/Languages/en/S12_TxOrigin_en/img/S12-4.jpg index 58909597c..10ed99333 100644 Binary files a/Languages/en/S12_TxOrigin_en/img/S12-4.jpg and b/Languages/en/S12_TxOrigin_en/img/S12-4.jpg differ diff --git a/Languages/en/S12_TxOrigin_en/img/S12_1.jpg b/Languages/en/S12_TxOrigin_en/img/S12_1.jpg index fcb7b7b59..ee568e607 100644 Binary files a/Languages/en/S12_TxOrigin_en/img/S12_1.jpg and b/Languages/en/S12_TxOrigin_en/img/S12_1.jpg differ diff --git a/Languages/en/S13_UncheckedCall_en/readme.md b/Languages/en/S13_UncheckedCall_en/readme.md index e551d585e..e9b6ccc48 100644 --- a/Languages/en/S13_UncheckedCall_en/readme.md +++ b/Languages/en/S13_UncheckedCall_en/readme.md @@ -24,7 +24,7 @@ In this lesson, we will discuss the unchecked low-level calls in smart contracts ## Low-Level Calls -Low-level calls in Ethereum include `call()`, `delegatecall()`, `staticcall()`, and `send()`. These functions are different from other functions in Solidity. When an exception occurs, they do not pass it to the upper layer, nor do they cause the transaction to revert; they only return a boolean value `false` to indicate that the call failed. Therefore, if the return value of the low-level function call is not checked, the code of the upper layer function will continue to run regardless of whether the low-level call fails or not. For more information about low-level calls, please read [WTF Solidity 20-23](https://github.com/AmazingAng/WTF-Solidity) +Low-level calls in Ethereum include `call()`, `delegatecall()`, `staticcall()`, and `send()`. These functions are different from other functions in Solidity. When an exception occurs, they do not pass it to the upper layer, nor do they cause the transaction to revert; they only return a boolean value `false` to indicate that the call failed. Therefore, if the return value of the low-level function call is not checked, the code of the upper-layer function will continue to run regardless of whether the low-level call fails or not. For more information about low-level calls, please read [WTF Solidity 20-23](https://github.com/AmazingAng/WTF-Solidity) Calling `send()` is the most error-prone: some contracts use `send()` to send `ETH`, but `send()` limits the gas to be less than 2300, otherwise it will fail. When the callback function of the target address is more complicated, the gas spent will be higher than 2300, which will cause `send()` to fail. If the return value is not checked in the upper layer function at this time, the transaction will continue to execute, and unexpected problems will occur. In 2016, there was a chain game called `King of Ether`, which caused the refund to fail to be sent normally due to this vulnerability (["autopsy" report](https://www.kingoftheether.com/postmortem.html)). @@ -128,4 +128,4 @@ You can use the following methods to prevent the unchecked low-level call vulner ## Summary -We introduced the vulnerability of unchecked low-level calls and how to prevent. Ethereum's low-level calls (`call`, `delegatecall`, `staticcall`, `send`) will return a boolean value `false` when they fail, but they will not cause the entire transaction to revert. If the developer does not check it, an accident will occur. \ No newline at end of file +We introduced the vulnerability of unchecked low-level calls and how to prevent them. Ethereum's low-level calls (`call`, `delegatecall`, `staticcall`, `send`) will return a boolean value `false` when they fail, but they will not cause the entire transaction to revert. If the developer does not check it, an accident will occur. diff --git a/Languages/en/S14_TimeManipulation_en/readme.md b/Languages/en/S14_TimeManipulation_en/readme.md index c20126dae..d550792c3 100644 --- a/Languages/en/S14_TimeManipulation_en/readme.md +++ b/Languages/en/S14_TimeManipulation_en/readme.md @@ -54,7 +54,7 @@ contract TimeManipulation is ERC721 { ## Reproduce on Foundry -Attackers only need to manipulate the block timestamp and set it to a number that can be divided by 170, and they can successfully mint NFTs. We choose Foundry to reproduce this attack because it provides cheatcode to modify the block timestamp. If you are not familiar with Foundry/cheatcode, you can read the [Foundry tutorial](https://github.com/AmazingAng/WTF-Solidity/blob/main/Topics/Tools/TOOL07_Foundry/readme.md) and [Foundry Book](https://book.getfoundry.sh/forge/cheatcodes). +Attackers only need to manipulate the block timestamp and set it to a number that can be divided by 170, and they can successfully mint NFTs. We chose Foundry to reproduce this attack because it provides a cheat code to modify the block timestamp. If you are not familiar with Foundry/cheatcode, you can read the [Foundry tutorial](https://github.com/AmazingAng/WTF-Solidity/blob/main/Topics/Tools/TOOL07_Foundry/readme.md) and [Foundry Book](https://book.getfoundry.sh/forge/cheatcodes). 1. Create a `TimeManipulation` contract variable `nft`. 2. Create a wallet address `alice`. diff --git a/Languages/en/S15_OracleManipulation_en/readme.md b/Languages/en/S15_OracleManipulation_en/readme.md index 1d2c12f0c..3b3e44f4d 100644 --- a/Languages/en/S15_OracleManipulation_en/readme.md +++ b/Languages/en/S15_OracleManipulation_en/readme.md @@ -40,9 +40,9 @@ One of the most commonly used oracles is a price oracle, which refers to any dat If an oracle is not used correctly by developers, it can pose significant security risks. -- In October 2021, Cream Finance, a DeFi platform on the Binance Smart Chain, suffered a [theft of $130 million in user funds](https://rekt.news/cream-rekt-2/) due to an oracle vulnerability. -- In May 2022, Mirror Protocol, a synthetic asset platform on the Terra blockchain, suffered a [theft of $115 million in user funds](https://rekt.news/mirror-rekt/) due to an oracle vulnerability. -- In October 2022, Mango Market, a decentralized lending platform on the Solana blockchain, suffered a [theft of $115 million in user funds](https://rekt.news/mango-markets-rekt/) due to an oracle vulnerability. +- In October 2021, Cream Finance, a DeFi platform on the Binance Smart Chain, suffered a [theft of $130 million in user funds](https://rekt.news/cream-rekt-2/) due to an Oracle vulnerability. +- In May 2022, Mirror Protocol, a synthetic asset platform on the Terra blockchain, suffered a [theft of $115 million in user funds](https://rekt.news/mirror-rekt/) due to an Oracle vulnerability. +- In October 2022, Mango Market, a decentralized lending platform on the Solana blockchain, suffered a [theft of $115 million in user funds](https://rekt.news/mango-markets-rekt/) due to an Oracle vulnerability. ## Vulnerability Example @@ -116,7 +116,7 @@ These 4 steps can be completed in a single transaction. ### Reproduce on Foundry -We will use Foundry to reproduce the manipulation attack on the oracle because it is fast and allows us to create a local fork of the mainnet for testing. If you are not familiar with Foundry, you can read [WTF Solidity Tools T07: Foundry](https://github.com/AmazingAng/WTF-Solidity/blob/main/Topics/Tools/TOOL07_Foundry/readme.md). +We will use Foundry to reproduce the manipulation attack on the Oracle because it is fast and allows us to create a local fork of the mainnet for testing. If you are not familiar with Foundry, you can read [WTF Solidity Tools T07: Foundry](https://github.com/AmazingAng/WTF-Solidity/blob/main/Topics/Tools/TOOL07_Foundry/readme.md). 1. After installing Foundry, start a new project and install the OpenZeppelin library by running the following command in the command line: diff --git a/Languages/en/S16_NFTReentrancy_en/readme.md b/Languages/en/S16_NFTReentrancy_en/readme.md index dea93a089..fc4a34f10 100644 --- a/Languages/en/S16_NFTReentrancy_en/readme.md +++ b/Languages/en/S16_NFTReentrancy_en/readme.md @@ -41,7 +41,7 @@ Now let's learn an example of an NFT contract with a reentrancy vulnerability. T ### Vulnerable Contract -The `NFTReentrancy` contract inherits from the `ERC721` contract. It has two main state variables: `totalSupply` to track the total supply of NFTs and `mintedAddress` to keep track of addresses that have already minted to prevent a user from minting multiple times. It has two main functions: +The `NFTReentrancy` contract inherits from the `ERC721` contract. It has two main state variables: `totalSupply` to track the total supply of NFTs and `mintedAddress` to keep track of addresses that have already been minted to prevent a user from minting multiple times. It has two main functions: - Constructor: Initializes the name and symbol of the `ERC721` NFT. - `mint()`: Mint function where each user can mint one NFT for free. **Note: This function has a reentrancy vulnerability!** diff --git a/S11_Frontrun/readme.md b/S11_Frontrun/readme.md index f59fe6111..2ada723c3 100644 --- a/S11_Frontrun/readme.md +++ b/S11_Frontrun/readme.md @@ -166,6 +166,7 @@ main() - 使用预提交方案(commit-reveal scheme)。 - 使用暗池,用户发出的交易将不进入公开的`mempool`,而是直接到矿工手里。例如 flashbots 和 TaiChi。 +- 在调用参数中加上保护性参数,如[滑点保护](https://uniswapv3book.com/milestone_3/slippage-protection.html),从而减少抢跑者的潜在收益。 ## 总结 diff --git a/S17_CrossReentrancy/readme.md b/S17_CrossReentrancy/readme.md index c015db901..e4879a609 100644 --- a/S17_CrossReentrancy/readme.md +++ b/S17_CrossReentrancy/readme.md @@ -311,6 +311,7 @@ contract VulnerableBank { *“我不杀伯仁,伯仁却因我而死...” -- 戴锁婆婆* +针对`Read-Only Reentrancy`, [Euler Finance](https://github.com/euler-xyz/euler-contracts/commit/91adeee39daf8ece00584b6f7ec3e60a1d226bc9#diff-05f47d885ccf959493d5c53203672966544d73232f5410184d5484a7aedf0c5eR260)采用`read-only reentrancy guard`,仅当未加锁时才能进行读取。同时,锁的可见性可以设置为`public`以供其他项目使用。 ## 4. ERC721 & ERC777 Reentrancy