怎么在Web3中创建智能合约

智能合约是在区块链上运行并在 web3 生态系统中启用去中心化应用程序 (dapps) 的自动执行协议。Web3 是指下一代互联网的术语,用户可以更好地控制自己的数据、身份和资产,并且可以在没有中介的情况下直接相互交互。

本篇文章将介绍如何使用 JavaScript 和 Solidity(最流行的 web3 开发语言)与 web3 创建智能合约。以及使用一些工具和平台来简化智能合约的编写、部署和交互过程。

前置条件

要开始,您将需要以下内容:

  • Visual Studio Code:一个代码编辑器,支持 web3 开发的语法高亮、调试和扩展。

  • Ganache:一个本地区块链模拟器,允许您在不花费真实以太币的情况下测试您的智能合约。

  • Node.js:一种运行时环境,可让您在浏览器之外运行 JavaScript 代码。

  • Web3.js:一个提供与以太坊节点和智能合约交互的接口的库。

  • Solidity:一种编程语言,专为在以太坊和其他区块链上编写智能合约而设计。

您可以按照各自网站上的说明或使用 npm 等包管理器来安装这些工具。

编写智能合约

智能合约是一段代码,它定义了两方或多方之间协议的逻辑和规则。例如,智能合约可以表示采购订单、保险单或游戏。

要编写智能合约,我们将使用 Solidity,它类似于 JavaScript,但具有一些用于区块链开发的特定功能和语法。Solidity 文件具有 .sol 扩展名,并以指定编译器版本的 pragma 语句开头。

让我们编写一个简单的智能合约,代表买卖双方之间的采购订单。该合约将具有以下特点:

  • 它将存储买家地址、卖家地址、商品名称和价格。

  • 它将有一个构造函数,在部署合约时初始化这些值。

  • 它将有一个购买功能,允许买家使用以太币为商品付款。

  • 它将有一个在执行购买功能时发出的事件。

该合约的代码如下:

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>// SPDX-License-Identifier: GPL-3.0
// Specify compiler version
pragma solidity ^0.8.0;
// Define contract name
contract PurchaseOrder {
  // Declare state variables
  address public buyer;
  address public seller;
  string public item;
  uint256 public price;
  // Declare event
  event Purchased(address buyer, address seller, string item);
  // Define constructor function
  constructor(address _buyer, address _seller,
              string memory _item,
              uint256 _price) {
    // Assign values to state variables
    buyer = _buyer;
    seller = _seller;
    item = _item;
    price = _price;
  }
  // Define buy function
  function buy() public payable {
    // Check if sender is buyer
    require(msg.sender == buyer,
            "Only buyer can buy");
    // Check if value is equal to price
    require(msg.value == price,
            "Value must be equal to price");
    // Transfer value to seller
    payable(seller).transfer(msg.value);
    // Emit event
    emit Purchased(buyer,seller,item);
  }
}
</code></span></span>

部署智能合约

要在以太坊或任何其他与 web3.js 兼容的区块链平台(例如 Moralis)上部署智能合约,我们需要两件事:

  • ABI(应用程序二进制接口):描述如何与合约的功能和事件交互的 JSON 文件。

  • 字节码:代表合约代码编译版本的十六进制字符串。

我们可以使用Remix IDE等在线工具或使用 solc-js 生成这些文件,solc-js 是 Solidity 编译器的 JavaScript 包装器。

为简单起见,将在本文中使用 Remix IDE。为此,

  1. 在浏览器中打开 Remix IDE 并创建一个名为 PurchaseOrder.sol 的新文件。

  2. 将我们的 PurchaseOrder.sol 代码复制并粘贴到其中。

  3. 单击左侧面板上的 Solidity Compiler 选项卡并选择编译器版本 ^0.8.0。

  4. 单击编译 PurchaseOrder.sol 按钮。

  5. 单击其下方的“编译详细信息”按钮。

  6. 将 ABI JSON 复制并粘贴到另一个名为 PurchaseOrderABI.json 的文件中。

  7. 将字节码十六进制字符串复制并粘贴到另一个名为 PurchaseOrderBytecode.txt 的文件中。

现在我们的 ABI 和字节码已准备好部署。

下一个,

  1. 在浏览器中打开 Ganache,然后单击“快速启动工作区”按钮。这会将 Remix IDE 连接到 Ganache,并向您显示 10 个帐户,每个帐户有 100 ETH。

  2. 单击左侧面板上的 Deploy & Run Transactions 选项卡,然后选择 Web3 Provider 作为环境。

  3. 单击我们的 PurchaseOrder 合同名称下方的 Deploy 按钮。这会将我们的合约部署到 Ganache 并向我们展示它的地址、ABI 和功能。

  4. 将合同地址复制粘贴到另一个名为 PurchaseOrderAddress.txt 的文件中。

现在我们已经使用 Remix IDE 在 Ganache 上部署了我们的智能合约。

与智能合约交互

要使用 web3.js 与智能合约进行交互,我们需要执行以下步骤:

  • 创建一个通过 HTTP 提供程序连接到 Ganache 的 web3 实例。

  • 创建一个使用我们的 ABI 和地址的合约实例。

  • 使用我们的合约实例调用或发送交易到我们的合约函数。

让我们编写一个 JavaScript 文件来执行这些步骤并打印出有关我们采购订单的一些信息。

创建一个名为 PurchaseOrder.js 的新文件并将以下代码复制粘贴到其中:

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>// Import web3 library
const Web3 = require("web3");
// Import fs library for reading files
const fs = require("fs");
// Read ABI JSON file
const abi = JSON.parse(fs.readFileSync("PurchaseOrderABI.json"));
// Read bytecode hex string file
const bytecode = fs.readFileSync("PurchaseOrderBytecode.txt", "utf8");
// Read contract address file
const address = fs.readFileSync("PurchaseOrderAddress.txt", "utf8");
// Create web3 instance and connect to Ganache
const web3 = new Web3("http://localhost:8545");
// Create contract instance using ABI and address
const contract = new web3.eth.Contract(abi, address);
// Get buyer's address from Ganache (first account)
web3.eth.getAccounts().then(accounts => {
  const buyer = accounts[0];
// Get seller's address from Ganache (second account)
  const seller = accounts[1];
// Get item name and price from constructor arguments
  const item = web3.utils.hexToUtf8(bytecode.slice(-128, -64));
  const price = web3.utils.hexToNumber(bytecode.slice(-64));
// Print purchase order details
  console.log("Purchase Order Details:");
  console.log("Buyer: " + buyer);
  console.log("Seller: " + seller);
  console.log("Item: " + item);
  console.log("Price: " + price + " wei");
// Call buy function using buyer's account and value equal to price
  contract.methods.buy().send({from: buyer, value: price})
    .then(receipt => {
      // Print transaction receipt
      console.log("Transaction Receipt:");
      console.log(receipt);
// Get purchased event from receipt logs
      const event = receipt.events.Purchased;
// Print event details
      console.log("Event Details:");
      console.log(event.returnValues);
    })
    .catch(error => {
      // Print error message
      console.error(error.message);
    });
});
</code></span></span>

此代码将执行以下操作:

  • 它将导入 web3 库和 fs 库来读取文件。

  • 它将读取我们的 ABI JSON 文件、字节码十六进制字符串文件和合约地址文件。

  • 它将创建一个通过 HTTP 提供程序连接到 Ganache 的 web3 实例。

  • 它将使用我们的 ABI 和地址创建一个合约实例。

  • 它将分别从 Ganache 帐户和构造函数参数中获取买家地址、卖家地址、商品名称和价格。

  • 它将在控制台上打印出这些详细信息。

  • 它将使用买方账户和等于价格的价值作为参数调用我们合约的购买功能。

  • 它将在控制台上打印出交易收据,其中包括执行购买功能时发出的 Purchased 事件的事件日志。

  • 它会在控制台打印出事件详情,包括买家地址、卖家地址和商品名称作为返回值。

要运行此代码,

  1. 在保存所有文件(PurchaseOrder.sol、PurchaseOrderABI.json、PurchaseOrderBytecode.txt、PurchaseOrderAddress.txt、PurchaseOrder.js)的项目文件夹中打开一个终端窗口。

  2. 确保 Ganache 正在另一个终端窗口或浏览器选项卡中运行。

  3. 在您的终端窗口中键入 node PurchaseOrder.js 并按回车键。

你应该看到这样的东西:

监听智能合约事件

智能合约可以发出事件,通知侦听器有关合约发生的某些更改或操作。例如,我们的 PurchaseOrder 合约会在执行购买功能时发出 Purchased 事件。

要使用 web3.js 监听智能合约事件,我们可以使用以下方法之一:

  • contract.once(event [, options], callback):此方法将仅获取一次事件并返回未定义。它对于侦听只发生一次的事件很有用,例如构造函数事件。

  • contract.events.MyEvent([options] [, callback]):此方法将获取名为“MyEvent”的事件并返回一个 eventEmitter 对象。它对于监听多次发生的特定事件很有用,例如 Transfer 事件。

  • contract.events.allEvents([options] [, callback]):此方法将获取合约发出的所有事件并返回一个 eventEmitter 对象。它对于监听合约上发生的所有事件很有用,无论它们的名称如何。

  • contract.getPastEvents(event [, options] [, callback]):此方法将获取块范围内可用的所有过去事件并返回事件对象数组。它对于获取历史数据或分析过去的交易很有用。

所有这些方法都有相似的参数:

  • event:一个字符串,指定要监听或获取的事件的名称。对 allEvents 或 getPastEvents 方法使用“*”。

  • options:一个对象,它指定了一些用于获取或监听事件的过滤器或设置,例如:

  • filter:一个对象,允许您通过索引参数过滤事件,例如 {from: "0x123…"} 或 {value: web3.utils.toWei("1", "ether")}。

  • fromBlock:一个数字或字符串,指定从中获取或侦听事件的块号。对当前区块使用“latest”,对未决交易使用“pending”,对创世区块使用“earliest”。

  • toBlock:一个数字或字符串,指定要获取过去事件的块号。对当前区块使用“latest”,对未决交易使用“pending”,对创世区块使用“earliest”。

  • callback:一个有两个参数的函数:错误和结果。如果没有发生错误,则错误参数将为 null,并且结果将是未定义的(对于 once 方法)、eventEmitter 对象(对于事件方法)或事件对象数组(对于 getPastEvents 方法)。

这些方法返回的事件对象有一些共同的属性:

  • event:表示事件名称的字符串。

  • address:一个字符串,指示发出事件的合约的地址。

  • transactionHash:一个字符串,指示触发事件的交易的哈希值。

  • blockHash:一个字符串,表示区块链中的块号

  • blockNumber:一个数字,指示发出事件的块号。

  • logIndex:一个数字,指示事件在块中的位置。

  • returnValues:包含事件的非索引参数值的对象。

events 方法返回的 eventEmitter 对象有一些常用的方法:

  • on(type, listener):此方法为给定类型的事件(例如“数据”、“错误”、“已更改”或“已连接”)注册一个侦听器函数。

  • once(type, listener):此方法为给定类型的事件注册一个监听函数,但只执行一次,然后将其删除。

  • off(type, listener):此方法删除给定事件类型的侦听器函数。

  • remove all listeners ([type]):此方法删除所有侦听器或仅删除指定类型的侦听器。

下面看一些代码,使用这些方法来监听我们的 Purchased 事件。

创建一个名为 PurchaseOrderEvents.js 的新文件,并将以下代码复制粘贴到其中:

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>// Import web3 library
const Web3 = require("web3");
// Import fs library for reading files
const fs = require("fs");
// Read ABI JSON file
const abi = JSON.parse(fs.readFileSync("PurchaseOrderABI.json"));
// Read contract address file
const address = fs.readFileSync("PurchaseOrderAddress.txt", "utf8");
// Create web3 instance and connect to Ganache via WebSocket provider
const web3 = new Web3("ws://localhost:8545");
// Create contract instance using ABI and address
const contract = new web3.eth.Contract(abi, address);
// Define a callback function for Purchased event
const purchasedCallback = (error, result) => {
  if (error) {
    // Print error message
    console.error(error.message);
  } else {
    // Print event details
    console.log("Purchased Event:");
    console.log(result.returnValues);
  }
};
// Listen to Purchased event using events method and callback function
contract.events.Purchased({}, purchasedCallback);
// Listen to Purchased event using once method and callback function
contract.once("Purchased", {}, purchasedCallback);
// Listen to all events using allEvents method and callback function
contract.events.allEvents({}, purchasedCallback);
// Get past Purchased events using getPastEvents method and callback function
contract.getPastEvents("Purchased", {}, purchasedCallback);
</code></span></span>

此代码将执行以下操作:

  • 它会导入用于读取文件的web3库和fs库。

  • 它将读取我们的 ABI JSON 文件和合约地址文件。

  • 它将创建一个通过 WebSocket 提供程序连接到 Ganache 的 web3 实例。请注意,我们在这里使用 ws:// 而不是 https:// 协议,因为订阅事件需要 WebSocket。

  • 它将使用我们的 ABI 和地址创建一个合约实例。

  • 它将定义一个接受错误和结果参数的回调函数。如果没有发生错误,错误参数将为 null,结果将是一个事件对象。回调函数将在控制台上打印出错误消息或事件详细信息。

  • 它将使用不同的方法来收听我们的 Purchased 事件或获取过去的事件。每个方法都接受一个空的选项对象(因为我们不需要任何过滤器)和我们的回调函数作为参数。

要运行此代码,

  1. 在保存了所有文件(PurchaseOrder.sol、PurchaseOrderABI.json、PurchaseOrderBytecode.txt、PurchaseOrderAddress.txt、PurchaseOrder.js、PurchaseOrderEvents.js)的项目文件夹中打开一个终端窗口。

  2. 确保 Ganache 在启用 WebSocket 的另一个终端窗口或浏览器选项卡中运行(使用 -w 标志)。

  3. 在您的终端窗口中键入 node PurchaseOrderEvents.js 并按回车键。

如您所见,我们已经使用不同的方法成功地监听了 Purchased 事件。请注意,根据您的用例,每种方法都有自己的优点和缺点。例如,

  • events 方法比 getPastEvents 方法更高效和实时,因为它使用 WebSocket 而不是 HTTP 协议,并且不需要轮询新块。

  • 对于一次性事件,once 方法比events 方法更方便,因为它执行后会自动移除监听器,不需要手动取消订阅。

  • 对于多个事件,allEvents 方法比 events 方法更全面,因为它会侦听合约发出的所有可能事件,并且不需要指定每个事件名称。

  • 对于历史数据,getPastEvents 方法比 events 方法更可靠,因为它可以检索过去可能因 WebSocket 连接问题或节点同步问题而错过的事件。

结论

在本文中,我们学习了如何使用 JavaScript 和 Solidity 通过 web3 创建智能合约。我们还学习了如何使用 web3.js 库和 Ganache 模拟器部署、交互和侦听智能合约事件。我们已经看到了 web3 及其相关技术的一些优点和缺点,例如区块链、加密货币、DeFi 和 AI。

Web3 是互联网未来的一个令人兴奋和充满希望的愿景,用户可以在其中更好地控制他们的数据、身份和资产,并且可以在没有中介的情况下直接相互交互。然而,web3 也带来了一些需要谨慎应对的挑战和风险。Web3 仍处于开发和采用的早期阶段,因此还有很大的改进和创新空间。

猜你喜欢

转载自blog.csdn.net/qq_37116560/article/details/130248777