{"author":{"address":"0x2f63c75aF66aEA13f76B9e0cbd02091c0C502F2C","user":"https://learnblockchain.cn/people/5473"},"content":{"body":"## 写在前面\r\n笔者目前正在学习智能合约开发，正好将日常积累的整理出来，以供后续朋友一起进步、学习，欢迎一起探讨。\r\n\r\n在开始本章节之前，建议先去阅读 \u003ca\u003eHardhat 入门（一）\u003c/a\u003e，前文中记录了如何初始化一个“hardhat的demo工程”，此文也是基于上一工程讲解的。\r\n\r\n## 合约代码讲解\r\n示例工程中，包含了一个Lock合约，文件位于contracts文件夹下，如图所示：\r\n![image.png](https://img.learnblockchain.cn/attachments/2024/08/9FhggPyX66b3126157c85.png)\r\n\r\n合约功能：\r\n1. 该合约名称为Lock\r\n1. 两个全局变量（unlockTime：解锁时间， owner：管理员地址）\r\n1. 一个事件：用于提现（Withdrawal）方法中，等同于传统项目中的log日志\r\n1. 构造方法：跟其它语言一样，实例化对象的时候，会同时执行其构造方法，并且构造方法也只会在这个时候被执行。\r\n1. 一个public方法：提现方法（withdraw）。\r\n\r\n总结该合约主要功能：该合约是一个简单的示例合约，在部署合约的时候，存入任意资金并设置解锁时间（解锁时间必须大于当前时间），合约中提供了“withdraw”方法，部署者可在时间到达后通过该方法取走之前存入的资金。\r\n\r\n代码示例，笔者在每一行代码中添加了注释，只要是为了方便理解：\r\n```sol\r\n// SPDX-License-Identifier: UNLICENSED\r\npragma solidity ^0.8.24; // 定义版本号，代表必须要此版本以上的编译器才可\r\n\r\n// Uncomment this line to use console.log\r\n// import \"hardhat/console.sol\";\r\n\r\ncontract Lock { // 定义合约名称 \r\n    uint public unlockTime; // 定义变量，保存解锁时间的时间戳，uint表示无符号整型，public表示公开可见，会自动生成public的get方法。\r\n    address payable public owner; // 定义地址变量，保存管理员地址也就是部署者的地址。当一个函数或合约被标记为payable时，它就可以接收以太币（原生代币）的转账。\r\n\r\n    event Withdrawal(uint amount, uint when); // 定义事件，可以理解为js中的console.log， 只不是会记录在交易中。\r\n\r\n    constructor(uint _unlockTime) payable { // 定义有参构造方法，需要传入解锁时间_unlockTime，并且该方法被payable标记，意味着在部署时，可以向该合约转账，在该合约中就代表存入的资金\r\n        require(\r\n            block.timestamp \u003c _unlockTime,\r\n            \"Unlock time should be in the future\"\r\n        ); // 判断语句，可以理解为其它语言中的if，然后throw异常。当require语句的条件为false时，会触发异常并导致当前交易被回滚。\r\n\r\n        unlockTime = _unlockTime; // 入参赋值\r\n        owner = payable(msg.sender); // 保存部署者即管理员的地址。msg.sender代表部署者的地址，注意此处将其转换为payable类型了，因为后面需要向该地址转账。\r\n    }\r\n\r\n    function withdraw() public { // 提现方法\r\n        // Uncomment this line, and the import of \"hardhat/console.sol\", to print a log in your terminal\r\n        // console.log(\"Unlock time is %o and block timestamp is %o\", unlockTime, block.timestamp);\r\n\r\n        require(block.timestamp \u003e= unlockTime, \"You can't withdraw yet\"); // 判断时间\r\n        require(msg.sender == owner, \"You aren't the owner\"); // 判断调用者地址是不是管理员地址\r\n\r\n        emit Withdrawal(address(this).balance, block.timestamp); // 输出日志\r\n\r\n        owner.transfer(address(this).balance); // 调用转账方法将合约的钱，发送给调用方法者的地址。\r\n    }\r\n}\r\n\r\n```\r\n\r\n## 举一反三\r\n### 特殊的全局变量\r\n在Solidity智能合约中，有一些特殊的全局变量可以提供有关区块链和当前交易的信息。这些全局变量可以在智能合约的任何地方访问，用于获取有关当前上下文的信息。以下是一些常用的全局变量：\r\n\r\n1. **`msg.sender`**：表示当前调用合约的地址，即交易发起者的地址。\r\n   \r\n2. **`msg.value`**：表示当前交易发送的以太币数量（单位为wei）。\r\n   \r\n3. **`msg.data`**：表示交易调用数据。\r\n   \r\n4. **`block.number`**：表示当前区块的区块号。\r\n   \r\n5. **`block.timestamp`**：表示当前区块的时间戳（以秒为单位）。\r\n   \r\n6. **`block.difficulty`**：表示当前区块的难度。\r\n   \r\n7. **`block.coinbase`**：表示当前区块的矿工地址。\r\n   \r\n8. **`tx.origin`**：表示交易的原始发送者地址，即最初发起交易的地址，而不是当前合约调用者的地址。\r\n\r\n这些全局变量提供了智能合约所需的关键信息，以便合约可以根据当前的上下文执行逻辑或验证操作。开发人员可以利用这些全局变量来编写更加智能和灵活的智能合约。\r\n\r\n### 特殊函数（Special Functions）\r\n在Solidity智能合约中，有一些特殊的函数类型具有特定的目的和行为。这些函数类型包括构造函数、接收函数、回退函数和纯函数。以下是这些特殊函数的简要说明：\r\n\r\n1. **构造函数（Constructor）**：\r\n   - 构造函数在合约部署时只会执行一次，用于初始化合约的状态变量。\r\n   - 构造函数的名称必须与合约名称相同。\r\n   - 在最新版本的Solidity中，构造函数使用`constructor`关键字声明。\r\n\r\n```solidity\r\npragma solidity ^0.8.0;\r\n\r\ncontract MyContract {\r\n    constructor() {\r\n        // 构造函数初始化逻辑\r\n    }\r\n}\r\n```\r\n\r\n2. **接收函数（Receive function）**：\r\n   - 接收函数是一个特殊的函数，用于接收以太币。\r\n   - 接收函数在合约接收以太币时被调用，且不能接受任何参数。\r\n   - 在最新版本的Solidity中，接收函数使用`receive`关键字声明。\r\n\r\n```solidity\r\npragma solidity ^0.8.0;\r\n\r\ncontract MyContract {\r\n    receive() external payable {\r\n        // 接收函数逻辑\r\n    }\r\n}\r\n```\r\n\r\n3. **回退函数（Fallback function）**：\r\n   - 回退函数是在合约接收到没有匹配任何其他函数调用时被调用的函数。\r\n   - 回退函数不接受任何参数，且不能声明为`pure`或`view`。\r\n   - 在最新版本的Solidity中，回退函数使用`fallback`关键字声明。\r\n\r\n```solidity\r\npragma solidity ^0.8.0;\r\n\r\ncontract MyContract {\r\n    fallback() external {\r\n        // 回退函数逻辑\r\n    }\r\n}\r\n```\r\n\r\n4. **纯函数（Pure function）**：\r\n   - 纯函数是一种特殊类型的函数，它不会修改合约的状态，也不会与区块链进行交互。\r\n   - 纯函数在声明时使用`pure`关键字。\r\n   - 纯函数只能访问其参数，并且不能访问合约的状态变量或全局变量。\r\n\r\n```solidity\r\npragma solidity ^0.8.0;\r\n\r\ncontract MyContract {\r\n    function add(uint a, uint b) public pure returns (uint) {\r\n        return a + b;\r\n    }\r\n}\r\n```\r\n\r\n这些特殊函数类型在Solidity合约中具有重要的作用，开发人员可以利用它们来实现不同的逻辑和功能。\r\n\r\n### 转账函数\r\n在Solidity智能合约中，可以使用一些内置的函数来实现以太币的转账操作。以下是一些常用的函数用于在智能合约中进行以太币的转账：\r\n\r\n1. **address.transfer()**：\r\n   - `address.transfer(amount)`函数用于将指定数量的以太币发送到另一个地址。\r\n   - 这个函数会尝试发送指定数量的以太币到目标地址，如果发送失败（例如由于 gas 不足），则会抛出异常并回滚所有更改。\r\n   - 这个函数适合用于小额转账，因为它会限制调用栈的深度，以防止攻击。\r\n\r\n```solidity\r\naddress payable recipient = 0x1234567890123456789012345678901234567890;\r\n\r\nfunction sendEther() public {\r\n    recipient.transfer(1 ether);\r\n}\r\n```\r\n\r\n2. **address.send()**：\r\n   - `address.send(amount)`函数与`transfer`函数类似，用于将指定数量的以太币发送到另一个地址。\r\n   - 不同之处在于，`send`函数在发送失败时不会抛出异常，而是返回一个布尔值来指示是否发送成功。\r\n\r\n```solidity\r\naddress payable recipient = 0x1234567890123456789012345678901234567890;\r\n\r\nfunction sendEther() public {\r\n    bool success = recipient.send(1 ether);\r\n    require(success, \"Transfer failed\");\r\n}\r\n```\r\n\r\n3. **address.call.value()()**：\r\n   - `address.call.value(amount).gas(gasLimit)()`函数是一种更底层的方式来发送以太币，并且可以指定 gas 的限制。\r\n   - 这种方式更为灵活，但也更容易出错，因此需要谨慎使用。\r\n\r\n```solidity\r\naddress payable recipient = 0x1234567890123456789012345678901234567890;\r\n\r\nfunction sendEther() public {\r\n    (bool success, ) = recipient.call{value: 1 ether}(\"\");\r\n    require(success, \"Transfer failed\");\r\n}\r\n```\r\n\r\n这些函数可以帮助您在智能合约中实现以太币的转账操作。在进行转账时，务必谨慎处理错误情况，以确保资金安全并避免潜在的漏洞。\r\n\r\n## 结尾\r\n以上便是对示例工程中的合约代码讲解，并且也针对用到的知识点做出相应的扩展。希望能与大家共同进步。\r\n\r\n若过程有错误，还望海涵，请多指教。","title":"Hardhat 入门（二）"},"history":null,"timestamp":1723011765,"version":1}