在比特币网络中的每一笔转账交易都可以被记录,而且无法篡改,这是比特币最核心也是最具价值的特性。
受比特币的启发,以太坊创新性的提出了智能合约,将单纯的转账交易泛化为可以支持复杂规则的合约,让每笔合约的执行都被记录在以太坊网络中,而且合约的执行结果无法被篡改,这就是以太坊相对于比特币的核心特点,也是质的飞跃。
智能合约的作用可以说是无穷无尽,其中之一就是发行虚拟货币。下面,我们将一步一步演示如何使用智能合约发布虚拟货币。希望读者可以和我一起思考,以太坊从哪里来,又到哪里去的这个深刻的问题。
在发行虚拟货币前,需要先给它取一个漂亮的名字。最近元宇宙(metaverse
)的概念比较火,拾人牙慧,不如就称它为metacoin
。
使用下面的命令初始化项目
$ mkdir metacoin
$ cd metacoin
$ truffle init
初始化完成以后,metacoin文件夹中将出现三个文件夹和一个配置文件:
\contracts
文件夹用来存放智能合约源代码,可以看到里面已经有一个sol
文件。
\migrations
文件夹用来存放部署智能合约的脚本,可以看到里面已经有一个js
文件。
\test
用来存放测试智能合约的代码,支持js
与sol
测试。
truffle-config.js
是Truffle
的配置文件,在这里可以配置智能合约需要部署的以太坊网络的位置。
构建完成后的文件夹结构如下图所示:
下面,我们创建一个合约和针对这个合约的测试
$ truffle create contract Metacoin
$ truffle create contract MetacoinTest
此时,再来查看目录结构,新增了两个文件,Metaconin.sol
表示的就是合约文件、metacoin_test.js
表示的就是合约的测试文件:
正如下面这段代码所示,使用truffle
创建的合约文件Metacoin.sol
中,除了必要的声明,并没有包含任何合约逻辑,真正的合约逻辑,还需要我们自己来编写。
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;
contract Metacoin {
constructor() public {
}
}
下面是一个可以运行的最简单的合约,这个合约包含一个构造函数和两个普通函数,构造函数用来给合约创建地址初始化10000
枚Metacoin
代币,sendConin
函数用来发送代币,getBalance
函数用来参数提供账户地址中代币余额。
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;
contract MetaCoin {
mapping (address => uint) balances;
event Transfer(address indexed _from, address indexed _to, uint256 _value);
constructor() public {
balances[tx.origin] = 10000;
}
function sendCoin(address receiver, uint amount) public returns(bool sufficient) {
if (balances[msg.sender] < amount) return false;
balances[msg.sender] -= amount;
balances[receiver] += amount;
emit Transfer(msg.sender, receiver, amount);
return true;
}
function getBalance(address addr) public view returns(uint) {
return balances[addr];
}
}
请先不要过分纠结这个合约的各个技术细节,后面我们会专门规划一篇文章对这个合约的技术细节进行解释。
在合约的根目录下,执行编译命令:
truffle compile -all
如果不加
-all
,默认编译的是文件夹中自从上次编译依赖修改过的合约文件,加上-all
表示的是编译所有的合约文件。
构建成功以后,可以在/build
目录下找到编译生成的文件,这就是最终编译形成的结果,以.json
文件表示的中间件,这个中间件就可以执行在以太坊EVM
虚拟机之上。
在migrations
文件夹下创建2_deploy_contracts.js
,文件中的内容如下所示,该文件的目的是在执行后续部署命令(truffle migrate
)时,部署MetaCoin.sol
中包含的智能合约。下面的内容也不需要纠结,大概意思是引入Metacoin
合约,然后部署到目标网络中去。
const Metacoin = artifacts.require("Metacoin");
module.exports = function(deployer) {
deployer.deploy(Metacoin);
};
然后,我们执行如下的命令进行合约部署:
$ truffle migrate
如果出了上述命令,不执行其他配置的话,肯定会报如下错误,下面的错误是说找不到网络去部署当前的合约。
这时,就需要使用我们以前安装的ganache-cli
对以太坊网络进行模拟,来进行网络部署。
根据前面的提示,使用一下的命令来启动本地测试网络:
$ ganache-cli.cmd -p 7545 --networkId 5777 --chainId 1337
待上述命令启动后,打开项目下的truffle-config.js
文件,寻找并配置下面这个字段。通过这种方式,可以告诉truffle
部署的目标网络在本地的哪个端口。
development: {
host: "127.0.0.1", // Localhost (default: none)
port: 7545, // Standard Ethereum port (default: none)
network_id: "5777", // Any network (default: none)
},
然后,在执行truffle migrate
即可成功部署合约。
截图内容显示成功部署一个合约,花费了0.00497708的ETH。
我们要与本地ganache
创建的智能合约网络中部署的智能合约进行交互,可以使用truffle
提供的命令行工具进行。
使用下面的命令登录truffle
控制台
$ truffle console
truffle(ganache)>
在控制台中执行如下脚本命令,可以实时看到执行的结果:
# 获得已经部署的MetaCoin的编译码
let instance = await Metacoin.deployed()
# 打印编译码对象
console.log(instance)
# 获得当前网络中的所有账户
let accounts = await web3.eth.getAccounts();
# 获得账户0的余额(10000)
let balance = await instance.getBalance(accounts[0]);
balance.toNumber();
# 获得账户1的余额(0)
let balance2 = await instance.getBalance(accounts[1]);
balance2.toNumber();
# 将余额由账户0转移至账户1
let result = await instance.sendCoin(accounts[1], 10, {from: accounts[0]})
console.log(result)
# 获得账户0的余额(9990,由于gas费用非常低,所以还是显示9990)
balance = await instance.getBalance(accounts[0]);
balance.toNumber();
# 获得账户1的余额(10)
balance2 = await instance.getBalance(accounts[1]);
balance2.toNumber();
到此,全部结束,账户0中的10000Metacoin
来自于最初合约创建的时候,然后账户0转账10Metacoin
给账户1,账户0剩余9990Metacoin
。
补充:
前面我们使用的以太坊模拟器ganache-cli
是命令行形式运行的,当然也可以用可视化版本的Ganache进行以太坊网络的模拟。