上文 Web3代币基本token概念 我们简单讲述了代币的概念
也讲到了ERC-20这个协议的概念
ERC-20 官方文档地址如下 https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md
说实话 给人的感觉不是很正规 连地址都是放在github上的 不过也没办法 官方都这么弄 我们也只能这么看了
下面会告诉我们 需要一个name方法
用view标注 表示他要读取状态变量 returns 返回的 是个字符串类型的值
这个name 就是我们自己代币的名字
好 那就别光说不练啦 先将ganache环境启起来
然后打开一个 Truffle 项目环境
在 contracts 目录下创建一个文件 叫 grToken.sol
参考代码如下
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
contract grToken{
//因为绑定了public 会自动生成get方法
string public name = "gerToken";
}
这样 就生成了一个叫 gerToken 的代币
甚至 就这样 就已经可以测试了
我们 在 migrations 目录下创建一个文件
还记得吧 migrations 下的文件 必须用数字开头 才能读到这个脚本
我们创建一个文件 叫 2_contract.js
参考代码如下
const Contacts = artifacts.require("grToken.sol")
module.exports = function(deployer) {
deployer.deploy(Contacts)
}
我们导入了contracts下的grToken
然后 我们终端运行 这个项目
然后我们执行
truffle migrate
将合约部署上去
这里有人会担心 truffle migrate之后 会不会将我们之前的文件都部署上去 其实是不用担心的
因为 truffle migrate 还是有点智能的 但不多
你已经部署上去的文件 他就不会再部署一次了
但是 如果你讲内容改了 再去truffle migrate 它一样不会重新部署
所以 如果你希望改的内容重新部署上去 你需要
truffle migrate --reser
这样 你改完的文件 才会重新部署到我们的区块链上
然后 我们终端运行
truffle console
运行到truffle控制台
然后 我们执行
const token = await grToken.deployed()
这样 常量token就拿到了我们grToken合约对象
然后 我们尝试调用它的name
token.name
这就算我们实现的第一个这个文档的name功能了
然后 文档中 name后面有个 symbol
这个你可以理解为 我们之前是 ETH 你也可以给自己的token起个标识
我们直接加一行就好了
和name都是一样的
string public symbol = "GRY";
然后 下面还有一个 decimals
之前我们讲过 这里 我们为了方便处理 有很多的单位 其中最基本的是 wei
就是你代币的换算单位
我们这里 可以这样写
uint public decimals = 18;
下面还有 totalSupply 啊 这完了哈
共用的总量
我们可以这样写
uint public totalSupply;
constructor(){
totalSupply = 1000000 *(19 * decimals);
}
这里 反正我们有多少 自己说了算 直接一百万走起啊 总量 然后 的话 因为是 wei单位的 后面我们就处理一下计算 把他真的变成和 ETH一样的单位
总量 一百万 十乘以十的十八次方
然后 我们重新运行终端 输入
truffle migrate --reser
因为之前部署过 grToken.sol 区块链上已经记录了 所以为了修改的内容能够上去 我们需要用–reser
然后 我们执行
truffle console
打开 truffle 控制台
然后 还是
const token = await grToken.deployed()
然后 我们可以 通过
token.name()
token.symbol()
然后
token.decimals()
然后就是
token.totalSupply()
这样 其实就差不多了 但是 其实后面还有一个 balanceOf
这个东西搞起来就真比较麻烦了
就是 我们每个账户 下面对应的余额是多少
方法传入的是账号的地址
下面就是返回来的余额值
其实 我们可以模拟一种清空
Object public userList = {
"admin": 5000,
"gangdan": 1000,
"zhanglei": 2000
}
我们这里设置一个对象 这样 当他 token.userList(“用户地址”)
就会返回余额
当然这种写法在solidity中是不能直接用的
但大体可以用这个思路
我们可以这样写 先声明一个mapping类型的变量
mapping(address => uint256) public balanceOf;
这个比较像 java的 哈希map集合
键值对应 这里 我们声明名称键 对应值 值是uint256类型
然后 名称叫 balanceOf
然后在constructor 对象初始化时 赋值
balanceOf[msg.sender] = totalSupply;
这里 这个msg.sender
之前 我们web3操作智能合约也出现过 那时可以拿到用户列表第一个
这个也差不多 我们说过 部署智能合约 需要消耗燃料值 我们不设置 他就默认取扣第一个的
那么 这个 msg.sender 拿到的就也是这个用户
然后 我们之前讲过totalSupply是总量 直接这样写相当于 发布者拥有所有的代币
然后下面又来一个transfer 发送的方法
因为你不可能一直放在发布者这里啊 是不是?
我们这样写
function transfer(address _to, uint256 _value) public returns (bool success) {
balanceOf[msg.sender] = balanceOf[msg.sender].sub(_value);
balanceOf[_to] = balanceOf[_to].add(_value);
}
但其实刚写这个地方会报错
因为我们模块中暂时没有这些函数
我们需要引入一个库
输入
npm install @openzeppelin/contracts
然后 我们在文件最上面引入
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
using SafeMath for uint256;
这样 我们去使用那两个函数的地方就不报错了
这里 transfer的业务并不复杂 balanceOf[msg.sender] 拿到当前正在操作的人
然后 将他减少_value数量的代币
然后 用balanceOf[_to] _to代币 我们需要操作的用户地址 给他加上 _value对应数量的代币
这里可能有个误解
我们constructor时 用msg.sender 拿到的是发布这个合约的用户
但 在transfer中拿到的msg.sender 是调用这个函数的用户
不过 我们这里肯定都是用的我们第一个用户 因为我们没有操作切换登录用户
但是这里需要注意的是 如果我们当前用户都没有等于_value的数值 哪那什么处理这个业务是吧?
所以 我们要在外面加个if
就是简单在外层加个大于等于的判断
但是 比起if solidity 中处理这种业务 有种更合适的语法 叫require
require的业务功能是 如果你给他的条件是 真(true) 则它继续往下走 如果不是 则直接抛出一个异常 而且这个异常会被我们区块链记录下来
然后 我们再严谨一点
require(_to != address(0));
因为这个地址是前端传过来的 我们还是要简单判断一下 虽然 他是不是有效地址 这个并不能确定 但是总归有点用
你可以理解为 != null
然后 这里我们还会注意到 这个transfer是有returns的
bool success 要求返回一个布尔类型的值
如果他能走完全部业务逻辑 我们返回一个 true 表示成功
但是文档这里 又说了 我们必须触发一个Transfer event.的事件
我们下拉文档找到一个Events
我们先将这个文档上的事件接口抄到自己的代码中来
event Transfer(address indexed _from, address indexed _to, uint256 _value)
这个你可以理解为 就是调用一下日志记录我们发送的操作
到时 所有的操作记录 都会在区块链的日志中可以看到
我们之前中心化 或许 有些人可以将自己的操作记录给他干掉 甚至说 有些互联网公司平台操作内部有鬼
但是 我们区块链是真的记录上去就很难去掉了 这个记录也随时可以被调出来
然后 我们在 transfer 中调用它
或者可以说 只要区块链还在 这个记录就在
emit Transfer(msg.sender,_to,_value);
这样 我们的业务就好了 这里 我们做个小小的测试
我们就不操作终端了 太麻烦了
我们找到根目录下的 scripts 没有就创建一个 下面来一个test.js
我们终端先执行
truffle migrate --reser
const GrToken = artifacts.require("grToken.sol")
module.exports = async function(callback) {
const grTokenDai = await GrToken.deployed();
let res1 = await grTokenDai.balanceOf("0x323E82B10DCd0E5fB100BcC3F0ABAB561AA04974");
console.log(res1);
callback()
}
这里 大家需要注意 0x323E82B10DCd0E5fB100BcC3F0ABAB561AA04974 是我ganache环境下的第一个账号的公钥地址
你们也要根据情况去填写 就是查看一下这个账号下的 getoken数
我们终端运行
truffle exec .\scripts\test.js
这里 我们前面输出的token.totalSupply() 共有量就到这来了
说明 我们constructor中的逻辑是完成了
但是这个单位 显然看着有点头疼
我们可以改成这样
const GrToken = artifacts.require("grToken.sol")
const towei = (bn)=> {
return web3.utils.fromwei(bn,"ether");
}
module.exports = async function(callback) {
const grTokenDai = await GrToken.deployed();
let res1 = await grTokenDai.balanceOf("0x323E82B10DCd0E5fB100BcC3F0ABAB561AA04974");
console.log(towei(res1));
callback()
}
很多人就会说 web3不是没引入吗?
这个大家可以放心 测试环境下 web3的方法是可以顺便用的
我们再次运行
truffle exec .\scripts\test.js
这次 单位就没问题了
那么 现在能确定的是 balanceOf确实是好用
接下来 测试 transfer
但是这里有个点比较要命
我们没有登录授权 transfer它拿不到当前登录的用户
我们可以这样写
这里 我们将test.js代码更改如下
const GrToken = artifacts.require("grToken.sol")
const toWei = (bn) => {
return web3.utils.fromWei(bn, "ether");
}
const inWei = (bn) => {
return web3.utils.toWei(bn.toString(), "ether");
}
module.exports = async function(callback) {
const grTokenDai = await GrToken.deployed();
let res1 = await grTokenDai.balanceOf("0x323E82B10DCd0E5fB100BcC3F0ABAB561AA04974");
console.log(toWei(res1));
await grTokenDai.transfer("0x096A90463E48723d3631b3291Dd76b6BA425eD4e",inWei(10000),{
from: "0x323E82B10DCd0E5fB100BcC3F0ABAB561AA04974"
});
let res2 = await grTokenDai.balanceOf("0x096A90463E48723d3631b3291Dd76b6BA425eD4e");
console.log(toWei(res2),"第二个用户");
let res3 = await grTokenDai.balanceOf("0x323E82B10DCd0E5fB100BcC3F0ABAB561AA04974");
console.log(toWei(res3),"第一个用户");
callback()
}
inWei就是一个反向转换 transfer在测试环境 我们这就这样用 第一个参数 需要发送给谁 第二个发送多少 第三个是个对象
注意 0x096A90463E48723d3631b3291Dd76b6BA425eD4e 也是一个我ganache模拟出来的用户地址 毕竟这个肯定需要两个用户才能操作嘛
{
from: 当前登录用户地址
}
然后 我们再次终端运行
truffle exec .\scripts\test.js