主页 > 中国区ios下载imtoken > 学习Solidity——智能合约开发手册(五)

学习Solidity——智能合约开发手册(五)

中国区ios下载imtoken 2023-06-27 08:06:33

目录
    1.这本手册是为谁准备的?
    2.必要的前期知识
    3.什么是 Solidity
    4.什么是智能合约?
    5.如何在 Solidity 中声明变量和函数?
    6.智能合约中的变量作用域
    7.可见性操作符如何工作
    8.什么是构造函数?
    9.接口和抽象合约
    10.智能合约示例 #2
    11.什么是合约状态?
    12.状态可变关键字(修饰符)
    13.数据位置——存储、内存和堆栈
    14.typing如何工作
    15.Solidity数据类型
    16.如何在 Solidity 中声明和初始化数组
    17.什么是函数修饰符?
    18.Solidity 中的错误处理——require, assert, revert
    19.Solidity 中的继承
    20.用构造器参数进行继承
    21.Solidity 中的类型转换和类型构造
    22.如何在Solidity中处理浮点数
    23.哈希、ABI编码和解码
    24.如何调用合约和使用 fallback 函数
    25.如何发送和接收以太币
    26.Solidity
    27.Solidity 中的事件和日志
    28.Solidity 中的时间逻辑
    29.结论和更多资源

如何发送和接收以太币

要将以太币从您的智能合约发送到目标合约,您需要使用三种内置的 Solidity 方法之一调用目标合约:transfer、send 或 call。

传输失败时将抛出异常,并且发送调用返回一个布尔值,您必须在继续之前检查该值。 在这三者中,出于安全原因不再推荐传输和发送,尽管您仍然可以使用它们并且它们会起作用。

除以下情况外,智能合约无法接收以太币:

如果你想让你的智能合约接收以太币,一般推荐使用 receive() 函数。 您可以通过创建一个 payable 回退函数来绕过,但推荐的方法是使用 receive() 函数。

如果你只依赖回退函数,你的编译器会给你以下信息:“*警告:这个合约有一个支付回退函数,但没有接收以太函数。考虑添加一个接收以太函数。*”

如果您同时拥有接收和回退功能,您可能想知道 Solidity 如何决定使用哪个函数来接收以太币。 该设计决策还告诉您这些功能的用途。

Receive 表示接收以太币。 回退是处理合约已经被调用的情况,但是正如我们在上一节中讨论的那样,合约中没有匹配的方法来处理调用。

Solidity 通过检查调用方发送的交易中的 msg.data 字段来匹配要调用的方法。 如果该字段是非空值,并且该值与调用合约中声明的任何其他函数不匹配,则将触发回退方法。

如果 msg.data 为空,则检查是否有已实现的接收函数。 如果是这样,它将用它来接受以太币。 如果 receive 不存在以太坊solidity智能合约开发,它将默认为 fallback 函数。 所以后备是......当没有其他任何意义时的后备(默认)方法。

接收函数是让你的合约接收以太币的更好方式。 在调用智能合约但没有任何东西可以“处理”调用的任何情况下,您都可以使用回退函数。

这是一个超级方便的逻辑树以太坊solidity智能合约开发,显示了接收和回退要处理的内容。

 Which function is called, fallback() or receive()?

           send Ether
               |
         msg.data is empty?
              / \
            yes  no
            /     \
receive() exists?  fallback()
         /   \
        yes   no
        /      \
    receive()   fallback()

(来源:Solidity By Example[1])

回到我们探索回退函数的示例,我们可以向 Target 添加一个接收函数,如下所示:

contract Target {
    int256 public count;

    function decrement() public payable {
        count = count - num;
    }

    fallback() external payable  {
        count++;
    }

    

    receive() external payable  {
        count+=5;
    }
}

interface ITarget {
    function decrement(int num) external payable;
    function nonExistentFunction() external;
}

contract TargetCaller {
    function callFallback(address _target) public {
        ITarget target = ITarget(_target);
        target.nonExistentFunction();


    }
}

我们已经看到 callFallback 将如何改变 Target 中的计数值。 但是,如果我们部署一个新的 Target 实例,我们现在可以向它发送 10 wei,如下所示,因为它现在具有 payablereceive 函数。 在发送 10 wei(或任何其他金额)之前,目标余额为零,如下所示。

什么是以太坊智能合约_以太坊智能合约取款_以太坊solidity智能合约开发

单击带有空调用数据 (msg.data) 的交易按钮将更改余额,如下图所示。 我们可以看count,加5,就是receive函数中的逻辑。

以太坊solidity智能合约开发_什么是以太坊智能合约_以太坊智能合约取款

什么是以太坊智能合约_以太坊solidity智能合约开发_以太坊智能合约取款

(将 Wei 发送到目标合约并观察更新后的余额)

如果我们调用 callFallback 并为其提供新 Target 实例的地址,我们会注意到它仅递增 1。如果我们包含一些 wei,这也会增加 Target 的余额。

因此,任何将 Ether 转移到智能合约都需要接收智能合约具有可以接受它的支付功能。 至少,接收智能合约需要一个 payable fallback 函数,尽管 payablereceive 函数是接收以太币付款的更好方式。

实体库

在任何编程语言中,库都是指一组辅助函数和实用函数,旨在跨多个代码库重用。 这些函数解决了特定的、重复出现的编程问题。

在 Solidity 中,库服务于相同的目的,但具有一些特殊的属性。

首先,它们是无状态的——也就是说,它们不存储数据(常量除外,因为它们不改变区块链的状态)。 他们也无法获得价值(这意味着他们无法获得应付款接收或回退功能)。

它们也不能从其他合约或库继承,库也不能有子(派生)合约。

库中声明的所有函数都不能是抽象的——也就是说,它们必须都有具体的实现。

由于 Solidity 库是无状态的,其中的任何方法都不能修改区块链的状态。 这意味着库中的所有方法都是纯函数或视图函数。

Solidity 库的另一个有趣特性是它们不需要导入到您的智能合约中。 它们可以作为独立的合约部署,然后通过它们在所有使用智能合约中的接口调用——就像传统工程世界中的 API 服务一样。

但是,仅当库包含公共或外部方法时才适用。 然后可以将该库部署为具有自己的以太坊地址的独立合约,并由所有使用智能合约调用。

如果库只包含内部方法,那么 EVM 只是将库代码“嵌入”到使用该库的智能合约中(因为内部函数无法从其他智能合约访问)。

Solidity 中的库具有代码重用之外的优势。 一次在区块链上部署一个库可以避免重复部署或导入库代码,从而节省未来的gas成本。

让我们看一个简单的库,然后剖析代码看看库的代码是如何使用的。

library WeirdMath {
    int private constant factor = 100;

    function applyFactor(int self) public pure returns (int) {
        return self * factor;
    }

    function add(int self, int numberToAdd) public pure returns (int) {
        return self + numberToAdd;
    }
}

该库有两种操作 int 数据类型的方法。 在第一个参数上调用 self 的原因很快就会变得清楚。 其中一种方法接受一个数字并将其乘以一个常量,该常量存储在库的代码中。 第二种方法接受两个数字并将它们相加。

现在让我们看看如何在消费智能合约中使用它。

// SPDX-License-Identifier: MIT

pragma solidity >=0.5.22 <=0.8.17;

contract StrangeMath {
    // Method 1 - using Library name with dot notation 
    function multiplyWithFactor(int num) public pure returns (int) {
        return WeirdMath.applyFactor(num);
    }


    // Method 2 - the 'using' keyword and dot notation.
    // Syntax: using <> for data type of the first argument in the method to be called.
    using WeirdMath for int;
    function addTwoNums(int num1, int num2) public pure returns (int) {
        return num1.add(num2);
    }
}

以太坊智能合约取款_什么是以太坊智能合约_以太坊solidity智能合约开发

首先要注意的是,有两种方法可以使用 WeirdMath 库。

您可以通过以下任何一种方式使用它:

1.调用库的名称后跟要调用的函数,或者

2. 直接在你希望函数操作的数据类型上调用函数。 此数据类型必须与库函数中的 self 参数类型相同。

第一种方法由代码片段中的方法 1 演示,我们使用 WeirdMath.add(num1, num2) 调用库;

第二种方法使用 Solidityusing 关键字。 表达式返回 num1.add(num2); 将 WeirdMath 库的 add 函数应用于 num1 变量。 这与将其作为 self 传入相同,self 是 add 函数的第一个参数。

Solidity 中的事件和日志

智能合约可以发出事件。 这些事件包含开发人员指定的数据片段。

事件不能被其他智能合约消费。 相反,它们作为日志存储在区块链上,可以通过从区块链读取的 API 进行检索。

这意味着您的应用程序(最常见的是您的前端应用程序)可以从区块链“读取”包含事件数据的日志。 通过这种方式,您的 UI 可以响应区块链上的事件。

这就是应用程序 UI 响应链上事件而更新的方式。 由于可以查询区块链上的这些日志,因此日志是一种廉价的存储形式,正如前面关于存储领域的讨论所讨论的那样。

智能合约发出的事件可以使用相关的区块链浏览器进行检查,因为公共区块链上的所有内容都是公开可见的。 但是如果智能合约的字节码没有被验证,事件数据可能不是人类可读的(它将被编码)。 经过验证的智能合约的事件将是人类可读的。

节点和其他区块链客户端可以监听(订阅)特定事件。 其核心是 Chainlink [2] 预言机的工作方式——去中心化的预言机节点侦听来自智能合约的事件,然后做出相应的响应。 他们甚至可以从事件中提取数据,在链下运行复杂且资源密集型的计算,并将加密可验证的计算结果提交回区块链。

由于能够通过智能合约发出的事件查询区块链数据,其他网络 API 和索引服务(例如子图 [3])成为可能。

这就是 Solidity 中事件的样子:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

contract SimpleStorage {
    uint256 favoriteNumber;

    event storedNumber(
        uint256 indexed oldNumber, // up to 3 indexed params allowed
        uint256 indexed newNumber,
        uint256 addedNumber,
        address sender
    );

    function store(uint256 newNumber) public {
        emit storedNumber(
            favoriteNumber,
            newNumber,
            newNumber + favoriteNumber,
            msg.sender
        );
        favoriteNumber = newNumber;
    }

    function retrieve() public view returns (uint256) {
        return favoriteNumber;
    }
}

首先声明一个事件并指定其参数及其数据类型。 任何带有索引关键字的数据都由 EVM 索引,以便对区块链日志的查询可以使用索引参数作为过滤器。 这使得检索日志更快。

一个事件最多可以存储 4 个索引参数——取决于它是否是匿名的。 索引事件参数在 Solidity 世界中也称为“主题”。

大多数事件都是非匿名的,这意味着它们包含有关事件名称和参数的数据。

非匿名事件只允许开发者指定 3 个主题,因为第一个主题保留用于指定事件签名的 ABI 编码十六进制形式。 您可以在此处阅读有关匿名和非匿名主题的更多信息[4]。

以太坊solidity智能合约开发_什么是以太坊智能合约_以太坊智能合约取款

您还可以在相关的区块链浏览器上探索事件,例如 etherscan.io[5]。

您可以从两个入口点之一进行处理。 您可以直接查看合约的地址,然后转到“事件”选项卡(它只会显示该合约发出的事件)。 或者您可以转到交易哈希并检查该交易涉及的所有合约发出的所有事件。

例如,下面是以太坊主网上 Chainlink VRF 协调器智能合约 [6] 中的一个事件的屏幕截图。

什么是以太坊智能合约_以太坊solidity智能合约开发_以太坊智能合约取款

合同选项卡有一个绿色勾号,这意味着合同已经过验证,因此事件名称和参数是人类可读的。 花点时间研究这张图片,因为它包含很多信息! 如果您想直接在 etherscan 上研究它,请单击此处 [7]。

Chainlink VRF 协调器合约响应对加密可验证随机数的请求,并向请求的智能合约提供随机数(称为“随机词”)。

如果您想了解“单词”在计算机科学中的含义,请查看我和我的同事在这个 Chainlink 2022 黑客马拉松视频 [8] 中解决这个问题的方法。

当 VRF 协调器合约满足随机数请求时,它会发出 RandomWordsFulfilled 事件。 该事件包含4条数据,第一个数据requestID已经被索引。

Solidity 事件包含三种类型的数据:

1.发送事件的合约地址。

2.主题(用于过滤日志查询的索引事件参数)。

3. 非索引参数,称为“数据”,采用 ABI 编码并以十六进制表示。 此数据需要按照 ABI 编码和解码部分中的描述进行 ABI 解码。

在 Remix 中工作时,您还可以在控制台中检查事件,如下所示:

什么是以太坊智能合约_以太坊solidity智能合约开发_以太坊智能合约取款

在 Remix Browser IDE 中检查事件数据

您还可以使用 EthersJS 中的合约接收者对象以编程方式访问事件。 [9] 使用我们上面在 SimpleStorage 合约中使用的代码片段,我们可以使用以下 JavaScript 使用 EthersJS 和 Hardhat 访问事件:

const transactionResponse = await simpleStorage.store(1981)
const receipt = await transactionResponse.wait()

console.log(receipt.events[0].args.newNumber.toString()) // 1981

您还可以在前端应用程序中使用诸如 EtherJs 库之类的库来监听事件 [10] 和过滤历史事件 [11]。 当您的应用程序需要响应区块链上的事件时,两者都很有用。

Solidity 中的时间逻辑

Solidity 中的时间是根据添加到区块链的每个块指定的。

以太坊solidity智能合约开发_以太坊智能合约取款_什么是以太坊智能合约

全局变量 block.timestamp 指的是区块生成并添加到区块链的时间,以毫秒为单位。 毫秒计数是自 Unix 时代开始以来经过的毫秒数(在计算中,这是 1970 年 1 月 1 日)。

与 Web2 以毫秒为单位引用时间戳不同,此值可能不会每毫秒递增。

一个区块通常包含多个交易,由于 block.timestamp 指的是区块被挖掘的时间,所以被挖掘区块中的所有交易将具有相同的时间戳值。 所以时间戳实际上是指出块的时间,而不是调用者发起交易的时间。

Solidity 支持直接引用以下时间单位:秒、分钟、小时、天和周。

所以我们可以做一些像 uint lastWeek = block.timestamp - 1 weeks; 计算当前区块被挖出前1周的时间戳,精确到毫秒。 该值将与 block.timestamp - 7 days; 相同。

您还可以使用它来计算未来的截止日期,例如,您可能希望某个操作在现在和下周之间可用。 你可以像 uint registrationDeadline = block.timestamp + 1 weeks; 那样做,然后我们可以像这样使用 registrationDeadline 作为验证或保护功能:

function registerVoter(address voter) public view {
        require(block.timestamp <= registrationDeadline, registration deadline has passed.”);
        
        // Register the Voter....
    }

在这个函数中,我们只在当前区块的时间戳没有超过注册截止日期的情况下注册选民。

当我们要确保某些操作在正确的时间或在特定的时间间隔内执行时,会广泛使用此逻辑。

这也是可以配置 Chainlink Automation [12] 的方式之一,这是一种自动执行智能合约的去中心化方式。 Chainlink 去中心化预言机网络可以配置为自动触发您的智能合约,您可以通过检查条件(包括与时间相关的条件)来运行各种自动化。 这些被广泛用于空投、促销、特殊奖励、收益等。

结论和更多资源

恭喜! 你完成了这个史诗般的旅程。 如果您花时间消化了本手册并在 Remix IDE 中运行了一些代码,那么您现在已经接受了 Solidity 培训。

从这里开始,这是一个练习、重复和经验的问题。 当您着手构建下一个出色的去中心化应用程序时,请记住重新审视基础知识并关注安全性。 安全性在 Web3 空间中尤为重要。

您可以从 OpenZeppelin 的博客[13] 和 Trail of Bits[14] 资源等获得有关最佳实践的良好信息。

您还可以通过完成我的同事 Patrick Collins 在 freeCodeCamp 发布的完整的端到端全栈区块链开发人员课程 [15](免费!)来获得更多实践经验。

还有其他资源,例如 blockchain.education 和 freeCodeCamp 自己即将推出的 Web3 课程,可以巩固您的学习。

无论如何,无论您的经验水平如何,本手册都可以成为您快速回顾基本概念的“桌面伴侣”。

要记住的重要一点是 Web3 技术总是在不断发展。 迫切需要愿意应对复杂挑战、学习新技能并解决去中心化架构带来的重要问题的开发人员。

那可能(也应该)是你! 所以只要跟随你的好奇心,不要害怕一路上的挣扎。

同样,我打算继续更新本手册。 因此,如果您发现任何不太正确、过时或不清楚的地方,只需发推特并标记我 [16] 和 freeCodeCamp - 感谢你们参与让本手册保持新鲜。

以太坊智能合约取款_以太坊solidity智能合约开发_什么是以太坊智能合约

现在......去真棒!

原版的:

以太坊智能合约取款_以太坊solidity智能合约开发_什么是以太坊智能合约

引用链接

[1] 举个例子:

[2] 链环:

[3] 索引服务(如子图):

[4] 你可以在这里找到它:#events-solidity

[5] 以太扫描网:

[6] 以太坊主网上的 Chainlink VRF 协调器智能合约:#ethereum-mainnet

[7] 点击此处:#events

[8]Chainlink 2022 黑客马拉松视频:

[9] 的合约接收对象以编程方式访问事件。 :

[10] 监听事件:#getting-started--events

[11] 过滤历史事件:#getting-started--history

[12] Chainlink 自动化:

[13] OpenZeppelin的博客:

[14]比特的踪迹:

[15] 端到端全栈区块链开发者课程:

[16] 标记我: