Remix 是以太坊提供的一个开发Solidity智能合约的网络版开发软件。合约的开发者在Remix里提供的JavaScript虚拟机上开发,调试好合约后,可以发布到以太坊,或者任何支持Solidity智能合约的区块链上。
怎样使用智能合约
我们先来了解一下怎样使用智能合约。智能合约的使用有两步,第一步是部署,就是合约的发起人把智能合约发布到区块链上,并且生成一个新的合约地址。第二步,则是调用部署在这个地址上的合约里的函数。一个合约只需部署一次,生成一个合约地址。但是这个地址上合约中的函数可以多次被调用。
大家不难理解区块链是一个记录不同地址之间交易的账本,而智能合约,可以理解成是区块链上一个特殊的交易,起始地址是合约部署或者调用者的地址,目标地址是合约地址。部署合约的时候目标地址是空,但是交易被记录到区块上是会生成一个地址。交易金额可以是零,也可以不是,然后交易金额也可以触发合约函数的调用,我们这里简化一点,暂时不提。支持智能合约的区块链交易还有一个叫做数据的参数,是一堆16进制代码,这个代码,实际上就是合约的代码。部署的时候,整个合约都会被编译。调用的时候,只有被调用的函数被编译。编译的规则,在这里不详细解释,我们主要讲的,就是怎样利用Remix,来对合约和函数进行16进制的编译,然后使用编译代码,手动发合约到区块链上。
我们这里使用一个发ERC20代币的智能合约来讲解。
发ERC20代币的智能合约
pragma solidity ^0.4.16;
contract Token{
uint256 public totalSupply;
function balanceOf(address _owner) public constant returns (uint256 balance);
function transfer(address _to, uint256 _value) public returns (bool success);
function transferFrom(address _from, address _to, uint256 _value) public returns
(bool success);
function approve(address _spender, uint256 _value) public returns (bool success);
function allowance(address _owner, address _spender) public constant returns
(uint256 remaining);
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256
_value);
}
contract TokenDemo is Token {
string public name;
uint8 public decimals;
string public symbol;
function TokenDemo(uint256 _initialAmount, string _tokenName, uint8 _decimalUnits, string _tokenSymbol) public {
totalSupply = _initialAmount * 10 ** uint256(_decimalUnits);
balances[msg.sender] = totalSupply;
name = _tokenName;
decimals = _decimalUnits;
symbol = _tokenSymbol;
}
function transfer(address _to, uint256 _value) public returns (bool success) {
require(balances[msg.sender] >= _value && balances[_to] + _value > balances[_to]);
require(_to != 0x0);
balances[msg.sender] -= _value;
balances[_to] += _value;
Transfer(msg.sender, _to, _value);
return true;
}
function transferFrom(address _from, address _to, uint256 _value) public returns
(bool success) {
require(balances[_from] >= _value && allowed[_from][msg.sender] >= _value);
balances[_to] += _value;
balances[_from] -= _value;
allowed[_from][msg.sender] -= _value;
Transfer(_from, _to, _value);
return true;
}
function balanceOf(address _owner) public constant returns (uint256 balance) {
return balances[_owner];
}
function approve(address _spender, uint256 _value) public returns (bool success)
{
allowed[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
return true;
}
function allowance(address _owner, address _spender) public constant returns (uint256 remaining) {
return allowed[_owner][_spender];
}
mapping (address => uint256) balances;
mapping (address => mapping (address => uint256)) allowed;
}
如何使用Remix
大家登陆Remix 后,把中间的编辑框里的合约内容删除,然后把上面的这个合约复制到编辑框里。在右上角的菜单里Compile下面选中Auto Compile。
然后到Run菜单下面的Environment选项中选JavaScript VM。下面的Account是这个JavaScript虚拟机提供的,有好几个Account,而且都有测试用的金额。随便选中一个。再往下有个菜单里有Token和TokenDemo两个选项,Token是我们上面这个合约定义的接口,TokenDemo是实际类。我们选中TokenDemo。再下面Create前的这个空格里,我们要给出一些参数的值,就是你要发行的代币的总金额,名称及代码等,我们输入下面的参数,然后点Create。
100000000, "Test Token", 10, "TTN"
这时,在中间下面的灰色对话框中,你会看到这么一堆小字,
creation of TokenDemo pending...
还有Detail和Debug两个按钮。这说明合约的构造函数(在JavaScript虚拟机上)被调用成功了。点开Detail看详细内容。在点开的一张表里找到Input那一条,右边的那一堆16进制代码就是我们要用来部署的合约内容。拖到代码的最后面,有一个复制的图标,点一下把代码拷贝下来。
打开文本编辑器,把刚才拷贝的内容复制并且赋值。
var constructCode = "0x6060604052341561000f57600080fd5b604051610d52380380610d52833981016040528080519060200190919080518201919060200180519060200190919080518201919050508160ff16600a0a8402600081905550600054600460003373......"
为节省文章空间,代码我没有全部复制在这里,用省略号代替了。但是前后一定要记得打上双引号。
接下来我们调用一个合约函数。在Remix右边的下面可以找到可调用函数approve, transfer, transferFrom, 我们来调用一下transfer这个函数。需要提供两个参数,转入地址和转入金额。我们可以用任何一个地址,但是为了方便我们下面的测试,我们最好能用等下要测试的区块链上的真实地址。
我们可以在transfer右边的对话框里输入下面的参数,然后点击transfer。
"0x7d357a39d4884aaec00e7b30f169fe289fb99174", 100
这时,在中间下面的灰色对话框中,你会看到这么一堆小字,
transact to TokenDemo.transfer pending...
还有Detail和Debug两个按钮。这说明合约的transfer函数(在JavaScript虚拟机上)被调用成功了。点开Detail看详细内容。在点开的表里找到Input那一条,然后拷贝16进制代码。赋值给一个变量等下用。
var transferCode = "0xa9059cbb0000000000000000000000007d357a39d4884aaec00e7b30f169fe289fb991740000000000000000000000000000000000000000000000000000000000000064"
在区块链上部署和调用合约
接下来我们就可以到区块链的一个节点上来进行合约的部署和调用。我们可以使用以太坊,但是这里我想介绍一个新的区块链叫MOAC。MOAC和以太坊最大的不同是,以太坊的交易和合约都是在同一条链上执行的,而且合约只能同步调用,所以当同时有很多合约需要执行的时候,就会要等待很长的时间。整个区块链的处理速度就会变得很慢。MOAC在以太坊的基础上增加了一个分层结构,下层是主链,只做交易,上层跑合约,然后每一个合约相当于生成一个子链,子链定期把结果刷新到主链上。这样的话,就实现了合约的异步调用,再多的合约都可以同时进行,处理速度要比以太坊快好多个数量级。所以,将来所有基于智能合约调用区块链的去中心化应用,都会在MOAC这样的链上实现。
MOAC 测试网在2018年3月正式上线。我们用Ubuntu的版本来做演示,大家可以到这里下载。
文件下载解压后,生成一个叫moac的目录。进入这个目录以后,运行下面这条命令,就可以连上MOAC测试网络了。
./moac --testnet
这时候,会有一个测试网的文件夹生成$HOME/.moac/testnet/
这时候我们可以打开一个新的进程,然后进入moac的目录,运行下面这条命令,就可以进入一个可以发合约的console。
./moac attach $HOME/.moac/testnet/moac.ipc
当然,我们在部署合约之前还要做一件事情,那就是拿到合约的abi编译代码。这个可以用编译器solc来做。这个不是在console里面进行,如果你已经执行moac attach ...
命令进入console了,请用exit
退出。
安装Solidity编译器
首先,看是否安装solidity编译器。
solc --version
如果没有的话,可以这样安装:
sudo add-apt-repository ppa:ethereum/ethereum
sudo apt-get update
sudo apt-get install solc
下一步,将我们的ERC20合约拷贝到该节点的moac目录下,可取名为Token.sol
。然后我们将合约编译,并把编译结果写到一个叫token.js
的文件里去,便于我们在console里调用。指令如下:
echo "var testOutput=`solc --optimize --combined-json abi,bin,interface Token.sol`" > token.js
部署合约
再次进入console。
./moac attach $HOME/.moac/testnet/moac.ipc
如果你是第一次连接moac节点,你还要生成一个账号地址。
personal.newAccount("自选密码")
然后我们用这个节点的第一个账号mc.account[0]
来部署和执行合约。如果你是第一次连接moac节点,那这个账号就是你刚刚生成的那个新账号。你可以再执行一次下面的命令,形成一个新账号。来接收合约发出来的ERC20代币。
personal.newAccount("自选密码")
这个账号就是mc.account[1]
。
下面我们来部署合约。首先我们把合约编译生成的JavaScript调到console里面来。
loadScript("token.js")
然后生成一个合约对象
var mintContract = mc.contract(JSON.parse(testOutput.contracts["Token.sol:Token"].abi));
然后我们就可以部署合约了。部署合约必须有一个发起地址,我们用mc.account[0]
,但是地址是锁定的,需要解锁才能用。我们通过下面的指令进行解锁:
personal.unlockAccount(mc.accounts[0])
提供正确的密码后,账号就被解锁。
再接下来,我们把下面这段代码从文本编辑器里拷贝到console里面来执行一下。
var constructCode = "0x6060604052341561000f57600080fd5b604051610d52380380610d52833981016040528080519060200190919080518201919060200180519060200190919080518201919050508160ff16600a0a8402600081905550600054600460003373......"
再执行下面的命令,合约的部署就被提交了。
mintContract.new({ from: mc.accounts[0], data: constructCode, gas: 4700000},
function (e, contract) {
console.log(e, contract);
if (typeof contract.address !== 'undefined') {
console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash);
}
}
)
可以看到上面的命令里有三个重要参数。from是合约发起地址,data是合约的16进制代码,gas是个估算值。如果想知道一个更确定的数字可以提前用下面这个指令得出。
mc.estimateGas({data: constructCode});
通过上面的指令将合约交易发到区块链上后,等到最新的块挖出来,你会得到下面这条信息,合约就算部署成功了。
Contract mined! address: 0x4daf2e6a51a93150503e06c6d40488ce7e8dedd6 transactionHash: 0x36f74b93c1e7b7ed8cf8a11ae3ffb00a2dbfdf4db2436f358876421deea7cb3c
记住这个合约地址0x4daf2e6a51a93150503e06c6d40488ce7e8dedd6
一会儿用得上。
调用合约
还记得我们在Solidity里面调用了一个transfer的函数吗,当时我们随便用了一个地址,现在为了验证,我们需要用moac测试链上的一个真正的地址。我们用的是mc.accounts[1]
,所以我们要把这个地址拷贝下来,到Remix上去把下面这个过程重新做一遍。这样,最后代币才会发到测试地址上来。
在transfer右边的对话框里输入下面的参数,然后点击transfer。
"0x1bace09665cbb505f90aae7f05b556d998960df6", 100
在中间下面的灰色对话框中,点开Detail看详细内容。找到Input,拷贝16进制代码。赋值给一个变量等下用。
var transferCode = "0xa9059cbb0000000000000000000000001bace09665cbb505f90aae7f05b556d998960df60000000000000000000000000000000000000000000000000000000000000064"
将这个变量赋值输入moac console中。就可以进行合约的调用了。调用合约相对简单很多,可以发一个简单的交易,moac有个自带的sendtx(fromAddr, toAddr, amount, data)
可以用来做这个事情非常方便。在这里
- fromAddr是发起地址
- toAddr在这里用合约地址,就是
0x4daf2e6a51a93150503e06c6d40488ce7e8dedd6
- amount可以是0。因为我们只发合约代币,不需要转账。
- data是合约内容,就是transferCode。
当然要使用sendtx()
首先要将这个函数调进console。
loadScript("mctest.js")
这个JavaScript是测试网自带的,所以都会存在moac目录下。
然后就做这一步进行合约调用
#如果跟合约的部署超过5分钟,还要重新unlockAccount
personal.unlockAccount(mc.accounts[0])
sendtx(mc.accounts[0], "0x4daf2e6a51a93150503e06c6d40488ce7e8dedd6", 0, transferCode);
合约调用交易发布以后,等当前区块挖出来后,合约就被执行,代币就会发到测试地址上。
验证合约调用结果
如果想查询合约调用的结果,可以使用moac explorer或者moan wallet,当然也可以用一个很快捷的方式,就是用如下链接
https://testserver.mytokenpocket.vip/v1/token/balance1?blockchain_id=3&address=0x1bace09665cbb505f90aae7f05b556d998960df6&contract_address=0x4daf2e6a51a93150503e06c6d40488ce7e8dedd6
把address改成mc.accounts[0]的地址,contract_address改成合约地址,然后就可以看到
{"data":"100","message":"success","result":0}
这就说明,我们的100代币发成功了。