使用OpenZeppelin在RSK上进行ERC20代币开发

在本文中,我们将讨论通过RSK网络部署和交互Smart-Contracts智能合约。我们的合约将是一个基于OpenZeppelin库的ERC20代币,我们将把它直接部署到Mainnet中。

创建合约

我们需要做的第一件事就是知道如何使用Truffle

当我们这样做

1
$ truffle init

在一个空文件夹中,除了创建配置文件外,我们还为项目和迁移合约创建了文件夹,以记录对同一合约的更改。

合约的.sol代码文件位于

1
~/Truffle/contracts

迁移脚本在

1
~/Truffle/migrations

已编译的合约在

1
~/Truffle/build

测试合约在

1
~/Truffle/test

我们现在只处理前两个文件夹。

在Truffle文件夹中,我们从OpenZeppelin导入库

1
$ npm install -E openzeppelin-solidity

这些库不仅会安装我们代币token的主要类库,还会安装所有权相关,安全数学运算和许多其他设施的库。值得一提的是,这些库已经过审核以实现高标准的安全性,因此依赖于它们的合约在正确使用时不易受到黑客攻击。

我们的库将安装在

1
~/Truffle/node_modules/openzeppelin-solidity/contracts

之后,我们可以将库ABCD.sol导入到我们的合约中,如下所示:

1
import 'zeppelin-solidity/contracts/token/ERC20/ABCD.sol';

要创建我们的ERC20代币,我们将从该存储库导入2个库:StandardToken.sol,它具有代币的主要功能,并且已经更多地导入了一堆库,例如SafeMath.solOwnable.sol,这些允许我们设置所有者对合约中的功能控制。

要继承库属性和函数,我们只需使用“is”关键字以这种方式将合约定义为StandardTokenOwnable

1
contract CoinFabrikToken is StandardToken, Ownable { }

之后,我们拥有这些库和导入库中的所有功能。

接下来,我们将代币的名称定义为CoinFabrik,这是它的符号,18个小数位,用于代币的精度(以太坊类网络中的标准,使我们有可能使用web3的以太转换功能)并将代币的初始供应量设置为1000,像这样:

1
2
3
4
string public name = 'CoinFabrik';
string public symbol = 'CF';
uint8 public decimals = 18;
uint public INITIAL_SUPPLY = 1000;

我们还将创建另一个字符串,一个与代币功能无关的非公共变量,以显示Ownable库属性的用法,该属性仅允许创建者与某些指定的函数进行交互。我们稍后会看到。

已经定义了我们的参数,现在是时候通过构造函数将它们分配给Token变量了。到目前为止,构造函数被定义为一个与智能合约同名的函数,但是从现在开始,将会有一个名为constructor()的函数,它将替换旧方法。如果你像以前一样调用构造函数,Solidity编译器将发出警告。

INITIAL_SUPPLY乘以小数精度的次方将分配给BasicToken合约的totalSupply_

1
totalSupply_ = INITIAL_SUPPLY * (10**uint(decimals));

并将它们存入创作者的帐户:

1
balancesb [msg.sender] = totalSupply_;

有了这个,我们就可以使用一个简单而标准的代币,但正如我们所说,我们将使用Ownable合约添加一些功能。首先,我们将定义一些函数:一个修改我们的非公共变量的状态,但只有你拥有权限,而另一个函数返回字符串的消息。定义如下:

1
2
3
4
5
6
7
8
function setON(string _n) public onlyOwner returns (bool) {
Owner = _n;
return true;
}

function getON() public view returns (string) {
return Owner;
}

两者都是公开的,所以任何人都可以尝试调用他们,但对于第一个,只有所有者的地址不会导致恢复。如果你是所有者并且调用了函数,则字符串将保存在我们的变量Owner(带有大写字母)中,并且它还将返回一个我们可以在交易中检查的true值。

由于Owner变量不是公共的并且没有Getter,我们需要一个函数来返回变量的值而不改变区块链的状态。这是第二个功能。

我们还将创建一个回调函数,如果有人错误地调用我们的合约,则会发出事件:

1
2
3
4
5
6
7
function () public payable {
if (msg.value > 0) {
emit Yes('Thanks for donating SBTC! :)');
} else {
emit No('Error 404: Function not found :P');
}
}

最后,我们在合约中添加了一个可销毁的功能,其中所有者是唯一可以执行它的人。

我们的简单代币已经完成。所有代码应该是一样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
pragma solidity ^0.4.17;

import 'zeppelin-solidity/contracts/token/ERC20/StandardToken.sol';
import "zeppelin-solidity/contracts/ownership/Ownable.sol";

contract CoinFabrikToken is StandardToken, Ownable {
string public name = 'CoinFabrik';
string public symbol = 'CF';
uint8 public decimals = 18;
uint public INITIAL_SUPPLY = 1000;

string Owner;

event Yes(string);
event No(string);

constructor() public {
totalSupply_ = INITIAL_SUPPLY * (10**uint(decimals));
balances[msg.sender] = totalSupply_;
}

function setON(string _n) public onlyOwner returns (bool) {
Owner = _n;
return true;
}

function getON() public view returns (string) {
return Owner;
}

function () public payable {
if (msg.value > 0) {
emit Yes('Thanks for donating SBTC! :)');
} else {
emit No('Error 404: Function not found :P');
}
}

function destroy() public onlyOwner {
selfdestruct(owner);
}

}

创建迁移

对于每个合约,我们需要告诉Truffle哪个合约是我们想要部署的合约以及我们可以在哪里找到合约。这是通过/Truffle/migrations文件夹中的迁移文件完成的。

迁移脚本02_deploy_token.js应如下所示

1
2
3
4
var CoinFabrikToken = artifacts.require("./CoinFabrikToken.sol");
module.exports = function(deployer) {
deployer.deploy(CoinFabrikToken);
};

我们已配置Truffle,我们的节点已同步,我们的合约已经编写并且我们的迁移已配置,完成部署就是个时间问题。

部署

如果我们之前停止了我们的节点,我们将恢复在线状态,然后我们将与Truffle连接:

1
2
$ sudo service rsk start
$ cd ~/Truffle/ && truffle console --network rsk

之后编译合约:

1
truffle(rsk)> compile --all

不应该对我们的合约有任何错误或警告。然后我们转移合约:

1
truffle(rsk)> migrate --reset

为了节约时间,我们可以在一行中执行两个命令

1
truffle(rsk)> migrate --all --reset

将首先部署迁移合约。Truffle为我们提供了每个操作的交易哈希,因此我们可以稍后检查详细信息或日志。这是我收到的完整输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
truffle(rsk)> migrate --all --reset
Compiling ./contracts/CoinFabrikToken.sol...
Compiling ./contracts/Migrations.sol...
Compiling zeppelin-solidity/contracts/math/SafeMath.sol...
Compiling zeppelin-solidity/contracts/ownership/Ownable.sol...
Compiling zeppelin-solidity/contracts/token/ERC20/BasicToken.sol...
Compiling zeppelin-solidity/contracts/token/ERC20/ERC20.sol...
Compiling zeppelin-solidity/contracts/token/ERC20/ERC20Basic.sol...
Compiling zeppelin-solidity/contracts/token/ERC20/StandardToken.sol...
Writing artifacts to ./build/contracts

Using network 'rsk'.

Running migration: 1_initial_migration.js
Deploying Migrations...
... 0xf00d4ecf2b5752022384f7609fe991aa72dda00a0167a974e8c69864844ae270
Migrations: 0x1dc2550023bc8858a7e5521292356a3d42cdcbe9
Saving successful migration to network...
... 0x3e759e8ff8a7b8e47a441481fa5573ccf502b83f3d591ad3047e622af0f9169e
Saving artifacts...
Running migration: 2_deploy_token.js
Deploying CoinFabrikToken...
... 0x300c8bb1e434e2aa4b13dcc76087d42fcbe0cb953989ca53a336c59298716433
CoinFabrikToken: 0xc341678c01bcffa4f7362b2fceb23fbfd33373ea
Saving successful migration to network...
... 0x71771f7ee5d4e251e386979122bdda8728fa519d95a054572751bb10d40eb8c5
Saving artifacts...

如果我们检查交易,我们可以计算所有部署过程的gas成本。在我这里的情况,它是2340788gas(277462+42008+1994310+27008)。

因此将其更改为真实的SBTC,我们得到2340788*183000000/10^18=0,000428364 SBTC。在撰写本文时,这大约是4美元左右。

我们的合约现在部署在0xc341678c01bcffa4f7362b2fceb23fbfd33373ea。

恭喜!

与合约互动

通过Truffle迁移给出的地址,以及合约的ABI,我们创建了一个实例,因此简化语法更容易处理函数。为此,在我们部署之后,我们写了

1
truffle(rsk)> var cfToken = web3.eth.contract(CoinFabrikToken.abi).at(CoinFabrikToken.address)

如果合约已经部署,并且知道它的地址和ABI,我们就可以做到

1
truffle(rsk)> var cfToken = web3.eth.contract(‘Contract_ABI’).at(‘Contract_ADDRESS’)

其中Contract_ABI是简化为一行ABI,Contract_ADDRESS不需要解释。

我之前创建了2个帐户,现在为方便起见,我们将它们重命名:

1
2
truffle(rsk)> var acc0 = web3.eth.accounts[0]
truffle(rsk)> var acc1 = web3.eth.accounts[1]

acc0是部署合约的人。Acc0被添加到truffle.jsnode.conf配置文件中。

所有权控制

我们将首先使用我们讨论过的库来测试合约的所有权功能。

如果我们从任何帐户调用getON函数,只要它是公开的并且没有任何所有权问题,我们就会得到:

1
2
truffle(rsk)> cfToken.getON()
''

现在,setON函数具有所有权属性。任何来自其他帐户的交易都将被驳回。例如,我们看到,试图用我的名字从acc1签订合约不会改变它的价值。

1
2
truffle(rsk)> cfToken.setON('Andres Bachfischer', {from: acc1})
0x5f115190b60238240bedf36d1c5bb69a443a0f8ee971b0fc40fe5ca9c727d47c

使用交易的哈希,我们看到返回的值为false,并且函数未正确执行。再次调用getON函数,我们看到变量没有改变它的值。

现在签署相同的交易但是从所有者的帐户acc0,我们得到状态’0x01’并且该功能正确执行。

1
2
truffle(rsk)> cfToken.setON('Andres Bachfischer', {from: acc0})
0x0c894fa7e5369573fb14addeaed4cd9d5b6cd1425cb4eeeae16cb4e1fa8e0364

再次调用函数getON,我们看到所有权库按照我们希望的那样工作。

1
truffle(rsk)> cfToken.getON()

Ownable.sol还具有允许我们将合约所有者更改为其他地址的功能。我们不会用它。然而,它的用法如下:

1
truffle(rsk)> cfToken.transferOwnership(acc1, {from: acc0})

有了这个,acc1将成为合约的新所有者。

让我们转到代币。

代币操作

我们要做的第一件事是检查在创建合约时是否正确分配了代币的余额。

我们检查每个帐户的余额如下:

1
2
web3.fromWei(cfToken.balanceOf(acc0).toString(10)) // = ‘1000’
web3.fromWei(cfToken.balanceOf(acc1).toString(10)) // = ‘0’

因此,我们可以看到所有代币都已正确分配到我们的初始帐户。

我们要做的第一笔交易是将一些代币转移到第二个帐户acc1,进行三次。

为第一笔交易这样做:

1
2
truffle(rsk)> cfToken.transfer(acc1, web3.toWei(88.8), {from: acc0})
0xd45437b777f1430e7cec57bd80b261ce8f87bf8a3f9a113fecd20563403c4d9c

1
2
truffle(rsk)> web3.fromWei(cfToken.balanceOf(acc0).toString(10)) // = '733.6'
truffle(rsk)> web3.fromWei(cfToken.balanceOf(acc1).toString(10)) // = '266.4'

我们看到从我们的部署帐户中获取的代币与在acc1中收到的代币数量相同。

使用StandardToken合约,我们还获得了代表某个帐户(在本例中为acc1)支出代币的权限。如果我们想在获得批准之前执行此操作,则交易将失败(状态为“0x00”)

1
2
truffle(rsk)> cfToken.transferFrom(acc1, acc0, web3.toWei(5), {from: acc0})
0x5cee7cf60849283a0088d71483a606ba2101b500e13f972abada4f75781596bf

检查后,acc0不允许从acc1发送:

1
truffle(rsk)> web3.fromWei(cfToken.allowance(acc1, acc0, {from: acc0}).toString(10)) // = '0'

我们授权acc0acc1的交易中以acc1的名义花费10个代币:

1
2
truffle(rsk)> cfToken.approve(acc0, web3.toWei(10), {from: acc1})
0x6e1a202f4ca7f43dfb28034952d54a572993b986a55857790aa51854afbc1fb4

在输出日志中,我们看到函数已成功完成,并且日志显示允许acc0用于支出的金额。检查allowance:

1
truffle(rsk)> web3.fromWei(cfToken.allowance(acc1, acc0, {from: acc0}).toString(10)) // = '10'

现在,如果我们再次执行支出交易:

1
2
truffle(rsk)> cfToken.transferFrom(acc1, acc0, web3.toWei(5), {from: acc0})
0x41f750eabb6e0d3ab576aac0333b0d337ca61808aae1eeafa9d8e2a0b81b979b

我们得到状态为“0x01”的成功交易。

再检查一下余额:

1
2
truffle(rsk)> web3.fromWei(cfToken.balanceOf(acc0).toString(10)) // = '738.6'
truffle(rsk)> web3.fromWei(cfToken.balanceOf(acc1).toString(10)) // = '261.4'

最后,如果我们签署一个调用不可用函数的事务,我们将调用我们的回退函数。 签署一个像这样的交易:

1
2
truffle(rsk)> web3.eth.sendTransaction({from: acc0, to: cfToken.address})
0x4106a287fc60669bf9682a73ec4c457b094c086ec7408a5dea95d200688c4ee9

将返回一个日志,其数据表示字符串Error 404:Function not found:P(十六进制:'0x00 ... 00204572726f72203430343a2046756e6374696f6e206e6f7420666f756e64203a50')。

我们的最后一个功能,即我们不会因为显而易见的原因而执行,就是销毁功能。我们需要合约不被销毁才能显示交易。要调用,所有者应该这样做:

1
truffle(rsk)> cfToken.destroy({from: acc0})

结论

在演练的第二部分中,我展示了在RSK网络中开发简单智能合约的示例。 我们已经看过:

  • 从OpenZeppelin套件导入库和合约,
  • 使用这些库创建一个简单的代币,
  • 配置Truffle的迁移过程,
  • 将我们的合约部署到RSK主网络,
  • 通过不同的账户与合约互动,
  • 检查块的日志以获取有关事务的反馈。

正如我们所看到的,RSK网络用于Solidity Smart Contracts部署和交互的用法几乎与以太坊节点中的相同。当然,这仍然是一个测试网络,预计会出现问题和错误,主要是在节点中,但RSK Labs团队在他们出现时尽可能快地解决它们。随着时间的推移,将实现稳健性。

======================================================================

分享一些以太坊、EOS、比特币等区块链相关的交互式在线编程实战教程:

  • java以太坊开发教程,主要是针对java和android程序员进行区块链以太坊开发的web3j详解。
  • python以太坊,主要是针对python工程师使用web3.py进行区块链以太坊开发的详解。
  • php以太坊,主要是介绍使用php进行智能合约开发交互,进行账号创建、交易、转账、代币开发以及过滤器和交易等内容。
  • 以太坊入门教程,主要介绍智能合约与dapp应用开发,适合入门。
  • 以太坊开发进阶教程,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。
  • C#以太坊,主要讲解如何使用C#开发基于.Net的以太坊应用,包括账户管理、状态与交易、智能合约开发与交互、过滤器和交易等。
  • EOS教程,本课程帮助你快速入门EOS区块链去中心化应用的开发,内容涵盖EOS工具链、账户与钱包、发行代币、智能合约开发与部署、使用代码与智能合约交互等核心知识点,最后综合运用各知识点完成一个便签DApp的开发。
  • java比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Java代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Java工程师不可多得的比特币开发学习课程。
  • php比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Php代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Php工程师不可多得的比特币开发学习课程。
  • tendermint区块链开发详解,本课程适合希望使用tendermint进行区块链开发的工程师,课程内容即包括tendermint应用开发模型中的核心概念,例如ABCI接口、默克尔树、多版本状态库等,也包括代币发行等丰富的实操代码,是go语言工程师快速入门区块链开发的最佳选择。

汇智网原创翻译,转载请标明出处。这里是原文使用OpenZeppelin在RSK上进行ERC20代币开发