狼视 ·

Time-locked Wallets:一个以太坊智能合约的教程

前言

本次推荐的是一篇关于通过以太坊了解区块链的教程,能力有限,本身没接触过,各位尽量看原文吧。

原文Time-locked Wallets: An Introduction to Ethereum Smart Contracts

作者:RADEK OSTROWSKI

正文

区块链及其应用如今从未像现在这样流行。特别是以太坊,提供智能合约功能,为可以以分布式,不可变和无可信赖的方式实施的新想法敞开大门。

由于学习曲线非常陡峭,因此在Ethereum智能合约部分开始可能会有点压倒性。我们希望这篇文章(以及以太坊系列未来的文章)能够缓解这种痛苦,并让您快速启动并运行。

Truffle, Solidity, 和ĐApps

在本文中,我们假设您对区块链应用和以太坊有一些基本的了解。如果你觉得你需要补充你的知识,我们推荐从Truffle框架中这个以太坊概述

这篇文章涵盖了什么内容:

  • 时间锁定钱包(Time-locked Wallets)的应用
  • 开发环境设置
  • 使用松露框架进行智能合约开发
    • 固体合约的说明
    • 如何编译,迁移和测试智能合约
  • 使用ÐApp与浏览器的智能合约交互
    • 使用MetaMask进行浏览器设置
    • 主要用例的贯穿

时间锁定钱包:用例

以太坊智能合约有许多不同的应用。目前最受欢迎的是加密货币(实现为ERC20令牌)和众筹令牌销售(也称为初始硬币产品或ICO)。实用ERC20令牌的一个很好的例子是Motoro Coin。在这篇博客文章中,我们将探讨一些不同之处:将资金锁定在加密钱包合同中的想法。这个想法本身有各种用例。

归属于ICO

有几个例子,但目前锁定资金的最常见原因可能是“归属”。想象一下,您刚刚提出了一个成功的ICO,并且您的公司仍然拥有分配给您的团队成员的大部分代币。

确保员工持有的代币不能直接交易是有益的。如果没有控制措施,任何特定的员工都可以通过出售所有代币,兑现和退出公司来采取行动。这将对市场价格产生负面影响,并使该项目的所有剩余贡献者不满。

基于密码的“最后遗嘱和遗嘱”

另一个想法是使用智能合约作为加密意志。想象一下,我们希望将我们的加密货币储蓄存储在家庭成员可以访问的合同中,但只能在我们发生某些事情后才能使用。假设我们应该通过钱包进行“登记”,通过每隔一段时间唤起一些合同电话。

如果我们没有按时办理登机手续,我们可能会发生一些事情,他们可以撤回资金。他们各自收到的资金比例可以在合同中明确规定,也可以由家庭成员协商一致决定。

养老金或信托基金

锁定资金的另一个应用可能是创建一个小型养老基金或基于时间的储蓄账户,即防止业主在未来一段时间之前提取任何资金。(这对于上瘾的加密交易者可以特别有用,以帮助保持其乙醚的完整性。)

我们将在本篇博文的其余部分探讨的用例是类似的:将一些加密资金留给其他人使用,比如将来的生日礼物。

让我们想象一下,在他们18岁生日的时候,我们想给某人献上一个以色列人。我们可以在一张纸上写下账户的私钥和持有这笔资金的钱包的地址,并将其交给他们。他们唯一需要做的就是在18岁时从他们的账户上调用合同的功能,并将所有资金转移给他们。或者,我们可以使用简单的应用程序。听起来不错?让我们开始吧!

以太坊开发设置

在开展智能合约开发之前,您需要在您的计算机上安装Node.jsGit。在这个博客中,我们将使用松露框架。即使你没有它,松露也能显着减少进入以太坊智能合约开发,测试和部署的门槛。我们完全同意他们的发言:

Truffle是以太坊最受欢迎的开发框架,其使命是让您的生活变得更加轻松。

要安装它,请运行以下命令:

npm install -g truffle

现在,得到这个项目的代码:

git clone https://github.com/radek1st/time-locked-wallets
cd time-locked-wallets

重要的是要注意,该项目遵循标准松露项目结构,并且感兴趣的目录是:

  • contracts:持有所有Solidity合约
  • migrations:包含描述迁移步骤的脚本
  • src:包含ÐApp代码
  • test:存储所有合同测试

包含的智能合同概述

这个项目包含了几个合同。这里是简历:

  • TimeLockedWallet.sol 是这个项目的主要合同,并在下面详细描述。
  • TimeLockedWalletFactory.solfactory任何人都可以轻松部署自己的合同TimeLockedWallet
  • ERC20.sol 是以太坊令牌的ERC20标准接口。
  • ToptalToken.sol 是一个定制的ERC20令牌。
  • SafeMath.sol 是ToptalToken用于执行安全算术运算的小型库。
  • Migrations.sol 是一个促进迁移的内部松露合同。

有关编写以太坊合同的任何问题,请参阅官方的Solidity智能合同文档

TimeLockedWallet.sol

我们的TimeLockedWallet.solSolidity合约看起来像这样:

pragma solidity ^0.4.18;

上面的行表示此合同所需的Solidity编译器的最低版本。

import "./ERC20.sol";

在这里,我们导入其他合约定义,稍后在代码中使用。

contract TimeLockedWallet {
    ...
}

以上是我们的主要目的。contract范围是我们合同的代码。下面介绍的代码来自大括号内。

address public creator;
address public owner;
uint public unlockDate;
uint public createdAt;

这里我们定义了几个public默认生成相应getter方法的变量。它们中的一些是类型uint(无符号整数),一对是address(16个字符长的以太坊地址)。

modifier onlyOwner {
  require(msg.sender == owner);
  _;
}

简而言之,modifier在开始执行它所连接的函数之前,必须满足这个先决条件。

function TimeLockedWallet(
    address _creator, address _owner, uint _unlockDate
) public {
    creator = _creator;
    owner = _owner;
    unlockDate = _unlockDate;
    createdAt = now;
}

这是我们的第一个功能。由于名称与我们的合同名称完全相同,因此它是构造函数,创建合同时仅调用一次。

请注意,如果您要更改合同名称,这将成为任何人都可以调用的正常功能,并在合同中形成后门,就像Parity Multisig Wallet缺陷中的情况一样。另外,请注意,案例也很重要,所以如果这个函数的名字是小写的,它也会成为一个常规函数,而不是你想要的东西。

function() payable public { 
  Received(msg.sender, msg.value);
}

上述功能是一种特殊类型,称为fallback功能。如果有人向本合同发送ETH,我们会很乐意收到。合同的ETH余额将会增加,并且会触发Received事件。要使任何其他功能接受传入的ETH,您可以使用payable关键字标记它们。

function info() public view returns(address, address, uint, uint, uint) {
    return (creator, owner, unlockDate, createdAt, this.balance);
}

这是我们的第一个常规功能。它没有函数参数并定义要返回的输出元组。请注意,this.balance返回此合约的当前以太平衡。

function withdraw() onlyOwner public {
   require(now >= unlockDate);
   msg.sender.transfer(this.balance);
   Withdrew(msg.sender, this.balance);
}

只有在onlyOwner先前定义的修饰符满足的情况下才能执行上述功能。如果该require陈述不正确,则合同退出并出现错误。这就是我们检查是否unlockDate已经过去的地方。msg.sender是这个函数的调用者,它被转移到合约的整个乙醚余额。在最后一行,我们也发起了一个Withdrew事件。事件稍后介绍。

有趣的是,now这相当于 - block.timestamp可能并不像人们想象的那么准确。矿工应该选择它,因此可能会长达15分钟(900秒),正如下面的公式所解释的那样:

parent.timestamp >= block.timestamp <= now + 900 seconds

因此,now不应该用于测量小时间单位。

function withdrawTokens(address _tokenContract) onlyOwner public {
   require(now >= unlockDate);
   ERC20 token = ERC20(_tokenContract);
   uint tokenBalance = token.balanceOf(this);
   token.transfer(owner, tokenBalance);
   WithdrewTokens(_tokenContract, msg.sender, tokenBalance);
}

这是我们提取ERC20令牌的功能。由于合同本身并不知道分配给此地址的任何令牌,因此我们必须传递我们想要撤回的已部署ERC20令牌的地址。我们通过实例化它,ERC20(_tokenContract)然后查找并将整个令牌余额转移给收件人。我们还发起了一个WithdrewTokens活动。

event Received(address _from, uint _amount);
event Withdrew(address _to, uint _amount);
event WithdrewTokens(address _tokenContract, address _to, uint _amount);

在这个片段中,我们定义了几个事件。触发事件基本上是附加在区块链交易收据上的日志条目。每个事务可以附加零个或多个日志条目。事件的主要用途是调试和监视

这就是我们需要的时间锁定ether和ERC20令牌 - 只需几行代码。不错,是吧?现在让我们来看看我们的其他合同TimeLockedWalletFactory.sol

TimeLockedWalletFactory.sol

创建更高级别的工厂合同背后有两个主要原因。第一个是安全问题。通过分离不同钱包中的资金,我们不会只有一个拥有大量乙醚和代币的合同。这将给钱包所有者100%的控制权,并希望阻止黑客尝试利用它。

其次,工厂合同允许轻松,轻松地创建TimeLockedWallet合同,而无需提供任何开发设置。所有你需要做的就是从另一个钱包或ĐApp调用一个函数。

pragma solidity ^0.4.18;

import "./TimeLockedWallet.sol";

contract TimeLockedWalletFactory {
    ...
}

以上内容与前一份合同相似,非常相似。

mapping(address => address[]) wallets;

在这里,我们定义一个mapping类型,它类似于字典或地图,但预设了所有可能的键并指向默认值。在address类型的情况下,默认值是零地址0x00。我们也有一个数组类型address[],它持有addresses。

在Solidity语言中,数组总是包含一个类型,并且可以具有固定或可变的长度。在我们的例子中,数组是无界的。

为了总结我们的业务逻辑,我们定义了一个映射wallets,它由用户地址(契约创建者和所有者)组成,每个映射指向一组关联的钱包契约地址。

function getWallets(address _user) 
    public
    view
    returns(address[])
{
    return wallets[_user];
}

在这里,我们使用上述函数返回_user创建或拥有的所有合约钱包。请注意view(在较旧版本的编译器版本中constant)表示这是一个不会更改区块链状态的函数,因此可以在不花费任何气体的情况下免费调用它。

function newTimeLockedWallet(address _owner, uint _unlockDate)
    payable
    public
    returns(address wallet)
{
    wallet = new TimeLockedWallet(msg.sender, _owner, _unlockDate);
    wallets[msg.sender].push(wallet);
    if(msg.sender != _owner){
        wallets[_owner].push(wallet);
    }
    wallet.transfer(msg.value);
    Created(wallet, msg.sender, _owner, now, _unlockDate, msg.value);
}

这是合同中最重要的部分:工厂方法。它让我们通过调用它的构造函数,即时创建一个新的时间锁定钱包new TimeLockedWallet(msg.sender, _owner, _unlockDate)。然后,我们为创作者和收件人存储它的地址。之后,我们将在此函数执行过程中传递的所有可选的乙醚转移到新创建的钱包地址。最后,我们将该Create事件表示为:

event Created(address wallet, address from, address to, uint createdAt, uint unlockDate, uint amount);

ToptalToken.sol

如果我们不创建自己的以太坊令牌,那么本教程并不是那么有趣,所以为了完整起见,我们正在实现ToptalTokenToptalToken是一个标准的ERC20令牌,实现了下面介绍的接口:

contract ERC20 {
  uint256 public totalSupply;

  function balanceOf(address who) public view returns (uint256);
  function transfer(address to, uint256 value) public returns (bool);
  function allowance(address owner, address spender) public view returns (uint256);
  function transferFrom(address from, address to, uint256 value) public returns (bool);
  function approve(address spender, uint256 value) public returns (bool);

  event Approval(address indexed owner, address indexed spender, uint256 value);
  event Transfer(address indexed from, address indexed to, uint256 value);
}

下面定义了与其他令牌的区别:

string public constant name = "Toptal Token";
string public constant symbol = "TTT";
uint256 public constant decimals = 6;

totalSupply = 1000000 * (10 ** decimals);

我们给它一个名字,一个符号,一百万的总供给量,并且可以将它整除六位小数。

要发现令牌合约的不同变体,请随时探索OpenZeppelin回购

松露控制台:编译,迁移和测试智能合同

要快速开始,请使用内置区块链运行Truffle:

truffle develop

你应该看到这样的东西:

Truffle Develop started at http://localhost:9545/

Accounts:
(0) 0x627306090abab3a6e1400e9345bc60c78a8bef57
(1) 0xf17f52151ebef6c7334fad080c5704d77216b732
(2) 0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef
(3) 0x821aea9a577a9b44299b9c15c88cf3087f3b5544
(4) 0x0d1d4e623d10f9fba5db95830f7d3839406c6af2
(5) 0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e
(6) 0x2191ef87e392377ec08e7c08eb105ef5448eced5
(7) 0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5
(8) 0x6330a553fc93768f612722bb8c2ec78ac90b3bbc
(9) 0x5aeda56215b167893e80b4fe645ba6d5bab767de

Mnemonic: candy maple cake sugar pudding cream honey rich smooth crumble sweet treat

记忆种子可让您重新创建私钥和公钥。例如,将其导入到MetaMask中,如下所示:

Time-locked Wallets:一个以太坊智能合约的教程

要编译合同,请运行:

> compile

你应该看到:

Compiling ./contracts/ERC20.sol...
Compiling ./contracts/Migrations.sol...
Compiling ./contracts/SafeMath.sol...
Compiling ./contracts/TimeLockedWallet.sol...
Compiling ./contracts/TimeLockedWalletFactory.sol...
Compiling ./contracts/ToptalToken.sol...
Writing artifacts to ./build/contracts

现在,我们需要定义我们想要部署哪些合约。这是在migrations/2_deploy_contracts.js

var TimeLockedWalletFactory = artifacts.require("TimeLockedWalletFactory");
var ToptalToken = artifacts.require("ToptalToken");

module.exports = function(deployer) {
  deployer.deploy(TimeLockedWalletFactory);
  deployer.deploy(ToptalToken);
};

我们首先导入我们的两个合同工件TimeLockedWalletFactoryToptalToken。然后我们简单地部署它们。我们错过TimeLockedWallet了目的,因为这份合同是动态部署的。有关迁移的更多信息,请参阅Truffle迁移文档

要迁移合同,请运行:

> migrate

这应该导致类似于以下内容:

Running migration: 1_initial_migration.js
     Deploying Migrations...
     ... 0x1c55ae0eb870ac1baae86eeb15f3aba3f521df46d9816e04400e9b5951ecc099
     Migrations: 0x8cdaf0cd259887258bc13a92c0a6da92698644c0
   Saving successful migration to network...
     ... 0xd7bc86d31bee32fa3988f1c1eabce403a1b5d570340a3a9cdba53a472ee8c956
   Saving artifacts...
   Running migration: 2_deploy_contracts.js
     Deploying TimeLockedWalletFactory...
     ... 0xe9d9c37508bb58a1591d0f052d6870810118a0a19f728bf0cea4f4e5c17acd7a
     TimeLockedWalletFactory: 0x345ca3e014aaf5dca488057592ee47305d9b3e10
     Deploying ToptalToken...
     ... 0x0469ce110735f27bbb1a85c85a77ba4b0ba0d5aa52c3d67164045b849d8b2ed6
     ToptalToken: 0xf25186b5081ff5ce73482ad761db0eb0d25abfbf
   Saving successful migration to network...
     ... 0x059cf1bbc372b9348ce487de910358801bbbd1c89182853439bec0afaee6c7db
   Saving artifacts...

你可以知道TimeLockedWalletFactoryToptalToken成功部署。

最后,为确保一切正常,我们运行一些测试。测试位于test目录中,并与主要合同TimeLockedWalletTest.jsTimeLockedWalletFactoryTest.js。为简洁起见,我们不会深入写作测试的细节,并将其作为读者的练习。要执行测试,只需运行:

> test

...希望你会看到所有的测试通过像这样:

Contract: TimeLockedWalletFactory
  ✓ Factory created contract is working well (365ms)

Contract: TimeLockedWallet
  ✓ Owner can withdraw the funds after the unlock date (668ms)
  ✓ Nobody can withdraw the funds before the unlock date (765ms)
  ✓ Nobody other than the owner can withdraw funds after the unlock date (756ms)
  ✓ Owner can withdraw the ToptalToken after the unlock date (671ms)
  ✓ Allow getting info about the wallet (362ms)

6 passing (4s)

时间锁定的钱包ÐApp

现在是时候看到这一切都在行动。与任何区块链交互的最简单方法是使用带有Web UI的分布式应用程序,即所谓的应用程序(有时称为“dapps”)。

分布式应用设置

为了运行此应用程序,您需要安装以太坊启用的浏览器。实现这个最简单的方法是安装MetaMask Chrome插件。还有一个关于用松露安装和配置MetaMask视觉指南

智能合约情景

回到我们的场景,我们为什么不先介绍演员?让我们假设爱丽丝将成为时间锁定钱包的创造者,鲍勃将成为基金的接受者/最终拥有者。

Time-locked Wallets:一个以太坊智能合约的教程

情景大纲:

  • Alice为Bob创建一个时间锁定的钱包并发送一些ETH
  • Alice另外发送一些ERC20 Toptal Tokens
  • 鲍勃可以看到他接触到的钱包和他创建的钱包
  • 在钱包的时间锁定到期之前,Bob不能提取任何资金
  • 当Bob解锁后,Bob撤回ETH
  • Bob退出所有ERC20 Toptal Tokens

首先,Alice为Bob创建一个时间锁定的钱包并发送一个初始的一个ether。我们可以看到一个新的合同钱包已经创建并由Bob拥有:

Time-locked Wallets:一个以太坊智能合约的教程

在合同创建后的任何时候,钱包都可以加满。充值可以来自任何人,并且可以是以太或ERC20令牌的形式。让我们让Alice将100个Toptal Tokens发送给Bob的新钱包,如下所示:

Time-locked Wallets:一个以太坊智能合约的教程

从Alice的角度来看,钱包在充值后看起来会像这样:

Time-locked Wallets:一个以太坊智能合约的教程

我们现在切换角色并以Bob身份登录。鲍勃应该能够看到他所创建的或者是收件人的所有钱包。由于Alice创造的合同仍然是时间锁定的,他不能提取任何资金:

 

 

Time-locked Wallets:一个以太坊智能合约的教程

耐心等待,直到锁定过期...

Time-locked Wallets:一个以太坊智能合约的教程

…Bob 现在现在准备撤消ether 和Toptal Tokens:

Time-locked Wallets:一个以太坊智能合约的教程

Time-locked Wallets:一个以太坊智能合约的教程

在清空时间锁定钱包后,他的地址余额增加了,并使他对爱丽丝非常高兴和感激:

Time-locked Wallets:一个以太坊智能合约的教程

以太坊网络

如果您想与所描述的合约交互,则不必在本地运行它们:我们已将它们部署到Ethereum Rinkeby测试网。ToptalToken部署在此处,TimeLockedWalletFactory部署在此处

您可以使用我们部署的ÐApp,链接到由GitHub页面提供的上述合同。请注意,您需要安装并连接到Rinkeby的MetaMask。

故障排除

在开发这个项目时,我们遇到了几个问题。第一个是Chrome中MetaMask的薄弱(比如抱怨无效nonce)。我们发现的最简单的修复方法就是重新安装插件。

另外,在编辑智能合同和抱怨invalid number of solidity parameters错误时,松露有时会失去同步。我们发现一个简单的rm -r build并且再次进行编译/迁移可以清除它。

以太坊的发展:值得陡峭的学习曲线

我们希望这篇文章激起了你的兴趣,并且你将开始你的开发者之旅进入以太坊之地。通向网络荣耀的道路将是陡峭且耗时的,但有很多资源可以帮助你(比如这个帮助我们的公平比特)。欢迎通过下面的评论与我们联系。

该项目的源代码在GitHub上可用

如果您想知道如何使用uPort移动应用程序而不是MetaMask,请查看该项目的另一种黑客马拉松获胜版本的演示源代码

感谢

非常感谢Maciek Zielinski对本项目的贡献。

理解基础知识

什么是智能合约?

智能合约是在Ethereum虚拟机之上执行的计算机代码。智能合约可以发送和接受以太网和数据。合约本质上是不可改变的,除非另有规定。

关于作者

Radek Ostrowski, Australia

Radek是经过认证的Toptal区块链工程师,他对以太坊和智能合约特别感兴趣。在菲亚特的世界里,他在大数据/机器学习项目方面经验丰富。他在两个不同的国际IBM Apache Spark比赛中获胜,是PlayStation 4后端的联合创始人,一名成功的黑客马拉松竞争者,以及在澳大利亚,波兰和塞尔维亚举行的会议上的演讲者。

参与评论

  • jimi2018

    谢谢楼主分享!推荐一个区块链新手入门的以太坊DApp开发教程:

    [鉴于存在广告性质,暂时给予屏蔽]

    6月前 (04-12)
    回复
    回复jimi2018
  • 汐枫

    感觉您的到来,但鉴于您发布的链接存在广告性质,暂时给予屏蔽,如需恢复,请说明相关缘由。
    Email:windcoderz@foxmail.com,QQ:2641914215

    6月前 (04-12)
    回复
    回复汐枫