从之前的文章,我们一直都是使用Nodejs以及使用web3js或者ethers与合约进行交互,在这篇文章中,我们讲讲如何使用go语言来与合约进行交互。由于go没有web3js或者ethers这样的第三方工具库来调用,所以go与合约的交互使用起来就比较复杂一些,需要自己去创建示例对象来调用合约接口。我们接下来看看,如何使用go来调用合约的方法。 在开始之前,我们需要下载go的环境,这就需要大家自己去安装了,我们就跳过安装go环境的教学了。好了,我们开始正式的学习步骤。 我们需要安装下以太坊源码并使用make命令编译得到solc和abigen可执行命令工具。
go get -u github.com/ethereum/go-ethereum
make
编写一个示例合约,文件名称叫做Sar.sol。
pragma solidity ^0.4.26; //编译器版本要求
// 父合约
contract ERC20Interface {
// 代币名称
string public constant name = "SAR Token";
// 代币符号
string public constant symbol = "SAR";
// 精度。使用的小数点后几位。比如如果设置为3,就是支持0.001表示。默认为18,之所以需要有小数位字段是因为EVM不支持小数点运算,需要在做计算的时候先转成整型,最后根据小数位把运算结果转换会对应的小数位
uint8 public constant decimals = 18;
// 总发行量
function totalSupply() public constant returns (uint);
// 余额。根据账户地址查询该地址的余额。返回某个地址(账户)的账户余额
function balanceOf(address tokenOwner) public constant returns (uint balance); //返回某个地址(账户)的账户余额
// 自己转账给别人。表示合约的调用者往_to账户转token
function transfer(address to, uint tokens) public returns (bool success);
/*
approve、transferFrom及allowance解释:
账户A有1000个ETH,想允许B账户随意调用100个ETH。A账户按照以下形式调用approve函数approve(B,100)。
当B账户想用这100个ETH中的10个ETH给C账户时,则调用transferFrom(A, C, 10)。
这时调用allowance(A, B)可以查看B账户还能够调用A账户多少个token。
*/
// 批准。限定spender能从自己账户中转出多少token
function approve(address spender, uint tokens) public returns (bool success); //授权第三方(比如某个合约)从发送者账户转移代币
// 让spender代替自己给别人转账,前提是得到了approve批准,此函数与approve搭配使用,approve批准之后,调用transferFrom函数来转移token
function transferFrom(address from, address to, uint tokens) public returns (bool success); //执行具体的转移操作
// 限额。返回spender还有多少token可以花费。返回_spender还能提取token的个数
function allowance(address tokenOwner, address spender) public constant returns (uint remaining); //返回_spender仍然被允许从_owner提取的金额
// 从代币合约的调用者地址上转移_value的数量token到的地址_to,并且必须触发Transfer事件,代币被转移时触发
event Transfer(address indexed from, address indexed to, uint tokens);
// 当调用approval函数成功时,一定要触发Approval事件
// 允许_spender多次取回您的帐户,最高达_value金额。 如果再次调用此函数,它将以_value覆盖当前的余量,调用approve方法时触发
event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
// 事件,用来通知客户端代币被消费
event Burn(address indexed from, uint256 value);
address public owner;
}
// SafeMath 是一个安全数字运算的合约
contract SafeMath {
// @dev Multiplies two numbers, throws on overflow.
function mul(uint256 a, uint256 b) internal pure returns (uint256 c)
{
if (a == 0) {
return 0;
}
c = a * b;
assert(c / a == b);
return c;
}
// @dev Integer division of two numbers, truncating the quotient.
function div(uint256 a, uint256 b) internal pure returns (uint256)
{
// assert(b > 0); // Solidity automatically throws when dividing by 0
// uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return a / b;
}
// @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
function sub(uint256 a, uint256 b) internal pure returns (uint256)
{
assert(b <= a);
return a - b;
}
// @dev Adds two numbers, throws on overflow.
function add(uint256 a, uint256 b) internal pure returns (uint256 c)
{
c = a + b;
assert(c >= a);
return c;
}
}
// 子合约。继承合约接口
contract SAR_Token is ERC20Interface, SafeMath {
// 代币名称
string public name;
// 代币符号
string public symbol;
// 代币小数点位数,代币的最小单位, 如3表示我们可以拥有 0.001单位个代币
uint8 public decimals;
// 发行代币总量
uint256 public totalSupply;
// 用mapping保存每个地址对应的余额
mapping(address => uint256) public balanceOf;
// allowanceOf保存每个地址(第一个address) 授权给其他地址(第二个address)的额度(uint256)也就是存取被授权人的额度
mapping(address => mapping(address => uint256)) public allowanceOf;
// 构造函数
constructor() public {
owner = msg.sender;
name = "SAR Token";
symbol = "SAR";
decimals = 18;
// 20个0,metamask显示的是100.000。18个0,metamask显示的是1.000。
// totalSupply = 1000000000;
// totalSupply = 1000000000000000000000000000; // 10亿
// totalSupply = 100000000 * (10 ** unit256(decimals));
totalSupply = 10000000000000000000000000000;
// 100亿
balanceOf[msg.sender] = totalSupply;
}
/* modifier是修改标志 */
modifier onlyOwner {
require(msg.sender == owner);
_;
}
// 代币交易转移的内部实现
function _transfer(address _from, address _to, uint _value) internal {
// 确保目标地址不为0x0,因为0x0地址代表销毁
// require(_to != 0x0);
// 检查发送者余额
require(balanceOf[_from] >= _value);
// 溢出检查
require(balanceOf[_to] + _value > balanceOf[_to]);
// 以下用来检查交易
uint previousBalances = balanceOf[_from] + balanceOf[_to];
balanceOf[_from] -= _value;
balanceOf[_to] += _value;
emit Transfer(_from, _to, _value);
// 用assert来检查代码逻辑。
assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
}
// 合约调用者转账
function transfer(address _to, uint256 _value) public returns (bool success) {
_transfer(msg.sender, _to, _value);
return true;
}
// 批准spender从合约调用者那里花费多少value
function approve(address _spender, uint256 _value) public returns (bool success) {
allowanceOf[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
// spender代替合约调用者转账
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
require(allowanceOf[_from][msg.sender] >= _value);
allowanceOf[_from][msg.sender] -= _value;
_transfer(_from, _to, _value);
return true;
}
// spender余额
function allowance(address _owner, address _spender) view public returns (uint remaining){
return allowanceOf[_owner][_spender];
}
// 发行代币总量
function totalSupply() public constant returns (uint totalsupply){
return totalSupply;
}
// 查看对应账号的代币余额
function balanceOf(address tokenOwner) public constant returns (uint balance){
return balanceOf[tokenOwner];
}
// 销毁创建者账户中指定个代币
function burn(uint256 _value) public returns (bool success) {
// Check if the sender has enough
require(balanceOf[msg.sender] >= _value);
// Subtract from the sender
balanceOf[msg.sender] -= _value;
// Updates totalSupply
totalSupply -= _value;
// 监听Burn事件
emit Burn(msg.sender, _value);
return true;
}
// 销毁用户账户中指定个代币
function burnFrom(address _from, uint256 _value) public returns (bool success) {
// Check if the targeted balance is enough
require(balanceOf[_from] >= _value);
// Check allowance
require(_value <= allowanceOf[_from][msg.sender]);
// Subtract from the targeted balance
balanceOf[_from] -= _value;
// Subtract from the sender's allowance
allowanceOf[_from][msg.sender] -= _value;
// Update totalSupply
totalSupply -= _value;
// 监听Burn事件
emit Burn(_from, _value);
return true;
}
/// 向指定账户增发资金,此时totalSupply就会增加数量,不会自动在后面补0
// 只有owner能分发,如果transferOwnership的newOwner发生改变那只能用newOwner来调用
function mintToken(address target, uint256 mintedAmount) public {
balanceOf[target] += mintedAmount;
totalSupply += mintedAmount;
emit Transfer(address(0), address(this), mintedAmount);
emit Transfer(address(this), target, mintedAmount);
}
}
我们将合约进行编译获取到abi和bin文件。
solc --abi Sar.sol
solc --bin Sar.sol
现在让我们用abigen将abi和bin转换为我们可以导入的Go文件。 这个abi文件将包含我们可以用来与Go应用程序中的智能合约进行交互的所有可用方法,bin文件可以让我们部署合约。
abigen --abi token.abi --bin token.bin --pkg=token --out=token.go
--abi token.abi 指定abi文件来源
--pkg token 指定输出文件的包名
--out token.go 指定合约交互文件名称
有了合约的接口之后,我们现在就可以开始调用合约的方法了。
package api
import (
"ccmWeb/token"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/gin-gonic/gin"
"log"
"net/http"
)
func Name(c *gin.Context) {
conn, err := ethclient.Dial(Url)
if err != nil {
log.Fatal(err)
}
// contractAddress
tokenAddress := common.HexToAddress("0xe0f8e3108109ac2cb2ed812b4fd91a38b11f1446")
instance, err := token.NewToken(tokenAddress, conn)
if err != nil {
log.Fatal(err)
}
// call token name
name, err := instance.Name(&bind.CallOpts{})
if err != nil {
log.Fatal(err)
}
c.JSON(http.StatusOK, gin.H{
"name": name,
})
}
对于详细的学习步骤,推荐大家学习这个教程。goethereumbook.org/zh/smart-co…
总结:本人在编写go调用合约的过程中,遇到的最多的问题就是关于拉取国外依赖,go的依赖包,以及调用合约的写入方法时会遇到比较多。当然了,go语言就是依赖包比较多的一种语言,要使用起来也不难。但是在用合约的开发调用过程中,我还是倾向于使用nodejs语言,web3js的使用让我们编写起来会更加顺畅一些,如果您是go开发出身,当然用go也是比较适合的。
关于以太坊的合约方面的入门学习,我们就讲到这里了,后面的课程中,我们再来讲讲其它的一些知识体系。