智能合约代码的编译可以通过第三方平台或者软件。不过,为了安全起见,还是搭建自己的编译器比较好。(But be aware that if the compiler is compromised, your contract is not safe. )按照下面的步骤来安装编译器solc。
sudo add-apt-repository ppa:ethereum/ethereum
sudo apt update
sudo apt install solc
下面实现了一个非常简单的智能合约。使用solidity语言。
pragma solidity ^0.4.13;
contract Simple {
function arithmetics(uint _a, uint _b) returns (uint sum, uint product) {
sum = _a + _b;
product = _a * _b;
}
function multiply(uint _a, uint _b) returns (uint) {
return _a * _b;
}
}
solc -o . --bin --abi simple.sol
下图是编译结果,可以看到生成了Simple.abi接口文件和Simple.bin二进制编译文件:
用cat可以看到编译后的文件内容。
对于编译输出的abi文件和bin文件,需要做如下处理,才能够加载到geth里面并执行。
var simpleContract = eth.contract([原来abi内容])
var simpleContract = eth.contract([{"constant":false,"inputs":[{"name":"_a","type":"uint256"},{"name":"_b","type":"uint256"}],"name":"multiply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_a","type":"uint256"},{"name":"_b","type":"uint256"}],"name":"arithmetics","outputs":[{"name":"sum","type":"uint256"},{"name":"product","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"}])
personal.unlockAccount(eth.accounts[0])
// 不同的合约有自己的实例名字,要注意区分。这里是simple 。
var simple = simpleContract.new(
{ from: eth.accounts[0],
data: "0x(原来的bin内容)",
gas: 500000
})
注意这里的gas汽油费,如果只是发现一个很简单的智能合约,50万wei还可以。
编辑后的结果如下:
进入geth控制台,执行如下的命令。
# 进入geth控制台
geth attach /data/00/geth.ipc
# 加载脚本
> loadScript("contract/Simple.abi")
> loadScript("contract/Simple.bin")
加载bin之后,如果不进行挖矿,会一直处于pending状态,合约不能真正执行。
可以看到address是undefined。
挖矿成功后,address就会写上实际的合约地址。
使用solidity 语言,写一个实现标准ERC20接口的代币发行智能合约。
pragma solidity ^0.4.20;
contract ERC20{
string public name;
string public symbol;
uint8 public decimals;
uint public totalSupply;
mapping(address => uint256) internal balances;
mapping (address => mapping(address => uint256)) internal allowed;
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
constructor() public{
name = "MeiToken";
symbol = "MTK";
// 本货币使用最大小数位数(定义货币最小单位)
decimals = 2;
// 总数为10亿
totalSupply = 1000000000*10**uint(decimals);
balances[msg.sender] = totalSupply;
}
// 获取指定账户余额
function balanceOf(address tokenOwner) public constant returns (uint balance) {
return balances[tokenOwner];
}
// 转账到指定账户
function transfer(address _to, uint256 _value) public returns (bool success){
success = false;
// 地址不能为0
require(_to != address(0));
// 目标地址不能跟sender相同
require(msg.sender != _to);
require(balances[msg.sender] >= _value);
require(balances[_to] + _value > balances[_to]);
balances[msg.sender] -= _value;
balances[_to] += _value;
emit Transfer(msg.sender, _to, _value);
success = true;
}
// 从一个账户转账到另外一个账户
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success){
require(_to != address(0));
require(balances[_from] >= _value);
require(allowed[_from][msg.sender] >= _value);
require(balances[_to] + _value > balances[_to]);
balances[_from] -= _value;
balances[_to] += _value;
emit Transfer(_from, _to, _value);
success = true;
}
// 授权指定账户从本账户可以取现的额度
function approve(address _spender, uint256 _value) public returns (bool success){
allowed[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
success = true;
}
// 查询给某个账户授权取现额度
function allowance(address _owner, address _spender) view public returns (uint256 remaining){
return allowed[_owner][_spender];
}
}
按照第三节的方式完成代币智能合约的编译和结果编辑,并进行加载。
注意ERC20.bin里面需要的gas汽油费,由于智能合约有点复杂,50万wei不够。50万wei连待处理列表都进不去。
如下所示:
> txpool.status
{
pending: 0,
queued: 0
}
也没有错误提示。最后设置为200万wei才成功。
# 编译智能合约
solc -o . --bin --abi ERC20.sol
# 按照3.2的介绍,编辑智能合约编译结果,生成加载脚本。
# 进入geth控制台
geth attach /data/00/geth.ipc
# 加载代币智能合约相关文件
> loadScript("contract/ERC20.abi")
> loadScript("contract/ERC20.bin")
执行情况:
> erc20.balanceOf.call(eth.accounts[0])
100000000000
> erc20.balanceOf.call(eth.accounts[1])
0
> erc20.symbol.call()
"MTK"
> erc20.name.call()
"MeiToken"
>
> personal.unlockAccount(eth.accounts[0])
Unlock account 0x4d82606518349bcfc1afb4dcf54415f4bd2bed47
Passphrase:
true
> erc20.transfer.sendTransaction(eth.accounts[1],500000,{from:eth.accounts[0]})
"0xde4166620cd352ddcf612a649cb4ac67c530cb710fe064b0d63b34c8ba2bf3ac"
> txpool.status
{
pending: 1,
queued: 0
}
> txpool.status
{
pending: 0,
queued: 0
}
> erc20.balanceOf.call(eth.accounts[1])
500000
> erc20.balanceOf.call(eth.accounts[0])
99999500000
容易掉坑之1:call()与sendTransaction()没有分清。
执行的时候,对于不需要修改内容的调用,可以使用call()函数来调用合约的方法。–其实这种情况也可以直接跳过call()都可以。
对于需要修改内容的调用,需要使用sendTransaction来发起一笔交易,并支付交易燃料费。
我开始使用erc20.transfer.call(eth.accounts[1],500000)来发起转账,调用结果返回true,但是账户余额根本没有变。耗费了我几乎一天的时间。
call与sendTransaction的区别详情可以参考网页What is the difference between a transaction and a call?
容易掉坑之2:sendTransaction操作中没有指定支付gas燃料的账户
跟call不一样,仅仅有本身的调用参数不够,需要知道支付gas燃料的账户。
即最后追加一个参数{from:address}
容易掉坑之3:sendTransaction操作中忘记解锁账户。
如果不解锁支付燃料的账户,转账也是不能成功的。