个性化阅读
专注于IT技术分析

限时钱包:以太坊智能合约简介

点击下载

本文概述

如今, 区块链及其应用前所未有地流行。特别是以太坊, 提供智能合约功能, 为新思想打开了大门, 这些新思想可以以分布式, 不变和不信任的方式实施。

由于学习曲线相当陡峭, 以太坊智能合约空间的入门可能会有些不知所措。我们希望本文(以及以太坊系列的后续文章)能够减轻这种痛苦并让你快速起步。

松露, 坚固性和ĐApps

在本文中, 我们假设你对区块链应用程序和以太坊有一些基本了解。如果你觉得需要复习知识, 我们建议你从Truffle框架中以太坊概述。

这篇文章涵盖了什么:

  • 定时钱包的应用
  • 开发环境设置
  • Truffle框架开发智能合约
    • 固守合同说明
    • 如何编译, 迁移和测试智能合约
  • 使用ÐApp与浏览器中的智能合约进行交互
    • 使用MetaMask设置浏览器
    • 主要用例的简要介绍

定时钱包:用例

以太坊智能合约有许多不同的应用。目前最受欢迎的是加密货币(实现为ERC20代币)和众筹代币销售(又称初始代币发行或ICO)。MotoroCoin是实用的ERC20代币的一个很好的例子。在此博客文章中, 我们将探索不同的东西:将资金锁定在加密钱包合约中的想法。这个想法本身有各种用例。

ICO的归属

有几个示例, 但是目前锁定资金的最常见原因可能是”归还”。想象一下, 你刚刚筹集了成功的ICO, 而你的公司仍然拥有在团队成员之间分配的大多数代币。

确保所有员工持有的代币不能被立即交易对所有相关方都是有益的。如果没有适当的控制措施, 则任何给定的员工都可以采取行动, 出售所有代币, 兑现并退出公司。这将对市场价格产生负面影响, 并使该项目的所有其他贡献者不满意。

基于加密的”遗嘱”

另一个想法是使用智能合约作为加密货币遗嘱。想象一下, 我们想将我们的加密货币储蓄存储在家庭成员可以访问的合同中, 但前提是发生了一些事。假设我们应该通过经常拨打一些合同电话来”签到”钱包。

如果我们不按时办理入住手续, 可能是我们发生了什么事, 他们可以提取资金。他们将各自获得的资金比例可以在合同中明确规定, 也可以由家庭成员之间的协商决定。

养恤金或信托基金

锁定资金的另一种应用可能是创建一个小的退休基金或基于时间的储蓄帐户, 即防止所有者在将来的某个时间之前提取任何资金的帐户。 (这对于上瘾的加密货币交易者有助于保持其以太币完好无用。)

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

想象一下, 我们想在18岁生日时向某人赠送一种以太。我们可以在纸上写下帐户的私钥和存放资金的钱包地址, 然后将其交给信封。他们唯一要做的就是在他们18岁时从他们的帐户中调用合同中的功能, 所有资金都将转移给他们。或者, 我们可以只使用一个简单的ÐApp。听起来不错?让我们开始吧!

以太坊开发设置

在继续进行智能合约开发之前, 你需要在计算机上安装Node.js和Git。在此博客中, 我们将使用Truffle框架。即使没有它, 你也可以做到, Truffle大大减少了开发, 测试和部署以太坊智能合约的入门门槛。我们完全同意他们的说法:

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

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

npm install -g truffle

现在, 获取该项目的代码:

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

重要的是要注意, 该项目遵循标准的Truffle项目结构, 感兴趣的目录为:

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

包含的智能合约概述

此项目中包含几个合同。概要:

  • TimeLockedWallet.sol是该项目的主要合同, 下面将对其进行详细描述。
  • TimeLockedWalletFactory.sol是工厂合同, 任何人都可以轻松部署自己的TimeLockedWallet。
  • ERC20.sol是用于以太坊令牌的ERC20标准的接口。
  • srcminiToken.sol是自定义的ERC20令牌。
  • SafeMath.sol是srcminiToken使用的小型库, 用于执行安全的算术运算。
  • Migrations.sol是内部Truffle合同, 可促进迁移。

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

TimeLockedWallet.sol

我们的TimeLockedWallet.sol Solidity合同如下所示:

pragma solidity ^0.4.18;

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

import "./ERC20.sol";

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

contract TimeLockedWallet {
    ...
}

以上是我们的主要目标。合同适用于我们合同的代码。下面描述的代码来自大括号内。

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

在这里, 我们定义了几个公共变量, 它们默认情况下会生成相应的getter方法。其中一些是uint类型(无符号整数), 另外两个是地址(16个字符长的以太坊地址)。

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

简而言之, 修饰符是前提条件, 甚至在开始执行附加到它的功能之前, 都必须满足该条件。

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

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

请注意, 如果你要更改合同名称, 那么它将变成任何人都可以调用的常规功能, 并且像Parity Multisig Wallet Bug一样在合同中形成后门。另外, 请注意, 大小写也很重要, 因此, 如果此函数名小写, 则它也将成为常规函数-再次, 这里你不需要的东西。

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

上面的函数是一种特殊类型, 称为后备函数。如果有人向该合同发送任何以太坊, 我们将很高兴收到。合同的ETH余额将增加, 并会触发”已接收”事件。要启用任何其他功能来接受传入的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语句不为真, 则合同退出并出现错误。那是我们检查解锁日期是否过去的地方。 msg.sender是此函数的调用者, 它已转移合同的全部以太余额。在最后一行, 我们还触发了Withdrew事件。事件将在稍后描述。

有趣的是, 现在-等同于block.timestamp-可能不像人们想象的那样准确。取决于采矿者, 因此最多可能需要15分钟(900秒)的时间, 如以下公式所述:

parent.timestamp> = block.timestamp <=现在+ 900秒

因此, 现在不应该用于测量较小的时间单位。

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);

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

这就是锁住以太坊和ERC20令牌所需的全部时间, 仅需几行代码。还不错吧?现在让我们看看我们的其他合同TimeLockedWalletFactory.sol。

TimeLockedWalletFactory.sol

创建更高级别的工厂合同的背后有两个主要原因。第一个是安全问题。通过将资金分配到不同的钱包中, 我们将不会只获得一份带有大量以太和代币的合约。这将仅对钱包所有者提供100%的控制权, 并希望阻止黑客尝试利用它。

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

pragma solidity ^0.4.18;

import "./TimeLockedWallet.sol";

contract TimeLockedWalletFactory {
    ...
}

以上内容很简单, 与以前的合同非常相似。

mapping(address => address[]) wallets;

在这里, 我们定义了一个映射类型, 类似于字典或地图, 但是所有可能的键都已预设并指向默认值。对于地址类型, 默认值为零地址0x00。我们还有一个数组类型address [], 它保存地址。

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

为了在这里总结我们的业务逻辑, 我们定义了一个称为电子钱包的映射, 该映射由用户地址(合同创建者和所有者都一样)组成, 每个用户地址都指向一组关联的电子钱包合同地址。

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

在这里, 我们使用上面的函数返回_user创建或有权使用的所有合约钱包。请注意, 视图(在较早的编译器版本中称为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);

srcminiToken.sol

如果我们不创建自己的以太坊令牌, 那么本教程将不会太有趣, 因此, 为完整起见, 我们将srcminiToken变为现实。 srcminiToken是实现下面所示接口的标准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 = "srcmini Token";
string public constant symbol = "TTT";
uint256 public constant decimals = 6;

totalSupply = 1000000 * (10 ** decimals);

我们给它起了个名字, 一个符号, 总供应量为100万个, 然后将其整除为六个小数。

要发现代币合约的不同变化, 请随时探索OpenZeppelin回购。

Truffle控制台:编译, 迁移和测试智能合约

为了快速入门, 请使用内置的区块链运行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, 如下所示:

RESTORE VAULT:使用助记符钱包种子将私钥和公钥导入MetaMask。

要编译合同, 请运行:

> compile

你应该看到:

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

现在, 我们需要定义要部署的合同。这是在migrations / 2_deploy_contracts.js中完成的:

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

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

我们首先导入两个合同工件TimeLockedWalletFactory和srcminiToken。然后, 我们只需部署它们。我们故意错过了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 srcminiToken...
     ... 0x0469ce110735f27bbb1a85c85a77ba4b0ba0d5aa52c3d67164045b849d8b2ed6
     srcminiToken: 0xf25186b5081ff5ce73482ad761db0eb0d25abfbf
   Saving successful migration to network...
     ... 0x059cf1bbc372b9348ce487de910358801bbbd1c89182853439bec0afaee6c7db
   Saving artifacts...

你可以说TimeLockedWalletFactory和srcminiToken已成功部署。

最后, 为确保一切正常, 让我们进行一些测试。这些测试位于测试目录中, 并且对应于主要合同TimeLockedWalletTest.js和TimeLockedWalletFactoryTest.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 srcminiToken after the unlock date (671ms)
  ✓ Allow getting info about the wallet (362ms)

6 passing (4s)

定时钱包ÐApp

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

分布式应用程序设置

为了运行此ÐApp, 你将需要具有启用了以太坊的浏览器。实现此目的的最简单方法是安装MetaMask Chrome插件。还有关于使用Truffle安装和配置MetaMask的直观指南。

智能合约场景

回到我们的场景, 为什么我们不首先介绍演员?假设爱丽丝将成为时间锁定钱包的创建者, 而鲍勃将成为资金的接收者/最终所有者。

在MetaMask上说明Alice和Bob。

方案大纲:

  • 爱丽丝为鲍勃创建了一个时间锁定钱包并发送了一些ETH
  • 爱丽丝还发送了一些ERC20 srcmini令牌
  • 鲍勃可以看到他可以访问的钱包和他创建的钱包
  • 鲍勃无法在钱包的时间锁到期之前提取任何资金
  • Bob解锁后撤回ETH
  • Bob撤回了所有ERC20 srcmini代币

首先, 爱丽丝为鲍勃创建了一个时间锁定的钱包, 并发送了一个初始的以太币。我们可以看到已经创建了一个新的合约钱包, 并由Bob拥有:

使用时间锁定钱包ĐApp为爱丽丝创建钱包。

合同创建后的任何时候都可以将钱包充值。充值可以来自任何人, 并且可以以太币或ERC20代币的形式。让我们爱丽丝向鲍勃的新钱包发送100个srcmini令牌, 如下所示:

爱丽丝使用时间锁定钱包ĐApp发送Bob 100 srcmini令牌。

从爱丽丝的角度来看, 充值后的钱包看起来像这样:

爱丽丝对充值钱包的看法。

现在, 我们切换角色并以Bob身份登录。鲍勃应该能够看到他创建的或接收者的所有钱包。由于爱丽丝订立的合同仍然是有时间限制的, 因此他不能提取任何资金:

鲍勃在充值的钱包仍处于锁定状态时的视图。

耐心地等待直到锁过期…

时间锁定钱包etsApp中已解锁的钱包。

…Bob现在可以提取以太币和srcmini代币了:

鲍勃从解锁的钱包中取出以太币。
鲍勃从解锁的钱包中取出srcmini代币。

清空了有时间限制的钱包后, 他的地址余额增加了, 这让他非常高兴和感谢Alice:

解锁的钱包,现在是空的。

以太坊网络

如果你想与所描述的合同进行交互, 则不必在本地运行它们:我们已经将它们部署到以太坊Rinkeby测试网。 srcminiToken部署在这里, TimeLockedWalletFactory部署在这里。

你可以使用已部署的ÐApp, 它链接到GitHub页面提供的上述合同。请注意, 你需要安装MetaMask并将其连接到Rinkeby。

故障排除

在开发此项目时, 我们遇到了两个问题。第一个是Chrome中MetaMask的脆弱性(例如抱怨无效的随机数)。我们发现最简单的修复方法是仅重新安装插件。

另外, 在编辑智能合约时, Truffle有时会不同步, 并抱怨无效数目的实体参数错误。我们发现, 简单的rm -r构建并再次执行编译/迁移将清除它。

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

我们希望本文引起了你的兴趣, 并希望你踏上以太坊之路的开发者之旅。网络荣耀之路将是艰巨且耗时的, 但是有很多资源可以帮助你(例如, 这为我们提供了相当大的帮助)。随时通过以下评论与我们联系。

该项目的源代码可在GitHub上找到。

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

我也欢迎你阅读我的后续教程, 该教程侧重于ĐApp创建。

学分

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

赞(0)
未经允许不得转载:srcmini » 限时钱包:以太坊智能合约简介

评论 抢沙发

评论前必须登录!