详解如何在以太坊智能合约中集成可证明的Oracle-part1

458 阅读9分钟

从本质上讲,智能合约是自包含的代码脚本,这意味着它们本质上无法访问外部信息,例如Web API或文件系统。 这有一个很好的理由:以太坊的情况全都与确定性和可验证的数据有关。

当某人成功发布智能合约时,它将每台机器上执行维护区块链完整副本。重要的是,此执行在所有计算机上均应保持一致,以便每次都产生相同的结果。

否则每个以太坊节点将无法就当前状态达成共识。互联网是具有不确定性因素,因为它随着时间的推移而改变。从外部资源(如比特币价格标签)检索数据可以而且通常会返回不同的结果。这就给我们留下了一个问题,我们如何将非确定性数据聚合到诸如智能合约之类的确定性环境中?

“Provable”(以前称为“Oraclize”)是一种称为“oracle”的服务,专门为解决此问题而构建。oracle通过聚合来自外部源(如随机数生成器、价格标签和计算引擎)的数据来充当中继器。一旦收集到数据,oracle就会将其输入智能合约。

您可能想知道,第三方中继器是否会破坏数据分散化的目的? 好吧,你是绝对正确的。因此,Provable实施了真实性证明的概念。使用安全加密技术,我们可以验证通过的信息没有有被原始来源篡改。因此可以相信oracle将确定性结果传递给确定性环境。

让我们通过一个示例来研究一下。也许您是房东,并且希望以安全可靠的方式收取押金和租金。在本教程中,我们将为房东创建一种为租户草拟租约的方法。运作方式如下:

  1. 房东和房客本人就租赁条款达成一致(即每月$1000,共6个月,另加按金$500。每次付款应在每月的第一天支付)。
  2. 房东通过智能合约创建租约,并提供条款和租户的以太坊地址。所有金额均以美元表示。
  3. 租户将初始押金发送到智能合约,智能合约规定了首笔每月租赁付款的期限。
  4. 智能合约通过使用Provable(可证明) Oracle提供的转换数据将发送的以太币金额转换为美元,从而确保存款和每月付款额。
  5. 租约完成后,租户可以收回押金。
  6. 如果租户未能在期限前每月付款,则房东可以没收押金。
  7. 房东可以随时存放以太币并提取租金收入。

对房东来说,唯一的风险是以太坊的价值可能在租赁期内下降。在这种情况下,必须在智能合约中存入更多以太坊,以满足租户的押金退款条件。

注意:这是一个中级教程。建议您对Ethereum、javascript、Solidity和Truffle框架有基本的了解,但并非完全必要。此外,本指南相当冗长。与往常一样,将逐行解释代码以确保正确理解。

搭建环境

1.安装NodeJS,访问其官方网站或使用HomeBrew安装。

brew install node

2.确保是在全局模式下安装truffle。打开命令行或终端:

npm install -g truffle

3.为项目创建一个新目录并输入。

mkdir leaseGenerator
cd leaseGenerator

4.初始化系统truffle项目。

truffle init

5.创建package.json和package-lock.json

npm init -y
npm install

6.安装ganache-cli,这是一个模拟以太坊的本地测试区块链。

npm install -g ganache-cli

7.确保安装了文本编辑器(我更喜欢Visual Studio Code),然后在编辑器中打开项目。

// if using VS code:
code .

8.文件/文件夹结构将如下所示:

contracts/
migrations/
test/
package-lock.json
package.json
truffle-config.js

9.现在安装ethereum-bridge,该软件允许我们在本地开发区块链上使用Provable,而无需将智能合约部署到实际网络中。

npm install ethereum-bridge

10.在package.json内部,使用以下命令创建脚本条目:

"scripts": {
    "bridge": "./node_modules/.bin/ethereum-bridge -a 9 -H 127.0.0.1 -p 8546 --dev",
    "chain": "ganache-cli -p 8546 --defaultBalanceEther 10000 --db ./ganache/"
  },

bridge负责启动以太坊桥。-a标志指定在我们的本地区块链上使用哪个帐户来部署Provable连接器智能合约,从而允许我们隔离的本地项目与网络的其余部分进行通信。然后我们传入运行区块链的主机和端口。–dev模式只是加快了内部桥接任务的速度,从而加快了开发速度。

chain在指定的主机和端口上启动ganache-cli的本地实例。defaultBalanceEther为每个帐户配备10,000的初始余额,这最终将非常有用。–db设置存储位置以保留ganache的数据,从而使我们下次运行启动命令时可以重新实例化同一实例。

配置Truffle

在我们必须编辑truffle配置文件以满足我们的开发环境,由于我们仅在本地进行开发,因此配置非常简单。打开truffle-config.js,删除内容并编写以下内容:

module.exports = {
  networks: {
    development: {
     host: "127.0.0.1",     
     port: 8546,            
     network_id: "*",       
    }
  },
  compilers: {
    solc: {
      version: "0.5.17",    
    }
  }
}

在这里,我们指定Truffle将使用哪些网络来部署智能合约。development是一个特殊的关键字,告诉Truffle这是默认网络。填写适当的主机和端口。Provide*对于network\u id.solc是一个solidity的编译器, 确保solc的版本设置为0.5.17。在撰写本文时,最新版本的Solidity为0.6.4。由于Provable尚未提供0.6兼容性,因此我们使用的是0.5系列的最新版本。

依赖库

该项目依赖于两个库SafeMath和provableAPI。 在/ contracts中创建2个新的.sol文件,并复制每个库的源。

租赁产生者智能合约

现在我们已经配置好了环境信息,是时候开始编写智能合约了。导航到/ contracts并创建LeaseGenerator.sol

(cd contracts && touch LeaseGenerator.sol)

括号可以让我们快速回到工作目录,这是一个不错的小技巧。打开LeaseGenerator.sol

pragma solidity ^0.5.17;
import "./SafeMath.sol";
import "./provableAPI.sol";

contract LeaseGenerator is usingProvable {

    using SafeMath for uint;

    address payable landlordAddress;
    address payable tenantAddress;

    uint ETHUSD;
    uint tenantPayment;
    uint leaseBalanceWei;

    enum State {
        payingLeaseDeposit,
        payingLease,
        collectingLeaseDeposit,
        reclaimingLeaseDeposit,
        idle
    }

    State workingState;

    struct Lease {
        uint8 numberOfMonths;
        uint8 monthsPaid;
        uint16 monthlyAmountUsd;
        uint16 leaseDepositUsd;
        uint32 leasePaymentWindowSeconds;
        uint64 leasePaymentWindowEnd;
        uint64 depositPaymentWindowEnd;
        bool leaseDepositPaid;
        bool leaseFullyPaid;
    }

    mapping (bytes32 => bool) validIds;
    mapping (address => Lease) tenantLease;

    modifier onlyLandlord() {
        require(msg.sender == landlordAddress, "Must be the landlord to create a lease");
        _;
    }

    event leaseCreated(
        uint8 numberOfMonths,
        uint8 monthsPaid,
        uint16 monthlyAmountUsd,
        uint16 leaseDepositUsd,
        uint32 leasePaymentWindowSeconds,
        bool leaseDepositPaid,
        bool leaseFullyPaid,
    );

    event leaseDepositPaid(
        address tenantAddress,
        uint amountSentUsd
    );

    event leasePaymentPaid(
        address tenantAddress,
        uint amountSentUsd
    );

    event leaseDepositCollected(
        address tenantAddress,
        uint amountCollected
    );

    event leaseDepositReclaimed(
        address tenantAddress,
        uint amountReclaimed
    );

    event leaseFullyPaid(
        address tenantAddress,
        uint numberOfmonths,
        uint monthsPaid
    );

    event fundsWithdrawn(
        uint transferAmount,
        uint leaseBalanceWei
    );
}

第1行是每个新dApp项目的开始。全球的以太坊开发人员应该珍惜这一刻,以此作为新起点的象征。 不要轻视在文件顶部声明solidity版本的重要性!

第2至3行导入了我们的库。

第5行打开了一个新智能合约,该智能合约继承自usingProvable,其中包含进行数据查询的所有逻辑。

第7行声明我们对uint变量类型使用SafeMath。这使我们可以将.add或.sub等添加到任何uint类型,以进行简单,安全的算术运算。

第9行声明了应付地址的类型变量,其中包含房东的以太坊地址。应付账款是确保可以将资金发送到该地址。

第10行声明了应为该租户另一个address payable。每当给定的租户执行某项操作(例如支付租赁押金)时,都会更新此变量。

第12行存储了ETH到USD的汇率。 每当我们从oracle中检索新汇率时,将设置此变量。

第13行用于设置wei中的租户发送的押金或月租付款额。然后用于计算等价的美元金额,以更新智能合约的状态。

第16-22行表示一个枚举类型,它本质上是一个自定义类型,可以是大括号中列出的任何值。第24行将workState声明为具有上面我们声明的State类型的变量。对于执行的每个操作(例如支付定金),都会更新workingState以反映该状态。例如如果租户要支付租赁押金,我们会将workingState更新为payingLeaseDeposit。目的是为oracle提供相关信息。oracle收到一些请求的数据后,它会触发一个回调函数__callback(),该函数使我们可以自定义处理传入数据的方式。这将完全取决于workingState的值。如果我们要支付LeaseDeposit,则回调函数会指示您支付租赁押金。

第26–36行声明Lease对象及其所有属性。当房东决定创建新租约时,会创建一个新的Lease实例。特性:

  1. numberOfMonths:租约的期限,以月为单位
  2. monthsPaid:租户支付的总月数
  3. monthAmountUsd:每月要支付的总金额(美元)
  4. leaseDepositUsd:要支付的租赁押金的价值
  5. leasePaymentWindowSeconds:租户每月支付的时间(以秒为单位),在租户支付了租赁押金后生效
  6. leasePaymentWindowEnd:租户支付租约为Unix时间戳的截止日期
  7. depositPaymentWindowEnd:租户进行租赁押金为Unix时间戳的截止日期
  8. leaseDepositPaid:告知是否已支付租赁押金
  9. leaseFullyPaid:告知整个租赁是否已全额支付

第38行存储了ID的映射。每次创建新的oracle查询时,都会返回唯一的ID。该ID被添加到__callback()所使用的映射中,以确保每个查询仅被处理一次。

第39行将租户映射到租约。当房东创建租约时,给定的租户地址将参考新的租约实例存储在此处,以便在执行租约操作时可以将其检索到。

第41-44行声明一个修饰符。我们会把这个附加到我们只想被房东调用的函数中。如果一个随机的人可以把智能合约里的钱都掏空,那就没有意义了,对吧?

第46-85行声明了所有可以执行的动作的事件。相应的动作完成后,将触发每个事件。在dApp中,此类事件对于前端代码响应和更新UI很有用。