赞
踩
从之前的文章,我们一直都是使用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, }) }
对于详细的学习步骤,推荐大家学习这个教程。https://goethereumbook.org/zh/smart-contract-deploy/。
总结:本人在编写go调用合约的过程中,遇到的最多的问题就是关于拉取国外依赖,go的依赖包,以及调用合约的写入方法时会遇到比较多。当然了,go语言就是依赖包比较多的一种语言,要使用起来也不难。但是在用合约的开发调用过程中,我还是倾向于使用nodejs语言,web3js的使用让我们编写起来会更加顺畅一些,如果您是go开发出身,当然用go也是比较适合的。
关于以太坊的合约方面的入门学习,我们就讲到这里了,后面的课程中,我们再来讲讲其它的一些知识体系。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。