智能合约的详细教程

443 阅读7分钟

简介

现在我们已经揭开了智能合约的神秘面纱,更好地理解了它们是什么以及它们是如何工作的,让我们在以太坊区块链上建立我们自己的合约。因为智能合约的核心是计算机程序,所以我们用来构建基本合约的许多概念看起来很熟悉。在Ethereum生态系统中,开发者工具和基础设施的重大进步使我们能够完全在浏览器中有效地开发一个智能合约,所以让我们开始吧。

混搭Solidity

以太坊虚拟机是一个轻量级操作系统和一个状态机的组合,专门用于理解和执行以太坊智能合约。这个虚拟机,通常被称为 "EVM",与构成其网络的每个Ethereum节点一起运送并嵌入其中。以太坊智能合约程序是一系列机器级别的指令,可以被EVM理解。

Solidity是一种高级、正式的编程语言,用于编写以太坊智能合约。EVM不能有机地理解Solidity代码;相反,Solidity带有自己的编译器,将人类可读的Solidity源代码翻译成与EVM兼容的低级别字节码。Solidity是一种图灵完备的语言,在语法和语义上都受到C++、Python和JavaScript等的启发。它是面向对象的,暴露了一个富有表现力的静态类型系统,它坚持了基本的面向对象的概念,如封装、继承和多态性。Solidity支持继承的事实,催生了一个丰富的合约工具库生态系统,为扩展而设计的基础合约,以及其他模块化功能,以帮助合约创建。

Remix是一个基于网络的开发环境,用于创建智能合约。它提供了快速制作基于Solidity的智能合约原型的能力,而不需要设置任何本地工具,它减轻了使用真实ETH来部署和测试智能合约功能的需要。要开始使用,请按照以下步骤打开Remix并创建一个新的合约文件。

  1. 访问remix.ethereum.org。
  2. 选择Solidity环境。
  3. 创建一个名为CellSubscription.sol 的新文件。

一个教程一份手机合同

去中心化编程语言的语义和复杂性在实践中变得更加清晰,所以让我们用Solidity来建立一个基本的Ethereum智能合约。我们的合约将是非常初级的,它将编纂一个手机公司和用户之间的非常简单的手机合约的协议。

1.1.创建一个合约类

编纂我们理论上的手机合同的第一步是定义合同本身:

pragma solidity >=0.4.22 <0.7.0;

contract CellSubscription {

}

在Solidity中,contract 是一个一流的语言公民,它是一个类似于传统类的构造:它包含一个函数的集合和一个内部状态的概念。在上面的代码中,我们还指定后面的源代码与大于0.5.10 的 Solidity 版本兼容。

2.设置内部状态

现在我们已经定义了合约本身的结构,让我们开始添加一个constructor 函数。与传统程序类似,Solidity 契约构造函数只在契约部署后立即被调用,此后不能再被调用:

pragma solidity >=0.4.22 <0.7.0;

contract CellSubscription {
	uint256 monthlyCost;

    constructor(uint256 cost) public {
        monthlyCost = cost;
    }
}

很多新的语法刚刚被快速引入,所以让我们把上面的代码逐句分解。首先,我们在合同本身上声明一个monthlyCost 成员变量,类型为uint256 ,意思是这是一个无符号的整数,大小不超过256比特。我们将在以后确定手机用户是否已经全额支付了合同时使用这个月费变量。接下来,我们定义一个构造函数,并使用函数可见性修改器将其可见性设置为public 。在 Solidity 中,函数和成员变量可以承担四种不同的可见性。public,private,internal, 和external 。公有和私有成员的行为就像它们的名字一样;内部成员类似于TypeScript中的protected 成员,只能从当前的继承树中调用,而external 是它的反面。最后,我们用传入构造函数的每月cost 参数初始化monthlyCost 成员变量。

3.增加订阅者功能

到目前为止,我们已经定义了一个初始合约结构,并初始化了内部合约状态。让我们考虑一下我们试图建立的实际订阅逻辑。为了简单起见,这个 "手机订阅 "的功能将是直截了当的:订阅者以乙醚向合同支付月费,而手机公司可以检查账户是否全额支付。现在让我们把订户的逻辑pahs编成代码,即每月向合约付款的能力:

pragma solidity >=0.4.22 <0.7.0;

contract CellSubscription {
	uint256 monthlyCost;

    constructor(uint256 cost) public {
        monthlyCost = cost;
    }

    function makePayment() payable public {

    }
}

以太坊智能合约默认充当钱包,这意味着它们可以像普通钱包地址一样发送、接收和存储以太坊。这种内置的合约持有价值的概念使它们对像这样的基于货币的协议特别有用。在上面的代码中,我们定义了一个新的公共makePayment 函数,允许订阅者向他们的账户付款。payable 函数修改器告诉合约在调用makePayment 函数时自动在内部存储任何发送的ETH,我们稍后会用它来确定订阅者在给定日期是否支付了足够的乙醚。注意这个函数根本没有主体;当payable 修改器出现时,发送的以太坊的存储是自动的。如果需要,复杂的验证逻辑可以被添加到函数主体中,例如,拒绝部分付款。

4.增加公司功能

在这一点上,我们有一个可以部署的合同,有一个既定的月费,并接受用户的乙醚账单支付。接下来,我们需要添加功能,使手机公司能够在给定的日期检查账户的状态:

pragma solidity >=0.4.22 <0.7.0;

contract CellSubscription {
	uint256 monthlyCost;

    constructor(uint256 cost) public {
        monthlyCost = cost;
    }

    function makePayment() payable public {
    
    }

    function isBalanceCurrent(uint256 monthsElapsed) public view returns (bool) {
        return monthlyCost * monthsElapsed >= address(this).balance;
    }
}

我们定义了另一个名为isBalanceCurrent 的公共函数,该函数接受一个monthsElapsed 参数,该参数被打成一个无符号整数。手机公司可以调用这个简化的函数,并传递一个给定的经过的月数,以确定用户的账户在某个时间点的状态。view 函数修饰符用来表示这个函数不修改内部状态,是只读的。returns (bool) 子句做了它所暗示的事情,表示将返回一个真实的或虚假的值。在函数体本身中,我们做了简单的算术逻辑来确定应该支付的总金额,并使用address(this).balance ,将其与合同的内部乙醚余额进行比较。就像在其他语言中一样,this 指的是正在执行的当前合同实例。address(...) 语句是一个全局函数,它接受一个合同实例并返回一个地址实例,所以address(this) 是访问当前地址实例的速记。最后,Solidity中的所有合约地址实例也暴露了一个balance 实例变量,指的是当前内部存储的乙醚数量。这意味着在上面的函数体中,如果账户有足够的基于已过去的月数的乙醚,它将返回true ,否则将返回false

我们仍然缺少一个与公司有关的关键功能:从合同中提取资金的能力,以便将它们转移到公司自己的账户。值得庆幸的是,Solidity为从合同账户中提取价值暴露了更多的便利语义:

pragma solidity >=0.4.22 <0.7.0;

contract CellSubscription {
	uint256 monthlyCost;

    constructor(uint256 cost) public {
        monthlyCost = cost;
    }

    function makePayment() payable public {
    
    }

    function withdrawBalance() public {
        msg.sender.transfer(address(this).balance);
    }

    function isBalanceCurrent(uint256 monthsElapsed) public view returns (bool) {
        return monthlyCost * monthsElapsed == address(this).balance;
    }
}

我们添加了一个名为withdrawBalance 的最终公共函数,它允许一个账户被清空。该函数主体使用全局msg 对象,该对象指的是最后传入的交易有效载荷。在这个不安全的基本例子中,整个账户余额被转移到withdrawBalance 的调用者,使用msg.sender.transfer 函数向发送者的账户发送以太网。我们再次使用address(this).balance ,访问存储的内部乙醚,用这个作为我们希望提取的金额。

5.编译和运行

现在我们有一个可以部署到Ethereum区块链的智能合约,让我们使用Solidity的内置虚拟机来模拟在Ethereum主网上运行这个合约:

  1. 根据你的操作系统,使用<strong>⌘ </strong>+ SCtrl + S 保存你的合约。
  2. 切换到部署选项卡。
  3. 定义一个要传递给构造器的每月费用,然后点击部署。
  4. 配置一个模拟的ETH值为1000 wei,并调用makePayment 函数。这模拟了一个用户在给定月份的付款。

  5. monthsElapsed 最后,调用isBalanceCurrent 函数,参数为1,意味着我们要检查一个月后的余额是否为当前值。你应该看到0: bool: true ,表明账户余额实际上是当前的。

这就是了!虽然看起来不多,但你现在已经写了一个初级的以太坊智能合约,它可以用状态初始化,持有、接收和分配货币价值,并暴露了确定金融账户状况的能力。虽然这些概念都没有以生产使用所需的安全性或完整性来实现,但它们展示了赋予Ethereum智能合约力量的关键功能。

真实世界的考虑

上面的教程对于介绍Solidity概念和展示它们的作用是很有用的,但是开发和部署真实世界的智能合约,旨在用于高价值的商业交流,需要仔细的计划和考虑。

我们编纂的基本手机订阅合同本质上是不安全的,可以大大改进。例如,资金提取应该限制在一组预先批准的sender ,以防止任意的用户清空账户。就目前的情况来看,任何人都可以调用withdrawBalance ,并接收合同中存储的所有ETH。此外,makePayment 功能可以增强,以验证收到的付款,以防止多付或少付,如果需要,可以验证发件人是否来自已知账户。

虽然Remix的开发环境对智能合约的原型设计和测试Solidity的正确性很有帮助,但大多数真实世界的智能合约将在本地使用更复杂的构建工具(如Truffle)来开发。也许最重要的是,与部署在Ethereum主网络上的智能合约互动需要花费以太坊形式的真金白银。这是一种设计,构成了以太坊经济自给自足的基石:在实时以太坊网络上做任何事情都要花钱。值得庆幸的是,Remix通过在浏览器中启动基于JavaScript的EVM,模仿真实网络EVM的行为,绕过了这个最初的复杂问题。在实践中,智能合约将通过Truffle这样的框架,使用Node.js驱动的CLI工具进行部署,而部署合约和调用功能都需要一个以太坊账户,并由真实的以太坊提供资金。

总结

编写智能合约的实践需要Solidity语义的编程知识和围绕Solidity和Ethereum的一般工具的知识。近年来,概念的抽象化程度和整体的以太坊开发者体验都得到了改善,而且它还在继续快速地改善。以太坊已经建立了自己作为一个成熟的智能合约平台,拥有令人印象深刻的功能集和强大的社区支持。企业是否对这种自动合约有兴趣和真正的需求,这个问题还有待观察。