├── .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 | -
36 | 区块链简史
37 |
38 | -
39 | 区块链的设计哲学:去中心化和共识
40 |
41 | -
42 | Web3:面向资产的互联网
43 |
44 | -
45 | 智能合约简介
46 |
47 | -
48 | 自托管钱包Metamask
49 |
50 | -
51 | 密码学基础 & Metamask配置
52 |
53 | -
54 | 领取测试币
55 |
56 | -
57 | 签名&发送交易
58 |
59 | -
60 | 燃料费(gas费)介绍
61 |
62 |
63 |
64 |
65 |
66 | 第二课:Solidity基础:Hello World
67 |
68 | -
69 | Remix,Solidity编译器和开源协议
70 |
71 | -
72 | Solidity基础数据类型
73 |
74 | -
75 | Solidity函数
76 |
77 | -
78 | Solidity存储模式:memory, storage, calldata
79 |
80 | -
81 | Solidity数据结构:结构体,数组和映射
82 |
83 | -
84 | 合约间交互:工厂模式
85 |
86 | -
87 | 总结
88 |
89 |
90 |
91 |
92 |
93 | 第三课:Solidity进阶:FundMe
94 |
95 | -
96 | 通过函数发送ETH
97 |
98 | -
99 | 通过预言机设定最小额度
100 |
101 | -
102 | 通过函数提取合约中的ETH
103 |
104 | -
105 | 修改器和时间锁
106 |
107 | -
108 | Token和Coin的区别
109 |
110 | -
111 | 创建一个Token合约
112 |
113 | -
114 | 继承ERC-20合约
115 |
116 | -
117 | 部署和验证合约
118 |
119 |
120 |
121 |
122 |
123 | 第四课:Hardhat基础:部署交互FundMe
124 |
125 | -
126 | 环境搭建:Hardhat介绍
127 |
128 | -
129 | 环境搭建:安装node.js
130 |
131 | -
132 | 环境搭建:安装VS Code和git
133 |
134 | -
135 | 创建Hardhat项目
136 |
137 | -
138 | 通过Hardhat编译和部署合约
139 |
140 | -
141 | Hardhat网络&其他配置
142 |
143 | -
144 | 与FundMe合约交互
145 |
146 | -
147 | 创建Hardhat自定义任务
148 |
149 |
150 |
151 |
152 |
153 | 第五课:Hardhat进阶:测试FundMe
154 |
155 | -
156 | Hardhat测试介绍
157 |
158 | -
159 | Hardhat deploy任务
160 |
161 | -
162 | 使用mock合约
163 |
164 | -
165 | 给FundMe写单元测试
166 |
167 | -
168 | gas reporter和coverage
169 |
170 |
171 |
172 |
173 |
174 | 第六课:跨链应用
175 |
176 | -
177 | NFT介绍
178 |
179 | -
180 | NFT的metadata
181 |
182 | -
183 | NFT基础合约
184 |
185 | -
186 | Chainlink ccip
187 |
188 | -
189 | 资产跨链池
190 |
191 | -
192 | chainlink-local & 单元测试
193 |
194 | -
195 | 跨链NFT的Hardhat的自定义任务
196 |
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 | -
491 | What is blockchain?
492 |
493 | -
494 | Philosophy of blockchain: Trust-minimization
495 |
496 | -
497 | Web3: decentralized internet for asset
498 |
499 | -
500 | Introduction for smart contracts
501 |
502 | -
503 | Self Custody wallet & metamask
504 |
505 | -
506 | Crypto basics & metamask setup
507 |
508 | -
509 | Claim test tokens on Sepolia testnet
510 |
511 | -
512 | Sign a transaction
513 |
514 | -
515 | Intro to gas
516 |
517 |
518 |
519 |
520 |
521 | Solidity Basics: Hello World
522 |
523 | -
524 | Remix & compiler version & license
525 |
526 | -
527 | Solidity: basic data types
528 |
529 | -
530 | Solidity: function
531 |
532 | -
533 | Storage & memory & calldata
534 |
535 | -
536 | Solidity: basic data structure
537 |
538 | -
539 | HelloWorld factory: interact with other contracts
540 |
541 |
542 |
543 |
544 |
545 | Lesson 3: Solidity Advanced: FundMe & ERC20
546 |
547 | -
548 | Payable function: send ETH to a contract
549 |
550 | -
551 | Set the minimum for USD with Chainlink Data feed
552 |
553 | -
554 | Transfer token using function
555 |
556 | -
557 | Modifer and timelock
558 |
559 | -
560 | Coin vs token
561 |
562 | -
563 | Create a token contract
564 |
565 | -
566 | ERC-20 token standard
567 |
568 | -
569 | Deployment & verification
570 |
571 |
572 |
573 |
574 |
575 | Lesson 4: hardhat FundMe
576 |
577 | -
578 | env setup: Introduction to Hardhat
579 |
580 | -
581 | env setup: Install nodejs
582 |
583 | -
584 | env setup: Install vscode & git
585 |
586 | -
587 | Create hardhat project
588 |
589 | -
590 | Compile and deploy the contract through Hardhat
591 |
592 | -
593 | Hardhat network & other configurations
594 |
595 | -
596 | Interact with FundMe
597 |
598 | -
599 | create custom hardhat task
600 |
601 |
602 |
603 |
604 |
605 | Lesson 5: Test FundMe
606 |
607 | -
608 | Introduction to the unit tests in Hardhat
609 |
610 | -
611 | Hardhat deploy task
612 |
613 | -
614 | mock contract
615 |
616 | -
617 | write unit test for FundMe
618 |
619 | -
620 | gas Reporter & coverage
621 |
622 |
623 |
624 |
625 |
626 | Lesson 6: cross-chain application
627 |
628 | -
629 | Introduction for NFT
630 |
631 | -
632 | NFT metadata
633 |
634 | -
635 | ERC-721 token standard
636 |
637 | -
638 | Chainlink ccip
639 |
640 | -
641 | Token pool for ccip
642 |
643 | -
644 | chainlink-local & unit test
645 |
646 | -
647 | Hardhat custom task cross-chain nft
648 |
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}
--------------------------------------------------------------------------------