从本质上讲,智能合约是自包含的代码脚本,这意味着它们本质上无法访问外部信息,例如Web API或文件系统。 这有一个很好的理由:以太坊的情况全都与确定性和可验证的数据有关。
当某人成功发布智能合约时,它将每台机器上执行维护区块链完整副本。重要的是,此执行在所有计算机上均应保持一致,以便每次都产生相同的结果。
否则每个以太坊节点将无法就当前状态达成共识。互联网是具有不确定性因素,因为它随着时间的推移而改变。从外部资源(如比特币价格标签)检索数据可以而且通常会返回不同的结果。这就给我们留下了一个问题,我们如何将非确定性数据聚合到诸如智能合约之类的确定性环境中?
“Provable”(以前称为“Oraclize”)是一种称为“oracle”的服务,专门为解决此问题而构建。oracle通过聚合来自外部源(如随机数生成器、价格标签和计算引擎)的数据来充当中继器。一旦收集到数据,oracle就会将其输入智能合约。
您可能想知道,第三方中继器是否会破坏数据分散化的目的? 好吧,你是绝对正确的。因此,Provable实施了真实性证明的概念。使用安全加密技术,我们可以验证通过的信息没有有被原始来源篡改。因此可以相信oracle将确定性结果传递给确定性环境。
让我们通过一个示例来研究一下。也许您是房东,并且希望以安全可靠的方式收取押金和租金。在本教程中,我们将为房东创建一种为租户草拟租约的方法。运作方式如下:
- 房东和房客本人就租赁条款达成一致(即每月$1000,共6个月,另加按金$500。每次付款应在每月的第一天支付)。
- 房东通过智能合约创建租约,并提供条款和租户的以太坊地址。所有金额均以美元表示。
- 租户将初始押金发送到智能合约,智能合约规定了首笔每月租赁付款的期限。
- 智能合约通过使用Provable(可证明) Oracle提供的转换数据将发送的以太币金额转换为美元,从而确保存款和每月付款额。
- 租约完成后,租户可以收回押金。
- 如果租户未能在期限前每月付款,则房东可以没收押金。
- 房东可以随时存放以太币并提取租金收入。
对房东来说,唯一的风险是以太坊的价值可能在租赁期内下降。在这种情况下,必须在智能合约中存入更多以太坊,以满足租户的押金退款条件。
注意:这是一个中级教程。建议您对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实例。特性:
- numberOfMonths:租约的期限,以月为单位
- monthsPaid:租户支付的总月数
- monthAmountUsd:每月要支付的总金额(美元)
- leaseDepositUsd:要支付的租赁押金的价值
- leasePaymentWindowSeconds:租户每月支付的时间(以秒为单位),在租户支付了租赁押金后生效
- leasePaymentWindowEnd:租户支付租约为Unix时间戳的截止日期
- depositPaymentWindowEnd:租户进行租赁押金为Unix时间戳的截止日期
- leaseDepositPaid:告知是否已支付租赁押金
- leaseFullyPaid:告知整个租赁是否已全额支付
第38行存储了ID的映射。每次创建新的oracle查询时,都会返回唯一的ID。该ID被添加到__callback()所使用的映射中,以确保每个查询仅被处理一次。
第39行将租户映射到租约。当房东创建租约时,给定的租户地址将参考新的租约实例存储在此处,以便在执行租约操作时可以将其检索到。
第41-44行声明一个修饰符。我们会把这个附加到我们只想被房东调用的函数中。如果一个随机的人可以把智能合约里的钱都掏空,那就没有意义了,对吧?
第46-85行声明了所有可以执行的动作的事件。相应的动作完成后,将触发每个事件。在dApp中,此类事件对于前端代码响应和更新UI很有用。