本文参考Microsoft使用 Solidity 编写 Ethereum 智能合同

用于处理智能合同的工具

许多工具都可帮助快速高效地开发智能合同。以下各节介绍了一些集成开发环境 (IDE)、扩展和框架。

IDE

  • Visual Studio Code:此代码编辑器已经过重新定义和优化,专门用于生成和调试现代Web和云应用程序。
  • Remix:一种基于浏览器的编译器和IDE,可以通过它来使用Solidity生成Ethereum合同,使用它来编写、测试和部署自己的合同,并调试交易。

扩展

  • Ethereum的区块链开发工具包:此扩展可以简化在Ethereum账本上创建、生成和部署智能合同的方式。

框架

  • Truffle 套件:在将Ethereum合同部署到公共账本并产生实际成本之前,使用Truffle工具套件对其进行测试。开发人员可在本地进行开发以便于其工作。
    • 该工具套件包括Truffle、Ganache和Drizzle。
  • OpenZeppelin:使用OpenZeppelin工具编写、部署和操作去中心化应用程序。
    • OpenZeppelin提供了两个产品:合同库和SDK。

安装扩展

在Visual Studio Code的左侧边栏中,选择“扩展”。搜索“区块链开发工具包”,然后选择它进行安装。

使用区块链开发工具包之前,请确保已安装:

  • Python:大多数计算机都预先安装了Python。
  • Node.js:若要确认已安装Node.js,请打开终端并键入node或npm(node包管理器)。如果安装了Node.js,终端将返回计算机上的Node.js的版本或npm命令列表。
  • Git:若要确认已安装Git,请打开终端并键入git。 如果安装了Git,终端将返回可用的Git命令的列表。
  • Truffle套件:该扩展提供了一个安装开发人员工具Truffle套件的链接(在扩展处于公共预览阶段时需要)。 (在扩展里面搜Truffle for Vs code)
  • Ganache CLI:该扩展提供了一个安装Ganache CLI的链接(在扩展处于公共预览阶段时需要)。
    • (实际上这个链接我并没有在扩展里面找到😓,于是另外在网上找了一个介绍Ganache的网站Nethereum Documentation)

如果尚未安装此软件,或者未具有其最低版本,扩展会提供关于如何安装这些工具的提示。

入门

  • 如果已安装所有依赖项,请使用区块链开发工具包创建第一个项目:
  • 在计算机上,为项目添加一个空目录。若要从Visual Studio Code中创建目录,请依次转到Terminal > New Terminal,然后键入mkdir newSolidityProject,记下这个新目录的位置,稍后需要用到此信息。
  • 在Visual Studio Code中,依次打开View > Command Palette。在搜索框中,键入“Blockchain: New Solidity Project”。键入时,将显示一个建议列表。
  • 对于Solidity项目类型,选择“创建基本项目”。
  • 使用"explorer"窗格查找在步骤 1 中创建的文件夹。选择该文件夹,在窗口的右下角,可以看到"creating a new project"。

创建Solidity项目后,打开"explorer"窗格以查看该项目的文件。

该项目包含 Solidity 代码样板,请注意以下目录:

  • 合同:包含 HelloBlockchain.sol 合同和 Migrations.sol 合同
  • 迁移:包含初始迁移和部署的合同
  • 测试:包含以 JavaScript 编写的对 HelloBlockchain合约的测试

你还会看到下面的配置文件:

  • package.json:定义项目详细信息和依赖项
  • truffle-config.json:定义Truffle的依赖项和配置
  • 若要保存工作区,请转到File > Save Workspace。将其命名为newSolidityProject。

编译合同

首先将从"contracts"文件夹内的HelloBlockchain.sol智能合同开始。

  • 在"explorer"窗格中的"contracts"文件夹中,右键单击合同名称HelloBlockchain。
  • 选择“生成合同”以编译智能合同。右下角的窗口会指示合同正在生成。
  • 选择View > Output以查看所编译的合同的相关信息。在此窗口中,可能需要选择“Azure 区块链”才能查看此扩展的输出。

部署合同

成功编译合同后,可以在本地部署它。 此步骤需要登录 Azure 帐户。

  • 在"explorer"窗格中,转到"contracts"文件夹,右键单击合同名称HelloBlockchain。
  • 选择“部署合同”。
  • 在显示的窗口中,选择“开发”。 如果你还未登录,系统将提示你登录。

在“输出”窗口(位于View > Output)中,将看到所部署的迁移和合同的相关信息。

可在此处查看所部署的合同的以下关键信息或元数据:

  • 合同的地址。
  • 合同创建交易所属的程序块的时间戳。
  • 部署合同的帐户地址。
  • 交易后帐户的余额(以以太币为单位)。余额为100个以太币(初始默认值)减去总成本。
  • 使用的gas量和gas价格。"gas"指在Ethereum区块链平台上执行交易或执行合同所需的费用。可以将它视为汽车所需的汽油。总成本=gas价格*gas使用量。
    • gas价格以gwei表示。1 gwei等价于0.000000001个以太币。

安装Truffle

Truffle是最常用的Ethereum开发框架。可以使用节点包管理器(NPM)安装它。

关于Truffle

Truffle具有以下好处:

  • 可生成、编译、部署和测试智能合同
  • 可管理网络,从而部署到公共和专用网络
  • 可管理项目依赖项的包
  • 具有交互式控制台,可直接进行合同通信和管理
  • 生成管道可配置,可自动运行检查和配置项目

安装Truffle

可以使用节点包管理器安装Truffle。在终端中键入以下内容:

npm install -g truffle

若要确认已安装 Truffle,请键入以下内容:

truffle

输出会显示已安装的版本,并显示可用于Truffle的命令列表:

Truffle入门

  • 在Visual Studio Code中,依次转到Terminal> New Terminal。
  • 在终端中运行 truffle compile。等待源文件编译成功。
  • 运行truffle migrate。等待迁移完成。

测试合同

下一步来运行在newSolidityProject中生成的测试。
可在test/HelloBlockchain.js中找到此测试。查看测试文件,尝试了解要测试的内容。
在终端中键入truffle test。
测试失败,因为Truffle无法连接到Ethereum客户端:

Could not connect to your Ethereum client with the following parameters:
    - host       > 127.0.0.1
    - port       > 8545
    - network_id > *
Please check that your Ethereum client:
    - is running
    - is accepting RPC connections (i.e., "--rpc" option is used in geth)
    - is accessible over the network
    - is properly configured in your Truffle configuration file (truffle-config.js)

此输出指示你需要一个处于运行状态的本地Ethereum区块链客户端,以便测试可以访问它。

Ganache CLI

Ganache是最常用的本地Ethereum区块链。我们将使用CLI版本,以便可以直接从终端与它交互。Ganache CLI常用于开发和测试。
若要在项目中安装Ganache CLI,请进入终端。右键单击,然后选择New Terminal。当“新终端”窗口打开时,运行以下内容:

npm install -g ganache-cli

安装 Ganache CLI 后,运行以下内容:

ganache-cli

请注意,区块链有10个已生成的帐户,每个帐户会收到100个测试以太币以便于使用。每个帐户还具有相应的私钥,同时也具有助记键。助记键是由12个单词构成的唯一短语,可通过它访问钱包,进而从帐户实现交易。
输出还会显示区块链的地址。我们将使用此地址连接到区块链。默认情况下,该地址为 127.0.0.1:8545。
现在重新运行 truffle test。
在终端中,可以看到所有测试都已通过。

使用Ethereum的区块链开发工具包编写智能合同

向前面创建的newSolidityProject添加新的智能合同。

创建装运合同

将创建的智能合同用于跟踪从在线市场购买的商品的状态。创建该合同时,装运状态设置为Pending。装运商品后,则将装运状态设置为Shipped并会发出事件。交货后则将商品的装运状态设置为 Delivered,并发出另一个事件。
在所创建项目的"contracts"目录中,右键单击该文件夹,然后选择新建名为Shipping.sol的文件。
复制以下合同内容,并将其粘贴到新文件中。

pragma solidity >=0.5.12<=0.8.0;

contract Shipping
{
    // Our predefined values for shipping listed as enums
    enum ShippingStatus { Pending, Shipped, Delivered }

    // Save enum ShippingStatus in variable status
    ShippingStatus private status;

    // Event to launch when package has arrived
    event LogNewAlert(string description);

    // This initializes our contract state (sets enum to Pending once the program starts)
    constructor() public {
        status = ShippingStatus.Pending;
    }
    // Function to change to Shipped
    function Shipped() public {
        status = ShippingStatus.Shipped;
        emit LogNewAlert("Your package has been shipped");
    }

    // Function to change to Delivered
    function Delivered() public {
        status = ShippingStatus.Delivered;
        emit LogNewAlert("Your package has arrived");
    }

    // Function to get the status of the shipping
    function getStatus(ShippingStatus _status) internal pure returns (string memory) {
     // Check the current status and return the correct name
     if (ShippingStatus.Pending == _status) return "Pending";
     if (ShippingStatus.Shipped == _status) return "Shipped";
     if (ShippingStatus.Delivered == _status) return "Delivered";
    }

   // Get status of your shipped item
    function Status() public view returns (string memory) {
         ShippingStatus _status = status;
         return getStatus(_status);
    }

}

查看该合同,以查看要发生的情况,确认你可以成功生成该合同。
在"Explorer"窗格中,右键单击该合同名称,然后选择“生成合同”以编译该智能合同。

添加迁移

现在我们将添加一个迁移文件,以便我们可以将该合同部署到Ethereum网络。
在"Explorer"窗格中,将鼠标悬停在"migration"文件夹上,然后选择“新建文件”。 将该文件命名为3_deploy_contracts.js。
将以下代码添加到该文件:

const Shipping = artifacts.require("Shipping");
module.exports = function (deployer) {
  deployer.deploy(Shipping);
};

部署

接下来要确保可以成功部署该合同,然后再继续。右键单击合同名称,然后选择“部署合同”。在显示的窗口中,选择“开发”,然后等待部署完成。
查看“输出”窗口中的信息。查找3_deploy_contracts.js部署。

测试智能合同

在此部分中,我们将为装运合同编写新的JavaScript测试。我们可以改用Solidity来编写测试,但JavaScript更为常用。我们将使用Truffle测试智能合同。

开始测试

首先我们创建一个新的测试文件。

  • 转到“终端”>“新终端”。
  • 在新终端中键入 truffle create test Shipping。 这将在测试文件夹中创建一个名为 Shipping.js 的新文件。
  • 粘贴下面的代码,以替换该文件中的代码:
const ShippingStatus= artifacts.require("Shipping");
contract('Shipping', () => {

  it("should return the status Pending", async ()=> {
    // Instance of our deployed contract
    const instance = await ShippingStatus.deployed();
    // Checking the initial status in our contract
    const status = await instance.Status();
    // Checking if the status is initially Pending as set in the constructor
    assert.equal(status, "Pending");
  });
it("should return the status Shipped", async ()=> {
// Instance of our deployed contract
    const instance = await ShippingStatus.deployed();

    // Calling the Shipped() function
    await instance.Shipped();

    // Checking the initial status in our contract
    const status = await instance.Status();

    // Checking if the status is Shipped
    assert.equal(status, "Shipped");
  });

    it("should return the status Delivered", async ()=> {

    // Instance of our deployed contract
    const instance = await ShippingStatus.deployed();

    // Calling the Shipped() function
    await instance.Delivered();

    // Checking the initial status in our contract
    const status = await instance.Status();

    // Checking if the status is Delivered
    assert.equal(status, "Delivered");
  });
});

事件测试

我们将使用truffle断言包来测试合同中发送的事件。通过使用此包,我们可以断言交易过程中发出了事件。

  • 在终端中,通过键入npm install truffle-assertions安装此库。
  • 请求ShippingStatus合同后,将以下代码添加到测试文件的第2行:
const truffleAssert = require('truffle-assertions');
  • 添加一个测试,以确认事件会返回所需的说明。 将此测试放在文件的最后一个测试之后。 在最后一行的一组右大括号的正前方创建新行,在其中添加此测试。
it('should return correct event description', async()=>{

    // Instance of our deployed contract
    const instance = await ShippingStatus.deployed();

    // Calling the Delivered() function
    const delivered = await instance.Delivered();

    // Check event description is correct
    truffleAssert.eventEmitted(delivered, 'LogNewAlert', (event) =>{
      return event.description == 'Your package has arrived';
    });
  });

使用async/await

.deployed() 数会返回一个承诺。因此我们在此函数的前面使用await,并在测试代码的前面使用async。此设置意味着,在部署合同后,直到承诺完成后才能继续进行测试。
此模式常用于测试,因为几乎所有智能合同交易都是异步交易。由于需要先对交易进行验证或挖掘才能将其添加到区块链账本中,因此它们是异步交易。
总之,应以对合同进行全面测试为目标,尤其是计划部署到主要的Ethereum网络(主网)时。

运行测试

在终端中键入以下内容:

truffle test

应该看到所有测试都成功通过:

Contract: HelloBlockchain
    ✓ testing ResponseMessage of HelloBlockchain
    ✓ testing Responder of HelloBlockchain
    ✓ testing RequestMessage of HelloBlockchain
    ✓ testing State of HelloBlockchain
    ✓ testing Requestor of HelloBlockchain
    ✓ testing SendRequest of HelloBlockchain (51ms)
    ✓ testing SendResponse of HelloBlockchain (46ms)

  Contract: Shipping
    ✓ should return the status Pending
    ✓ should return the status Shipped (59ms)
    ✓ should return the status Delivered (58ms)
    ✓ should return correct event description (39ms)