├── .gitignore ├── LICENSE ├── README.md ├── lesson-2 ├── HelloWorld.sol ├── HelloWorldFactory.sol └── README.md ├── lesson-3 ├── FundMe.sol ├── FundMe_flattened.sol ├── FundToken.sol ├── FundTokenERC20.sol ├── FundTokenERC20_flattened.sol └── README.md ├── lesson-4 ├── .gitignore ├── README.md ├── contracts │ └── FundMe.sol ├── hardhat.config.js ├── package-lock.json ├── package.json ├── scripts │ └── deployFundMe.js └── tasks │ ├── deploy-fundme.js │ ├── index.js │ └── interact-fundme.js ├── lesson-5 ├── README.md ├── contracts │ ├── FundMe.sol │ └── mocks │ │ └── MockV3Aggregator.sol ├── deploy │ ├── 00-deploy-mocks.js │ └── 01-deploy-fund-me.js ├── hardhat.config.js ├── helper-hardhat-config.js ├── package-lock.json ├── package.json ├── scripts │ └── deploy.js ├── tasks │ ├── deploy-fundme.js │ ├── index.js │ └── interact-fundme.js ├── test │ ├── staging │ │ └── FundMe.staging.test.js │ └── unit │ │ └── FundMe.test.js ├── utils │ └── verify.js └── yarn.lock └── lesson-6 ├── README.md ├── contracts ├── CCIPSimulator.sol ├── MyNFT.sol ├── NFTPoolBurnAndMint.sol ├── NFTPoolLockAndRelease.sol └── WrappedNFT.sol ├── deploy ├── 00_deploy_local_ccip.js ├── 01_deploy_nft.js ├── 02_deploy_lnu_pool.js ├── 03_deploy_wrapped_nft.js └── 04_deploy_mnb_pool.js ├── hardhat.config.js ├── helper-hardhat-config.js ├── package-lock.json ├── package.json ├── scripts ├── deploy.js └── deployAndInteract.js ├── task ├── burn-and-cross.js ├── check-nft.js ├── check-wrapped-nft.js ├── index.js ├── lock-and-cross.js └── mint-nft.js ├── test ├── staging │ └── FundMe.staging.test.js └── unit │ └── cross-chain-nft.test.js └── utils └── verify.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | .env.enc 4 | 5 | # Hardhat files 6 | cache 7 | artifacts 8 | deployments 9 | 10 | # TypeChain files 11 | typechain 12 | typechain-types 13 | 14 | # solidity-coverage files 15 | coverage 16 | coverage.json 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 SmartContract 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Web3 教程 - Solidity,Hardhat,Token standard,Interoperability 2 | This tutorial represents an educational example to use a Chainlink system, product, or service and is provided to demonstrate how to interact with Chainlink's systems, products, and services to integrate them into your own. This template is provided "AS IS" and "AS AVAILABLE" without warranties of any kind, it has not been audited, and it may be missing key checks or error handling to make the usage of the system, product or service more clear. Do not use the code in this example in a production environment without completing your own audits and application of best practices. Neither Chainlink Labs, the Chainlink Foundation, nor Chainlink node operators are responsible for unintended outputs that are generated due to errors in code. 3 | 4 | [中文](#内容介绍) | [English for review](#english-for-review) 5 | 6 | 这个 repository 是初学者课程《Web3 和区块链技术》的代码部分,并且包括课程中提到过的资料。 7 | 8 | ## 测试网水龙头(Testnet Faucets) 9 | Chainlink 测试网水龙头:https://faucets.chain.link
10 | Alchemy 测试网水龙头:https://sepoliafaucet.com/
11 | Infura 测试网水龙头:https://www.infura.io/faucet/sepolia 12 | 13 | ## 在哪里讨论,问题? 14 | - [GitHub Discussion](https://github.com/smartcontractkit/Web3_tutorial_Chinese/discussions) 页面
15 | 提课程相关的问题 16 | - [登链社区](https://learnblockchain.cn/)
17 | 提任何区块链相关的技术问题 18 | - [StackOverflow Ethereum](https://ethereum.stackexchange.com/)
19 | 提出任何以太坊相关的技术问题(需要英文) 20 | - [StackOverflow](https://stackoverflow.com/)
21 | 提出任何技术问题(需要英文) 22 | - 其他可能会用到的技术文档 23 | - [Solidity技术手册](https://www.freecodecamp.org/chinese/news/learn-solidity-handbook/#what-are-function-modifiers) 24 | - [以太坊官方文档](https://ethereum.org/zh) 25 | - [Solidity官方文档](https://docs.soliditylang.org/zh/v0.8.20/) 26 | - [Chainlink官方文档](https://docs.chain.link/) 27 | - [Hardhat官方文档](https://hardhat.org/) 28 | - [ether.js官方文档](https://docs.ethers.org/v6/) 29 | - [openzeppelin官方文档](https://docs.openzeppelin.com/contracts/5.x/) 30 | 31 | # 目录 32 |
33 | 第一课:区块链基础知识 & 操作 34 |
    35 |
  1. 36 | 区块链简史 37 |
  2. 38 |
  3. 39 | 区块链的设计哲学:去中心化和共识 40 |
  4. 41 |
  5. 42 | Web3:面向资产的互联网 43 |
  6. 44 |
  7. 45 | 智能合约简介 46 |
  8. 47 |
  9. 48 | 自托管钱包Metamask 49 |
  10. 50 |
  11. 51 | 密码学基础 & Metamask配置 52 |
  12. 53 |
  13. 54 | 领取测试币 55 |
  14. 56 |
  15. 57 | 签名&发送交易 58 |
  16. 59 |
  17. 60 | 燃料费(gas费)介绍 61 |
  18. 62 |
63 |
64 | 65 |
66 | 第二课:Solidity基础:Hello World 67 |
    68 |
  1. 69 | Remix,Solidity编译器和开源协议 70 |
  2. 71 |
  3. 72 | Solidity基础数据类型 73 |
  4. 74 |
  5. 75 | Solidity函数 76 |
  6. 77 |
  7. 78 | Solidity存储模式:memory, storage, calldata 79 |
  8. 80 |
  9. 81 | Solidity数据结构:结构体,数组和映射 82 |
  10. 83 |
  11. 84 | 合约间交互:工厂模式 85 |
  12. 86 |
  13. 87 | 总结 88 |
  14. 89 |
90 |
91 | 92 |
93 | 第三课:Solidity进阶:FundMe 94 |
    95 |
  1. 96 | 通过函数发送ETH 97 |
  2. 98 |
  3. 99 | 通过预言机设定最小额度 100 |
  4. 101 |
  5. 102 | 通过函数提取合约中的ETH 103 |
  6. 104 |
  7. 105 | 修改器和时间锁 106 |
  8. 107 |
  9. 108 | Token和Coin的区别 109 |
  10. 110 |
  11. 111 | 创建一个Token合约 112 |
  12. 113 |
  13. 114 | 继承ERC-20合约 115 |
  14. 116 |
  15. 117 | 部署和验证合约 118 |
  16. 119 |
120 |
121 | 122 |
123 | 第四课:Hardhat基础:部署交互FundMe 124 |
    125 |
  1. 126 | 环境搭建:Hardhat介绍 127 |
  2. 128 |
  3. 129 | 环境搭建:安装node.js 130 |
  4. 131 |
  5. 132 | 环境搭建:安装VS Code和git 133 |
  6. 134 |
  7. 135 | 创建Hardhat项目 136 |
  8. 137 |
  9. 138 | 通过Hardhat编译和部署合约 139 |
  10. 140 |
  11. 141 | Hardhat网络&其他配置 142 |
  12. 143 |
  13. 144 | 与FundMe合约交互 145 |
  14. 146 |
  15. 147 | 创建Hardhat自定义任务 148 |
  16. 149 |
150 |
151 | 152 |
153 | 第五课:Hardhat进阶:测试FundMe 154 |
    155 |
  1. 156 | Hardhat测试介绍 157 |
  2. 158 |
  3. 159 | Hardhat deploy任务 160 |
  4. 161 |
  5. 162 | 使用mock合约 163 |
  6. 164 |
  7. 165 | 给FundMe写单元测试 166 |
  8. 167 |
  9. 168 | gas reporter和coverage 169 |
  10. 170 |
171 |
172 | 173 |
174 | 第六课:跨链应用 175 |
    176 |
  1. 177 | NFT介绍 178 |
  2. 179 |
  3. 180 | NFT的metadata 181 |
  4. 182 |
  5. 183 | NFT基础合约 184 |
  6. 185 |
  7. 186 | Chainlink ccip 187 |
  8. 188 |
  9. 189 | 资产跨链池 190 |
  10. 191 |
  11. 192 | chainlink-local & 单元测试 193 |
  12. 194 |
  13. 195 | 跨链NFT的Hardhat的自定义任务 196 |
  14. 197 |
198 |
199 | 200 |
201 | 202 | # 教程中用到的代码 203 | - 第一课:无 204 | - 第二课:https://github.com/smartcontractkit/Web3_tutorial_Chinese/tree/main/lesson-2 205 | - 第三课:https://github.com/smartcontractkit/Web3_tutorial_Chinese/tree/main/lesson-3 206 | - 第四课:https://github.com/smartcontractkit/Web3_tutorial_Chinese/tree/main/lesson-4 207 | - 第五课:https://github.com/smartcontractkit/Web3_tutorial_Chinese/tree/main/lesson-5 208 | - 第六课:https://github.com/smartcontractkit/Web3_tutorial_Chinese/tree/main/lesson-6 209 |

210 | 211 | # 第一课:区块链基础知识 & 操作 212 | 第一课视频教程:WIP
213 | 第一课代码:本章没有代码 214 | ## 区块链简史 215 | - [《比特币白皮书,一种点对点的电子现金系统》](https://bitcoin.org/files/bitcoin-paper/bitcoin_zh_cn.pdf) 216 | - [比特币发明人 - 中本聪(Nakamoto Satoshi)](https://zh.wikipedia.org/zh-cn/%E4%B8%AD%E6%9C%AC%E8%81%AA) 217 | - [图灵完备是什么意思?](https://www.zhihu.com/question/20115374) 218 | - [《以太坊白皮书》](https://github.com/ethereum/wiki/wiki/%5B%E4%B8%AD%E6%96%87%5D-%E4%BB%A5%E5%A4%AA%E5%9D%8A%E7%99%BD%E7%9A%AE%E4%B9%A6) 219 | - [以太坊发明人 - 维塔利克·布特林(Vitalik Buterin)](https://zh.wikipedia.org/zh-cn/%E7%B6%AD%E5%A1%94%E5%88%A9%E5%85%8B%C2%B7%E5%B8%83%E7%89%B9%E6%9E%97) 220 | - [智能合约概念提出人 - 尼克·萨博(Nick Szabo)](https://zh.wikipedia.org/zh-cn/%E5%B0%BC%E5%85%8B%C2%B7%E8%96%A9%E5%8D%9A) 221 | - [Nick Szabo 对于智能合约定义](https://www.fon.hum.uva.nl/rob/Courses/InformationInSpeech/CDROM/Literature/LOTwinterschool2006/szabo.best.vwh.net/smart.contracts.html) 222 | ## 区块链设计哲学:去中心化和共识 223 | - [什么是区块链中的去中心化?](https://aws.amazon.com/cn/blockchain/decentralization-in-blockchain/) 224 | - [共识机制](https://ethereum.org/zh/developers/docs/consensus-mechanisms) 225 | - [PoW工作量证明](https://ethereum.org/zh/developers/docs/consensus-mechanisms/pow) 226 | - [PoS权益证明](https://ethereum.org/zh/developers/docs/consensus-mechanisms/pos) 227 | - [女巫攻击](https://www.51cto.com/article/742890.html) 228 | ## Web3:面向资产的互联网 229 | - [Web3介绍(详细)](https://learn.metamask.io/zh-CN/lessons/what-is-web3) 230 | - [Web3介绍(简略)](https://ethereum.org/zh/web3#introduction) 231 | - [什么是数据所有权](https://learn.metamask.io/zh-CN/lessons/the-advent-of-digital-ownership) 232 | - [什么是dApp?](https://ethereum.org/zh/dapps#what-are-dapps) 233 | ## 智能合约简介 234 | - [什么是Defi](https://ethereum.org/zh/defi) 235 | - [什么是NFT](https://ethereum.org/zh/nft#what-are-nfts) 236 | - [智能合约介绍](https://ethereum.org/zh/smart-contracts#introduction-to-smart-contracts) 237 | ## 自托管钱包Metamask 238 | - [Metamask钱包简介](https://metamask.io/) 239 | - [什么是加密钱包](https://learn.metamask.io/zh-CN/lessons/what-is-a-crypto-wallet) 240 | - [什么是自主托管](https://learn.metamask.io/zh-CN/lessons/what-is-a-self-custody-wallet) 241 | ## 密码学基础 & Metamask配置 242 | - [BIP-32密钥生成算法](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) 243 | - [BIP-39助记词列表](https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md) 244 | - [如何将助记词转换为私钥](https://mdrza.medium.com/how-to-convert-mnemonic-12-word-to-private-key-address-wallet-bitcoin-and-ethereum-81aa9ca91a57) 245 | - [分层确定性钱包(HD钱包)介绍](https://help.tokenpocket.pro/cn/faq/multichain-wallet/hd) 246 | - [Metamask 安装](https://metamask.io/download/) 247 | - [Chainlist 查看测试网配置](https://chainlist.org/) 248 | - [SepoliaScan 区块链浏览器](https://sepolia.etherscan.io/) 249 | ## 领取测试币 250 | - [Chainlink水龙头](https://faucets.chain.link/) 251 | - [Infura水龙头](https://www.infura.io/faucet/sepolia) 252 | - [Alchemy水龙头](https://sepoliafaucet.com/) 253 | ## 签名&发送交易 254 | - [椭圆曲线签名算法](https://blog.csdn.net/weixin_43586667/article/details/122766815) 255 | - [签名模拟](https://andersbrownworth.com/) 256 | ## 燃料费(gas费)介绍 257 | - [gas费介绍](https://ethereum.org/zh/developers/docs/gas) 258 | - [EIP-1559](https://ethereum.org/zh/developers/docs/gas#what-was-the-london-upgrade-eip-1559) 259 | 260 |
:tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada:
261 | 恭喜完成第一课的学习! 262 |
:tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada:
263 | 264 | # 第二课:Solidity基础:Hello World 265 | 第二课视频教程:WIP
266 | 第二课代码:https://github.com/smartcontractkit/Web3_tutorial_Chinese/tree/main/lesson-2 267 | ## Remix,Solidity编译器和开源协议 268 | - [开源软件许可协议简介](https://developer.aliyun.com/article/25089) 269 | - [EVM(以太坊虚拟机) 介绍](https://ethereum.org/zh/developers/docs/evm) 270 | - [EVM 版本](https://docs.soliditylang.org/en/v0.8.21/using-the-compiler.html) 271 | - [Solidity 编译器介绍](https://docs.soliditylang.org/zh/v0.8.16/using-the-compiler.html) 272 | - [Solidity 官方文档](https://docs.soliditylang.org/zh/v0.8.16/index.html) 273 | ## Solidity 基础数据类型 274 | - [Solidity 类型官方文档](https://docs.soliditylang.org/zh/v0.8.16/types.html#) 275 | - [Bytes vs bytes32](https://ethereum.stackexchange.com/questions/11770/what-is-the-difference-between-bytes-and-bytes32) 276 | ## Solidity 函数 277 | - [智能合约结构](https://docs.soliditylang.org/zh/v0.8.17/layout-of-source-files.html) 278 | - [如何拼接两个string变量(英文)](https://medium.com/@jamaltheatlantean/how-to-concatenate-two-strings-using-solidity-fada6051b1a6) 279 | ## Solidity 存储模式:memory, storage, calldata 280 | 这一节知识是底层原理,不需要完全理解也可以继续学习,这部分英文文档会更加清晰。 281 | - [EVM底层数据存储原理(英文)](https://www.netspi.com/blog/technical/blockchain-penetration-testing/ethereum-virtual-machine-internals-part-2/) 282 | - [storage存储方式](https://docs.soliditylang.org/en/v0.8.24/internals/layout_in_storage.html) 283 | - [memory存储方式](https://docs.soliditylang.org/en/v0.8.24/internals/layout_in_memory.html) 284 | - [calldata存储方式](https://docs.soliditylang.org/en/v0.8.24/internals/layout_in_calldata.html) 285 | ## Solidity 基础数据结构:结构体,数组,和映射 286 | Solidity 官方文档中,关于数据和结构体,英文文档比中文文档在定义上表述更清晰。 287 | - [数据结构 - 数组(英文)](https://docs.soliditylang.org/en/v0.8.24/types.html#arrays) 288 | - [数据结构 - 数组(中文对照)](https://docs.soliditylang.org/zh/v0.8.17/types.html#arrays) 289 | - [数据结构 - 结构体(英文)](https://docs.soliditylang.org/en/v0.8.24/types.html#structs) 290 | - [数据结构 - 结构体(中文对照)](https://docs.soliditylang.org/zh/v0.8.17/types.html#structs) 291 | ## 合约间交互:工厂模式 292 | - [Solidity 工厂模式介绍](https://learnblockchain.cn/article/1952) 293 | - [如何使用工厂模式(英文)](https://betterprogramming.pub/learn-solidity-the-factory-pattern-75d11c3e7d29) 294 | - [Solidity中的工厂模式(英文)](https://medium.com/@solidity101/demystifying-the-factory-pattern-in-solidity-efficient-contract-deployment-with-factory-pattern-e233ea6d1ec0#:~:text=Understanding%20the%20Factory%20Pattern&text=In%20the%20context%20of%20Ethereum,with%20predefined%20functionalities%20and%20structures.) 295 | 296 |
:tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada:
297 | 恭喜完成第二课的学习! 298 |
:tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada:
299 | 300 | # 第三课:Solidity进阶:FundMe 301 | 第三课视频教程:WIP
302 | 第三课代码:https://github.com/smartcontractkit/Web3_tutorial_Chinese/tree/main/lesson-3 303 | ## 通过函数发送ETH 304 | - [payable关键字](https://docs.alchemy.com/docs/solidity-payable-functions) 305 | - [以太坊账户:EOA和合约账户](https://ethereum.org/zh/developers/docs/accounts#types-of-account) 306 | - [Wei,GWei,Finney和ether](https://www.alchemy.com/gwei-calculator) 307 | ## 通过预言机设定最小额度 308 | - [预言机(oracle)定义](https://chain.link/education/blockchain-oracles) 309 | - [Chainlink技术文档](https://docs.chain.link/) 310 | - [Chainlink喂价文档](https://docs.chain.link/data-feeds) 311 | - [Chainlink喂价合约地址列表](https://docs.chain.link/data-feeds/price-feeds/addresses?network=ethereum&page=1) 312 | 313 | -Solidity的数据类型没有double或者floating(小数),如果想要表示带有小数的以太币,把wei当成最小单位,它是ether的10e-18,也就是0.000000000000000001。 314 | - [以太币面额转换器](https://eth-converter.com/) 315 | - [以太币面额](https://ethereum.org/zh/developers/docs/intro-to-ether#denominations) 316 | - [Solidity 中如何使用浮点数](https://www.freecodecamp.org/chinese/news/learn-solidity-handbook/#how-to-work-with-floating-point-numbers-in-solidity) 317 | ## 通过函数提取合约中的ETH 318 | - [如何发送和接受ETH](https://www.freecodecamp.org/chinese/news/learn-solidity-handbook/#how-to-send-and-receive-ether) 319 | - [三种转账方式: transfer, send, call](https://solidity-by-example.org/sending-ether/) 320 | ## 函数修饰符和时间锁 321 | - [函数修饰符是什么](https://www.freecodecamp.org/chinese/news/learn-solidity-handbook/#what-are-function-modifiers) 322 | - [怎样开发智能合约中的时间锁](https://blog.chain.link/timelock-smart-contracts-zh/#post-title) 323 | - [Uinx时间戳](https://www.unixtimestamp.com/) 324 | ## Token和Coin的区别 325 | - [Token和Coin的区别](https://www.ledger.com/academy/crypto/what-is-the-difference-between-coins-and-tokens) 326 | ## 创建一个Token合约 327 | - [Token介绍](https://docs.openzeppelin.com/contracts/5.x/tokens) 328 | - [Solidity中的继承](https://www.freecodecamp.org/chinese/news/learn-solidity-handbook/#inheritance-in-solidity) 329 | ## 继承ERC-20合约 330 | - [ERC-20标准合约](https://docs.openzeppelin.com/contracts/5.x/erc20) 331 | - [ERC-20标准合约代码](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol) 332 | - [virtual和override](https://learnblockchain.cn/docs/solidity/0.6.12/contracts/inheritance.html) 333 | ## 部署和验证合约 334 | - 注册[从区块链浏览器(Etherscan)](https://etherscan.io/)账户,并且获取API key 335 | - [怎样通过 Etherscan 验证智能合约](https://blog.chain.link/how-to-verify-a-smart-contract-on-etherscan-zh/) 336 | 337 |
:tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada:
338 | 恭喜完成第三课的学习! 339 |
:tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada:
340 | 341 | # 第四课:Hardhat基础:部署交互FundMe 342 | 第四课视频教程:WIP
343 | 第四课代码:https://github.com/smartcontractkit/Web3_tutorial_Chinese/tree/main/lesson-4 344 | 345 | ## 环境搭建:Hardhat介绍 346 | - [Hardhat官网(英文)](https://hardhat.org/) 347 | - [Hardhat,Truffle和Foundry对比(英文)](https://smartcontract.tips/articoli/truffle-hardhat-foundry-compare/) 348 | - [Hardhat和Foundry在测试方面的对比(英文)](https://ethereum.stackexchange.com/questions/143171/hardhat-vs-foundry-which-to-use-for-testing) 349 | ## 环境搭建:安装node.js 350 | - [如何在Windows上安装Linux](https://learn.microsoft.com/zh-cn/windows/wsl/install) 351 | - [在MacOS安装node的5种方式](https://stackoverflow.com/questions/28017374/what-is-the-recommended-way-to-install-node-js-nvm-and-npm-on-macos-x) 352 | - [MacOS Homebrew安装](https://brew.sh/) 353 | - [面向初学者的 Linux Shell——解释 Bash、Zsh 和 Fish](https://www.freecodecamp.org/chinese/news/linux-shells-explained/) 354 | - [zsh的配置文件(英文)](https://www.freecodecamp.org/news/how-do-zsh-configuration-files-work) 355 | - [如何在MacOS上卸载node](https://macpaw.com/how-to/uninstall-node-mac) 356 | 357 | ## 环境搭建:安装VS Code和git 358 | - [brew cask和formulae的区别(英文)](https://stackoverflow.com/questions/46403937/what-is-the-difference-between-brew-install-x-and-brew-cask-install-x) 359 | - [git官网](https://git-scm.com/) 360 | - [使用git和github的常见命令](https://www.freecodecamp.org/chinese/news/how-to-use-basic-git-and-github-commands/) 361 | 362 | ## 创建Hardhat项目 363 | - [Hardhat官网:创建Hardhat项目](https://hardhat.org/tutorial/creating-a-new-hardhat-project) 364 | - [怎样开发智能合约中的时间锁](https://blog.chain.link/timelock-smart-contracts-zh/#post-title) 365 | - [Uinx时间戳](https://www.unixtimestamp.com/) 366 | ## 通过Hardhat编译和部署合约 367 | - [通过Hardhat编译合约](https://hardhat.org/hardhat-runner/docs/guides/compile-contracts) 368 | - [通过Hardhat部署合约](https://hardhat.org/hardhat-runner/docs/guides/deploying) 369 | - [Hardhat 所使用的 ethers](https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-ethers) 370 | - [ethersjs v6官方文档](https://docs.ethers.org/v6/getting-started/) 371 | - [ethersjs v5到v6](https://docs.ethers.org/v6/migrating/) 372 | ## Hardhat网络&其他配置 373 | - [Hardhat官网:暂时网络和独立网络(英文)](https://hardhat.org/hardhat-network/docs/overview) 374 | - [dotenv介绍](https://juejin.cn/post/6844904198929121288) 375 | - [NPM:Chainlink/env-enc介绍(英文)](https://www.npmjs.com/package/@chainlink/env-enc) 376 | - [Alchemy注册](https://www.alchemy.com/) 377 | 378 | ## 与FundMe合约交互 379 | - [Hardhat官网:provider(英文)](https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-ethers#provider-object) 380 | 381 | ## 创建Hardhat自定义任务 382 | - [module.exports 介绍](https://www.freecodecamp.org/chinese/news/module-exports-how-to-export-in-node-js-and-javascript) 383 | - [Hardhat官网:创建任务(英文)](https://hardhat.org/hardhat-runner/docs/advanced/create-task) 384 | 385 |
:tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada:
386 | 恭喜完成第四课的学习! 387 |
:tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada:
388 | 389 | # 第五课:Hardhat进阶:测试FundMe 390 | 第五课视频教程:WIP
391 | 第五课代码:https://github.com/smartcontractkit/Web3_tutorial_Chinese/tree/main/lesson-5 392 | 393 | ## Hardhat 测试介绍 394 | - [智能合约测试](https://ethereum.org/zh/developers/docs/smart-contracts/testing/) 395 | - [Hardhat test官方文档](https://hardhat.org/tutorial/testing-contracts) 396 | - [mocha官网](https://mochajs.org/) 397 | - [chai官网](https://www.chaijs.com/) 398 | 399 | ## Hardhat deploy 400 | - [Hardhat Deploy官方文档](https://hardhat.org/hardhat-runner/docs/guides/deploying) 401 | - [Hadhat Deploy插件](https://www.npmjs.com/package/hardhat-deploy) 402 | 403 | ## 使用mock合约 404 | - [什么是mock合约](https://ethereum.org/zh/developers/tutorials/how-to-mock-solidity-contracts-for-testing/) 405 | - [Chainlink喂价Mock合约](https://github.com/smartcontractkit/chainlink/blob/develop/contracts/src/v0.8/tests/MockV3Aggregator.sol) 406 | 407 | ## 给FundMe写单元&集成测试 408 | - [什么是单元测试](https://aws.amazon.com/cn/what-is/unit-testing/) 409 | - [javascript测试框架Mocha](https://mochajs.org/) 410 | - [javascript测试框架Chai](https://www.chaijs.com/) 411 | 412 | ## gas reporter和coverage 413 | - [Hardhat gas reporter](https://www.npmjs.com/package/hardhat-gas-reporter) 414 | - [Solidity coverage](https://www.npmjs.com/package/solidity-coverage) 415 | 416 |
:tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada:
417 | 恭喜完成第五课的学习! 418 |
:tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada:
419 | 420 | # 第六课:跨链应用 421 | 第六课视频教程:WIP
422 | 第六课代码:https://github.com/smartcontractkit/Web3_tutorial_Chinese/tree/main/lesson-6 423 | 424 | ## NFT介绍 425 | - [NFT非同质代币是什么](https://ethereum.org/zh/nft/#what-are-nfts) 426 | - [NFT有哪些用户案例](https://chain.link/education/nfts) 427 | - [动态NFT是什么](https://chain.link/education-hub/what-is-dynamic-nft) 428 | 429 | ## NFT的metadata 430 | - [NFT metadata(Opensea标准)](https://docs.opensea.io/docs/metadata-standards) 431 | 432 | ## ERC721基础合约 433 | - [Openzeppelin合约 wizard](https://www.openzeppelin.com/contracts) 434 | - [ERC721(OpenZeppelin)](https://docs.openzeppelin.com/contracts/3.x/erc721) 435 | - [ERC1155(OpenZeppelin)](https://docs.openzeppelin.com/contracts/3.x/erc1155) 436 | - [ERC721 vs ERC1155](https://www.alchemy.com/blog/comparing-erc-721-to-erc-1155) 437 | 438 | ## Chainlink CCIP (跨链互操作协议) 439 | - [什么是链间互操作性](https://chain.link/education-hub/blockchain-interoperability) 440 | - [Chainlink CCIP官方文档](https://docs.chain.link/ccip) 441 | - [跨链桥的7个风险](https://blog.chain.link/cross-chain-bridge-vulnerabilities/) 442 | - [使用CCIP的5个例子](https://blog.chain.link/how-to-use-ccip/) 443 | - [区块状态](https://www.alchemy.com/overviews/ethereum-commitment-levels) 444 | - [跨链桥的5种安全等级](https://blog.chain.link/five-levels-cross-chain-security/) 445 | 446 | ## 跨链资产池 447 | - [NFT池子样例合约](https://github.com/QingyangKong/Web3_tutorial_lesson6/blob/1-chainlink-local/contracts/NFTPoolLockAndRelease.sol) 448 | - [什么是封装加密资产](https://www.kraken.com/learn/what-are-wrapped-crypto-assets) 449 | 450 | ## Chainlink-local 和单元测试 451 | - [chainlink local](https://github.com/smartcontractkit/chainlink-local/tree/main) 452 | 453 | ## 跨链NFT的hardhat自定义任务 454 | - [hardhat-deploy companion网络](https://github.com/wighawag/hardhat-deploy?tab=readme-ov-file#companionnetworks) 455 | 456 |
:tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada:
457 | 恭喜完成第六课的学习! 458 |
:tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada:
459 | 460 | 这个 repository 是初学者课程《Web3 和区块链技术》的代码部分,并且包括课程中提到过的资料。 461 | 462 | # English for review 463 | ## Testnet Faucets 464 | Chainlink testnet faucets:https://faucets.chain.link
465 | Alchemy testnet faucets:https://sepoliafaucet.com/
466 | Infura testnet faucets:https://www.infura.io/faucet/sepolia 467 | 468 | ## Where to discuss? 469 | - [GitHub Discussion](https://github.com/smartcontractkit/Web3_tutorial_Chinese/discussions) page
470 | Ask questions related to the course 471 | - [Learnblockchain.cn](https://learnblockchain.cn/)
472 | Ask any question related to Web3 and blockchain 473 | - [StackOverflow Ethereum](https://ethereum.stackexchange.com/)
474 | Ask any question about Ethereum 475 | - [StackOverflow](https://stackoverflow.com/)
476 | Ask any question about programming 477 | - Other technical documents that might be used 478 | - [Solidity Handbook](https://www.freecodecamp.org/chinese/news/learn-solidity-handbook/#what-are-function-modifiers) 479 | - [Ethereum Official Documentation](https://ethereum.org/zh) 480 | - [Solidity Official Documentation](https://docs.soliditylang.org/zh/v0.8.20/) 481 | - [Chainlink Official Documentation](https://docs.chain.link/) 482 | - [Hardhat Official Documentation](https://hardhat.org/) 483 | - [ether.js Official Documentation](https://docs.ethers.org/v6/) 484 | - [openzeppelin Official Documentation](https://docs.openzeppelin.com/contracts/5.x/) 485 | 486 | # 目录 487 |
488 | Lesson 1: blockchain basics 489 |
    490 |
  1. 491 | What is blockchain? 492 |
  2. 493 |
  3. 494 | Philosophy of blockchain: Trust-minimization 495 |
  4. 496 |
  5. 497 | Web3: decentralized internet for asset 498 |
  6. 499 |
  7. 500 | Introduction for smart contracts 501 |
  8. 502 |
  9. 503 | Self Custody wallet & metamask 504 |
  10. 505 |
  11. 506 | Crypto basics & metamask setup 507 |
  12. 508 |
  13. 509 | Claim test tokens on Sepolia testnet 510 |
  14. 511 |
  15. 512 | Sign a transaction 513 |
  16. 514 |
  17. 515 | Intro to gas 516 |
  18. 517 |
518 |
519 | 520 |
521 | Solidity Basics: Hello World 522 |
    523 |
  1. 524 | Remix & compiler version & license 525 |
  2. 526 |
  3. 527 | Solidity: basic data types 528 |
  4. 529 |
  5. 530 | Solidity: function 531 |
  6. 532 |
  7. 533 | Storage & memory & calldata 534 |
  8. 535 |
  9. 536 | Solidity: basic data structure 537 |
  10. 538 |
  11. 539 | HelloWorld factory: interact with other contracts 540 |
  12. 541 |
542 |
543 | 544 |
545 | Lesson 3: Solidity Advanced: FundMe & ERC20 546 |
    547 |
  1. 548 | Payable function: send ETH to a contract 549 |
  2. 550 |
  3. 551 | Set the minimum for USD with Chainlink Data feed 552 |
  4. 553 |
  5. 554 | Transfer token using function 555 |
  6. 556 |
  7. 557 | Modifer and timelock 558 |
  8. 559 |
  9. 560 | Coin vs token 561 |
  10. 562 |
  11. 563 | Create a token contract 564 |
  12. 565 |
  13. 566 | ERC-20 token standard 567 |
  14. 568 |
  15. 569 | Deployment & verification 570 |
  16. 571 |
572 |
573 | 574 |
575 | Lesson 4: hardhat FundMe 576 |
    577 |
  1. 578 | env setup: Introduction to Hardhat 579 |
  2. 580 |
  3. 581 | env setup: Install nodejs 582 |
  4. 583 |
  5. 584 | env setup: Install vscode & git 585 |
  6. 586 |
  7. 587 | Create hardhat project 588 |
  8. 589 |
  9. 590 | Compile and deploy the contract through Hardhat 591 |
  10. 592 |
  11. 593 | Hardhat network & other configurations 594 |
  12. 595 |
  13. 596 | Interact with FundMe 597 |
  14. 598 |
  15. 599 | create custom hardhat task 600 |
  16. 601 |
602 |
603 | 604 |
605 | Lesson 5: Test FundMe 606 |
    607 |
  1. 608 | Introduction to the unit tests in Hardhat 609 |
  2. 610 |
  3. 611 | Hardhat deploy task 612 |
  4. 613 |
  5. 614 | mock contract 615 |
  6. 616 |
  7. 617 | write unit test for FundMe 618 |
  8. 619 |
  9. 620 | gas Reporter & coverage 621 |
  10. 622 |
623 |
624 | 625 |
626 | Lesson 6: cross-chain application 627 |
    628 |
  1. 629 | Introduction for NFT 630 |
  2. 631 |
  3. 632 | NFT metadata 633 |
  4. 634 |
  5. 635 | ERC-721 token standard 636 |
  6. 637 |
  7. 638 | Chainlink ccip 639 |
  8. 640 |
  9. 641 | Token pool for ccip 642 |
  10. 643 |
  11. 644 | chainlink-local & unit test 645 |
  12. 646 |
  13. 647 | Hardhat custom task cross-chain nft 648 |
  14. 649 |
650 |
651 | 652 |
653 | 654 | # Codes used in the course 655 | - Lesson 1: NA 656 | - Lesson 2: https://github.com/smartcontractkit/Web3_tutorial_Chinese/tree/main/lesson-2 657 | - Lesson 3: https://github.com/smartcontractkit/Web3_tutorial_Chinese/tree/main/lesson-3 658 | - Lesson 4: https://github.com/smartcontractkit/Web3_tutorial_Chinese/tree/main/lesson-4 659 | - Lesson 5: https://github.com/smartcontractkit/Web3_tutorial_Chinese/tree/main/lesson-5 660 | - Lesson 6: https://github.com/smartcontractkit/Web3_tutorial_Chinese/tree/main/lesson-6 661 |

662 | 663 | # Lesson1: blockchain basics 664 | Video of lesson 1:WIP
665 | Codes of lesseon 1:NA 666 | ## What is blockchain 667 | - [Bitcoin whitepaper, A Peer-to-Peer Electronic Cash System](https://bitcoin.org/files/bitcoin-paper/bitcoin_zh_cn.pdf) 668 | - [Nakamoto Satoshi](https://zh.wikipedia.org/zh-cn/%E4%B8%AD%E6%9C%AC%E8%81%AA) 669 | - [What is Turing completeness?](https://www.zhihu.com/question/20115374) 670 | - [Ethereum Whitepaper](https://github.com/ethereum/wiki/wiki/%5B%E4%B8%AD%E6%96%87%5D-%E4%BB%A5%E5%A4%AA%E5%9D%8A%E7%99%BD%E7%9A%AE%E4%B9%A6) 671 | - [Vitalik Buterin](https://zh.wikipedia.org/zh-cn/%E7%B6%AD%E5%A1%94%E5%88%A9%E5%85%8B%C2%B7%E5%B8%83%E7%89%B9%E6%9E%97) 672 | - [Inventor of smart contract - Nick Szabo](https://zh.wikipedia.org/zh-cn/%E5%B0%BC%E5%85%8B%C2%B7%E8%96%A9%E5%8D%9A) 673 | - [How Nick Szabo describes smart contract](https://www.fon.hum.uva.nl/rob/Courses/InformationInSpeech/CDROM/Literature/LOTwinterschool2006/szabo.best.vwh.net/smart.contracts.html) 674 | ## Philosophy of blockchain: Trust-minimization 675 | - [What is decentralization in blockchain?](https://aws.amazon.com/cn/blockchain/decentralization-in-blockchain/) 676 | - [Consensus](https://ethereum.org/zh/developers/docs/consensus-mechanisms) 677 | - [Proof of work](https://ethereum.org/zh/developers/docs/consensus-mechanisms/pow) 678 | - [Proof of stake](https://ethereum.org/zh/developers/docs/consensus-mechanisms/pos) 679 | - [Sybil attack](https://www.51cto.com/article/742890.html) 680 | ## Web3: decentralized internet for asset 681 | - [Web3 introduction](https://learn.metamask.io/zh-CN/lessons/what-is-web3) 682 | - [Web3 introduction(Brief)](https://ethereum.org/zh/web3#introduction) 683 | - [What is digital ownership?](https://learn.metamask.io/zh-CN/lessons/the-advent-of-digital-ownership) 684 | - [What is dApp?](https://ethereum.org/zh/dapps#what-are-dapps) 685 | ## Introduction for smart contracts 686 | - [What is Defi](https://ethereum.org/zh/defi) 687 | - [What is NFT](https://ethereum.org/zh/nft#what-are-nfts) 688 | - [Introduction to smart contract](https://ethereum.org/zh/smart-contracts#introduction-to-smart-contracts) 689 | ## Self Custody wallet & metamask 690 | - [Metamask wallet introduction](https://metamask.io/) 691 | - [What is a crypto wallet?](https://learn.metamask.io/zh-CN/lessons/what-is-a-crypto-wallet) 692 | - [What is a self custody wallet?](https://learn.metamask.io/zh-CN/lessons/what-is-a-self-custody-wallet) 693 | ## Crypto basics & metamask setup 694 | - [BIP-32 key generation algorithm](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) 695 | - [BIP-39 wordlist](https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md) 696 | - [How to convert mnemonics to private key](https://mdrza.medium.com/how-to-convert-mnemonic-12-word-to-private-key-address-wallet-bitcoin-and-ethereum-81aa9ca91a57) 697 | - [Introduction to hierachical deterministic(HD) wallet](https://help.tokenpocket.pro/cn/faq/multichain-wallet/hd) 698 | - [Metamask installation](https://metamask.io/download/) 699 | - [Chainlist](https://chainlist.org/) 700 | - [SepoliaScan blockchain explorer](https://sepolia.etherscan.io/) 701 | ## Claim test tokens on Sepolia testnet 702 | - [Chainlink faucets](https://faucets.chain.link/) 703 | - [Infura faucets](https://www.infura.io/faucet/sepolia) 704 | - [Alchemy facuvets](https://sepoliafaucet.com/) 705 | ## Sign a transaction 706 | - [elliptic curve cryptography](https://blog.csdn.net/weixin_43586667/article/details/122766815) 707 | - [Signature Demo](https://andersbrownworth.com/) 708 | ## Intro to gas fee 709 | - [Introduction to gas fee](https://ethereum.org/zh/developers/docs/gas) 710 | - [EIP-1559](https://ethereum.org/zh/developers/docs/gas#what-was-the-london-upgrade-eip-1559) 711 | 712 |
:tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada:
713 | Congratulations! You complete the lesson 1! 714 |
:tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada:
715 | 716 | # Lesson 2: Solidity Basics: Hello World 717 | Video of lesson 2: WIP
718 | Codes of lesson 2: https://github.com/smartcontractkit/Web3_tutorial_Chinese/tree/main/lesson-2 719 | 720 | ## Remix & compiler version & license 721 | - [Introduction to Open Source Software Licenses](https://developer.aliyun.com/article/25089) 722 | - [Introduction to EVM(Ethereum Virtual Machine)](https://ethereum.org/zh/developers/docs/evm) 723 | - [EVM versions](https://docs.soliditylang.org/en/v0.8.21/using-the-compiler.html) 724 | - [Introduction to Solidity compilers](https://docs.soliditylang.org/zh/v0.8.16/using-the-compiler.html) 725 | - [Solidity official documentation](https://docs.soliditylang.org/zh/v0.8.16/index.html) 726 | 727 | ## Solidity: basic data types 728 | - [Solidity types](https://docs.soliditylang.org/zh/v0.8.16/types.html#) 729 | - [Bytes vs bytes32](https://ethereum.stackexchange.com/questions/11770/what-is-the-difference-between-bytes-and-bytes32) 730 | 731 | ## Solidity: function 732 | - [Smart contract layout](https://docs.soliditylang.org/zh/v0.8.17/layout-of-source-files.html) 733 | - [How to concatenate 2 strings in solidity](https://medium.com/@jamaltheatlantean/how-to-concatenate-two-strings-using-solidity-fada6051b1a6) 734 | 735 | ## Storage & memory & calldata 736 | - [EVM internals](https://www.netspi.com/blog/technical/blockchain-penetration-testing/ethereum-virtual-machine-internals-part-2/) 737 | - [layout in storage](https://docs.soliditylang.org/en/v0.8.24/internals/layout_in_storage.html) 738 | - [layout in memory](https://docs.soliditylang.org/en/v0.8.24/internals/layout_in_memory.html) 739 | - [layout in calldata](https://docs.soliditylang.org/en/v0.8.24/internals/layout_in_calldata.html) 740 | ## Solidity: basic data structure 741 | - [Data structure - Array](https://docs.soliditylang.org/en/v0.8.24/types.html#arrays) 742 | - [Data structure - Array(Chinese)](https://docs.soliditylang.org/zh/v0.8.17/types.html#arrays) 743 | - [Data structure - Struct](https://docs.soliditylang.org/en/v0.8.24/types.html#structs) 744 | - [Data structure - Struct(Chinese)](https://docs.soliditylang.org/zh/v0.8.17/types.html#structs) 745 | 746 | ## HelloWorld factory: interact with other contracts 747 | - [Learn factory pattern](https://betterprogramming.pub/learn-solidity-the-factory-pattern-75d11c3e7d29) 748 | - [Factory pattern in solidity](https://medium.com/@solidity101/demystifying-the-factory-pattern-in-solidity-efficient-contract-deployment-with-factory-pattern-e233ea6d1ec0#:~:text=Understanding%20the%20Factory%20Pattern&text=In%20the%20context%20of%20Ethereum,with%20predefined%20functionalities%20and%20structures.) 749 | 750 |
:tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada:
751 | Congratulations! You complete the lesson 2! 752 |
:tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada:
753 | 754 | # Lesson 3: Solidity Advanced: FundMe & ERC20 755 | Video of lesson 3: WIP
756 | Codes of lesson 3:https://github.com/smartcontractkit/Web3_tutorial_Chinese/tree/main/lesson-3 757 | 758 | ## Payable function: send ETH to a contract 759 | - [Ethereum account: EOA and smart contract](https://ethereum.org/zh/developers/docs/accounts#types-of-account) 760 | 761 | ## Set the minimum for USD with Chainlink Data feed 762 | - [Oracle Definition](https://chain.link/education/blockchain-oracles) 763 | - [Chainlink documentation](https://docs.chain.link/) 764 | - [Chainlink data feed doc](https://docs.chain.link/data-feeds) 765 | - [Chainlink data feed addresses](https://docs.chain.link/data-feeds/price-feeds/addresses?network=ethereum&page=1) 766 | 767 | - [ETH unit converter](https://eth-converter.com/) 768 | - [ETH denominations](https://ethereum.org/zh/developers/docs/intro-to-ether#denominations) 769 | - [How to work with floating point numbers in solidity](https://www.freecodecamp.org/chinese/news/learn-solidity-handbook/#how-to-work-with-floating-point-numbers-in-solidity) 770 | ## Transfer token using function 771 | - [How to send and receive ETH](https://www.freecodecamp.org/chinese/news/learn-solidity-handbook/#how-to-send-and-receive-ether) 772 | - [2 ways to transer ETH: transfer, send, call](https://solidity-by-example.org/sending-ether/) 773 | ## Modifier and timelock 774 | - [What is function modifier?](https://www.freecodecamp.org/chinese/news/learn-solidity-handbook/#what-are-function-modifiers) 775 | - [How to develop timelock in smart contract](https://blog.chain.link/timelock-smart-contracts-zh/#post-title) 776 | - [Uinx timestamp](https://www.unixtimestamp.com/) 777 | ## Token vs Coin 778 | - [Difference between Token and Coin](https://www.ledger.com/academy/crypto/what-is-the-difference-between-coins-and-tokens) 779 | ## Create a token contract 780 | - [Token introduction](https://docs.openzeppelin.com/contracts/5.x/tokens) 781 | - [Inheritance in solidity](https://www.freecodecamp.org/chinese/news/learn-solidity-handbook/#inheritance-in-solidity) 782 | ## ERC-20 token standard 783 | - [ERC-20 token stadard](https://docs.openzeppelin.com/contracts/5.x/erc20) 784 | ## Deployment & verification 785 | - Register [Etherscan](https://etherscan.io/) and get a API key 786 | - [How to verify a smart contract on Etherscan](https://blog.chain.link/how-to-verify-a-smart-contract-on-etherscan-zh/) 787 | 788 |
:tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada:
789 | Congratulations! You complete the lesson 3! 790 |
:tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada:
791 | 792 | # Lesson 4: hardhat FundMe 793 | Video of lesson 4: WIP
794 | Codes of lesson 4: https://github.com/smartcontractkit/Web3_tutorial_Chinese/tree/main/lesson-4 795 | 796 | ## env setup: Introduction to Hardhat 797 | - [Hardhat official](https://hardhat.org/) 798 | - [Hardhat, Truffle and Foundry](https://smartcontract.tips/articoli/truffle-hardhat-foundry-compare/) 799 | - [Hardhat vs Foundry, which to use for testing](https://ethereum.stackexchange.com/questions/143171/hardhat-vs-foundry-which-to-use-for-testing) 800 | ## env setup: Install nodejs 801 | - [How to install Linux on Windows](https://learn.microsoft.com/zh-cn/windows/wsl/install) 802 | - [5 ways to install node on MacOS](https://stackoverflow.com/questions/28017374/what-is-the-recommended-way-to-install-node-js-nvm-and-npm-on-macos-x) 803 | - [MacOS Homebrew](https://brew.sh/) 804 | - [Linux Shell for beginners- Bash, Zsh and Fish](https://www.freecodecamp.org/chinese/news/linux-shells-explained/) 805 | - [zsh configuration](https://www.freecodecamp.org/news/how-do-zsh-configuration-files-work) 806 | - [uninstall node on MacOS](https://macpaw.com/how-to/uninstall-node-mac) 807 | 808 | ## env setup: Install vscode & git 809 | - [brew cask vs formulae](https://stackoverflow.com/questions/46403937/what-is-the-difference-between-brew-install-x-and-brew-cask-install-x) 810 | - [git official](https://git-scm.com/) 811 | - [basic git and github commands](https://www.freecodecamp.org/chinese/news/how-to-use-basic-git-and-github-commands/) 812 | 813 | ## Create hardhat project 814 | - [Create a new Hardhat project](https://hardhat.org/tutorial/creating-a-new-hardhat-project) 815 | - [timelock smart contract](https://blog.chain.link/timelock-smart-contracts-zh/#post-title) 816 | - [Uinx timestamp](https://www.unixtimestamp.com/) 817 | 818 | ## Compile and deploy the contract through Hardhat 819 | - [Compile contract in Hardhat](https://hardhat.org/hardhat-runner/docs/guides/compile-contracts) 820 | - [Deploy contract in Hardhat](https://hardhat.org/hardhat-runner/docs/guides/deploying) 821 | - [Hardhat ethers](https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-ethers) 822 | - [ethersjs v6 official documentation](https://docs.ethers.org/v6/getting-started/) 823 | - [ethersjs migrate v5 to v6](https://docs.ethers.org/v6/migrating/) 824 | 825 | ## Hardhat network & other configurations 826 | - [transient and standalone network](https://hardhat.org/hardhat-network/docs/overview) 827 | - [dotenv intro](https://juejin.cn/post/6844904198929121288) 828 | - [NPM: Chainlink/env-enc](https://www.npmjs.com/package/@chainlink/env-enc) 829 | - [Alchemy](https://www.alchemy.com/) 830 | 831 | ## Interact with FundMe 832 | - [provider](https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-ethers#provider-object) 833 | 834 | ## create custom hardhat task 835 | - [intro to module.exports](https://www.freecodecamp.org/chinese/news/module-exports-how-to-export-in-node-js-and-javascript) 836 | - [Create a task in Hardhat](https://hardhat.org/hardhat-runner/docs/advanced/create-task) 837 | 838 |
:tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada:
839 | Congratulations! You complete the lesson 4! 840 |
:tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada:
841 | 842 | # Lesson 5: Test FundMe 843 | Video of lesson 5:WIP
844 | Codes of lesson 5: https://github.com/smartcontractkit/Web3_tutorial_Chinese/tree/main/lesson-5 845 | 846 | ## Introduction to the unit tests in Hardhat 847 | - [testing for smart contract](https://ethereum.org/zh/developers/docs/smart-contracts/testing/) 848 | - [Hardhat test](https://hardhat.org/tutorial/testing-contracts) 849 | 850 | ## Hardhat deploy task 851 | - [Hardhat Deploy](https://hardhat.org/hardhat-runner/docs/guides/deploying) 852 | - [Hadhat Deploy plugin](https://www.npmjs.com/package/hardhat-deploy) 853 | 854 | ## mock contract 855 | - [What is mock contract](https://ethereum.org/zh/developers/tutorials/how-to-mock-solidity-contracts-for-testing/) 856 | - [Chainlink data feed mock contract](https://github.com/smartcontractkit/chainlink/blob/develop/contracts/src/v0.6/tests/MockV3Aggregator.sol) 857 | 858 | ## write unit test for FundMe 859 | - [What is unit test](https://aws.amazon.com/cn/what-is/unit-testing/) 860 | - [javascript Mocha](https://mochajs.org/) 861 | - [javascript Chai](https://www.chaijs.com/) 862 | 863 | ## gas Reporter & coverage 864 | - [Hardhat gas reporter](https://www.npmjs.com/package/hardhat-gas-reporter) 865 | - [Solidity coverage](https://www.npmjs.com/package/solidity-coverage) 866 | 867 |
:tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada:
868 | Congratulations! You complete the lesson 5! 869 |
:tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada:
870 | 871 | # Lesson 6: cross-chain application 872 | Video of lesson 6: WIP
873 | Codes of lesson 6: https://github.com/smartcontractkit/Web3_tutorial_Chinese/tree/main/lesson-6 874 | 875 | ## Introduction for NFT 876 | - [What are NFTs](https://ethereum.org/zh/nft/#what-are-nfts) 877 | - [NFT use cases](https://chain.link/education/nfts) 878 | - [What is dynamic NFT](https://chain.link/education-hub/what-is-dynamic-nft) 879 | 880 | ## NFT metadata 881 | - [NFT metadata(Opensea)](https://docs.opensea.io/docs/metadata-standards) 882 | 883 | ## ERC-721 token standard 884 | - [Openzeppelin wizard](https://www.openzeppelin.com/contracts) 885 | - [ERC721(OpenZeppelin)](https://docs.openzeppelin.com/contracts/3.x/erc721) 886 | - [ERC1155(OpenZeppelin)](https://docs.openzeppelin.com/contracts/3.x/erc1155) 887 | - [ERC721 vs ERC1155](https://www.alchemy.com/blog/comparing-erc-721-to-erc-1155) 888 | 889 | ## Chainlink CCIP (Cross-chain Interoperability Protocol) 890 | - [What is blockchain interoperability](https://chain.link/education-hub/blockchain-interoperability) 891 | - [Chainlink CCIP official document](https://docs.chain.link/ccip) 892 | - [Cross chain bridge vilnerabilities](https://blog.chain.link/cross-chain-bridge-vulnerabilities/) 893 | - [CCIP use cases](https://blog.chain.link/how-to-use-ccip/) 894 | - [ethereum commitment levels](https://www.alchemy.com/overviews/ethereum-commitment-levels) 895 | - [The Five Levels of Cross-Chain Security](https://blog.chain.link/five-levels-cross-chain-security/) 896 | 897 | ## Token pool in CCIP 898 | - [NFT pool smart conrtact](https://github.com/smartcontractkit/Web3_tutorial_Chinese/blob/main/lesson-6/contracts/NFTPoolLockAndRelease.sol) 899 | - [What is wrapped asset](https://www.kraken.com/learn/what-are-wrapped-crypto-assets) 900 | 901 | ## Chainlink-local & unit test 902 | - [chainlink local](https://github.com/smartcontractkit/chainlink-local/tree/main) 903 | 904 | ## hardhat custom task for cross-chain NFT 905 | - [hardhat-deploy companion网络](https://github.com/wighawag/hardhat-deploy?tab=readme-ov-file#companionnetworks) 906 | 907 |
:tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada:
908 | Congratulations! You complete the lesson 6! 909 |
:tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada::tada:
910 | -------------------------------------------------------------------------------- /lesson-2/HelloWorld.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | contract HelloWorld { 5 | string strVar = "Hello World"; 6 | 7 | struct Info { 8 | string phrase; 9 | uint256 id; 10 | address addr; 11 | } 12 | 13 | Info[] infos; 14 | 15 | mapping(uint256 id => Info info) infoMapping; 16 | 17 | function sayHello(uint256 _id) public view returns(string memory) { 18 | if(infoMapping[_id].addr == address(0x0)) { 19 | return addinfo(strVar); 20 | } else { 21 | return addinfo(infoMapping[_id].phrase); 22 | } 23 | } 24 | 25 | function setHelloWorld(string memory newString, uint256 _id) public { 26 | Info memory info = Info(newString, _id, msg.sender); 27 | infoMapping[_id] = info; 28 | } 29 | 30 | 31 | function addinfo(string memory helloWorldStr) internal pure returns(string memory) { 32 | return string.concat(helloWorldStr, " from Frank's contract."); 33 | } 34 | } -------------------------------------------------------------------------------- /lesson-2/HelloWorldFactory.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import { HelloWorld } from "./HelloWorld.sol"; 5 | 6 | contract HelloWorldFactory { 7 | HelloWorld hw; 8 | 9 | HelloWorld[] hws; 10 | 11 | function createHelloWorld() public { 12 | hw = new HelloWorld(); 13 | hws.push(hw); 14 | } 15 | 16 | function getHelloWorldByIndex(uint256 _index) public view returns (HelloWorld) { 17 | return hws[_index]; 18 | } 19 | 20 | function callSayHelloFromFactory(uint256 _index, uint256 _id) 21 | public 22 | view 23 | returns (string memory) { 24 | return hws[_index].sayHello(_id); 25 | } 26 | 27 | function callSetHelloWorldFromFactory(uint256 _index, string memory newString, uint256 _id) public { 28 | hws[_index].setHelloWorld(newString, _id); 29 | } 30 | } -------------------------------------------------------------------------------- /lesson-2/README.md: -------------------------------------------------------------------------------- 1 | # 第二课 | Lesson 2 2 | [中文](#内容介绍) | [English](#introduction) 3 | 4 | ## 内容介绍 5 | 这是 Web3_tutorial 的一部分。
6 | 视频链接:WIP
7 | 8 | 在这一课中,你可以学习智能合约的编程语言 Solidity 相关的语法和数据结构。在视频中出现的代码将出现在 lesson-2 中。 9 | 10 | 在这一课中,你将使用 Remix 的在线 IDE 来编译和部署所有的合约。在 lesson2 中,你将通过 `HelloWorld.sol` 和 `HelloWorldFactory.sol` 来学习 Solidity 编程语言的基础知识。 11 | 12 | ## 如何使用 13 | 1. 编译并且部署 `HelloWorld` 合约 14 | - 打开 [Remix](https://remix.ethereum.org/) 15 | - 新建一个文件,并且命名为 `HelloWorld.sol` 16 | - 点击 Compile 17 | - 点击 Deploy 18 | 2. 编译并且部署 `HelloWorldFactory`合约 19 | - 新建一个文件,并且命名为 `HelloWorldFactory.sol` 20 | - 点击 Compile 21 | - 点击 Deploy 22 | 23 | 更多的相关内容请查看[Web3_tutorial](https://github.com/QingyangKong/Web3_tutorial)的 `README.md`。 24 | 25 | ## introduction 26 | This is part of the Web3_tutorial.
27 | Video link: WIP
28 | 29 | In this lesson, you can learn about the syntax and data structures of Solidity programming language for smart contracts. Codes shown in the video are saved in lesson-2. 30 | 31 | In this lesson, you will write and deploy your first smart contract using Remix's online IDE. In lesson 2, you will learn the basics of Solidity programming language through `HelloWorld.sol` and `HelloWorldFactory.sol`. 32 | 33 | ## Getting started 34 | 1. Compile and deploy the HelloWorld contract 35 | - Open [Remix](https://remix.ethereum.org/) 36 | - Create a new file and name it `HelloWorld.sol` 37 | - Click Compile 38 | - Click Deploy 39 | 2. Compile and deploy the `HelloWorldFactory` contract 40 | - Create a new file and name it `HelloWorldFactory.sol` 41 | - Click Compile 42 | - Click Deploy 43 | 44 | For more related content, please refer to the README.md of [Web3_tutorial](https://github.com/QingyangKong/Web3_tutorial). -------------------------------------------------------------------------------- /lesson-3/FundMe.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; 5 | 6 | // 1. 创建一个收款函数 7 | // 2. 记录投资人并且查看 8 | // 3. 在锁定期内,达到目标值,生产商可以提款 9 | // 4. 在锁定期内,没有达到目标值,投资人在锁定期以后退款 10 | 11 | contract FundMe { 12 | mapping(address => uint256) public fundersToAmount; 13 | 14 | uint256 constant MINIMUM_VALUE = 100 * 10 ** 18; //USD 15 | 16 | AggregatorV3Interface internal dataFeed; 17 | 18 | uint256 constant TARGET = 1000 * 10 ** 18; 19 | 20 | address public owner; 21 | 22 | uint256 deploymentTimestamp; 23 | uint256 lockTime; 24 | 25 | address erc20Addr; 26 | 27 | bool public getFundSuccess = false; 28 | 29 | constructor(uint256 _lockTime) { 30 | // sepolia testnet 31 | dataFeed = AggregatorV3Interface(0x694AA1769357215DE4FAC081bf1f309aDC325306); 32 | owner = msg.sender; 33 | deploymentTimestamp = block.timestamp; 34 | lockTime = _lockTime; 35 | } 36 | 37 | function fund() external payable { 38 | require(convertEthToUsd(msg.value) >= MINIMUM_VALUE, "Send more ETH"); 39 | require(block.timestamp < deploymentTimestamp + lockTime, "window is closed"); 40 | fundersToAmount[msg.sender] = msg.value; 41 | } 42 | 43 | function getChainlinkDataFeedLatestAnswer() public view returns (int) { 44 | // prettier-ignore 45 | ( 46 | /* uint80 roundID */, 47 | int answer, 48 | /*uint startedAt*/, 49 | /*uint timeStamp*/, 50 | /*uint80 answeredInRound*/ 51 | ) = dataFeed.latestRoundData(); 52 | return answer; 53 | } 54 | 55 | function convertEthToUsd(uint256 ethAmount) internal view returns(uint256){ 56 | uint256 ethPrice = uint256(getChainlinkDataFeedLatestAnswer()); 57 | return ethAmount * ethPrice / (10 ** 8); 58 | } 59 | 60 | function transferOwnership(address newOwner) public onlyOwner{ 61 | owner = newOwner; 62 | } 63 | 64 | function getFund() external windowClosed onlyOwner{ 65 | require(convertEthToUsd(address(this).balance) >= TARGET, "Target is not reached"); 66 | // transfer: transfer ETH and revert if tx failed 67 | // payable(msg.sender).transfer(address(this).balance); 68 | 69 | // send: transfer ETH and return false if failed 70 | // bool success = payable(msg.sender).send(address(this).balance); 71 | // require(success, "tx failed"); 72 | 73 | // call: transfer ETH with data return value of function and bool 74 | bool success; 75 | (success, ) = payable(msg.sender).call{value: address(this).balance}(""); 76 | require(success, "transfer tx failed"); 77 | fundersToAmount[msg.sender] = 0; 78 | getFundSuccess = true; // flag 79 | } 80 | 81 | function refund() external windowClosed { 82 | require(convertEthToUsd(address(this).balance) < TARGET, "Target is reached"); 83 | require(fundersToAmount[msg.sender] != 0, "there is no fund for you"); 84 | bool success; 85 | (success, ) = payable(msg.sender).call{value: fundersToAmount[msg.sender]}(""); 86 | require(success, "transfer tx failed"); 87 | fundersToAmount[msg.sender] = 0; 88 | } 89 | 90 | function setFunderToAmount(address funder, uint256 amountToUpdate) external { 91 | require(msg.sender == erc20Addr, "you do not have permission to call this funtion"); 92 | fundersToAmount[funder] = amountToUpdate; 93 | } 94 | 95 | function setErc20Addr(address _erc20Addr) public onlyOwner { 96 | erc20Addr = _erc20Addr; 97 | } 98 | 99 | modifier windowClosed() { 100 | require(block.timestamp >= deploymentTimestamp + lockTime, "window is not closed"); 101 | _; 102 | } 103 | 104 | modifier onlyOwner() { 105 | require(msg.sender == owner, "this function can only be called by owner"); 106 | _; 107 | } 108 | 109 | } -------------------------------------------------------------------------------- /lesson-3/FundMe_flattened.sol: -------------------------------------------------------------------------------- 1 | 2 | // File: @chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol 3 | 4 | 5 | pragma solidity ^0.8.0; 6 | 7 | interface AggregatorV3Interface { 8 | function decimals() external view returns (uint8); 9 | 10 | function description() external view returns (string memory); 11 | 12 | function version() external view returns (uint256); 13 | 14 | function getRoundData( 15 | uint80 _roundId 16 | ) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound); 17 | 18 | function latestRoundData() 19 | external 20 | view 21 | returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound); 22 | } 23 | 24 | // File: FundMe.sol 25 | 26 | //SPDX-License-Identifier: MIT 27 | pragma solidity ^0.8.20; 28 | 29 | 30 | // 1. 创建一个收款函数 31 | // 2. 记录投资人并且查看 32 | // 3. 在锁定期内,达到目标值,生产商可以提款 33 | // 4. 在锁定期内,没有达到目标值,投资人在锁定期以后退款 34 | 35 | contract FundMe { 36 | mapping(address => uint256) public fundersToAmount; 37 | 38 | uint256 constant MINIMUM_VALUE = 100 * 10 ** 18; //USD 39 | 40 | AggregatorV3Interface internal dataFeed; 41 | 42 | uint256 constant TARGET = 1000 * 10 ** 18; 43 | 44 | address public owner; 45 | 46 | uint256 deploymentTimestamp; 47 | uint256 lockTime; 48 | 49 | address erc20Addr; 50 | 51 | bool public getFundSuccess = false; 52 | 53 | constructor(uint256 _lockTime) { 54 | // sepolia testnet 55 | dataFeed = AggregatorV3Interface(0x694AA1769357215DE4FAC081bf1f309aDC325306); 56 | owner = msg.sender; 57 | deploymentTimestamp = block.timestamp; 58 | lockTime = _lockTime; 59 | } 60 | 61 | function fund() external payable { 62 | require(convertEthToUsd(msg.value) >= MINIMUM_VALUE, "Send more ETH"); 63 | require(block.timestamp < deploymentTimestamp + lockTime, "window is closed"); 64 | fundersToAmount[msg.sender] = msg.value; 65 | } 66 | 67 | function getChainlinkDataFeedLatestAnswer() public view returns (int) { 68 | // prettier-ignore 69 | ( 70 | /* uint80 roundID */, 71 | int answer, 72 | /*uint startedAt*/, 73 | /*uint timeStamp*/, 74 | /*uint80 answeredInRound*/ 75 | ) = dataFeed.latestRoundData(); 76 | return answer; 77 | } 78 | 79 | function convertEthToUsd(uint256 ethAmount) internal view returns(uint256){ 80 | uint256 ethPrice = uint256(getChainlinkDataFeedLatestAnswer()); 81 | return ethAmount * ethPrice / (10 ** 8); 82 | } 83 | 84 | function transferOwnership(address newOwner) public onlyOwner{ 85 | owner = newOwner; 86 | } 87 | 88 | function getFund() external windowClosed onlyOwner{ 89 | require(convertEthToUsd(address(this).balance) >= TARGET, "Target is not reached"); 90 | // transfer: transfer ETH and revert if tx failed 91 | // payable(msg.sender).transfer(address(this).balance); 92 | 93 | // send: transfer ETH and return false if failed 94 | // bool success = payable(msg.sender).send(address(this).balance); 95 | // require(success, "tx failed"); 96 | 97 | // call: transfer ETH with data return value of function and bool 98 | bool success; 99 | (success, ) = payable(msg.sender).call{value: address(this).balance}(""); 100 | require(success, "transfer tx failed"); 101 | fundersToAmount[msg.sender] = 0; 102 | getFundSuccess = true; // flag 103 | } 104 | 105 | function refund() external windowClosed { 106 | require(convertEthToUsd(address(this).balance) < TARGET, "Target is reached"); 107 | require(fundersToAmount[msg.sender] != 0, "there is no fund for you"); 108 | bool success; 109 | (success, ) = payable(msg.sender).call{value: fundersToAmount[msg.sender]}(""); 110 | require(success, "transfer tx failed"); 111 | fundersToAmount[msg.sender] = 0; 112 | } 113 | 114 | function setFunderToAmount(address funder, uint256 amountToUpdate) external { 115 | require(msg.sender == erc20Addr, "you do not have permission to call this funtion"); 116 | fundersToAmount[funder] = amountToUpdate; 117 | } 118 | 119 | function setErc20Addr(address _erc20Addr) public onlyOwner { 120 | erc20Addr = _erc20Addr; 121 | } 122 | 123 | modifier windowClosed() { 124 | require(block.timestamp >= deploymentTimestamp + lockTime, "window is not closed"); 125 | _; 126 | } 127 | 128 | modifier onlyOwner() { 129 | require(msg.sender == owner, "this function can only be called by owner"); 130 | _; 131 | } 132 | 133 | } -------------------------------------------------------------------------------- /lesson-3/FundToken.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | contract FundToken { 5 | // 1. 通证的名字 6 | // 2. 通证的简称 7 | // 3. 通证的发行数量 8 | // 4. owner地址 9 | // 5. balance address => uint256 10 | string public tokenName; 11 | string public tokenSymbol; 12 | uint256 public totalSupply; 13 | address public owner; 14 | mapping(address => uint256) public balances; 15 | 16 | constructor(string memory _tokenName, string memory _tokenSymbol) { 17 | tokenName = _tokenName; 18 | tokenSymbol = _tokenSymbol; 19 | owner = msg.sender; 20 | } 21 | 22 | // mint: 获取通证 23 | function mint(uint256 amountToMint) public { 24 | balances[msg.sender] += amountToMint; 25 | totalSupply += amountToMint; 26 | } 27 | 28 | // transfer: transfer 通证 29 | function transfer(address payee, uint256 amount) public { 30 | require(balances[msg.sender] >= amount, "You do not have enough balance to transfer"); 31 | balances[msg.sender] -= amount; 32 | balances[payee] += amount; 33 | } 34 | 35 | // balanceOf: 查看某一个地址的通证数量 36 | function balanceOf(address addr) public view returns(uint256) { 37 | return balances[addr]; 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /lesson-3/FundTokenERC20.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | // FundMe 6 | // 1. 让FundMe的参与者,基于 mapping 来领取相应数量的通证 7 | // 2. 让FundMe的参与者,transfer 通证 8 | // 3. 在使用完成以后,需要burn 通证 9 | 10 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 11 | import {FundMe} from "./FundMe.sol"; 12 | 13 | contract FundTokenERC20 is ERC20 { 14 | FundMe fundMe; 15 | constructor(address fundMeAddr) ERC20("FundTokenERC20", "FT") { 16 | fundMe = FundMe(fundMeAddr); 17 | } 18 | 19 | function mint(uint256 amountToMint) public { 20 | require(fundMe.fundersToAmount(msg.sender) >= amountToMint, "You cannot mint this many tokens"); 21 | require(fundMe.getFundSuccess(), "The fundme is not completed yet"); 22 | _mint(msg.sender, amountToMint); 23 | fundMe.setFunderToAmount(msg.sender, fundMe.fundersToAmount(msg.sender) - amountToMint); 24 | } 25 | 26 | function claim(uint256 amountToClaim) public { 27 | // complete cliam 28 | require(balanceOf(msg.sender) >= amountToClaim, "You dont have enough ERC20 tokens"); 29 | require(fundMe.getFundSuccess(), "The fundme is not completed yet"); 30 | /*to add */ 31 | // burn amountToClaim Tokens 32 | _burn(msg.sender, amountToClaim); 33 | } 34 | } -------------------------------------------------------------------------------- /lesson-3/FundTokenERC20_flattened.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | // File: @chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol 3 | 4 | 5 | pragma solidity ^0.8.0; 6 | 7 | interface AggregatorV3Interface { 8 | function decimals() external view returns (uint8); 9 | 10 | function description() external view returns (string memory); 11 | 12 | function version() external view returns (uint256); 13 | 14 | function getRoundData( 15 | uint80 _roundId 16 | ) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound); 17 | 18 | function latestRoundData() 19 | external 20 | view 21 | returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound); 22 | } 23 | 24 | // File: FundMe.sol 25 | 26 | 27 | pragma solidity ^0.8.20; 28 | 29 | 30 | // 1. 创建一个收款函数 31 | // 2. 记录投资人并且查看 32 | // 3. 在锁定期内,达到目标值,生产商可以提款 33 | // 4. 在锁定期内,没有达到目标值,投资人在锁定期以后退款 34 | 35 | contract FundMe { 36 | mapping(address => uint256) public fundersToAmount; 37 | 38 | uint256 constant MINIMUM_VALUE = 100 * 10 ** 18; //USD 39 | 40 | AggregatorV3Interface internal dataFeed; 41 | 42 | uint256 constant TARGET = 1000 * 10 ** 18; 43 | 44 | address public owner; 45 | 46 | uint256 deploymentTimestamp; 47 | uint256 lockTime; 48 | 49 | address erc20Addr; 50 | 51 | bool public getFundSuccess = false; 52 | 53 | constructor(uint256 _lockTime) { 54 | // sepolia testnet 55 | dataFeed = AggregatorV3Interface(0x694AA1769357215DE4FAC081bf1f309aDC325306); 56 | owner = msg.sender; 57 | deploymentTimestamp = block.timestamp; 58 | lockTime = _lockTime; 59 | } 60 | 61 | function fund() external payable { 62 | require(convertEthToUsd(msg.value) >= MINIMUM_VALUE, "Send more ETH"); 63 | require(block.timestamp < deploymentTimestamp + lockTime, "window is closed"); 64 | fundersToAmount[msg.sender] = msg.value; 65 | } 66 | 67 | function getChainlinkDataFeedLatestAnswer() public view returns (int) { 68 | // prettier-ignore 69 | ( 70 | /* uint80 roundID */, 71 | int answer, 72 | /*uint startedAt*/, 73 | /*uint timeStamp*/, 74 | /*uint80 answeredInRound*/ 75 | ) = dataFeed.latestRoundData(); 76 | return answer; 77 | } 78 | 79 | function convertEthToUsd(uint256 ethAmount) internal view returns(uint256){ 80 | uint256 ethPrice = uint256(getChainlinkDataFeedLatestAnswer()); 81 | return ethAmount * ethPrice / (10 ** 8); 82 | } 83 | 84 | function transferOwnership(address newOwner) public onlyOwner{ 85 | owner = newOwner; 86 | } 87 | 88 | function getFund() external windowClosed onlyOwner{ 89 | require(convertEthToUsd(address(this).balance) >= TARGET, "Target is not reached"); 90 | // transfer: transfer ETH and revert if tx failed 91 | // payable(msg.sender).transfer(address(this).balance); 92 | 93 | // send: transfer ETH and return false if failed 94 | // bool success = payable(msg.sender).send(address(this).balance); 95 | // require(success, "tx failed"); 96 | 97 | // call: transfer ETH with data return value of function and bool 98 | bool success; 99 | (success, ) = payable(msg.sender).call{value: address(this).balance}(""); 100 | require(success, "transfer tx failed"); 101 | fundersToAmount[msg.sender] = 0; 102 | getFundSuccess = true; // flag 103 | } 104 | 105 | function refund() external windowClosed { 106 | require(convertEthToUsd(address(this).balance) < TARGET, "Target is reached"); 107 | require(fundersToAmount[msg.sender] != 0, "there is no fund for you"); 108 | bool success; 109 | (success, ) = payable(msg.sender).call{value: fundersToAmount[msg.sender]}(""); 110 | require(success, "transfer tx failed"); 111 | fundersToAmount[msg.sender] = 0; 112 | } 113 | 114 | function setFunderToAmount(address funder, uint256 amountToUpdate) external { 115 | require(msg.sender == erc20Addr, "you do not have permission to call this funtion"); 116 | fundersToAmount[funder] = amountToUpdate; 117 | } 118 | 119 | function setErc20Addr(address _erc20Addr) public onlyOwner { 120 | erc20Addr = _erc20Addr; 121 | } 122 | 123 | modifier windowClosed() { 124 | require(block.timestamp >= deploymentTimestamp + lockTime, "window is not closed"); 125 | _; 126 | } 127 | 128 | modifier onlyOwner() { 129 | require(msg.sender == owner, "this function can only be called by owner"); 130 | _; 131 | } 132 | 133 | } 134 | // File: @openzeppelin/contracts/interfaces/draft-IERC6093.sol 135 | 136 | 137 | // OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol) 138 | pragma solidity ^0.8.20; 139 | 140 | /** 141 | * @dev Standard ERC20 Errors 142 | * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens. 143 | */ 144 | interface IERC20Errors { 145 | /** 146 | * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. 147 | * @param sender Address whose tokens are being transferred. 148 | * @param balance Current balance for the interacting account. 149 | * @param needed Minimum amount required to perform a transfer. 150 | */ 151 | error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed); 152 | 153 | /** 154 | * @dev Indicates a failure with the token `sender`. Used in transfers. 155 | * @param sender Address whose tokens are being transferred. 156 | */ 157 | error ERC20InvalidSender(address sender); 158 | 159 | /** 160 | * @dev Indicates a failure with the token `receiver`. Used in transfers. 161 | * @param receiver Address to which tokens are being transferred. 162 | */ 163 | error ERC20InvalidReceiver(address receiver); 164 | 165 | /** 166 | * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers. 167 | * @param spender Address that may be allowed to operate on tokens without being their owner. 168 | * @param allowance Amount of tokens a `spender` is allowed to operate with. 169 | * @param needed Minimum amount required to perform a transfer. 170 | */ 171 | error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed); 172 | 173 | /** 174 | * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. 175 | * @param approver Address initiating an approval operation. 176 | */ 177 | error ERC20InvalidApprover(address approver); 178 | 179 | /** 180 | * @dev Indicates a failure with the `spender` to be approved. Used in approvals. 181 | * @param spender Address that may be allowed to operate on tokens without being their owner. 182 | */ 183 | error ERC20InvalidSpender(address spender); 184 | } 185 | 186 | /** 187 | * @dev Standard ERC721 Errors 188 | * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens. 189 | */ 190 | interface IERC721Errors { 191 | /** 192 | * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20. 193 | * Used in balance queries. 194 | * @param owner Address of the current owner of a token. 195 | */ 196 | error ERC721InvalidOwner(address owner); 197 | 198 | /** 199 | * @dev Indicates a `tokenId` whose `owner` is the zero address. 200 | * @param tokenId Identifier number of a token. 201 | */ 202 | error ERC721NonexistentToken(uint256 tokenId); 203 | 204 | /** 205 | * @dev Indicates an error related to the ownership over a particular token. Used in transfers. 206 | * @param sender Address whose tokens are being transferred. 207 | * @param tokenId Identifier number of a token. 208 | * @param owner Address of the current owner of a token. 209 | */ 210 | error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner); 211 | 212 | /** 213 | * @dev Indicates a failure with the token `sender`. Used in transfers. 214 | * @param sender Address whose tokens are being transferred. 215 | */ 216 | error ERC721InvalidSender(address sender); 217 | 218 | /** 219 | * @dev Indicates a failure with the token `receiver`. Used in transfers. 220 | * @param receiver Address to which tokens are being transferred. 221 | */ 222 | error ERC721InvalidReceiver(address receiver); 223 | 224 | /** 225 | * @dev Indicates a failure with the `operator`’s approval. Used in transfers. 226 | * @param operator Address that may be allowed to operate on tokens without being their owner. 227 | * @param tokenId Identifier number of a token. 228 | */ 229 | error ERC721InsufficientApproval(address operator, uint256 tokenId); 230 | 231 | /** 232 | * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. 233 | * @param approver Address initiating an approval operation. 234 | */ 235 | error ERC721InvalidApprover(address approver); 236 | 237 | /** 238 | * @dev Indicates a failure with the `operator` to be approved. Used in approvals. 239 | * @param operator Address that may be allowed to operate on tokens without being their owner. 240 | */ 241 | error ERC721InvalidOperator(address operator); 242 | } 243 | 244 | /** 245 | * @dev Standard ERC1155 Errors 246 | * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens. 247 | */ 248 | interface IERC1155Errors { 249 | /** 250 | * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. 251 | * @param sender Address whose tokens are being transferred. 252 | * @param balance Current balance for the interacting account. 253 | * @param needed Minimum amount required to perform a transfer. 254 | * @param tokenId Identifier number of a token. 255 | */ 256 | error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId); 257 | 258 | /** 259 | * @dev Indicates a failure with the token `sender`. Used in transfers. 260 | * @param sender Address whose tokens are being transferred. 261 | */ 262 | error ERC1155InvalidSender(address sender); 263 | 264 | /** 265 | * @dev Indicates a failure with the token `receiver`. Used in transfers. 266 | * @param receiver Address to which tokens are being transferred. 267 | */ 268 | error ERC1155InvalidReceiver(address receiver); 269 | 270 | /** 271 | * @dev Indicates a failure with the `operator`’s approval. Used in transfers. 272 | * @param operator Address that may be allowed to operate on tokens without being their owner. 273 | * @param owner Address of the current owner of a token. 274 | */ 275 | error ERC1155MissingApprovalForAll(address operator, address owner); 276 | 277 | /** 278 | * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. 279 | * @param approver Address initiating an approval operation. 280 | */ 281 | error ERC1155InvalidApprover(address approver); 282 | 283 | /** 284 | * @dev Indicates a failure with the `operator` to be approved. Used in approvals. 285 | * @param operator Address that may be allowed to operate on tokens without being their owner. 286 | */ 287 | error ERC1155InvalidOperator(address operator); 288 | 289 | /** 290 | * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation. 291 | * Used in batch transfers. 292 | * @param idsLength Length of the array of token identifiers 293 | * @param valuesLength Length of the array of token amounts 294 | */ 295 | error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength); 296 | } 297 | 298 | // File: @openzeppelin/contracts/utils/Context.sol 299 | 300 | 301 | // OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol) 302 | 303 | pragma solidity ^0.8.20; 304 | 305 | /** 306 | * @dev Provides information about the current execution context, including the 307 | * sender of the transaction and its data. While these are generally available 308 | * via msg.sender and msg.data, they should not be accessed in such a direct 309 | * manner, since when dealing with meta-transactions the account sending and 310 | * paying for execution may not be the actual sender (as far as an application 311 | * is concerned). 312 | * 313 | * This contract is only required for intermediate, library-like contracts. 314 | */ 315 | abstract contract Context { 316 | function _msgSender() internal view virtual returns (address) { 317 | return msg.sender; 318 | } 319 | 320 | function _msgData() internal view virtual returns (bytes calldata) { 321 | return msg.data; 322 | } 323 | 324 | function _contextSuffixLength() internal view virtual returns (uint256) { 325 | return 0; 326 | } 327 | } 328 | 329 | // File: @openzeppelin/contracts/token/ERC20/IERC20.sol 330 | 331 | 332 | // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol) 333 | 334 | pragma solidity ^0.8.20; 335 | 336 | /** 337 | * @dev Interface of the ERC20 standard as defined in the EIP. 338 | */ 339 | interface IERC20 { 340 | /** 341 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 342 | * another (`to`). 343 | * 344 | * Note that `value` may be zero. 345 | */ 346 | event Transfer(address indexed from, address indexed to, uint256 value); 347 | 348 | /** 349 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 350 | * a call to {approve}. `value` is the new allowance. 351 | */ 352 | event Approval(address indexed owner, address indexed spender, uint256 value); 353 | 354 | /** 355 | * @dev Returns the value of tokens in existence. 356 | */ 357 | function totalSupply() external view returns (uint256); 358 | 359 | /** 360 | * @dev Returns the value of tokens owned by `account`. 361 | */ 362 | function balanceOf(address account) external view returns (uint256); 363 | 364 | /** 365 | * @dev Moves a `value` amount of tokens from the caller's account to `to`. 366 | * 367 | * Returns a boolean value indicating whether the operation succeeded. 368 | * 369 | * Emits a {Transfer} event. 370 | */ 371 | function transfer(address to, uint256 value) external returns (bool); 372 | 373 | /** 374 | * @dev Returns the remaining number of tokens that `spender` will be 375 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 376 | * zero by default. 377 | * 378 | * This value changes when {approve} or {transferFrom} are called. 379 | */ 380 | function allowance(address owner, address spender) external view returns (uint256); 381 | 382 | /** 383 | * @dev Sets a `value` amount of tokens as the allowance of `spender` over the 384 | * caller's tokens. 385 | * 386 | * Returns a boolean value indicating whether the operation succeeded. 387 | * 388 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 389 | * that someone may use both the old and the new allowance by unfortunate 390 | * transaction ordering. One possible solution to mitigate this race 391 | * condition is to first reduce the spender's allowance to 0 and set the 392 | * desired value afterwards: 393 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 394 | * 395 | * Emits an {Approval} event. 396 | */ 397 | function approve(address spender, uint256 value) external returns (bool); 398 | 399 | /** 400 | * @dev Moves a `value` amount of tokens from `from` to `to` using the 401 | * allowance mechanism. `value` is then deducted from the caller's 402 | * allowance. 403 | * 404 | * Returns a boolean value indicating whether the operation succeeded. 405 | * 406 | * Emits a {Transfer} event. 407 | */ 408 | function transferFrom(address from, address to, uint256 value) external returns (bool); 409 | } 410 | 411 | // File: @openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol 412 | 413 | 414 | // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol) 415 | 416 | pragma solidity ^0.8.20; 417 | 418 | 419 | /** 420 | * @dev Interface for the optional metadata functions from the ERC20 standard. 421 | */ 422 | interface IERC20Metadata is IERC20 { 423 | /** 424 | * @dev Returns the name of the token. 425 | */ 426 | function name() external view returns (string memory); 427 | 428 | /** 429 | * @dev Returns the symbol of the token. 430 | */ 431 | function symbol() external view returns (string memory); 432 | 433 | /** 434 | * @dev Returns the decimals places of the token. 435 | */ 436 | function decimals() external view returns (uint8); 437 | } 438 | 439 | // File: @openzeppelin/contracts/token/ERC20/ERC20.sol 440 | 441 | 442 | // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/ERC20.sol) 443 | 444 | pragma solidity ^0.8.20; 445 | 446 | 447 | 448 | 449 | 450 | /** 451 | * @dev Implementation of the {IERC20} interface. 452 | * 453 | * This implementation is agnostic to the way tokens are created. This means 454 | * that a supply mechanism has to be added in a derived contract using {_mint}. 455 | * 456 | * TIP: For a detailed writeup see our guide 457 | * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How 458 | * to implement supply mechanisms]. 459 | * 460 | * The default value of {decimals} is 18. To change this, you should override 461 | * this function so it returns a different value. 462 | * 463 | * We have followed general OpenZeppelin Contracts guidelines: functions revert 464 | * instead returning `false` on failure. This behavior is nonetheless 465 | * conventional and does not conflict with the expectations of ERC20 466 | * applications. 467 | * 468 | * Additionally, an {Approval} event is emitted on calls to {transferFrom}. 469 | * This allows applications to reconstruct the allowance for all accounts just 470 | * by listening to said events. Other implementations of the EIP may not emit 471 | * these events, as it isn't required by the specification. 472 | */ 473 | abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { 474 | mapping(address account => uint256) private _balances; 475 | 476 | mapping(address account => mapping(address spender => uint256)) private _allowances; 477 | 478 | uint256 private _totalSupply; 479 | 480 | string private _name; 481 | string private _symbol; 482 | 483 | /** 484 | * @dev Sets the values for {name} and {symbol}. 485 | * 486 | * All two of these values are immutable: they can only be set once during 487 | * construction. 488 | */ 489 | constructor(string memory name_, string memory symbol_) { 490 | _name = name_; 491 | _symbol = symbol_; 492 | } 493 | 494 | /** 495 | * @dev Returns the name of the token. 496 | */ 497 | function name() public view virtual returns (string memory) { 498 | return _name; 499 | } 500 | 501 | /** 502 | * @dev Returns the symbol of the token, usually a shorter version of the 503 | * name. 504 | */ 505 | function symbol() public view virtual returns (string memory) { 506 | return _symbol; 507 | } 508 | 509 | /** 510 | * @dev Returns the number of decimals used to get its user representation. 511 | * For example, if `decimals` equals `2`, a balance of `505` tokens should 512 | * be displayed to a user as `5.05` (`505 / 10 ** 2`). 513 | * 514 | * Tokens usually opt for a value of 18, imitating the relationship between 515 | * Ether and Wei. This is the default value returned by this function, unless 516 | * it's overridden. 517 | * 518 | * NOTE: This information is only used for _display_ purposes: it in 519 | * no way affects any of the arithmetic of the contract, including 520 | * {IERC20-balanceOf} and {IERC20-transfer}. 521 | */ 522 | function decimals() public view virtual returns (uint8) { 523 | return 18; 524 | } 525 | 526 | /** 527 | * @dev See {IERC20-totalSupply}. 528 | */ 529 | function totalSupply() public view virtual returns (uint256) { 530 | return _totalSupply; 531 | } 532 | 533 | /** 534 | * @dev See {IERC20-balanceOf}. 535 | */ 536 | function balanceOf(address account) public view virtual returns (uint256) { 537 | return _balances[account]; 538 | } 539 | 540 | /** 541 | * @dev See {IERC20-transfer}. 542 | * 543 | * Requirements: 544 | * 545 | * - `to` cannot be the zero address. 546 | * - the caller must have a balance of at least `value`. 547 | */ 548 | function transfer(address to, uint256 value) public virtual returns (bool) { 549 | address owner = _msgSender(); 550 | _transfer(owner, to, value); 551 | return true; 552 | } 553 | 554 | /** 555 | * @dev See {IERC20-allowance}. 556 | */ 557 | function allowance(address owner, address spender) public view virtual returns (uint256) { 558 | return _allowances[owner][spender]; 559 | } 560 | 561 | /** 562 | * @dev See {IERC20-approve}. 563 | * 564 | * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on 565 | * `transferFrom`. This is semantically equivalent to an infinite approval. 566 | * 567 | * Requirements: 568 | * 569 | * - `spender` cannot be the zero address. 570 | */ 571 | function approve(address spender, uint256 value) public virtual returns (bool) { 572 | address owner = _msgSender(); 573 | _approve(owner, spender, value); 574 | return true; 575 | } 576 | 577 | /** 578 | * @dev See {IERC20-transferFrom}. 579 | * 580 | * Emits an {Approval} event indicating the updated allowance. This is not 581 | * required by the EIP. See the note at the beginning of {ERC20}. 582 | * 583 | * NOTE: Does not update the allowance if the current allowance 584 | * is the maximum `uint256`. 585 | * 586 | * Requirements: 587 | * 588 | * - `from` and `to` cannot be the zero address. 589 | * - `from` must have a balance of at least `value`. 590 | * - the caller must have allowance for ``from``'s tokens of at least 591 | * `value`. 592 | */ 593 | function transferFrom(address from, address to, uint256 value) public virtual returns (bool) { 594 | address spender = _msgSender(); 595 | _spendAllowance(from, spender, value); 596 | _transfer(from, to, value); 597 | return true; 598 | } 599 | 600 | /** 601 | * @dev Moves a `value` amount of tokens from `from` to `to`. 602 | * 603 | * This internal function is equivalent to {transfer}, and can be used to 604 | * e.g. implement automatic token fees, slashing mechanisms, etc. 605 | * 606 | * Emits a {Transfer} event. 607 | * 608 | * NOTE: This function is not virtual, {_update} should be overridden instead. 609 | */ 610 | function _transfer(address from, address to, uint256 value) internal { 611 | if (from == address(0)) { 612 | revert ERC20InvalidSender(address(0)); 613 | } 614 | if (to == address(0)) { 615 | revert ERC20InvalidReceiver(address(0)); 616 | } 617 | _update(from, to, value); 618 | } 619 | 620 | /** 621 | * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from` 622 | * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding 623 | * this function. 624 | * 625 | * Emits a {Transfer} event. 626 | */ 627 | function _update(address from, address to, uint256 value) internal virtual { 628 | if (from == address(0)) { 629 | // Overflow check required: The rest of the code assumes that totalSupply never overflows 630 | _totalSupply += value; 631 | } else { 632 | uint256 fromBalance = _balances[from]; 633 | if (fromBalance < value) { 634 | revert ERC20InsufficientBalance(from, fromBalance, value); 635 | } 636 | unchecked { 637 | // Overflow not possible: value <= fromBalance <= totalSupply. 638 | _balances[from] = fromBalance - value; 639 | } 640 | } 641 | 642 | if (to == address(0)) { 643 | unchecked { 644 | // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply. 645 | _totalSupply -= value; 646 | } 647 | } else { 648 | unchecked { 649 | // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256. 650 | _balances[to] += value; 651 | } 652 | } 653 | 654 | emit Transfer(from, to, value); 655 | } 656 | 657 | /** 658 | * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0). 659 | * Relies on the `_update` mechanism 660 | * 661 | * Emits a {Transfer} event with `from` set to the zero address. 662 | * 663 | * NOTE: This function is not virtual, {_update} should be overridden instead. 664 | */ 665 | function _mint(address account, uint256 value) internal { 666 | if (account == address(0)) { 667 | revert ERC20InvalidReceiver(address(0)); 668 | } 669 | _update(address(0), account, value); 670 | } 671 | 672 | /** 673 | * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply. 674 | * Relies on the `_update` mechanism. 675 | * 676 | * Emits a {Transfer} event with `to` set to the zero address. 677 | * 678 | * NOTE: This function is not virtual, {_update} should be overridden instead 679 | */ 680 | function _burn(address account, uint256 value) internal { 681 | if (account == address(0)) { 682 | revert ERC20InvalidSender(address(0)); 683 | } 684 | _update(account, address(0), value); 685 | } 686 | 687 | /** 688 | * @dev Sets `value` as the allowance of `spender` over the `owner` s tokens. 689 | * 690 | * This internal function is equivalent to `approve`, and can be used to 691 | * e.g. set automatic allowances for certain subsystems, etc. 692 | * 693 | * Emits an {Approval} event. 694 | * 695 | * Requirements: 696 | * 697 | * - `owner` cannot be the zero address. 698 | * - `spender` cannot be the zero address. 699 | * 700 | * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument. 701 | */ 702 | function _approve(address owner, address spender, uint256 value) internal { 703 | _approve(owner, spender, value, true); 704 | } 705 | 706 | /** 707 | * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event. 708 | * 709 | * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by 710 | * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any 711 | * `Approval` event during `transferFrom` operations. 712 | * 713 | * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to 714 | * true using the following override: 715 | * ``` 716 | * function _approve(address owner, address spender, uint256 value, bool) internal virtual override { 717 | * super._approve(owner, spender, value, true); 718 | * } 719 | * ``` 720 | * 721 | * Requirements are the same as {_approve}. 722 | */ 723 | function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual { 724 | if (owner == address(0)) { 725 | revert ERC20InvalidApprover(address(0)); 726 | } 727 | if (spender == address(0)) { 728 | revert ERC20InvalidSpender(address(0)); 729 | } 730 | _allowances[owner][spender] = value; 731 | if (emitEvent) { 732 | emit Approval(owner, spender, value); 733 | } 734 | } 735 | 736 | /** 737 | * @dev Updates `owner` s allowance for `spender` based on spent `value`. 738 | * 739 | * Does not update the allowance value in case of infinite allowance. 740 | * Revert if not enough allowance is available. 741 | * 742 | * Does not emit an {Approval} event. 743 | */ 744 | function _spendAllowance(address owner, address spender, uint256 value) internal virtual { 745 | uint256 currentAllowance = allowance(owner, spender); 746 | if (currentAllowance != type(uint256).max) { 747 | if (currentAllowance < value) { 748 | revert ERC20InsufficientAllowance(spender, currentAllowance, value); 749 | } 750 | unchecked { 751 | _approve(owner, spender, currentAllowance - value, false); 752 | } 753 | } 754 | } 755 | } 756 | 757 | // File: FundTokenERC20.sol 758 | 759 | 760 | pragma solidity ^0.8.20; 761 | 762 | // FundMe 763 | // 1. 让FundMe的参与者,基于 mapping 来领取相应数量的通证 764 | // 2. 让FundMe的参与者,transfer 通证 765 | // 3. 在使用完成以后,需要burn 通证 766 | 767 | 768 | 769 | contract FundTokenERC20 is ERC20 { 770 | FundMe fundMe; 771 | constructor(address fundMeAddr) ERC20("FundTokenERC20", "FT") { 772 | fundMe = FundMe(fundMeAddr); 773 | } 774 | 775 | function mint(uint256 amountToMint) public { 776 | require(fundMe.fundersToAmount(msg.sender) >= amountToMint, "You cannot mint this many tokens"); 777 | require(fundMe.getFundSuccess(), "The fundme is not completed yet"); 778 | _mint(msg.sender, amountToMint); 779 | fundMe.setFunderToAmount(msg.sender, fundMe.fundersToAmount(msg.sender) - amountToMint); 780 | } 781 | 782 | function claim(uint256 amountToClaim) public { 783 | // complete cliam 784 | require(balanceOf(msg.sender) >= amountToClaim, "You dont have enough ERC20 tokens"); 785 | require(fundMe.getFundSuccess(), "The fundme is not completed yet"); 786 | /*to add */ 787 | // burn amountToClaim Tokens 788 | _burn(msg.sender, amountToClaim); 789 | } 790 | } -------------------------------------------------------------------------------- /lesson-3/README.md: -------------------------------------------------------------------------------- 1 | # 第三课 | Lesson 3 2 | [中文](#内容介绍) | [English](#introduction) 3 | 4 | ## 内容介绍 5 | 这是 Web3_tutorial 的一部分。
6 | 视频链接:WIP
7 | 8 | 在第三课,你将会编写第二个智能合约 `FundMe`,通过编写和学习这个智能合约中,我们将会学习到如何在一条区块链上发送原生token给一个合约,记录转账信息,如何提取收集的token,如何设置时间锁。 9 | 10 | 在这一课中,你将完成以下合约: 11 | - `FundMe.sol`合约:该合约是募集资金的合约,通过函数来接受funder的ETH 12 | - `FundMe_flattened.sol`合约:该合约是FundMe合约的flatten版本,可以通过 Remix flatten 插件生成,用于EtherScan上的合约验证 13 | - `FundMeToken.sol`合约:该合约是带有基本功能的 Token 合约 14 | - `FundMeTokenWithBurn.sol`合约:该合约继承FundToken合约,并且增加了`burn`函数 15 | - `FundMeTokenERC20.sol`合约:该合约是继承Openzeppelin ERC20标准的 FundToken 合约 16 | - `FundMeTokenERC20_flattened.sol`合约:该合约是FundMeTokenERC20合约的flatten版本,可以通过 Remix flatten 插件生成,用于EtherScan上的合约验证 17 | 18 | ## 如何使用 19 | 1. 编译并且部署 `FundMe.sol` 合约 20 | - 打开 [Remix](https://remix.ethereum.org/) 21 | - 新建一个文件,并且命名为 `FundMe.sol` 22 | - 点击 Compile 23 | - 通过 metamask 领取 SepoliaETH 24 | - 选择 injected provider,点击 Deploy 25 | - 在 remix 中,右键点击合约名称,选择 flatten 26 | - 在 [sepolia 区块链浏览器](https://sepolia.etherscan.io/)中对合约进行验证 27 | 2. 编译并且部署 `FundMeToken.sol`合约 28 | - 新建一个文件,并且命名为 `FundMeToken.sol` 29 | - 点击 Compile 30 | - 点击 Deploy 31 | - 在 remix 中,右键点击合约名称,选择 flatten 32 | - 在 [sepolia 区块链浏览器](https://sepolia.etherscan.io/)中对合约进行验证 33 | 3. 编译并且部署 `FundMeTokenERC20.sol`合约 34 | - 新建一个文件,并且命名为 `FundMeTokenERC20.sol` 35 | - 点击 Compile 36 | - 在 remix 中,右键点击合约名称,选择 flatten 37 | - 在 [sepolia 区块链浏览器](https://sepolia.etherscan.io/)中对合约进行验证 38 | 39 | 更多的相关内容请查看[Web3_tutorial](https://github.com/QingyangKong/Web3_tutorial)的 `README.md`。 40 | 41 | ## introduction 42 | This is part of the Web3_tutorial.
43 | Video link: WIP
44 | 45 | In the third lesson, you will write the second smart contract `FundMe`. By writing and learning about this smart contract, we will learn how to send native tokens to a contract on a blockchain, record transfer information, how to withdraw collected tokens, and how to set a time lock. 46 | 47 | In this lesson, you will complete the following contracts: 48 | 49 | - `FundMe.sol` contract: This contract is for fundraising, it accepts ETH from funders through functions. 50 | - `FundMe_flattened.sol` contract: This contract is a flattened version of the FundMe contract, which can be generated using the Remix flatten plugin for contract verification on EtherScan. 51 | - `FundMeToken.sol` contract: This contract is a Token contract with basic functionality. 52 | - `FundMeTokenWithBurn.sol` contract: This contract inherits the FundToken contract and adds a `burn` function. 53 | - `FundMeTokenERC20.sol` contract: This contract is a FundToken contract that inherits the OpenZeppelin ERC20 standard. 54 | - `FundMeTokenERC20_flattened.sol` contract: This contract is a flattened version of the FundMeTokenERC20 contract, which can be generated using the Remix flatten plugin for contract verification on EtherScan. 55 | 56 | ## Getting Started 57 | 1. Compile and deploy the `FundMe.sol` contract 58 | - Open [Remix](https://remix.ethereum.org/) 59 | - Create a new file named `FundMe.sol` 60 | - Click Compile 61 | - Get SepoliaETH through Metamask 62 | - Select the injected provider and click Deploy 63 | - In Remix, right-click on the contract name and select flatten 64 | - Verify the contract on [SepoliaScan](https://sepolia.etherscan.io/) 65 | 2. Compile and deploy the `FundMeToken.sol` contract 66 | - Create a new file named `FundMeToken.sol` 67 | - Click Compile 68 | - Click Deploy 69 | - In Remix, right-click on the contract name and select flatten 70 | - Verify the contract on [SepoliaScan](https://sepolia.etherscan.io/) 71 | 3. Compile and deploy the `FundMeTokenERC20.sol` contract 72 | - Create a new file named `FundMeTokenERC20.sol` 73 | - Click Compile 74 | - In Remix, right-click on the contract name and select flatten 75 | - Verify the contract on [SepoliaScan](https://sepolia.etherscan.io/) 76 | 77 | 78 | For more related content, please refer to the `README.md` of [Web3_tutorial](https://github.com/QingyangKong/Web3_tutorial). 79 | -------------------------------------------------------------------------------- /lesson-4/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | .env.enc 4 | 5 | # Hardhat files 6 | /cache 7 | /artifacts 8 | 9 | # TypeChain files 10 | /typechain 11 | /typechain-types 12 | 13 | # solidity-coverage files 14 | /coverage 15 | /coverage.json 16 | -------------------------------------------------------------------------------- /lesson-4/README.md: -------------------------------------------------------------------------------- 1 | # 第四课 | Lesson 4 2 | [中文](#内容介绍) | [English](#introduction) 3 | 4 | ## 内容介绍 5 | 这是 Web3_tutorial 的一部分。
6 | 视频链接:WIP
7 | 8 | 在第四课,你将会学习如何使用 Hardhat 框架来编译,部署并且与合约交互,在这一课你将会安装开发环境,开发环境包括:nodejs,git,VSCode等,你将会学到如何启动 hardhat 网络,并且对 Hardhat config 进行配置以适配任何网络。 9 | 10 | ## 如何使用 11 | 1. 将 repo clone到本地: 12 | `git clone https://github.com/smartcontractkit/Web3_tutorial_Chinese.git` 13 | 2. 进入 lesson-4 文件夹 14 | `cd Web3_tutorial_Chinese/lesson-4` 15 | 3. 安装 NPM package 16 | - 运行 `npm install` 安装 NPM package 17 | 4. 添加环境变量 18 | - `npx hardhat env-enc set-pw` 为 `.env.enc` 设置密码 19 | - 添加环境变量`npx hardhat env-enc set`: `PRIVATE_KEY`, `PRIVATE_KEY_1`, `SEPOLIA_RPC_URL` 和 `ETHERSCAN_API_KEY` 20 | 5. 编译并且与 `FundMe.sol` 交互 21 | - `npx hardhat run scripts/deployFundMe.js --network sepolia` 运行 deploy 脚本。 22 | 6. 在 [Sepolia 区块链浏览器](https://sepolia.etherscan.io/)中查看验证的合约 23 | 7. [可选] 运行 `npx hardhat deploy-fundme --network sepolia` 通过hardhat task部署`FundMe`合约 24 | 8. [可选] 运行 `npx hardhat fund-fundme --network sepolia`通过hardhat task与`FundMe`合约交互 25 | 26 | 更多的相关内容请查看[Web3_tutorial](https://github.com/smartcontractkit/Web3_tutorial_Chinese/tree/main)的 `README.md`。 27 | 28 | 29 | ## introduction 30 | This is part of the Web3_tutorial.
31 | Video link: WIP
32 | 33 | In lesson 4, you will learn how to compile, deploy, and interact with contracts using the Hardhat framework. In this lesson, you will set up your development environment, including: nodejs, git, VSCode, etc. You will learn how to start the Hardhat network and configure the Hardhat config to adapt to any network. 34 | 35 | ## Getting Started 36 | 1. clone the repo 37 | `git clone https://github.com/smartcontractkit/Web3_tutorial_Chinese.git` 38 | 2. change directory to folder lesson-4 39 | `cd Web3_tutorial_Chinese/lesson-4` 40 | 3. Install NPM package 41 | - Run `npm install` to install NPM packages. 42 | 4. Add environment variables 43 | - Set a password for `.env.enc` with `npx hardhat env-enc set-pw`. 44 | - Add environment variables using `npx hardhat env-enc set`: `PRIVATE_KEY`, `PRIVATE_KEY_1`, `SEPOLIA_RPC_URL`, and `ETHERSCAN_API_KEY`. 45 | 5. Compile and interact with `FundMe.sol` 46 | Run the deploy script with `npx hardhat run script/deploy.js --network sepolia`. 47 | View verified contracts on the [SepoliaScan](https://sepolia.etherscan.io/). 48 | 6. For more related content, please refer to the README.md of [Web3_tutorial](https://github.com/smartcontractkit/Web3_tutorial_Chinese/tree/main). -------------------------------------------------------------------------------- /lesson-4/contracts/FundMe.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol"; 5 | 6 | // 1. 创建一个收款函数 7 | // 2. 记录投资人并且查看 8 | // 3. 在锁定期内,达到目标值,生产商可以提款 9 | // 4. 在锁定期内,没有达到目标值,投资人在锁定期以后退款 10 | 11 | contract FundMe { 12 | mapping(address => uint256) public fundersToAmount; 13 | 14 | uint256 constant MINIMUM_VALUE = 100 * 10 ** 18; //USD 15 | 16 | AggregatorV3Interface internal dataFeed; 17 | 18 | uint256 constant TARGET = 1000 * 10 ** 18; 19 | 20 | address public owner; 21 | 22 | uint256 deploymentTimestamp; 23 | uint256 lockTime; 24 | 25 | address erc20Addr; 26 | 27 | bool public getFundSuccess = false; 28 | 29 | constructor(uint256 _lockTime) { 30 | // sepolia testnet 31 | dataFeed = AggregatorV3Interface(0x694AA1769357215DE4FAC081bf1f309aDC325306); 32 | owner = msg.sender; 33 | deploymentTimestamp = block.timestamp; 34 | lockTime = _lockTime; 35 | } 36 | 37 | function fund() external payable { 38 | require(convertEthToUsd(msg.value) >= MINIMUM_VALUE, "Send more ETH"); 39 | require(block.timestamp < deploymentTimestamp + lockTime, "window is closed"); 40 | fundersToAmount[msg.sender] = msg.value; 41 | } 42 | 43 | function getChainlinkDataFeedLatestAnswer() public view returns (int) { 44 | // prettier-ignore 45 | ( 46 | /* uint80 roundID */, 47 | int answer, 48 | /*uint startedAt*/, 49 | /*uint timeStamp*/, 50 | /*uint80 answeredInRound*/ 51 | ) = dataFeed.latestRoundData(); 52 | return answer; 53 | } 54 | 55 | function convertEthToUsd(uint256 ethAmount) internal view returns(uint256){ 56 | uint256 ethPrice = uint256(getChainlinkDataFeedLatestAnswer()); 57 | return ethAmount * ethPrice / (10 ** 8); 58 | } 59 | 60 | function transferOwnership(address newOwner) public onlyOwner{ 61 | owner = newOwner; 62 | } 63 | 64 | function getFund() external windowClosed onlyOwner{ 65 | require(convertEthToUsd(address(this).balance) >= TARGET, "Target is not reached"); 66 | // transfer: transfer ETH and revert if tx failed 67 | // payable(msg.sender).transfer(address(this).balance); 68 | 69 | // send: transfer ETH and return false if failed 70 | // bool success = payable(msg.sender).send(address(this).balance); 71 | // require(success, "tx failed"); 72 | 73 | // call: transfer ETH with data return value of function and bool 74 | bool success; 75 | (success, ) = payable(msg.sender).call{value: address(this).balance}(""); 76 | require(success, "transfer tx failed"); 77 | fundersToAmount[msg.sender] = 0; 78 | getFundSuccess = true; // flag 79 | } 80 | 81 | function refund() external windowClosed { 82 | require(convertEthToUsd(address(this).balance) < TARGET, "Target is reached"); 83 | require(fundersToAmount[msg.sender] != 0, "there is no fund for you"); 84 | bool success; 85 | (success, ) = payable(msg.sender).call{value: fundersToAmount[msg.sender]}(""); 86 | require(success, "transfer tx failed"); 87 | fundersToAmount[msg.sender] = 0; 88 | } 89 | 90 | function setFunderToAmount(address funder, uint256 amountToUpdate) external { 91 | require(msg.sender == erc20Addr, "you do not have permission to call this funtion"); 92 | fundersToAmount[funder] = amountToUpdate; 93 | } 94 | 95 | function setErc20Addr(address _erc20Addr) public onlyOwner { 96 | erc20Addr = _erc20Addr; 97 | } 98 | 99 | modifier windowClosed() { 100 | require(block.timestamp >= deploymentTimestamp + lockTime, "window is not closed"); 101 | _; 102 | } 103 | 104 | modifier onlyOwner() { 105 | require(msg.sender == owner, "this function can only be called by owner"); 106 | _; 107 | } 108 | 109 | } -------------------------------------------------------------------------------- /lesson-4/hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("@nomicfoundation/hardhat-toolbox"); 2 | require("@chainlink/env-enc").config() 3 | require("./tasks") 4 | 5 | const SEPOLIA_RPC_URL = process.env.SEPOLIA_RPC_URL 6 | const PRIVATE_KEY = process.env.PRIVATE_KEY 7 | const PRIVATE_KEY_1 = process.env.PRIVATE_KEY_1 8 | const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY 9 | 10 | /** @type import('hardhat/config').HardhatUserConfig */ 11 | module.exports = { 12 | solidity: "0.8.24", 13 | networks: { 14 | sepolia: { 15 | url: SEPOLIA_RPC_URL, 16 | accounts: [PRIVATE_KEY, PRIVATE_KEY_1], 17 | chainId: 11155111 18 | } 19 | }, 20 | etherscan: { 21 | apiKey: { 22 | sepolia: ETHERSCAN_API_KEY 23 | } 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /lesson-4/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lesson-4", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "@chainlink/contracts": "^1.0.0", 14 | "@chainlink/env-enc": "^1.0.5", 15 | "@nomicfoundation/hardhat-toolbox": "^5.0.0", 16 | "dotenv": "^16.4.5", 17 | "hardhat": "^2.22.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lesson-4/scripts/deployFundMe.js: -------------------------------------------------------------------------------- 1 | // import ethers.js 2 | // create main function 3 | // execute main function 4 | 5 | const { ethers } = require("hardhat") 6 | 7 | async function main() { 8 | // create factory 9 | const fundMeFactory = await ethers.getContractFactory("FundMe") 10 | console.log("contract deploying") 11 | // deploy contract from factory 12 | const fundMe = await fundMeFactory.deploy(300) 13 | await fundMe.waitForDeployment() 14 | console.log(`contract has been deployed successfully, contract address is ${fundMe.target}`); 15 | 16 | // verify fundme 17 | if(hre.network.config.chainId == 11155111 && process.env.ETHERSCAN_API_KEY) { 18 | console.log("Waiting for 5 confirmations") 19 | await fundMe.deploymentTransaction().wait(5) 20 | await verifyFundMe(fundMe.target, [300]) 21 | } else { 22 | console.log("verification skipped..") 23 | } 24 | 25 | // init 2 accounts 26 | const [firstAccount, secondAccount] = await ethers.getSigners() 27 | 28 | // fund contract with first account 29 | const fundTx = await fundMe.fund({value: ethers.parseEther("0.5")}) 30 | await fundTx.wait() 31 | 32 | console.log(`2 accounts are ${firstAccount.address} and ${secondAccount.address}`) 33 | 34 | // check balance of contract 35 | const balanceOfContract = await ethers.provider.getBalance(fundMe.target) 36 | console.log(`Balance of the contract is ${balanceOfContract}`) 37 | 38 | // fund contract with second account 39 | const fundTxWithSecondAccount = await fundMe.connect(secondAccount).fund({value: ethers.parseEther("0.5")}) 40 | await fundTxWithSecondAccount.wait() 41 | 42 | // check balance of contract 43 | const balanceOfContractAfterSecondFund = await ethers.provider.getBalance(fundMe.target) 44 | console.log(`Balance of the contract is ${balanceOfContractAfterSecondFund}`) 45 | 46 | // check mapping 47 | const firstAccountbalanceInFundMe = await fundMe.fundersToAmount(firstAccount.address) 48 | const secondAccountbalanceInFundMe = await fundMe.fundersToAmount(secondAccount.address) 49 | console.log(`Balance of first account ${firstAccount.address} is ${firstAccountbalanceInFundMe}`) 50 | console.log(`Balance of second account ${secondAccount.address} is ${secondAccountbalanceInFundMe}`) 51 | 52 | } 53 | 54 | async function verifyFundMe(fundMeAddr, args) { 55 | await hre.run("verify:verify", { 56 | address: fundMeAddr, 57 | constructorArguments: args, 58 | }); 59 | } 60 | 61 | 62 | main().then().catch((error) => { 63 | console.error(error) 64 | process.exit(0) 65 | }) -------------------------------------------------------------------------------- /lesson-4/tasks/deploy-fundme.js: -------------------------------------------------------------------------------- 1 | const { task } = require("hardhat/config") 2 | 3 | task("deploy-fundme", "deploy and verify fundme conract").setAction(async(taskArgs, hre) => { 4 | // create factory 5 | const fundMeFactory = await ethers.getContractFactory("FundMe") 6 | console.log("contract deploying") 7 | // deploy contract from factory 8 | const fundMe = await fundMeFactory.deploy(300) 9 | await fundMe.waitForDeployment() 10 | console.log(`contract has been deployed successfully, contract address is ${fundMe.target}`); 11 | 12 | // verify fundme 13 | if(hre.network.config.chainId == 11155111 && process.env.ETHERSCAN_API_KEY) { 14 | console.log("Waiting for 5 confirmations") 15 | await fundMe.deploymentTransaction().wait(5) 16 | await verifyFundMe(fundMe.target, [300]) 17 | } else { 18 | console.log("verification skipped..") 19 | } 20 | } ) 21 | 22 | async function verifyFundMe(fundMeAddr, args) { 23 | await hre.run("verify:verify", { 24 | address: fundMeAddr, 25 | constructorArguments: args, 26 | }); 27 | } 28 | 29 | module.exports = {} -------------------------------------------------------------------------------- /lesson-4/tasks/index.js: -------------------------------------------------------------------------------- 1 | exports.deployConract = require("./deploy-fundme") 2 | exports.interactContract = require("./interact-fundme") -------------------------------------------------------------------------------- /lesson-4/tasks/interact-fundme.js: -------------------------------------------------------------------------------- 1 | const { task } = require("hardhat/config") 2 | 3 | task("interact-fundme", "interact with fundme contract") 4 | .addParam("addr", "fundme contract address") 5 | .setAction(async(taskArgs, hre) => { 6 | const fundMeFactory = await ethers.getContractFactory("FundMe") 7 | const fundMe = fundMeFactory.attach(taskArgs.addr) 8 | 9 | // init 2 accounts 10 | const [firstAccount, secondAccount] = await ethers.getSigners() 11 | 12 | // fund contract with first account 13 | const fundTx = await fundMe.fund({value: ethers.parseEther("0.5")}) 14 | await fundTx.wait() 15 | 16 | // check balance of contract 17 | const balanceOfContract = await ethers.provider.getBalance(fundMe.target) 18 | console.log(`Balance of the contract is ${balanceOfContract}`) 19 | 20 | // fund contract with second account 21 | const fundTxWithSecondAccount = await fundMe.connect(secondAccount).fund({value: ethers.parseEther("0.5")}) 22 | await fundTxWithSecondAccount.wait() 23 | 24 | // check balance of contract 25 | const balanceOfContractAfterSecondFund = await ethers.provider.getBalance(fundMe.target) 26 | console.log(`Balance of the contract is ${balanceOfContractAfterSecondFund}`) 27 | 28 | // check mapping 29 | const firstAccountbalanceInFundMe = await fundMe.fundersToAmount(firstAccount.address) 30 | const secondAccountbalanceInFundMe = await fundMe.fundersToAmount(secondAccount.address) 31 | console.log(`Balance of first account ${firstAccount.address} is ${firstAccountbalanceInFundMe}`) 32 | console.log(`Balance of second account ${secondAccount.address} is ${secondAccountbalanceInFundMe}`) 33 | }) 34 | 35 | module.exports = {} -------------------------------------------------------------------------------- /lesson-5/README.md: -------------------------------------------------------------------------------- 1 | # 第五课 | Lesson 5 2 | [中文](#内容介绍) | [English](#introduction) 3 | 4 | ## 内容介绍 5 | 这是 Web3_tutorial 的一部分。
6 | 视频链接:WIP
7 | 8 | 在第五课,在 Solidity 编程中,安全是非常重要的。你将会学习如何使用 Hardhat 框架对智能合约进行单元测试和集成测试,让你编写的合约更加安全。 9 | 10 | ## 如何使用 11 | 1. 将 repo clone到本地: 12 | `git clone https://github.com/smartcontractkit/Web3_tutorial_Chinese.git` 13 | 2. 进入 lesson-5 文件夹 14 | `cd Web3_tutorial_Chinese/lesson-5` 15 | 3. 安装 NPM package 16 | - 运行 `npm install` 安装 NPM package 17 | 4. 添加环境变量 18 | - `npx hardhat env-enc set-pw` 为 `.env.enc` 设置密码 19 | - 添加环境变量`npx hardhat env-enc set`: `PRIVATE_KEY`, `PRIVATE_KEY_1`, `SEPOLIA_URL` 和 `ETHERSCAN_API_KEY` 20 | 5. 对 `FundMe.sol` 进行单元测试 21 | - `npx hardhat test` 运行单元测试脚本。 22 | 6. 对 `FundMe.sol` 进行集成测试 23 | - `npx hardhat test --network sepolia` 运行集成测试脚本。 24 | 25 | 更多的相关内容请查看[Web3_tutorial](https://github.com/smartcontractkit/Web3_tutorial_Chinese)的 `README.md`。 26 | 27 | 28 | ## introduction 29 | This is part of the Web3_tutorial.
30 | Video link: WIP
31 | 32 | In lesson 5, safety is cannot be ignored in Solidity programming. You will learn how to use the Hardhat framework to conduct unit tests and integration tests on smart contracts, ensuring the contracts you write are secure. 33 | 34 | ## Getting Started 35 | 1. clone the repo 36 | `git clone https://github.com/smartcontractkit/Web3_tutorial_Chinese.git` 37 | 2. change directory to folder lesson-5 38 | `cd Web3_tutorial_Chinese/lesson-5` 39 | 3. Install NPM package 40 | - Run `npm install` to install NPM packages. 41 | 4. Add environment variables 42 | - Set a password for `.env.enc` with `npx hardhat env-enc set-pw`. 43 | - Add environment variables using `npx hardhat env-enc set`: `PRIVATE_KEY`, `PRIVATE_KEY_1`, `SEPOLIA_RPC_URL`, and `ETHERSCAN_API_KEY`. 44 | 5. Unit test `FundMe.sol` 45 | - Run unit tests with `npx hardhat test`. 46 | 6. Satging test `FundMe.sol` 47 | - Run staging tests with `npx hardhat test --network sepolia`. 48 | 49 | For more related content, please refer to the README.md of [Web3_tutorial](https://github.com/smartcontractkit/Web3_tutorial_Chinese). -------------------------------------------------------------------------------- /lesson-5/contracts/FundMe.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol"; 5 | 6 | // 1. 创建一个收款函数 7 | // 2. 记录投资人并且查看 8 | // 3. 在锁定期内,达到目标值,生产商可以提款 9 | // 4. 在锁定期内,没有达到目标值,投资人在锁定期以后退款 10 | 11 | contract FundMe { 12 | mapping(address => uint256) public fundersToAmount; 13 | 14 | uint256 constant MINIMUM_VALUE = 100 * 10 ** 18; //USD 15 | 16 | AggregatorV3Interface public dataFeed; 17 | 18 | uint256 constant TARGET = 1000 * 10 ** 18; 19 | 20 | address public owner; 21 | 22 | uint256 deploymentTimestamp; 23 | uint256 lockTime; 24 | 25 | address erc20Addr; 26 | 27 | bool public getFundSuccess = false; 28 | 29 | event FundWithdrawByOwner(uint256); 30 | event RefundByFunder(address, uint256); 31 | 32 | constructor(uint256 _lockTime, address dataFeedAddr) { 33 | // sepolia testnet 34 | dataFeed = AggregatorV3Interface(dataFeedAddr); 35 | owner = msg.sender; 36 | deploymentTimestamp = block.timestamp; 37 | lockTime = _lockTime; 38 | } 39 | 40 | function fund() external payable { 41 | require(convertEthToUsd(msg.value) >= MINIMUM_VALUE, "Send more ETH"); 42 | require(block.timestamp < deploymentTimestamp + lockTime, "window is closed"); 43 | fundersToAmount[msg.sender] = msg.value; 44 | } 45 | 46 | function getChainlinkDataFeedLatestAnswer() public view returns (int) { 47 | // prettier-ignore 48 | ( 49 | /* uint80 roundID */, 50 | int answer, 51 | /*uint startedAt*/, 52 | /*uint timeStamp*/, 53 | /*uint80 answeredInRound*/ 54 | ) = dataFeed.latestRoundData(); 55 | return answer; 56 | } 57 | 58 | function convertEthToUsd(uint256 ethAmount) internal view returns(uint256){ 59 | uint256 ethPrice = uint256(getChainlinkDataFeedLatestAnswer()); 60 | return ethAmount * ethPrice / (10 ** 8); 61 | } 62 | 63 | function transferOwnership(address newOwner) public onlyOwner{ 64 | owner = newOwner; 65 | } 66 | 67 | function getFund() external windowClosed onlyOwner{ 68 | require(convertEthToUsd(address(this).balance) >= TARGET, "Target is not reached"); 69 | // transfer: transfer ETH and revert if tx failed 70 | // payable(msg.sender).transfer(address(this).balance); 71 | 72 | // send: transfer ETH and return false if failed 73 | // bool success = payable(msg.sender).send(address(this).balance); 74 | // require(success, "tx failed"); 75 | 76 | // call: transfer ETH with data return value of function and bool 77 | bool success; 78 | uint256 balance = address(this).balance; 79 | (success, ) = payable(msg.sender).call{value: balance}(""); 80 | require(success, "transfer tx failed"); 81 | fundersToAmount[msg.sender] = 0; 82 | getFundSuccess = true; // flag 83 | // emit event 84 | emit FundWithdrawByOwner(balance); 85 | } 86 | 87 | function refund() external windowClosed { 88 | require(convertEthToUsd(address(this).balance) < TARGET, "Target is reached"); 89 | require(fundersToAmount[msg.sender] != 0, "there is no fund for you"); 90 | bool success; 91 | uint256 balance = fundersToAmount[msg.sender]; 92 | (success, ) = payable(msg.sender).call{value: balance}(""); 93 | require(success, "transfer tx failed"); 94 | fundersToAmount[msg.sender] = 0; 95 | emit RefundByFunder(msg.sender, balance); 96 | } 97 | 98 | function setFunderToAmount(address funder, uint256 amountToUpdate) external { 99 | require(msg.sender == erc20Addr, "you do not have permission to call this funtion"); 100 | fundersToAmount[funder] = amountToUpdate; 101 | } 102 | 103 | function setErc20Addr(address _erc20Addr) public onlyOwner { 104 | erc20Addr = _erc20Addr; 105 | } 106 | 107 | modifier windowClosed() { 108 | require(block.timestamp >= deploymentTimestamp + lockTime, "window is not closed"); 109 | _; 110 | } 111 | 112 | modifier onlyOwner() { 113 | require(msg.sender == owner, "this function can only be called by owner"); 114 | _; 115 | } 116 | 117 | } -------------------------------------------------------------------------------- /lesson-5/contracts/mocks/MockV3Aggregator.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | import "@chainlink/contracts/src/v0.8/tests/MockV3Aggregator.sol"; 4 | -------------------------------------------------------------------------------- /lesson-5/deploy/00-deploy-mocks.js: -------------------------------------------------------------------------------- 1 | const { DECIMAL, INITIAL_ANSWER, devlopmentChains} = require("../helper-hardhat-config") 2 | 3 | module.exports= async({getNamedAccounts, deployments}) => { 4 | 5 | if(devlopmentChains.includes(network.name)) { 6 | const {firstAccount} = await getNamedAccounts() 7 | const {deploy} = deployments 8 | 9 | await deploy("MockV3Aggregator", { 10 | from: firstAccount, 11 | args: [DECIMAL, INITIAL_ANSWER], 12 | log: true 13 | }) 14 | } else { 15 | console.log("environment is not local, mock contract depployment is skipped") 16 | } 17 | } 18 | 19 | module.exports.tags = ["all", "mock"] -------------------------------------------------------------------------------- /lesson-5/deploy/01-deploy-fund-me.js: -------------------------------------------------------------------------------- 1 | // function deployFunction() { 2 | // console.log("this is a deploy function") 3 | // } 4 | 5 | const { network } = require("hardhat") 6 | const {devlopmentChains, networkConfig, LOCK_TIME, CONFIRMATIONS} = require("../helper-hardhat-config") 7 | // module.exports.default=deployFunction 8 | // module.exports= async(hre) => { 9 | // const getNamdeAccounts = hre.getNamdeAccounts 10 | // const deployments = hre.deployments 11 | // console.log("this is a deploy function") 12 | // } 13 | 14 | module.exports= async({getNamedAccounts, deployments}) => { 15 | const {firstAccount} = await getNamedAccounts() 16 | const {deploy} = deployments 17 | 18 | let dataFeedAddr 19 | let confirmations 20 | if(devlopmentChains.includes(network.name)) { 21 | const mockV3Aggregator = await deployments.get("MockV3Aggregator") 22 | dataFeedAddr = mockV3Aggregator.address 23 | confirmations = 0 24 | } else { 25 | dataFeedAddr = networkConfig[network.config.chainId].ethUsdDataFeed 26 | confirmations = CONFIRMATIONS 27 | } 28 | 29 | const fundMe = await deploy("FundMe", { 30 | from: firstAccount, 31 | args: [LOCK_TIME, dataFeedAddr], 32 | log: true, 33 | waitConfirmations: confirmations 34 | }) 35 | // remove deployments directory or add --reset flag if you redeploy contract 36 | 37 | if(hre.network.config.chainId == 11155111 && process.env.ETHERSCAN_API_KEY) { 38 | await hre.run("verify:verify", { 39 | address: fundMe.address, 40 | constructorArguments: [LOCK_TIME, dataFeedAddr], 41 | }); 42 | } else { 43 | console.log("Network is not sepolia, verification skipped...") 44 | } 45 | 46 | 47 | } 48 | 49 | module.exports.tags = ["all", "fundme"] -------------------------------------------------------------------------------- /lesson-5/hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("@nomicfoundation/hardhat-toolbox"); 2 | require("@chainlink/env-enc").config() 3 | require("./tasks") 4 | require("hardhat-deploy") 5 | require("@nomicfoundation/hardhat-ethers"); 6 | require("hardhat-deploy"); 7 | require("hardhat-deploy-ethers"); 8 | 9 | const SEPOLIA_URL = process.env.SEPOLIA_URL 10 | const PRIVATE_KEY = process.env.PRIVATE_KEY 11 | const PRIVATE_KEY_1 = process.env.PRIVATE_KEY_1 12 | const EHTERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY 13 | 14 | /** @type import('hardhat/config').HardhatUserConfig */ 15 | module.exports = { 16 | solidity: "0.8.24", 17 | defaultNetwork: "hardhat", 18 | mocha: { 19 | timeout: 300000 20 | }, 21 | networks: { 22 | sepolia: { 23 | url: SEPOLIA_URL, 24 | accounts: [PRIVATE_KEY, PRIVATE_KEY_1], 25 | chainId: 11155111 26 | } 27 | }, 28 | etherscan: { 29 | apiKey: { 30 | sepolia: EHTERSCAN_API_KEY 31 | } 32 | }, 33 | namedAccounts: { 34 | firstAccount: { 35 | default: 0 36 | }, 37 | secondAccount: { 38 | default: 1 39 | }, 40 | }, 41 | gasReporter: { 42 | enabled: true 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /lesson-5/helper-hardhat-config.js: -------------------------------------------------------------------------------- 1 | const DECIMAL = 8 2 | const INITIAL_ANSWER = 300000000000 3 | const devlopmentChains = ["hardhat", "local"] 4 | const LOCK_TIME = 180 5 | const CONFIRMATIONS = 5 6 | const networkConfig = { 7 | 11155111: { 8 | ethUsdDataFeed: "0x694AA1769357215DE4FAC081bf1f309aDC325306" 9 | }, 10 | 97: { 11 | ethUsdDataFeed: "0x143db3CEEfbdfe5631aDD3E50f7614B6ba708BA7" 12 | } 13 | } 14 | 15 | module.exports = { 16 | DECIMAL, 17 | INITIAL_ANSWER, 18 | devlopmentChains, 19 | networkConfig, 20 | LOCK_TIME, 21 | CONFIRMATIONS 22 | } -------------------------------------------------------------------------------- /lesson-5/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web3_tutorial_lesson4", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "@chainlink/contracts": "^1.0.0", 14 | "@chainlink/env-enc": "^1.0.5", 15 | "@nomicfoundation/hardhat-ethers": "^3.0.6", 16 | "@nomicfoundation/hardhat-toolbox": "^5.0.0", 17 | "dotenv": "^16.4.5", 18 | "ethers": "^6.13.0", 19 | "hardhat": "^2.22.2", 20 | "hardhat-deploy": "^0.12.4", 21 | "hardhat-deploy-ethers": "^0.4.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lesson-5/scripts/deploy.js: -------------------------------------------------------------------------------- 1 | // import the ethers from hardhat 2 | // write a main function 3 | // call the main function 4 | 5 | const { ethers } = require("hardhat"); 6 | 7 | async function main() { 8 | const fundMeFacotry = await ethers.getContractFactory("FundMe"); 9 | console.log("deploiying the contract..."); 10 | 11 | const fundMe = await fundMeFacotry.deploy(10); 12 | await fundMe.waitForDeployment(); 13 | console.log(`contract fundme is deployed successfully at addree ${fundMe.target}`) 14 | 15 | if(hre.network.config.chainId == 11155111 && process.env.ETHERSCAN_API_KEY) { 16 | console.log("wait for 3 confirmations") 17 | await fundMe.deploymentTransaction().wait(3) 18 | console.log("verifying contract on etherscan...") 19 | await verify(fundMe.target, [10]) 20 | } else { 21 | console.log("skipping verification") 22 | } 23 | 24 | console.log("fund the contract...") 25 | const fundTx = await fundMe.fund({ 26 | value: ethers.parseEther("0.1") 27 | }) 28 | await fundTx.wait() 29 | console.log("funded the contract") 30 | 31 | const fundMeBalance = await ethers.provider.getBalance(fundMe.target); 32 | console.log(`balance of the fundme is ${fundMeBalance}`) 33 | 34 | const [firstAccount, secondAccount] = await ethers.getSigners(); 35 | console.log(`first account address is ${firstAccount.address}`) 36 | console.log(`second account address is ${secondAccount.address}`) 37 | 38 | console.log("fetching the funds of the first account...") 39 | const fundsOfFirstAccount = await fundMe.listOfFunders(firstAccount.address); 40 | console.log(`Current funds of first account is ${fundsOfFirstAccount}`) 41 | 42 | console.log("fund the contract on behalf of the second account...") 43 | const secondFundTx = await fundMe.connect(secondAccount).fund({ 44 | value: ethers.parseEther("0.1") 45 | }) 46 | await secondFundTx.wait() 47 | 48 | console.log("fetching the funds of the second account...") 49 | const fundsOfSecondAccount = await fundMe.listOfFunders(secondAccount.address); 50 | console.log(`Current funds of second account is ${fundsOfSecondAccount}`) 51 | } 52 | 53 | async function verify(address, args) { 54 | await hre.run("verify:verify", { 55 | address: address, 56 | constructorArguments: args, 57 | }); 58 | } 59 | 60 | main().then().catch((error) => { 61 | console.log(`error is ${error}`) 62 | process.exit(1) 63 | }) -------------------------------------------------------------------------------- /lesson-5/tasks/deploy-fundme.js: -------------------------------------------------------------------------------- 1 | const { task } = require("hardhat/config") 2 | 3 | task("deploy-fundme", "deploy and verify fundme conract").setAction(async(taskArgs, hre) => { 4 | // create factory 5 | const fundMeFactory = await ethers.getContractFactory("FundMe") 6 | console.log("contract deploying") 7 | // deploy contract from factory 8 | const fundMe = await fundMeFactory.deploy(300) 9 | await fundMe.waitForDeployment() 10 | console.log(`contract has been deployed successfully, contract address is ${fundMe.target}`); 11 | 12 | // verify fundme 13 | if(hre.network.config.chainId == 11155111 && process.env.ETHERSCAN_API_KEY) { 14 | console.log("Waiting for 5 confirmations") 15 | await fundMe.deploymentTransaction().wait(5) 16 | await verifyFundMe(fundMe.target, [300]) 17 | } else { 18 | console.log("verification skipped..") 19 | } 20 | } ) 21 | 22 | async function verifyFundMe(fundMeAddr, args) { 23 | await hre.run("verify:verify", { 24 | address: fundMeAddr, 25 | constructorArguments: args, 26 | }); 27 | } 28 | 29 | module.exports = {} -------------------------------------------------------------------------------- /lesson-5/tasks/index.js: -------------------------------------------------------------------------------- 1 | exports.deployConract = require("./deploy-fundme") 2 | exports.interactContract = require("./interact-fundme") -------------------------------------------------------------------------------- /lesson-5/tasks/interact-fundme.js: -------------------------------------------------------------------------------- 1 | const { task } = require("hardhat/config") 2 | 3 | task("interact-fundme", "interact with fundme contract") 4 | .addParam("addr", "fundme contract address") 5 | .setAction(async(taskArgs, hre) => { 6 | const fundMeFactory = await ethers.getContractFactory("FundMe") 7 | const fundMe = fundMeFactory.attach(taskArgs.addr) 8 | 9 | // init 2 accounts 10 | const [firstAccount, secondAccount] = await ethers.getSigners() 11 | 12 | // fund contract with first account 13 | const fundTx = await fundMe.fund({value: ethers.parseEther("0.5")}) 14 | await fundTx.wait() 15 | 16 | // check balance of contract 17 | const balanceOfContract = await ethers.provider.getBalance(fundMe.target) 18 | console.log(`Balance of the contract is ${balanceOfContract}`) 19 | 20 | // fund contract with second account 21 | const fundTxWithSecondAccount = await fundMe.connect(secondAccount).fund({value: ethers.parseEther("0.5")}) 22 | await fundTxWithSecondAccount.wait() 23 | 24 | // check balance of contract 25 | const balanceOfContractAfterSecondFund = await ethers.provider.getBalance(fundMe.target) 26 | console.log(`Balance of the contract is ${balanceOfContractAfterSecondFund}`) 27 | 28 | // check mapping 29 | const firstAccountbalanceInFundMe = await fundMe.fundersToAmount(firstAccount.address) 30 | const secondAccountbalanceInFundMe = await fundMe.fundersToAmount(secondAccount.address) 31 | console.log(`Balance of first account ${firstAccount.address} is ${firstAccountbalanceInFundMe}`) 32 | console.log(`Balance of second account ${secondAccount.address} is ${secondAccountbalanceInFundMe}`) 33 | }) 34 | 35 | module.exports = {} -------------------------------------------------------------------------------- /lesson-5/test/staging/FundMe.staging.test.js: -------------------------------------------------------------------------------- 1 | const { ethers, deployments, getNamedAccounts } = require("hardhat") 2 | const { assert, expect } = require("chai") 3 | const helpers = require("@nomicfoundation/hardhat-network-helpers") 4 | const {devlopmentChains} = require("../../helper-hardhat-config") 5 | 6 | devlopmentChains.includes(network.name) 7 | ? describe.skip 8 | : describe("test fundme contract", async function() { 9 | let fundMe 10 | let firstAccount 11 | beforeEach(async function() { 12 | await deployments.fixture(["all"]) 13 | firstAccount = (await getNamedAccounts()).firstAccount 14 | const fundMeDeployment = await deployments.get("FundMe") 15 | fundMe = await ethers.getContractAt("FundMe", fundMeDeployment.address) 16 | }) 17 | 18 | // test fund and getFund successfully 19 | it("fund and getFund successfully", 20 | async function() { 21 | // make sure target reached 22 | await fundMe.fund({value: ethers.parseEther("0.5")}) // 3000 * 0.5 = 1500 23 | // make sure window closed 24 | await new Promise(resolve => setTimeout(resolve, 181 * 1000)) 25 | // make sure we can get receipt 26 | const getFundTx = await fundMe.getFund() 27 | const getFundReceipt = await getFundTx.wait() 28 | expect(getFundReceipt) 29 | .to.be.emit(fundMe, "FundWithdrawByOwner") 30 | .withArgs(ethers.parseEther("0.5")) 31 | } 32 | ) 33 | // test fund and refund successfully 34 | it("fund and refund successfully", 35 | async function() { 36 | // make sure target not reached 37 | await fundMe.fund({value: ethers.parseEther("0.1")}) // 3000 * 0.1 = 300 38 | // make sure window closed 39 | await new Promise(resolve => setTimeout(resolve, 181 * 1000)) 40 | // make sure we can get receipt 41 | const refundTx = await fundMe.refund() 42 | const refundReceipt = await refundTx.wait() 43 | expect(refundReceipt) 44 | .to.be.emit(fundMe, "RefundByFunder") 45 | .withArgs(firstAccount, ethers.parseEther("0.1")) 46 | } 47 | ) 48 | 49 | 50 | }) -------------------------------------------------------------------------------- /lesson-5/test/unit/FundMe.test.js: -------------------------------------------------------------------------------- 1 | const { ethers, deployments, getNamedAccounts, network } = require("hardhat") 2 | const { assert, expect } = require("chai") 3 | const helpers = require("@nomicfoundation/hardhat-network-helpers") 4 | const {devlopmentChains} = require("../../helper-hardhat-config") 5 | 6 | !devlopmentChains.includes(network.name) 7 | ? describe.skip 8 | : describe("test fundme contract", async function() { 9 | let fundMe 10 | let fundMeSecondAccount 11 | let firstAccount 12 | let secondAccount 13 | let mockV3Aggregator 14 | beforeEach(async function() { 15 | await deployments.fixture(["all"]) 16 | firstAccount = (await getNamedAccounts()).firstAccount 17 | secondAccount = (await getNamedAccounts()).secondAccount 18 | const fundMeDeployment = await deployments.get("FundMe") 19 | mockV3Aggregator = await deployments.get("MockV3Aggregator") 20 | fundMe = await ethers.getContractAt("FundMe", fundMeDeployment.address) 21 | fundMeSecondAccount = await ethers.getContract("FundMe", secondAccount) 22 | }) 23 | 24 | it("test if the owner is msg.sender", async function() { 25 | await fundMe.waitForDeployment() 26 | assert.equal((await fundMe.owner()), firstAccount) 27 | }) 28 | 29 | it("test if the datafeed is assigned correctly", async function() { 30 | await fundMe.waitForDeployment() 31 | assert.equal((await fundMe.dataFeed()), mockV3Aggregator.address) 32 | }) 33 | 34 | // fund, getFund, refund 35 | // unit test for fund 36 | // window open, value greater then minimum value, funder balance 37 | it("window closed, value grater than minimum, fund failed", 38 | async function() { 39 | // make sure the window is closed 40 | await helpers.time.increase(200) 41 | await helpers.mine() 42 | //value is greater minimum value 43 | await expect(fundMe.fund({value: ethers.parseEther("0.1")})) 44 | .to.be.revertedWith("window is closed") 45 | } 46 | ) 47 | 48 | it("window open, value is less than minimum, fund failed", 49 | async function() { 50 | await expect(fundMe.fund({value: ethers.parseEther("0.01")})) 51 | .to.be.revertedWith("Send more ETH") 52 | } 53 | ) 54 | 55 | it("Window open, value is greater minimum, fund success", 56 | async function() { 57 | // greater than minimum 58 | await fundMe.fund({value: ethers.parseEther("0.1")}) 59 | const balance = await fundMe.fundersToAmount(firstAccount) 60 | await expect(balance).to.equal(ethers.parseEther("0.1")) 61 | } 62 | ) 63 | 64 | // unit test for getFund 65 | // onlyOwner, windowClose, target reached 66 | it("not onwer, window closed, target reached, getFund failed", 67 | async function() { 68 | // make sure the target is reached 69 | await fundMe.fund({value: ethers.parseEther("1")}) 70 | 71 | // make sure the window is closed 72 | await helpers.time.increase(200) 73 | await helpers.mine() 74 | 75 | await expect(fundMeSecondAccount.getFund()) 76 | .to.be.revertedWith("this function can only be called by owner") 77 | } 78 | ) 79 | 80 | it("window open, target reached, getFund failed", 81 | async function() { 82 | await fundMe.fund({value: ethers.parseEther("1")}) 83 | await expect(fundMe.getFund()) 84 | .to.be.revertedWith("window is not closed") 85 | } 86 | ) 87 | 88 | it("window closed, target not reached, getFund failed", 89 | async function() { 90 | await fundMe.fund({value: ethers.parseEther("0.1")}) 91 | // make sure the window is closed 92 | await helpers.time.increase(200) 93 | await helpers.mine() 94 | await expect(fundMe.getFund()) 95 | .to.be.revertedWith("Target is not reached") 96 | } 97 | ) 98 | 99 | it("window closed, target reached, getFund success", 100 | async function() { 101 | await fundMe.fund({value: ethers.parseEther("1")}) 102 | // make sure the window is closed 103 | await helpers.time.increase(200) 104 | await helpers.mine() 105 | await expect(fundMe.getFund()) 106 | .to.emit(fundMe, "FundWithdrawByOwner") 107 | .withArgs(ethers.parseEther("1")) 108 | } 109 | ) 110 | 111 | // refund 112 | // windowClosed, target not reached, funder has balance 113 | it("window open, target not reached, funder has balance", 114 | async function() { 115 | await fundMe.fund({value: ethers.parseEther("0.1")}) 116 | await expect(fundMe.refund()) 117 | .to.be.revertedWith("window is not closed"); 118 | } 119 | ) 120 | 121 | it("window closed, target reach, funder has balance", 122 | async function() { 123 | await fundMe.fund({value: ethers.parseEther("1")}) 124 | // make sure the window is closed 125 | await helpers.time.increase(200) 126 | await helpers.mine() 127 | await expect(fundMe.refund()) 128 | .to.be.revertedWith("Target is reached"); 129 | } 130 | ) 131 | 132 | it("window closed, target not reach, funder does not has balance", 133 | async function() { 134 | await fundMe.fund({value: ethers.parseEther("0.1")}) 135 | // make sure the window is closed 136 | await helpers.time.increase(200) 137 | await helpers.mine() 138 | await expect(fundMeSecondAccount.refund()) 139 | .to.be.revertedWith("there is no fund for you"); 140 | } 141 | ) 142 | 143 | it("window closed, target not reached, funder has balance", 144 | async function() { 145 | await fundMe.fund({value: ethers.parseEther("0.1")}) 146 | // make sure the window is closed 147 | await helpers.time.increase(200) 148 | await helpers.mine() 149 | await expect(fundMe.refund()) 150 | .to.emit(fundMe, "RefundByFunder") 151 | .withArgs(firstAccount, ethers.parseEther("0.1")) 152 | } 153 | ) 154 | 155 | }) -------------------------------------------------------------------------------- /lesson-5/utils/verify.js: -------------------------------------------------------------------------------- 1 | async function verify(address, args) { 2 | await hre.run("verify:verify", { 3 | address: address, 4 | constructorArguments: args, 5 | }); 6 | } 7 | 8 | 9 | module.exports = {verify} -------------------------------------------------------------------------------- /lesson-6/README.md: -------------------------------------------------------------------------------- 1 | # 第六课 Lesson 6 2 | [中文](#内容介绍) | [English](#introduction) 3 | ## 内容介绍 4 | 这是第六课的代码部分,在这部分代码中,我们构建一个 ERC721 的合约,让这个合约可以被从 Sepolia 区块链被跨链跨到 Amoy 区块链。
5 | 6 | 完成整个过程需要先在 Sepolia 区块链部署合约: 7 | - ERC-721合约 MyToken:这个合约是我们需要用到的 NFT 8 | - NFTPoolLockAndRelease:用来锁定用户合约,并且执行跨链操作,在 Amoy 区块链上铸造一个新的 NFT 9 | 10 | 在 Amoy 区块链上部署合约 11 | - 基于 ERC-721 合约的包装合约 WrappedMyToken:这个合约会用来铸造和燃烧 NFT,因为 NFT 的主合约在 Sepolia 上,所在 Amoy 的 NFT 合约需要先进行铸造。 12 | - NFTPoolMintAndBurn:通过 `ccipReceive` 来接受跨链消息,然后基于消息内容铸造 NFT,同时在 Amoy 中的 NFT 跨链回到 Sepolia 的时候,将 NFT 进行燃烧。 13 | 14 | ## 如何使用 15 | 1. 将 repo clone到本地: 16 | `git clone https://github.com/smartcontractkit/Web3_tutorial_Chinese.git` 17 | 18 | 2. 进入 lesson-6 文件夹 19 | `cd Web3_tutorial_Chinese/lesson-6` 20 | 21 | 3. 安装 npm package:`npm install` 22 | 23 | 4. 测试合约:`npx hardhat test`,此过程使用到了 chainlink-local,会在链下模拟 ccip 行为 24 | 25 | 5. 通过 env-enc 添加配置信息: 26 | ``` 27 | npx env-enc set-pw 28 | npx env-enc set 29 | ``` 30 | 依次加入环境变量: 31 | ``` 32 | PRIVATE_KEY 33 | SEPOLIA_RPC_URL 34 | AMOY_RPC_URL 35 | ``` 36 | 37 | 6. 在 source chain 部署合约:`npx hardhat deploy --tags sourcechain --network sepolia`,如果你在上一步使用的不是 sepolia 和 amoy,那么请相应调整 network 名字 38 | 39 | 7. 在 dest chain 部署合约:`npx hardhat deploy --tags destchain --network amoy` 如果你在上一步使用的不是 sepolia 和 amoy,那么请相应调整 network 名字 40 | 41 | 8. 铸造 nft:`npx hardhat mint-nft --network sepolia` 42 | 43 | 9. 查看 nft 状态:`npx hardhat check-nft --network sepolia` 44 | 45 | 10. 锁定并且跨链 nft:`npx hardhat lock-and-cross --tokenid 0 --network sepolia` 46 | 47 | 11. 查看 wrapped NFT 状态:`npx hardhat check-wrapped-nft --tokenid 0 --network amoy` 48 | 49 | 12. 燃烧并且跨链 wnft:`npx hardhat burn-and-cross --tokenid 0 --network amoy` 50 | 51 | 13. 再次查看 nft 状态:`npx hardhat check-nft --network sepolia` 52 | 53 | 更多的相关内容请查看[Web3_tutorial](https://github.com/smartcontractkit/Web3_tutorial_Chinese)的 `README.md`。 54 | 55 | 56 | ## Introduction 57 | This is the code section of lesson 6. In this part of the code, we build an ERC721 contract that allows the NFT to be cross-chained from the Sepolia blockchain to the Amoy blockchain.
58 | 59 | To complete the entire process, it is necessary to deploy contracts on the Sepolia blockchain first: 60 | 61 | - ERC-721 contract MyToken: This contract is the NFT we need. 62 | - NFTPoolLockAndRelease: Used to lock user contracts and perform cross-chain operations, minting a new NFT on the Amoy blockchain. 63 | 64 | Deploy contracts on the Amoy blockchain: 65 | 66 | - WrappedMyToken, a wrapper contract based on the ERC-721 contract: This contract is used to mint and burn NFTs. Because the main NFT contract is on Sepolia, the NFT contract on Amoy needs to be minted first. 67 | - NFTPoolMintAndBurn: Used to receive cross-chain messages through ccipReceive, mint NFTs based on message content, and burn NFTs when they are cross-chained back to Sepolia from Amoy. 68 | ## Getting started 69 | 1. Clone the repo to your local machine: 70 | `git clone https://github.com/QingyangKong/Web3_tutorial_lesson6.git` 71 | 72 | 2. change directory to folder lesson-6 73 | `cd Web3_tutorial_Chinese/lesson-6` 74 | 75 | 3. Install npm packages: `npm install` 76 | 77 | 4. Test contracts: `npx hardhat test`. This process uses chainlink-local to simulate ccip behavior on the chain. 78 | 79 | 5. Add configuration information through env-enc: 80 | ``` 81 | npx env-enc set-pw 82 | npx env-enc set 83 | ``` 84 | Add environment variables in sequence: 85 | ``` 86 | PRIVATE_KEY 87 | SEPOLIA_RPC_URL 88 | AMOY_RPC_URL 89 | ``` 90 | 91 | 6. Deploy contracts on the source chain: `npx hardhat deploy --tags sourcechain --network sepolia`. If you did not use sepolia and Amoy in the previous step, adjust the network name accordingly. 92 | 93 | 7. Deploy contracts on the dest chain: `npx hardhat deploy --tags destchain --network amoy`. If you did not use sepolia and Amoy in the previous step, adjust the network name accordingly. 94 | 95 | 8. Mint nft: `npx hardhat mint-nft --network sepolia` 96 | 97 | 9. Check nft status: `npx hardhat check-nft --network sepolia` 98 | 99 | 10. Lock and cross nft: `npx hardhat lock-and-cross --tokenid 0 --network sepolia` 100 | 101 | 11. Check wrapped NFT status: `npx hardhat check-wrapped-nft --tokenid 0 --network amoy` 102 | 103 | 12. Burn and cross wnft: `npx hardhat burn-and-cross --tokenid 0 --network amoy` 104 | 105 | 13. Check nft status again:`npx hardhat check-nft --network sepolia` 106 | 107 | For more related content, please refer to the README.md of [Web3_tutorial](https://github.com/smartcontractkit/Web3_tutorial_Chinese). -------------------------------------------------------------------------------- /lesson-6/contracts/CCIPSimulator.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import "@chainlink/local/src/ccip/CCIPLocalSimulator.sol"; -------------------------------------------------------------------------------- /lesson-6/contracts/MyNFT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Compatible with OpenZeppelin Contracts ^5.0.0 3 | pragma solidity ^0.8.20; 4 | 5 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 6 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; 7 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; 8 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol"; 9 | import "@openzeppelin/contracts/access/Ownable.sol"; 10 | 11 | contract MyToken is ERC721, ERC721Enumerable, ERC721URIStorage, ERC721Burnable, Ownable { 12 | string constant public METADATA_URI = "ipfs://QmXw7TEAJWKjKifvLE25Z9yjvowWk2NWY3WgnZPUto9XoA"; 13 | uint256 private _nextTokenId; 14 | 15 | constructor(string memory tokenName, string memory tokenSymbol) 16 | ERC721(tokenName, tokenSymbol) 17 | Ownable(msg.sender) 18 | {} 19 | 20 | function safeMint(address to) 21 | public 22 | { 23 | uint256 tokenId = _nextTokenId++; 24 | _safeMint(to, tokenId); 25 | _setTokenURI(tokenId, METADATA_URI); 26 | } 27 | 28 | // The following functions are overrides required by Solidity. 29 | 30 | function _update(address to, uint256 tokenId, address auth) 31 | internal 32 | override(ERC721, ERC721Enumerable) 33 | returns (address) 34 | { 35 | return super._update(to, tokenId, auth); 36 | } 37 | 38 | function _increaseBalance(address account, uint128 value) 39 | internal 40 | override(ERC721, ERC721Enumerable) 41 | { 42 | super._increaseBalance(account, value); 43 | } 44 | 45 | function tokenURI(uint256 tokenId) 46 | public 47 | view 48 | override(ERC721, ERC721URIStorage) 49 | returns (string memory) 50 | { 51 | return super.tokenURI(tokenId); 52 | } 53 | 54 | function supportsInterface(bytes4 interfaceId) 55 | public 56 | view 57 | override(ERC721, ERC721Enumerable, ERC721URIStorage) 58 | returns (bool) 59 | { 60 | return super.supportsInterface(interfaceId); 61 | } 62 | } -------------------------------------------------------------------------------- /lesson-6/contracts/NFTPoolBurnAndMint.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol"; 5 | import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol"; 6 | import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; 7 | import {CCIPReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/applications/CCIPReceiver.sol"; 8 | import {IERC20} from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; 9 | import {SafeERC20} from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol"; 10 | import {WrappedNFT} from "./WrappedNFT.sol"; 11 | 12 | /** 13 | * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY. 14 | * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE. 15 | * DO NOT USE THIS CODE IN PRODUCTION. 16 | */ 17 | 18 | /// @title - A simple messenger contract for sending/receving string data across chains. 19 | contract NFTPoolBurnAndMint is CCIPReceiver, OwnerIsCreator { 20 | using SafeERC20 for IERC20; 21 | 22 | // Custom errors to provide more descriptive revert messages. 23 | error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); // Used to make sure contract has enough balance. 24 | error NothingToWithdraw(); // Used when trying to withdraw Ether but there's nothing to withdraw. 25 | error FailedToWithdrawEth(address owner, address target, uint256 value); // Used when the withdrawal of Ether fails. 26 | 27 | // Event emitted when a message is sent to another chain. 28 | event TokenBurnedAndSent( 29 | bytes32 indexed messageId, // The unique ID of the CCIP message. 30 | uint64 indexed destinationChainSelector, // The chain selector of the destination chain. 31 | address receiver, // The address of the receiver on the destination chain. 32 | bytes text, // The text being sent. 33 | address feeToken, // the token address used to pay CCIP fees. 34 | uint256 fees // The fees paid for sending the CCIP message. 35 | ); 36 | 37 | // Event emitted when a message is received from another chain. 38 | // event MessageReceived( 39 | // bytes32 indexed messageId, // The unique ID of the CCIP message. 40 | // uint64 indexed sourceChainSelector, // The chain selector of the source chain. 41 | // address sender, // The address of the sender from the source chain. 42 | // string text // The text that was received. 43 | // ); 44 | 45 | struct RequestData{ 46 | uint256 tokenId; 47 | address newOwner; 48 | } 49 | 50 | bytes32 private s_lastReceivedMessageId; // Store the last received messageId. 51 | string private s_lastReceivedText; // Store the last received text. 52 | 53 | // Mapping to keep track of allowlisted destination chains. 54 | mapping(uint64 => bool) public allowlistedDestinationChains; 55 | 56 | // Mapping to keep track of allowlisted source chains. 57 | mapping(uint64 => bool) public allowlistedSourceChains; 58 | 59 | // Mapping to keep track of allowlisted senders. 60 | mapping(address => bool) public allowlistedSenders; 61 | 62 | IERC20 private s_linkToken; 63 | 64 | WrappedNFT public wnft; 65 | 66 | /// @notice Constructor initializes the contract with the router address. 67 | /// @param _router The address of the router contract. 68 | /// @param _link The address of the link contract. 69 | constructor(address _router, address _link, address wnftAddr) CCIPReceiver(_router) { 70 | wnft = WrappedNFT(wnftAddr); 71 | s_linkToken = IERC20(_link); 72 | } 73 | 74 | /// @dev Updates the allowlist status of a destination chain for transactions. 75 | function allowlistDestinationChain( 76 | uint64 _destinationChainSelector, 77 | bool allowed 78 | ) external onlyOwner { 79 | allowlistedDestinationChains[_destinationChainSelector] = allowed; 80 | } 81 | 82 | /// @dev Updates the allowlist status of a source chain for transactions. 83 | function allowlistSourceChain( 84 | uint64 _sourceChainSelector, 85 | bool allowed 86 | ) external onlyOwner { 87 | allowlistedSourceChains[_sourceChainSelector] = allowed; 88 | } 89 | 90 | /// @dev Updates the allowlist status of a sender for transactions. 91 | function allowlistSender(address _sender, bool allowed) external onlyOwner { 92 | allowlistedSenders[_sender] = allowed; 93 | } 94 | 95 | function burnAndMint( 96 | uint256 _tokenId, 97 | address newOwner, 98 | uint64 destChainSelector, 99 | address receiver) public { 100 | // verify if the sender is the owner of NFT 101 | // comment this because the check is already performed by ERC721 102 | // require(wnft.ownerOf(_tokenId) == msg.sender, "you are not the owner of the NFT"); 103 | 104 | // transfer NFT to the pool 105 | wnft.transferFrom(msg.sender, address(this), _tokenId); 106 | // burn the NFT 107 | wnft.burn(_tokenId); 108 | // send transaction to the destination chain 109 | bytes memory payload = abi.encode(_tokenId, newOwner); 110 | sendMessagePayLINK(destChainSelector, receiver, payload); 111 | } 112 | 113 | 114 | /// @notice Sends data to receiver on the destination chain. 115 | /// @notice Pay for fees in LINK. 116 | /// @dev Assumes your contract has sufficient LINK. 117 | /// @param _destinationChainSelector The identifier (aka selector) for the destination blockchain. 118 | /// @param _receiver The address of the recipient on the destination blockchain. 119 | /// @param _payload The data to be sent. 120 | /// @return messageId The ID of the CCIP message that was sent. 121 | function sendMessagePayLINK( 122 | uint64 _destinationChainSelector, 123 | address _receiver, 124 | bytes memory _payload 125 | ) 126 | internal 127 | onlyOwner 128 | returns (bytes32 messageId) 129 | { 130 | // Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message 131 | Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage( 132 | _receiver, 133 | _payload, 134 | address(s_linkToken) 135 | ); 136 | 137 | // Initialize a router client instance to interact with cross-chain router 138 | IRouterClient router = IRouterClient(this.getRouter()); 139 | 140 | // Get the fee required to send the CCIP message 141 | uint256 fees = router.getFee(_destinationChainSelector, evm2AnyMessage); 142 | 143 | if (fees > s_linkToken.balanceOf(address(this))) 144 | revert NotEnoughBalance(s_linkToken.balanceOf(address(this)), fees); 145 | 146 | // approve the Router to transfer LINK tokens on contract's behalf. It will spend the fees in LINK 147 | s_linkToken.approve(address(router), fees); 148 | 149 | // Send the CCIP message through the router and store the returned CCIP message ID 150 | messageId = router.ccipSend(_destinationChainSelector, evm2AnyMessage); 151 | 152 | // Emit an event with message details 153 | emit TokenBurnedAndSent( 154 | messageId, 155 | _destinationChainSelector, 156 | _receiver, 157 | _payload, 158 | address(s_linkToken), 159 | fees 160 | ); 161 | 162 | // Return the CCIP message ID 163 | return messageId; 164 | } 165 | 166 | /// handle a received message 167 | function _ccipReceive( 168 | Client.Any2EVMMessage memory any2EvmMessage 169 | ) 170 | internal 171 | override 172 | { 173 | RequestData memory reqData = abi.decode(any2EvmMessage.data, (RequestData)); 174 | address newOwner = reqData.newOwner; 175 | uint256 tokenId = reqData.tokenId; 176 | 177 | // mint a wrappedToken 178 | wnft.mintWithSpecificTokenId(newOwner, tokenId); 179 | 180 | // emit MessageReceived( 181 | // any2EvmMessage.messageId, 182 | // any2EvmMessage.sourceChainSelector, // fetch the source chain identifier (aka selector) 183 | // abi.decode(any2EvmMessage.sender, (address)), // abi-decoding of the sender address, 184 | // abi.decode(any2EvmMessage.data, (string)) 185 | // ); 186 | } 187 | 188 | /// @notice Construct a CCIP message. 189 | /// @dev This function will create an EVM2AnyMessage struct with all the necessary information for sending a text. 190 | /// @param _receiver The address of the receiver. 191 | /// @param _payload The string data to be sent. 192 | /// @param _feeTokenAddress The address of the token used for fees. Set address(0) for native gas. 193 | /// @return Client.EVM2AnyMessage Returns an EVM2AnyMessage struct which contains information for sending a CCIP message. 194 | function _buildCCIPMessage( 195 | address _receiver, 196 | bytes memory _payload, 197 | address _feeTokenAddress 198 | ) private pure returns (Client.EVM2AnyMessage memory) { 199 | // Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message 200 | return 201 | Client.EVM2AnyMessage({ 202 | receiver: abi.encode(_receiver), // ABI-encoded receiver address 203 | data: _payload, // ABI-encoded string 204 | tokenAmounts: new Client.EVMTokenAmount[](0), // Empty array aas no tokens are transferred 205 | extraArgs: Client._argsToBytes( 206 | // Additional arguments, setting gas limit 207 | Client.EVMExtraArgsV1({gasLimit: 200_000}) 208 | ), 209 | // Set the feeToken to a feeTokenAddress, indicating specific asset will be used for fees 210 | feeToken: _feeTokenAddress 211 | }); 212 | } 213 | 214 | /// @notice Fetches the details of the last received message. 215 | /// @return messageId The ID of the last received message. 216 | /// @return text The last received text. 217 | function getLastReceivedMessageDetails() 218 | external 219 | view 220 | returns (bytes32 messageId, string memory text) 221 | { 222 | return (s_lastReceivedMessageId, s_lastReceivedText); 223 | } 224 | 225 | /// @notice Fallback function to allow the contract to receive Ether. 226 | /// @dev This function has no function body, making it a default function for receiving Ether. 227 | /// It is automatically called when Ether is sent to the contract without any data. 228 | receive() external payable {} 229 | 230 | /// @notice Allows the contract owner to withdraw the entire balance of Ether from the contract. 231 | /// @dev This function reverts if there are no funds to withdraw or if the transfer fails. 232 | /// It should only be callable by the owner of the contract. 233 | /// @param _beneficiary The address to which the Ether should be sent. 234 | function withdraw(address _beneficiary) public onlyOwner { 235 | // Retrieve the balance of this contract 236 | uint256 amount = address(this).balance; 237 | 238 | // Revert if there is nothing to withdraw 239 | if (amount == 0) revert NothingToWithdraw(); 240 | 241 | // Attempt to send the funds, capturing the success status and discarding any return data 242 | (bool sent, ) = _beneficiary.call{value: amount}(""); 243 | 244 | // Revert if the send failed, with information about the attempted transfer 245 | if (!sent) revert FailedToWithdrawEth(msg.sender, _beneficiary, amount); 246 | } 247 | 248 | /// @notice Allows the owner of the contract to withdraw all tokens of a specific ERC20 token. 249 | /// @dev This function reverts with a 'NothingToWithdraw' error if there are no tokens to withdraw. 250 | /// @param _beneficiary The address to which the tokens will be sent. 251 | /// @param _token The contract address of the ERC20 token to be withdrawn. 252 | function withdrawToken( 253 | address _beneficiary, 254 | address _token 255 | ) public onlyOwner { 256 | // Retrieve the balance of this contract 257 | uint256 amount = IERC20(_token).balanceOf(address(this)); 258 | 259 | // Revert if there is nothing to withdraw 260 | if (amount == 0) revert NothingToWithdraw(); 261 | 262 | IERC20(_token).safeTransfer(_beneficiary, amount); 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /lesson-6/contracts/NFTPoolLockAndRelease.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol"; 5 | import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol"; 6 | import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; 7 | import {CCIPReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/applications/CCIPReceiver.sol"; 8 | import {IERC20} from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; 9 | import {SafeERC20} from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol"; 10 | import {MyToken} from "./MyNFT.sol"; 11 | 12 | /** 13 | * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY. 14 | * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE. 15 | * DO NOT USE THIS CODE IN PRODUCTION. 16 | */ 17 | 18 | /// @title - A simple messenger contract for sending/receving string data across chains. 19 | contract NFTPoolLockAndRelease is CCIPReceiver, OwnerIsCreator { 20 | using SafeERC20 for IERC20; 21 | 22 | // Custom errors to provide more descriptive revert messages. 23 | error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); // Used to make sure contract has enough balance. 24 | error NothingToWithdraw(); // Used when trying to withdraw Ether but there's nothing to withdraw. 25 | error FailedToWithdrawEth(address owner, address target, uint256 value); // Used when the withdrawal of Ether fails. 26 | 27 | // Event emitted when a message is sent to another chain. 28 | event MessageSent( 29 | bytes32 indexed messageId, // The unique ID of the CCIP message. 30 | uint64 indexed destinationChainSelector, // The chain selector of the destination chain. 31 | address receiver, // The address of the receiver on the destination chain. 32 | bytes text, // The text being sent. 33 | address feeToken, // the token address used to pay CCIP fees. 34 | uint256 fees // The fees paid for sending the CCIP message. 35 | ); 36 | 37 | // Event emitted when a message is received from another chain. 38 | event TokenUnlocked( 39 | uint256 tokenId, 40 | address newOwner 41 | ); 42 | 43 | bytes32 private s_lastReceivedMessageId; // Store the last received messageId. 44 | string private s_lastReceivedText; // Store the last received text. 45 | 46 | // Mapping to keep track of allowlisted destination chains. 47 | mapping(uint64 => bool) public allowlistedDestinationChains; 48 | 49 | // Mapping to keep track of allowlisted source chains. 50 | mapping(uint64 => bool) public allowlistedSourceChains; 51 | 52 | // Mapping to keep track of allowlisted senders. 53 | mapping(address => bool) public allowlistedSenders; 54 | 55 | IERC20 private s_linkToken; 56 | 57 | // remember to add visibility for the variable 58 | MyToken public nft; 59 | 60 | struct RequestData{ 61 | uint256 tokenId; 62 | address newOwner; 63 | } 64 | 65 | // remember to add visibility for the variable 66 | mapping(uint256 => bool) public tokenLocked; 67 | 68 | 69 | /// @notice Constructor initializes the contract with the router address. 70 | /// @param _router The address of the router contract. 71 | /// @param _link The address of the link contract. 72 | constructor(address _router, address _link, address nftAddr) CCIPReceiver(_router) { 73 | s_linkToken = IERC20(_link); 74 | nft = MyToken(nftAddr); 75 | } 76 | 77 | /// @dev Updates the allowlist status of a destination chain for transactions. 78 | function allowlistDestinationChain( 79 | uint64 _destinationChainSelector, 80 | bool allowed 81 | ) external onlyOwner { 82 | allowlistedDestinationChains[_destinationChainSelector] = allowed; 83 | } 84 | 85 | /// @dev Updates the allowlist status of a source chain for transactions. 86 | function allowlistSourceChain( 87 | uint64 _sourceChainSelector, 88 | bool allowed 89 | ) external onlyOwner { 90 | allowlistedSourceChains[_sourceChainSelector] = allowed; 91 | } 92 | 93 | /// @dev Updates the allowlist status of a sender for transactions. 94 | function allowlistSender(address _sender, bool allowed) external onlyOwner { 95 | allowlistedSenders[_sender] = allowed; 96 | } 97 | 98 | // lock NFT and send CCIP transaction 99 | function lockAndSendNFT( 100 | uint256 tokenId, 101 | address newOwner, 102 | uint64 destChainSelector, 103 | address destReceiver 104 | ) public returns (bytes32){ 105 | // verify if the transaction is sent by owner 106 | // comment this because the check is already performed by ERC721 107 | // require(nft.ownerOf(tokenId) == msg.sender, "you are not the owner of the NFT"); 108 | 109 | // tansfer the NFT from owner to the pool 110 | nft.transferFrom(msg.sender, address(this), tokenId); 111 | // send request to Chainlink CCIP to send the NFT to the other Chain 112 | bytes memory payload = abi.encode(tokenId, newOwner); 113 | bytes32 messageId = sendMessagePayLINK(destChainSelector, destReceiver, payload); 114 | tokenLocked[tokenId] = true; 115 | return messageId; 116 | } 117 | 118 | /// @notice Sends data to receiver on the destination chain. 119 | /// @notice Pay for fees in LINK. 120 | /// @dev Assumes your contract has sufficient LINK. 121 | /// @param _destinationChainSelector The identifier (aka selector) for the destination blockchain. 122 | /// @param _receiver The address of the recipient on the destination blockchain. 123 | /// @param _payload The data to be sent. 124 | /// @return messageId The ID of the CCIP message that was sent. 125 | function sendMessagePayLINK( 126 | uint64 _destinationChainSelector, 127 | address _receiver, 128 | bytes memory _payload 129 | ) 130 | internal 131 | returns (bytes32 messageId) 132 | { 133 | // Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message 134 | Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage( 135 | _receiver, 136 | _payload, 137 | address(s_linkToken) 138 | ); 139 | 140 | // Initialize a router client instance to interact with cross-chain router 141 | IRouterClient router = IRouterClient(this.getRouter()); 142 | 143 | // Get the fee required to send the CCIP message 144 | uint256 fees = router.getFee(_destinationChainSelector, evm2AnyMessage); 145 | 146 | if (fees > s_linkToken.balanceOf(address(this))) 147 | revert NotEnoughBalance(s_linkToken.balanceOf(address(this)), fees); 148 | 149 | // approve the Router to transfer LINK tokens on contract's behalf. It will spend the fees in LINK 150 | s_linkToken.approve(address(router), fees); 151 | 152 | // Send the CCIP message through the router and store the returned CCIP message ID 153 | messageId = router.ccipSend(_destinationChainSelector, evm2AnyMessage); 154 | 155 | // Emit an event with message details 156 | emit MessageSent( 157 | messageId, 158 | _destinationChainSelector, 159 | _receiver, 160 | _payload, 161 | address(s_linkToken), 162 | fees 163 | ); 164 | 165 | // Return the CCIP message ID 166 | return messageId; 167 | } 168 | 169 | /// handle a received message 170 | function _ccipReceive( 171 | Client.Any2EVMMessage memory any2EvmMessage 172 | ) 173 | internal 174 | override 175 | { 176 | s_lastReceivedMessageId = any2EvmMessage.messageId; // fetch the messageId 177 | RequestData memory requestData = abi.decode(any2EvmMessage.data, (RequestData)); 178 | uint256 tokenId = requestData.tokenId; 179 | address newOwner = requestData.newOwner; 180 | require(tokenLocked[tokenId], "the NFT is not locked"); 181 | nft.transferFrom(address(this), newOwner, tokenId); 182 | emit TokenUnlocked(tokenId, newOwner); 183 | } 184 | 185 | /// @notice Construct a CCIP message. 186 | /// @dev This function will create an EVM2AnyMessage struct with all the necessary information for sending a text. 187 | /// @param _receiver The address of the receiver. 188 | /// @param _payload The string data to be sent. 189 | /// @param _feeTokenAddress The address of the token used for fees. Set address(0) for native gas. 190 | /// @return Client.EVM2AnyMessage Returns an EVM2AnyMessage struct which contains information for sending a CCIP message. 191 | function _buildCCIPMessage( 192 | address _receiver, 193 | bytes memory _payload, 194 | address _feeTokenAddress 195 | ) private pure returns (Client.EVM2AnyMessage memory) { 196 | // Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message 197 | return 198 | Client.EVM2AnyMessage({ 199 | receiver: abi.encode(_receiver), // ABI-encoded receiver address 200 | data: _payload, // ABI-encoded string 201 | tokenAmounts: new Client.EVMTokenAmount[](0), // Empty array aas no tokens are transferred 202 | extraArgs: Client._argsToBytes( 203 | // Additional arguments, setting gas limit 204 | Client.EVMExtraArgsV1({gasLimit: 200_000}) 205 | ), 206 | // Set the feeToken to a feeTokenAddress, indicating specific asset will be used for fees 207 | feeToken: _feeTokenAddress 208 | }); 209 | } 210 | 211 | /// @notice Fetches the details of the last received message. 212 | /// @return messageId The ID of the last received message. 213 | /// @return text The last received text. 214 | function getLastReceivedMessageDetails() 215 | external 216 | view 217 | returns (bytes32 messageId, string memory text) 218 | { 219 | return (s_lastReceivedMessageId, s_lastReceivedText); 220 | } 221 | 222 | /// @notice Fallback function to allow the contract to receive Ether. 223 | /// @dev This function has no function body, making it a default function for receiving Ether. 224 | /// It is automatically called when Ether is sent to the contract without any data. 225 | receive() external payable {} 226 | 227 | /// @notice Allows the contract owner to withdraw the entire balance of Ether from the contract. 228 | /// @dev This function reverts if there are no funds to withdraw or if the transfer fails. 229 | /// It should only be callable by the owner of the contract. 230 | /// @param _beneficiary The address to which the Ether should be sent. 231 | function withdraw(address _beneficiary) public onlyOwner { 232 | // Retrieve the balance of this contract 233 | uint256 amount = address(this).balance; 234 | 235 | // Revert if there is nothing to withdraw 236 | if (amount == 0) revert NothingToWithdraw(); 237 | 238 | // Attempt to send the funds, capturing the success status and discarding any return data 239 | (bool sent, ) = _beneficiary.call{value: amount}(""); 240 | 241 | // Revert if the send failed, with information about the attempted transfer 242 | if (!sent) revert FailedToWithdrawEth(msg.sender, _beneficiary, amount); 243 | } 244 | 245 | /// @notice Allows the owner of the contract to withdraw all tokens of a specific ERC20 token. 246 | /// @dev This function reverts with a 'NothingToWithdraw' error if there are no tokens to withdraw. 247 | /// @param _beneficiary The address to which the tokens will be sent. 248 | /// @param _token The contract address of the ERC20 token to be withdrawn. 249 | function withdrawToken( 250 | address _beneficiary, 251 | address _token 252 | ) public onlyOwner { 253 | // Retrieve the balance of this contract 254 | uint256 amount = IERC20(_token).balanceOf(address(this)); 255 | 256 | // Revert if there is nothing to withdraw 257 | if (amount == 0) revert NothingToWithdraw(); 258 | 259 | IERC20(_token).safeTransfer(_beneficiary, amount); 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /lesson-6/contracts/WrappedNFT.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {MyToken} from "./MyNFT.sol"; 5 | 6 | contract WrappedNFT is MyToken { 7 | constructor(string memory tokenName, string memory tokenSymbol) 8 | MyToken(tokenName, tokenSymbol) {} 9 | 10 | function mintWithSpecificTokenId(address to, uint256 _tokenId) public { 11 | _safeMint(to, _tokenId); 12 | } 13 | } -------------------------------------------------------------------------------- /lesson-6/deploy/00_deploy_local_ccip.js: -------------------------------------------------------------------------------- 1 | const { getNamedAccounts, deployments, network } = require("hardhat"); 2 | const { developmentChains } = require("../helper-hardhat-config") 3 | 4 | module.exports = async({getNamedAccounts, deployments}) => { 5 | 6 | if(developmentChains.includes(network.name)) { 7 | const { firstAccount } = await getNamedAccounts() 8 | const { deploy, log } = deployments 9 | log("deploy the CCIP local simulator") 10 | await deploy("CCIPLocalSimulator", { 11 | contract: "CCIPLocalSimulator", 12 | from: firstAccount, 13 | log: true, 14 | args: [] 15 | }) 16 | log("CCIP local simulator deployed!") 17 | } else { 18 | log("not in local, skip CCIP local") 19 | } 20 | } 21 | 22 | module.exports.tags = ["all", "test"] -------------------------------------------------------------------------------- /lesson-6/deploy/01_deploy_nft.js: -------------------------------------------------------------------------------- 1 | const { getNamedAccounts } = require("hardhat"); 2 | 3 | module.exports = async({getNamedAccounts, deployments}) => { 4 | const {firstAccount} = await getNamedAccounts() 5 | const {deploy, log} = deployments 6 | 7 | log("Deploying the nft contract") 8 | await deploy("MyToken", { 9 | contract: "MyToken", 10 | from: firstAccount, 11 | log: true, 12 | args: ["MyNFT", "MNT"] 13 | }) 14 | log("MyToken is deployed!") 15 | } 16 | 17 | module.exports.tags = ["all", "sourcechain"] -------------------------------------------------------------------------------- /lesson-6/deploy/02_deploy_lnu_pool.js: -------------------------------------------------------------------------------- 1 | const {getNamedAccounts, network} = require("hardhat") 2 | const {developmentChains, networkConfig} = require("../helper-hardhat-config") 3 | 4 | module.exports = async({getNamedAccounts, deployments}) => { 5 | const { firstAccount } = await getNamedAccounts() 6 | const { deploy, log } = deployments 7 | 8 | // get parameters for constructor 9 | let sourceChainRouter 10 | let linkToken 11 | let nftAddr 12 | if(developmentChains.includes(network.name)) { 13 | const ccipSimulatorTx = await deployments.get("CCIPLocalSimulator") 14 | const ccipSimulator = await ethers.getContractAt("CCIPLocalSimulator", ccipSimulatorTx.address) 15 | const ccipSimulatorConfig = await ccipSimulator.configuration() 16 | sourceChainRouter = ccipSimulatorConfig.sourceRouter_ 17 | linkToken = ccipSimulatorConfig.linkToken_ 18 | log(`local environment: sourcechain router: ${sourceChainRouter}, link token: ${linkToken}`) 19 | } else { 20 | // get router and linktoken based on network 21 | sourceChainRouter = networkConfig[network.config.chainId].router 22 | linkToken = networkConfig[network.config.chainId].linkToken 23 | log(`non local environment: sourcechain router: ${sourceChainRouter}, link token: ${linkToken}`) 24 | } 25 | 26 | const nftTx = await deployments.get("MyToken") 27 | nftAddr = nftTx.address 28 | log(`NFT address: ${nftAddr}`) 29 | 30 | log("deploying the lmn pool") 31 | await deploy("NFTPoolLockAndRelease", { 32 | contract: "NFTPoolLockAndRelease", 33 | from: firstAccount, 34 | log: true, 35 | args: [sourceChainRouter, linkToken, nftAddr] 36 | }) 37 | log("lmn pool deployed") 38 | } 39 | 40 | module.exports.tags = ["all", "sourcechain"] -------------------------------------------------------------------------------- /lesson-6/deploy/03_deploy_wrapped_nft.js: -------------------------------------------------------------------------------- 1 | module.exports = async({getNamedAccounts, deployments}) => { 2 | const { firstAccount } = await getNamedAccounts() 3 | const { deploy, log } = deployments 4 | 5 | log("deploying wrapped NFT on destination chain") 6 | await deploy("WrappedNFT", { 7 | contract: "WrappedNFT", 8 | from: firstAccount, 9 | log: true, 10 | args: ["WrappedNFT", "WNFT"] 11 | }) 12 | log("deployed wrapped nft") 13 | } 14 | 15 | module.exports.tags = ["all", "destchain"] -------------------------------------------------------------------------------- /lesson-6/deploy/04_deploy_mnb_pool.js: -------------------------------------------------------------------------------- 1 | const { network } = require("hardhat") 2 | 3 | module.exports = async({getNamedAccounts, deployments}) => { 4 | const { firstAccount } = await getNamedAccounts() 5 | const { deploy, log } = deployments 6 | const {developmentChains, networkConfig} = require("../helper-hardhat-config") 7 | 8 | let router 9 | let linkTokenAddr 10 | let wnftAddr 11 | if(developmentChains.includes(network.name)) { 12 | const ccipSimulatorTx = await deployments.get("CCIPLocalSimulator") 13 | const ccipSimulator = await ethers.getContractAt("CCIPLocalSimulator", ccipSimulatorTx.address) 14 | const ccipConfig = await ccipSimulator.configuration() 15 | router = ccipConfig.destinationRouter_ 16 | linkTokenAddr = ccipConfig.linkToken_ 17 | } else { 18 | router = networkConfig[network.config.chainId].router 19 | linkTokenAddr = networkConfig[network.config.chainId].linkToken 20 | } 21 | 22 | const wnftTx = await deployments.get("WrappedNFT") 23 | wnftAddr = wnftTx.address 24 | 25 | log(`get the parameters: ${router}, ${linkTokenAddr}, ${wnftAddr}`) 26 | log("deploying nftPoolBurnAndMint") 27 | await deploy("NFTPoolBurnAndMint", { 28 | contract: "NFTPoolBurnAndMint", 29 | from: firstAccount, 30 | log: true, 31 | args: [router, linkTokenAddr, wnftAddr] 32 | }) 33 | log("nftPoolBurnAndMint deployed") 34 | } 35 | 36 | module.exports.tags = ["all", "destchain"] -------------------------------------------------------------------------------- /lesson-6/hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("@chainlink/env-enc").config() 2 | require("@nomicfoundation/hardhat-toolbox"); 3 | require("@nomicfoundation/hardhat-ethers"); 4 | require("hardhat-deploy"); 5 | require("hardhat-deploy-ethers"); 6 | require("./task") 7 | 8 | 9 | const PRIVATE_KEY = process.env.PRIVATE_KEY 10 | const SEPOLIA_RPC_URL = process.env.SEPOLIA_RPC_URL 11 | const AMOY_RPC_URL = process.env.AMOY_RPC_URL 12 | 13 | /** @type import('hardhat/config').HardhatUserConfig */ 14 | module.exports = { 15 | solidity:{ 16 | compilers: [ 17 | { 18 | version: "0.8.20" 19 | }, 20 | { 21 | version: "0.8.19" 22 | } 23 | ] 24 | }, 25 | namedAccounts: { 26 | firstAccount: { 27 | default: 0 28 | } 29 | }, 30 | networks: { 31 | sepolia: { 32 | url: SEPOLIA_RPC_URL, 33 | accounts: [PRIVATE_KEY], 34 | chainId: 11155111, 35 | blockConfirmations: 6, 36 | companionNetworks: { 37 | destChain: "amoy" 38 | } 39 | }, 40 | amoy: { 41 | url: AMOY_RPC_URL, 42 | accounts: [PRIVATE_KEY], 43 | chainId: 80002, 44 | blockConfirmations: 6, 45 | companionNetworks: { 46 | destChain: "sepolia" 47 | } 48 | } 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /lesson-6/helper-hardhat-config.js: -------------------------------------------------------------------------------- 1 | developmentChains = ["hardhat", "localhost"] 2 | const networkConfig = { 3 | 11155111: { 4 | name: "sepolia", 5 | router: "0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59", 6 | linkToken: "0x779877A7B0D9E8603169DdbD7836e478b4624789", 7 | companionChainSelector: "16281711391670634445" 8 | }, 9 | 80002: { 10 | name: "amoy", 11 | router: "0x9C32fCB86BF0f4a1A8921a9Fe46de3198bb884B2", 12 | linkToken: "0x0Fd9e8d3aF1aaee056EB9e802c3A762a667b1904", 13 | companionChainSelector: "16015286601757825753" 14 | } 15 | 16 | } 17 | module.exports ={ 18 | developmentChains, 19 | networkConfig 20 | } -------------------------------------------------------------------------------- /lesson-6/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web3_tutorial_lesson6", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "@chainlink/contracts": "^1.1.1", 14 | "@chainlink/env-enc": "^1.0.5", 15 | "@chainlink/local": "^0.1.0", 16 | "@nomicfoundation/hardhat-ethers": "^3.0.6", 17 | "@nomicfoundation/hardhat-toolbox": "^5.0.0", 18 | "@openzeppelin/contracts": "^5.0.2", 19 | "ethers": "^6.13.1", 20 | "hardhat": "^2.22.5", 21 | "hardhat-deploy": "^0.12.4", 22 | "hardhat-deploy-ethers": "^0.4.2" 23 | }, 24 | "dependencies": { 25 | "@chainlink/contracts-ccip": "^1.4.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lesson-6/scripts/deploy.js: -------------------------------------------------------------------------------- 1 | // import the ethers from hardhat 2 | // write a main function 3 | // call the main function 4 | 5 | const { ethers } = require("hardhat"); 6 | 7 | async function main() { 8 | const fundMeFacotry = await ethers.getContractFactory("FundMe"); 9 | console.log("deploiying the contract..."); 10 | 11 | const fundMe = await fundMeFacotry.deploy(10); 12 | await fundMe.waitForDeployment(); 13 | console.log(`contract fundme is deployed successfully at addree ${fundMe.target}`) 14 | 15 | if(hre.network.config.chainId == 11155111 && process.env.ETHERSCAN_API_KEY) { 16 | console.log("wait for 3 confirmations") 17 | await fundMe.deploymentTransaction().wait(3) 18 | console.log("verifying contract on etherscan...") 19 | await verify(fundMe.target, [10]) 20 | } else { 21 | console.log("skipping verification") 22 | } 23 | 24 | console.log("fund the contract...") 25 | const fundTx = await fundMe.fund({ 26 | value: ethers.parseEther("0.1") 27 | }) 28 | await fundTx.wait() 29 | console.log("funded the contract") 30 | 31 | const fundMeBalance = await ethers.provider.getBalance(fundMe.target); 32 | console.log(`balance of the fundme is ${fundMeBalance}`) 33 | 34 | const [firstAccount, secondAccount] = await ethers.getSigners(); 35 | console.log(`first account address is ${firstAccount.address}`) 36 | console.log(`second account address is ${secondAccount.address}`) 37 | 38 | console.log("fetching the funds of the first account...") 39 | const fundsOfFirstAccount = await fundMe.listOfFunders(firstAccount.address); 40 | console.log(`Current funds of first account is ${fundsOfFirstAccount}`) 41 | 42 | console.log("fund the contract on behalf of the second account...") 43 | const secondFundTx = await fundMe.connect(secondAccount).fund({ 44 | value: ethers.parseEther("0.1") 45 | }) 46 | await secondFundTx.wait() 47 | 48 | console.log("fetching the funds of the second account...") 49 | const fundsOfSecondAccount = await fundMe.listOfFunders(secondAccount.address); 50 | console.log(`Current funds of second account is ${fundsOfSecondAccount}`) 51 | } 52 | 53 | async function verify(address, args) { 54 | await hre.run("verify:verify", { 55 | address: address, 56 | constructorArguments: args, 57 | }); 58 | } 59 | 60 | main().then().catch((error) => { 61 | console.log(`error is ${error}`) 62 | process.exit(1) 63 | }) -------------------------------------------------------------------------------- /lesson-6/scripts/deployAndInteract.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require("hardhat"); 2 | 3 | async function main() { 4 | const [deployer] = await ethers.getSigners() 5 | 6 | const localSimulatorFactory = await ethers.getContractFactory("CCIPLocalSimulator") 7 | const localSimulator = await localSimulatorFactory.deploy(); 8 | 9 | const config = await localSimulator.DOCUMENTATION(); 10 | const { 11 | chainSelector_, 12 | sourceRouter_, 13 | destinationRouter_, 14 | wrappedNative_, 15 | linkToken_, 16 | ccipBnM_, 17 | ccipLnM_ 18 | } = config; 19 | 20 | console.log(`ChainSelector is ${chainSelector_}`) 21 | console.log(`SourceRouter is ${sourceRouter_}`) 22 | console.log(`DestinationRouter is ${destinationRouter_}`) 23 | console.log(`WrappedNative is ${wrappedNative_}`) 24 | console.log(`LinkToken is ${linkToken_}`) 25 | console.log(`ccipBnM is ${ccipBnM_}`) 26 | console.log(`ccipLnM is ${ccipLnM_}`) 27 | 28 | // deploy nft contract 29 | const nftFactory = await ethers.getContractFactory("MyToken") 30 | const nft = await nftFactory.deploy(deployer); 31 | await nft.waitForDeployment() 32 | console.log(`NFT is deployed at address ${nft.target}`) 33 | 34 | // deploy NFTLockAndReleasePool 35 | const nftLockAndReleasePoolFactory = await ethers.getContractFactory("NFTPoolLockAndRelease") 36 | const nftLockAndReleasePool = await nftLockAndReleasePoolFactory.deploy(sourceRouter_, linkToken_, nft.target) 37 | await nftLockAndReleasePool.waitForDeployment() 38 | console.log(`LockAndReleasePool was deployed at address ${nftLockAndReleasePool.target}`) 39 | 40 | // deploy the WrappedNFT 41 | const wrappedNftFactory = await ethers.getContractFactory("WrappedMyToken") 42 | const wrappedNft = await wrappedNftFactory.deploy(deployer) 43 | await wrappedNft.waitForDeployment() 44 | console.log(`WrappedNFT was deployed at address ${wrappedNft.target}`) 45 | 46 | // deploy NFTBurnAndMintPool 47 | const nftBurnAndMintPoolFactory = await ethers.getContractFactory("NFTPoolBurnAndMint") 48 | const nftBurnAndMintPool = await nftBurnAndMintPoolFactory.deploy(destinationRouter_, linkToken_, wrappedNft.target) 49 | console.log(`BurnAndMintPool was deployed at address ${nftBurnAndMintPool.target}`) 50 | 51 | // mint a nft 52 | await nft.safeMint(deployer) 53 | const nftOwnedByDeployer = await nft.balanceOf(deployer) 54 | console.log(`there was ${nftOwnedByDeployer} nft owned by deployer`) 55 | const owner = await nft.ownerOf(0) 56 | console.log(`owner of the tokenId 0 in nft is ${owner}`) 57 | 58 | //request a LINK tokens from LINK faucet 59 | const linkTokenFactory = await ethers.getContractFactory("LinkToken") 60 | const linkToken = await linkTokenFactory.attach(linkToken_) 61 | const balanceBefore = await linkToken.balanceOf(nftLockAndReleasePool.target) 62 | console.log(`balance before: ${balanceBefore}`) 63 | await localSimulator.requestLinkFromFaucet(nftLockAndReleasePool.target, ethers.parseEther("100")) 64 | const balanceAfter = await linkToken.balanceOf(nftLockAndReleasePool.target) 65 | console.log(`balance before: ${balanceAfter}`) 66 | 67 | // approve the pool have the permision to transfer deployer's token 68 | await nft.approve(nftLockAndReleasePool.target, 0) 69 | console.log("approve successfully") 70 | 71 | // send nft to from LockAndRelease to BurnAndMint 72 | await nftLockAndReleasePool.lockAndCrossChainNft( 73 | 0, //tokenId 74 | deployer, //newOwner 75 | chainSelector_, //router 76 | nftBurnAndMintPool //receiver 77 | ) 78 | 79 | // check if the wrapped NFT minted 80 | const balanceOfNewOwner = await wrappedNft.balanceOf(deployer) 81 | console.log(`balance of new owner in the wrappedNFT is ${balanceOfNewOwner}`) 82 | const newOwner = await wrappedNft.ownerOf(0) 83 | console.log(`owner of the tokenId 0 in wrapped NFT is ${newOwner}`) 84 | } 85 | 86 | main().then().catch((e)=>{ 87 | console.error(e) 88 | process.exit(1) 89 | }) -------------------------------------------------------------------------------- /lesson-6/task/burn-and-cross.js: -------------------------------------------------------------------------------- 1 | const { task } = require("hardhat/config") 2 | const { networkConfig } = require("../helper-hardhat-config") 3 | 4 | task("burn-and-cross") 5 | .addParam("tokenid", "token id to be burned and crossed") 6 | .addOptionalParam("chainselector", "chain selector of destination chain") 7 | .addOptionalParam("receiver", "receiver in the destination chain") 8 | .setAction(async(taskArgs, hre) => { 9 | const { firstAccount } = await getNamedAccounts() 10 | 11 | // get token id from parameter 12 | const tokenId = taskArgs.tokenid 13 | 14 | const wnft = await ethers.getContract("WrappedNFT", firstAccount) 15 | const nftPoolBurnAndMint = await ethers.getContract("NFTPoolBurnAndMint", firstAccount) 16 | 17 | // approve the pool have the permision to transfer deployer's token 18 | const approveTx = await wnft.approve(nftPoolBurnAndMint.target, tokenId) 19 | await approveTx.wait(6) 20 | 21 | // transfer 10 LINK token from deployer to pool 22 | console.log("transfering 10 LINK token to NFTPoolBurnAndMint contract") 23 | const linkAddr = networkConfig[network.config.chainId].linkToken 24 | const linkToken = await ethers.getContractAt("LinkToken", linkAddr) 25 | const transferTx = await linkToken.transfer(nftPoolBurnAndMint.target, ethers.parseEther("10")) 26 | await transferTx.wait(6) 27 | 28 | // get chain selector 29 | let chainSelector 30 | if(taskArgs.chainselector) { 31 | chainSelector = taskArgs.chainselector 32 | } else { 33 | chainSelector = networkConfig[network.config.chainId].companionChainSelector 34 | } 35 | 36 | // get receiver 37 | let receiver 38 | if(taskArgs.receiver) { 39 | receiver = taskArgs.receiver 40 | } else { 41 | receiver = (await hre.companionNetworks["destChain"].deployments.get("NFTPoolLockAndRelease")).address 42 | } 43 | 44 | // burn and cross 45 | const burnAndCrossTx = await nftPoolBurnAndMint.burnAndMint(tokenId, firstAccount, chainSelector, receiver) 46 | console.log(`NFT burned and crossed with txhash ${burnAndCrossTx.hash}`) 47 | }) 48 | 49 | module.exports = {} -------------------------------------------------------------------------------- /lesson-6/task/check-nft.js: -------------------------------------------------------------------------------- 1 | const { task } = require("hardhat/config") 2 | 3 | task("check-nft").setAction(async(taskArgs, hre) => { 4 | const { firstAccount } = await getNamedAccounts() 5 | const nft = await ethers.getContract("MyToken", firstAccount) 6 | 7 | console.log("checking status of ERC-721") 8 | const totalSupply = await nft.totalSupply() 9 | console.log(`there are ${totalSupply} tokens under the collection`) 10 | for(let tokenId = 0; tokenId < totalSupply; tokenId++) { 11 | const owner = await nft.ownerOf(tokenId) 12 | console.log(`TokenId: ${tokenId}, Owner is ${owner}`) 13 | } 14 | }) 15 | 16 | module.exports = {} -------------------------------------------------------------------------------- /lesson-6/task/check-wrapped-nft.js: -------------------------------------------------------------------------------- 1 | const { task } = require("hardhat/config") 2 | 3 | task("check-wrapped-nft") 4 | .addParam("tokenid", "tokenid to check") 5 | .setAction(async(taskArgs, hre) => { 6 | const tokenId = taskArgs.tokenid 7 | const {firstAccount} = await getNamedAccounts() 8 | const nft = await ethers.getContract("WrappedNFT", firstAccount) 9 | 10 | console.log("checking status of ERC-721") 11 | const totalSupply = await nft.totalSupply() 12 | console.log(`there are ${totalSupply} tokens under the collection`) 13 | const owner = await nft.ownerOf(tokenId) 14 | console.log(`TokenId: ${tokenId}, Owner is ${owner}`) 15 | 16 | }) 17 | 18 | module.exports = {} -------------------------------------------------------------------------------- /lesson-6/task/index.js: -------------------------------------------------------------------------------- 1 | exports.mintNft = require("./mint-nft") 2 | exports.lockAndCross = require("./lock-and-cross") 3 | exports.checkNft = require("./check-nft") 4 | exports.checkWrappedNft = require("./check-wrapped-nft") 5 | exports.burnAndMint = require("./burn-and-cross") -------------------------------------------------------------------------------- /lesson-6/task/lock-and-cross.js: -------------------------------------------------------------------------------- 1 | const { task } = require("hardhat/config") 2 | const { networkConfig } = require("../helper-hardhat-config") 3 | 4 | task("lock-and-cross") 5 | .addParam("tokenid", "tokenId to be locked and crossed") 6 | .addOptionalParam("chainselector", "chain selector of destination chain") 7 | .addOptionalParam("receiver", "receiver in the destination chain") 8 | .setAction(async(taskArgs, hre) => { 9 | // get tokenId from parameter 10 | const tokenId = taskArgs.tokenid 11 | 12 | const { firstAccount } = await getNamedAccounts() 13 | console.log(`deployer is ${firstAccount}`) 14 | 15 | // get receiver contract 16 | // deployed contract will be used if there is no receiver provided 17 | let destReceiver 18 | if(taskArgs.receiver) { 19 | destReceiver = taskArgs.receiver 20 | } else { 21 | const nftBurnAndMint = await hre.companionNetworks["destChain"].deployments.get("NFTPoolBurnAndMint") 22 | destReceiver = nftBurnAndMint.address 23 | } 24 | console.log(`NFTPoolBurnAndMint address on destination chain is ${destReceiver}`) 25 | 26 | // get the chain selector of destination chain 27 | // deployed contract will be used if there is no chain selector provided 28 | let destChainSelector 29 | if(taskArgs.chainselector) { 30 | destChainSelector = taskArgs.chainselector 31 | } else { 32 | destChainSelector = networkConfig[network.config.chainId].companionChainSelector 33 | } 34 | console.log(`destination chain selector is ${destChainSelector}`) 35 | 36 | const linkTokenAddr = networkConfig[network.config.chainId].linkToken 37 | const linkToken = await ethers.getContractAt("LinkToken", linkTokenAddr) 38 | const nftPoolLockAndRelease = await ethers 39 | .getContract("NFTPoolLockAndRelease", firstAccount) 40 | 41 | // transfer 10 LINK token from deployer to pool 42 | const balanceBefore = await linkToken.balanceOf(nftPoolLockAndRelease.target) 43 | console.log(`balance before: ${balanceBefore}`) 44 | const transferTx = await linkToken.transfer(nftPoolLockAndRelease.target, ethers.parseEther("10")) 45 | await transferTx.wait(6) 46 | const balanceAfter = await linkToken.balanceOf(nftPoolLockAndRelease.target) 47 | console.log(`balance after: ${balanceAfter}`) 48 | 49 | // approve the pool have the permision to transfer deployer's token 50 | const nft = await ethers.getContract("MyToken", firstAccount) 51 | await nft.approve(nftPoolLockAndRelease.target, tokenId) 52 | console.log("approve successfully") 53 | 54 | // ccip send 55 | console.log(`${tokenId}, ${firstAccount}, ${destChainSelector}, ${destReceiver}`) 56 | const lockAndCrossTx = await nftPoolLockAndRelease 57 | .lockAndSendNFT( 58 | tokenId, 59 | firstAccount, 60 | destChainSelector, 61 | destReceiver 62 | ) 63 | 64 | // provide t 65 | console.log(`NFT locked and crossed, transaction hash is ${lockAndCrossTx.hash}`) 66 | }) 67 | 68 | module.exports = {} -------------------------------------------------------------------------------- /lesson-6/task/mint-nft.js: -------------------------------------------------------------------------------- 1 | // const { deployments } = require("hardhat") 2 | const { task } = require("hardhat/config") 3 | 4 | task("mint-nft").setAction(async(taskArgs, hre) => { 5 | const {firstAccount} = await getNamedAccounts() 6 | const nft = await ethers.getContract("MyToken", firstAccount) 7 | 8 | console.log(`nft address is ${nft.target}`) 9 | 10 | console.log("minting NFT...") 11 | const mintTx = await nft.safeMint(firstAccount) 12 | await mintTx.wait(6) 13 | const tokenAmount = await nft.totalSupply() 14 | const tokenId = tokenAmount - 1n 15 | console.log(`NFT minted, tokenId is ${tokenId}`) 16 | }) 17 | 18 | 19 | module.exports = {} -------------------------------------------------------------------------------- /lesson-6/test/staging/FundMe.staging.test.js: -------------------------------------------------------------------------------- 1 | const { getNamedAccounts, ethers, network } = require("hardhat") 2 | const { developmentChains } = require("../../helper-hardhat-config") 3 | const { assert } = require("chai") 4 | 5 | developmentChains.includes(network.name) 6 | ? describe.skip 7 | : describe("FundMe", async function() { 8 | let fundMe 9 | let deployer 10 | const sendValue = ethers.parseEther("0.1") 11 | this.beforeEach(async function() { 12 | deployer = (await getNamedAccounts()).deployer 13 | fundMe = await ethers.getContract("FundMe", deployer) 14 | }) 15 | 16 | // test fund function 17 | describe("The contract can be funded", async function() { 18 | it("Users can fund the contract successfully", async function() { 19 | await fundMe.fund({value: ethers.parseEther("0.1")}) 20 | assert.equal((await ethers.provider.getBalance(fundMe.target)), ethers.parseEther("0.1")) 21 | }) 22 | }) 23 | }) -------------------------------------------------------------------------------- /lesson-6/test/unit/cross-chain-nft.test.js: -------------------------------------------------------------------------------- 1 | const { getNamedAccounts, deployments, ethers } = require("hardhat") 2 | const { expect } = require("chai") 3 | 4 | let firstAccount 5 | let nft 6 | let wnft 7 | let poolLnU 8 | let poolMnB 9 | let chainSelector 10 | 11 | before(async function(){ 12 | firstAccount = (await getNamedAccounts()).firstAccount 13 | await deployments.fixture(["all"]) 14 | nft = await ethers.getContract("MyToken", firstAccount) 15 | wnft = await ethers.getContract("WrappedNFT", firstAccount) 16 | poolLnU = await ethers.getContract("NFTPoolLockAndRelease", firstAccount) 17 | poolMnB = await ethers.getContract("NFTPoolBurnAndMint", firstAccount) 18 | ccipLocalSimulator = await ethers.getContract("CCIPLocalSimulator", firstAccount) 19 | chainSelector = (await ccipLocalSimulator.configuration()).chainSelector_ 20 | }) 21 | 22 | describe("test if the nft can be minted successfully", 23 | async function(){ 24 | it("test if the owner of nft is minter", 25 | async function(){ 26 | // get nft 27 | await nft.safeMint(firstAccount) 28 | // check the owner 29 | const ownerOfNft = await nft.ownerOf(0) 30 | expect(ownerOfNft).to.equal(firstAccount) 31 | }) 32 | }) 33 | 34 | describe("test if the nft can be locked and transferred to destchain" 35 | , async function() { 36 | // transfer NFT from source chain to dest chain, check if the nft is locked 37 | it("transfer NFT from source chain to dest chain, check if the nft is locked", 38 | async function() { 39 | await ccipLocalSimulator.requestLinkFromFaucet(poolLnU.target, ethers.parseEther("10")) 40 | 41 | 42 | // lock and send with CCIP 43 | await nft.approve(poolLnU.target, 0) 44 | await poolLnU.lockAndSendNFT(0, firstAccount, chainSelector, poolMnB.target) 45 | 46 | // check if owner of nft is pool's address 47 | const newOwner = await nft.ownerOf(0) 48 | console.log("test") 49 | expect(newOwner).to.equal(poolLnU.target) 50 | } 51 | ) 52 | // check if the wnft is owned by new owner 53 | it("check if wnft's account is owner", 54 | async function() { 55 | const newOwner = await wnft.ownerOf(0) 56 | expect(newOwner).to.equal(firstAccount) 57 | } 58 | ) 59 | } 60 | ) 61 | 62 | describe("test if the nft can be burned and transferred back to sourcechain", 63 | async function() { 64 | it("wnft can be burned", 65 | async function() { 66 | // fund some Link tokens 67 | ccipLocalSimulator.requestLinkFromFaucet(poolMnB.target, ethers.parseEther("10")) 68 | 69 | // grant permission 70 | await wnft.approve(poolMnB.target, 0) 71 | 72 | // transfer the token 73 | await poolMnB.burnAndMint(0, firstAccount, chainSelector, poolLnU.target) 74 | const wnftTotalSupply = await wnft.totalSupply() 75 | expect(wnftTotalSupply).to.equal(0) 76 | } 77 | ) 78 | it("owner of the NFT is transferred to firstAccount", 79 | async function() { 80 | const newOwner = await nft.ownerOf(0) 81 | expect(newOwner).to.equal(firstAccount) 82 | } 83 | ) 84 | } 85 | ) -------------------------------------------------------------------------------- /lesson-6/utils/verify.js: -------------------------------------------------------------------------------- 1 | async function verify(address, args) { 2 | await hre.run("verify:verify", { 3 | address: address, 4 | constructorArguments: args, 5 | }); 6 | } 7 | 8 | 9 | module.exports = {verify} --------------------------------------------------------------------------------