web3 solidity 基础 ERC20 大白话搞懂

这篇具有很好参考价值的文章主要介绍了web3 solidity 基础 ERC20 大白话搞懂。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、什么是标准什么是ERC20标准

ERC20 是 eth 的一个标准,怎么理解标准一词呢?

标准是大家遵循的一个协议,根据这个协议大家都知道该怎么去做,例如去吃饭的时候人多,你就需要排队,然后去窗口跟阿姨说你要吃什么,阿姨就会帮你打;若你不准守这个标准,直接冲进后厨,翻开泔水,大喊着我要吃饭…这个时候就完全背离了这个标准,所以被赶走了。

以上所述以开玩笑的方式讲述了什么是标准,所以在我们要使用 ERC20 标准完成这个标准的结果时,就需要遵守这个标准。

ERC20 是以太坊上的一种代币合约标准,你要实现这个代币或者说你要发个币那么就得给这个代币一个名字、怎么转账、总量、授权 等这些功能(标准),否则别人拿你的币都不能转账,难道就是看嘛,所以标准我们得实现。

具体标准我们可以看这个:https://eips.ethereum.org/EIPS/eip-20

以上的标准简而言之就是实现一些接口就可以了,以下是一个标准的ERC20标准:

// SPDX-License-Identifier:MIT
pragma solidity 0.8.17;
interface IERC20 {
    //发行的代币总量
    function totalSupply() external view returns (uint256);
    //某地址余额
    function balanceOf(address account) external view returns (uint256);
    //从当前账户对某个地址转amount的钱
    function transfer(address account, uint256 amount) external returns (bool);
    //授权某个账户可以用你的钱(用多少钱是指定的)
    function approve(address spender, uint256 amount) external returns (bool);
    //你授权的账户还可以有多少你授权的钱可以用
    function allowance(address owner, address spender) external view returns (uint256);
    //授权用户的转账方法,只针对授权用户使用
    function transferFrom(address from,address to,uint256 amount) external returns (bool);
    //转账时触发转账事件
    event Transfer(address indexed from, address indexed to, uint256 value);
    //授权时触发授权事件
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

用文字列出来,那么这些接口方法就包括:

  • 代币总量 totalSupply
  • 某地址余额 balanceOf
  • 转账 transfer
  • 授权 approve
  • 查看授权账户余额 allowance
  • 授权用户转账 transferFrom
  • 转账事件 Transfer
  • 授权事件Approval

二、简单 ERC20标准实现

2.1 父合约

实现 ERC20 标准首先我们创建一个合约,在合约中将第一点的接口作为父合约之后我们将其继承:

// SPDX-License-Identifier:MIT
pragma solidity 0.8.17;
interface IERC20 {
    //发行的代币总量
    function totalSupply() external view returns (uint256);
    //某地址余额
    function balanceOf(address account) external view returns (uint256);
    //从当前账户对某个地址转amount的钱
    function transfer(address account, uint256 amount) external returns (bool);
    //授权某个账户可以用你的钱(用多少钱是指定的)
    function approve(address spender, uint256 amount) external returns (bool);
    //你授权的账户还可以有多少你授权的钱可以用
    function allowance(address owner, address spender) external view returns (uint256);
    //授权用户的转账方法,只针对授权用户使用
    function transferFrom(address from,address to,uint256 amount) external returns (bool);
    //转账时触发转账事件
    event Transfer(address indexed from, address indexed to, uint256 value);
    //授权时触发授权事件
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

2.2 创建合约

接着编写一个合约叫做 BitCoinDemo:

contract BitCoinDemo is IERC20{
    
}

2.3 代币名称、总量、余额等状态变量编写

接下来是不是应该到我们需要用到一些变量来存储这个代币名称、总量以及余额了?

此时创建一个变量用来存储一个地址与余额的关系,那么使用 map 类型的数据:

//余额
mapping(address => uint256)public balances;

那么接下来就是对应的总量了:

//总量
uint256 public total = 100000000;

那么就还有你的代币全名、简称和小数了:

//代币名
string public constant name = "1BITCOINERC20DEMO";
//简称
string public constant symbol = "1BitCoin";
//小数点
uint8 public constant decimals = 18;

其中小数点位置我们为 18就好,一般都是写18。

当然,你这些内容都可以在合约部署的时候再传入,在这里我就简单编写了。

2.4 构造函数给自己好多钱

接着,我们可以编写一个构造函数,将即将我们要创建的代币给与当前合约的创建者:

constructor() {
    balances[msg.sender] = total;
}

毕竟这个关系就是某个地址又多少余额,那么我总量是 total,那不就是给当前创建合约的人所有余额就好了。

2.5 实现 totalSupply() 方法

接着开始实现 totalSupply() 方法,我们只需要返回当前合约的总量即可,那么就可以写成:

//返回总量
function totalSupply()override public view returns (uint256) {
    return total;
}

记得,一定要写 override,毕竟是父接口的方法重写。

2.6 实现 转账 方法

既然我有了币,那么接下来就应该有一个转账方法,我们实现 transfer 方法:

//转账代币
function transfer(address account, uint256 amount) public override returns (bool) {
    require(balances[msg.sender]>amount);//判断钱够不够
    balances[msg.sender] = balances[msg.sender]-amount;//原账户减去给自己
    balances[account] = balances[account]+amount;//给别人账户加上转账的钱
    emit Transfer(msg.sender, account, amount);//响应这个事件
    return true;
}

以上的转账方法很简单,接收两个参数,一个是你要转给谁的账户 account,还有一个你要转多少钱 amount,最后返回 bool 是否转账成功

在方法中首先判断钱是否足够,够的话就给原账户减去转出去的钱,别人账户加上转出去的钱就ok了。

2.7 查看余额

既然已经转账到别人账户了,那么此时还需要对应的查看一下别人的余额,那么此时就实现 balanceOf 方法:

//余额查看
function balanceOf(address account)override public view returns (uint256) {
    return balances[account];
}

直接返回那个 balances 的映射结果就得到余额了。

2.8 指定授权账户

授权账户需要一个 map 来存储,但是跟之前的 map 不太一样,如下:

//授权用户及余额
mapping(address => mapping (address => uint256)) appbalances;

为啥要这样写这个 map 呢?那是因为你授权肯定是 A账户 授权给了 B账户 多少钱,所以此时就是两个 address,最外层的 address 就是授权人,这个授权人下的 address 就是授权给的某人,二 对应的 uint256 数据则是授权的金额,方法如下:

//授权方法
function approve(address spender, uint256 amount)override public returns (bool) {
    appbalances[msg.sender][spender] = amount;
    emit Approval(msg.sender, spender, amount);
    return true;
}

以上这个方法呢 spender 就是需要授权给的地址,amount 就是给这个地址授权的金额数量,那么 appbalances[msg.sender][spender] = amount; 就表示在这个 appbalances 授权金额 map 中添加一个记录,msg.sender 是我自己的地址,那意思就是我自己授权给了 spender 一个金额,这个金额是 amount。

随后再用 emit 触发一个事件。

2.9 指定授权账户

查看授权账户余额也很简单了,传入两个地址,一个地址是授权人,另一个是被授权人,返回对应的 appbalances 数据,那么就得到值了,那么这个方法编写如下:

//查看授权账户余额
function allowance(address owner, address spender)override public view returns (uint) {
    return appbalances[owner][spender];
}

2.10 授权用户的专用转账方法

授权用户是有一个专用的转账方法的,毕竟这两者存储的结构都不一样,那么此时就实现最后一个需要实现的授权用户的转账方法:

//授权用户的转账方法
function transferFrom(address from, address to, uint256 amount)override public returns (bool) {
	require(amount <= balances[from]);
	require(amount <= appbalances[from][msg.sender]);
	
	balances[from] = balances[from]-amount;
	appbalances[from][msg.sender] = appbalances[from][msg.sender]-amount;
	balances[to] = balances[to]+amount;
	emit Transfer(from, to, amount);
	return true;
}

以上代码中的第一行,为啥要判断 balances 非授权用户的余额呢?那是因为我授权给你的钱那也是我的钱,那么肯定我的钱都不够,你肯定也不够了。要注意这个关系,是授权而不是转账给你。

知道以后那就明白为什么要写 require(amount <= balances[from]); 了,那么判断完授权用户的余额后开始判断被授权用户的余额是否足够,足够了就继续往下走。

首先得从授权账户的余额里面扣除要支出的部分 balances[from] = balances[from]-amount;,接着再从被授权的人那里扣除支出部分 appbalances[from][msg.sender] = appbalances[from][msg.sender]-amount;;有些同学可能会问不是已经从授权账户扣除了为什么还要从被授权账户扣除呢?这是因为这是被授权用户发起的支出,而不是授权账户发起的支出,所以被授权账户要减去已经授权的余额。

接下来就直接导 balances 中为获得方添加余额即可,记住这个金额不是授权金额,所以直接 balances 进行添加即可。

最后响应一个事件及解决。

2.11 完整代码

那么此时的完整代码如下:

// SPDX-License-Identifier:MIT
pragma solidity 0.8.17;
interface IERC20 {
    //发行的代币总量
    function totalSupply() external view returns (uint256);
    //某地址余额
    function balanceOf(address account) external view returns (uint256);
    //从当前账户对某个地址转amount的钱
    function transfer(address account, uint256 amount) external returns (bool);
    //授权某个账户可以用你的钱(用多少钱是指定的)
    function approve(address spender, uint256 amount) external returns (bool);
    //你授权的账户还可以有多少你授权的钱可以用
    function allowance(address owner, address spender) external view returns (uint256);
    //授权用户的转账方法,只针对授权用户使用
    function transferFrom(address from,address to,uint256 amount) external returns (bool);
    //转账时触发转账事件
    event Transfer(address indexed from, address indexed to, uint256 value);
    //授权时触发授权事件
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

contract BitCoinDemo is IERC20{

    //余额
    mapping(address => uint256)public balances;
    //总量
    uint256 public total = 10000000000000000;
    //代币名
    string public constant name = "1BITCOINERC20DEMO";
    //简称
    string public constant symbol = "1BitCoin";
    //小数点
    uint8 public constant decimals = 18;
    //授权用户及余额
    mapping(address => mapping (address => uint256)) appbalances;

    constructor() {
        balances[msg.sender] = total;
    }

    //返回总量
    function totalSupply()override public view returns (uint256) {
        return total;
    }
    //转账代币
    function transfer(address account, uint256 amount) public override returns (bool) {
        require(balances[msg.sender]>amount);//判断钱够不够
        balances[msg.sender] = balances[msg.sender]-amount;//原账户减去给自己
        balances[account] = balances[account]+amount;//给别人账户加上转账的钱
        emit Transfer(msg.sender, account, amount);//响应这个事件
        return true;
    }
    //余额查看
    function balanceOf(address account)override public view returns (uint256) {
        return balances[account];
    }

    //授权方法
    function approve(address spender, uint256 amount)override public returns (bool) {
        appbalances[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }

    //查看授权账户余额
    function allowance(address owner, address spender)override public view returns (uint) {
        return appbalances[owner][spender];
    }

    //授权用户的转账方法
    function transferFrom(address from, address to, uint256 amount)override public returns (bool) {
        require(amount <= balances[from]);
        require(amount <= appbalances[from][msg.sender]);

        balances[from] = balances[from]-amount;
        appbalances[from][msg.sender] = appbalances[from][msg.sender]-amount;
        balances[to] = balances[to]+amount;
        emit Transfer(from, to, amount);
        return true;
    }
}

三、新增功能

3.1 增发铸币

此时我们还可以对合约增加一些新的功能,例如铸币功能。你可以理解为铸币就是对代币进行增发(不是增头发),通过铸币可以创造出更多的代币,但是在代币总量上我们需要对其进行增加,否则你的发行跟实际记录不符,这样你的合约将会不可信。

//增发
function mint(address account, uint256 amount) external virtual {
   total += amount;
   balances[account] += amount;
   emit Transfer(address(0), account, amount);
}

以上是一个增发方法,通过传入增发后代币增加的账户地址,以及一个增发量,再到方法内对总量进行增加,并且记录增发的代币存储到哪一个账户之中,当然也要响应一个对应的交易事件(你也可以再搞一个增发事件)。

3.2 代币销毁

既然有了代币增发,那就来一个代币销毁。代表销毁可以销毁指定账户的代币,当然你也可以设定为只有“owner”账户,最开始存储的账户代币都行,在此只是做一个示例。

//销毁
function burn(address account, uint256 amount) external virtual {
    require(balances[account] >= amount, "burn error");
    balances[account] = balances[account] - amount;
    total -= amount;
    emit Transfer(account, address(0), amount);
}

代币销毁的方式跟增发的方式相反,当然还需要判断你指定的账户的余额否大于或等于需要销毁的量,接着就是往对应的 balances 里面去减去对应的 amount 了,总量也要对应的减去值,最后触发一个 Transfer 事件。

在此我们可以看到我们通过 发送方或者是接收方 为 0 地址表示销毁和增加,发送方为 0 地址则是增发,接收方为 0 地址则是销毁。

四、优化合约

通过以上的最基础的 ERC20 合约内容大概已经明白了怎么玩了,接下来我们为其新增一点内容,循序渐进感觉挺棒。

4.1 增发及销毁条件

增发及销毁条件需要满足是否是合约的 owner 调用,否则任意一个人都可以增发和销毁就乱套了,在此我们增加对应的 require:

require(owner==msg.sender,"sender error");

在此我们还需要创建一个状态变量存储当前的 owner:

address owner;
owner=msg.sender;

web3 solidity 基础 ERC20 大白话搞懂,NFT web3 solidity ethers.js 实战,web3,区块链

4.2 销毁代币优化

销毁代币之前是随便一个账户指定了就可以销毁那个账户代币,这明显就不对,不然我的钱就非常不安全了,在此我们设置只能够销毁自己的代码:
web3 solidity 基础 ERC20 大白话搞懂,NFT web3 solidity ethers.js 实战,web3,区块链

还需要把多余的参数删除哟。

4.3 完整代码

其他情况就增加判断是否是 0 地址就ok了,毕竟本篇文章只是介绍基础的 ERC20,并不是做一个“完善”的ERC20 合约:
web3 solidity 基础 ERC20 大白话搞懂,NFT web3 solidity ethers.js 实战,web3,区块链
接着我们部署完毕后(测试网)导入代币:
web3 solidity 基础 ERC20 大白话搞懂,NFT web3 solidity ethers.js 实战,web3,区块链

web3 solidity 基础 ERC20 大白话搞懂,NFT web3 solidity ethers.js 实战,web3,区块链

接着我们来个增发,输入地址和增加量:

web3 solidity 基础 ERC20 大白话搞懂,NFT web3 solidity ethers.js 实战,web3,区块链

等待交易完成:
web3 solidity 基础 ERC20 大白话搞懂,NFT web3 solidity ethers.js 实战,web3,区块链
现在钱多多了,并且使 Mint 方法发送的。

在测试一下销毁:

web3 solidity 基础 ERC20 大白话搞懂,NFT web3 solidity ethers.js 实战,web3,区块链
销毁成功:
web3 solidity 基础 ERC20 大白话搞懂,NFT web3 solidity ethers.js 实战,web3,区块链
基本上都操作正常:
web3 solidity 基础 ERC20 大白话搞懂,NFT web3 solidity ethers.js 实战,web3,区块链

最终代码完整如下:文章来源地址https://www.toymoban.com/news/detail-815428.html

// SPDX-License-Identifier:MIT
pragma solidity 0.8.17;
interface IERC20 {
    //发行的代币总量
    function totalSupply() external view returns (uint256);
    //某地址余额
    function balanceOf(address account) external view returns (uint256);
    //从当前账户对某个地址转amount的钱
    function transfer(address account, uint256 amount) external returns (bool);
    //授权某个账户可以用你的钱(用多少钱是指定的)
    function approve(address spender, uint256 amount) external returns (bool);
    //你授权的账户还可以有多少你授权的钱可以用
    function allowance(address owner, address spender) external view returns (uint256);
    //授权用户的转账方法,只针对授权用户使用
    function transferFrom(address from,address to,uint256 amount) external returns (bool);
    //转账时触发转账事件
    event Transfer(address indexed from, address indexed to, uint256 value);
    //授权时触发授权事件
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

contract BitCoinDemo is IERC20{

    //余额
    mapping(address => uint256)public balances;
    //总量
    uint256 public total = 10000000000000000;
    //代币名
    string public constant name = "1BITCOINERC20DEMO";
    //简称
    string public constant symbol = "1BitCoin";
    //小数点
    uint8 public constant decimals = 18;
    //授权用户及余额
    mapping(address => mapping (address => uint256)) appbalances;
    //拥有者
    address owner;

    constructor() {
        owner=msg.sender;
        balances[msg.sender] = total;
    }

    //返回总量
    function totalSupply()override public view returns (uint256) {
        return total;
    }
    //转账代币
    function transfer(address account, uint256 amount) public override returns (bool) {
        require(account==address(0),"sender error");
        require(balances[msg.sender]>amount);//判断钱够不够
        balances[msg.sender] = balances[msg.sender]-amount;//原账户减去给自己
        balances[account] = balances[account]+amount;//给别人账户加上转账的钱
        emit Transfer(msg.sender, account, amount);//响应这个事件
        return true;
    }
    //余额查看
    function balanceOf(address account)override public view returns (uint256) {
        return balances[account];
    }

    //授权方法
    function approve(address spender, uint256 amount)override public returns (bool) {
        require(spender==address(0),"sender error");
        appbalances[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }

    //查看授权账户余额
    function allowance(address owner, address spender)override public view returns (uint) {
        return appbalances[owner][spender];
    }

    //授权用户的转账方法
    function transferFrom(address from, address to, uint256 amount)override public returns (bool) {
        require(amount <= balances[from]);
        require(amount <= appbalances[from][msg.sender]);
        require(to==address(0),"sender error");

        balances[from] = balances[from]-amount;
        appbalances[from][msg.sender] = appbalances[from][msg.sender]-amount;
        balances[to] = balances[to]+amount;
        emit Transfer(from, to, amount);
        return true;
    }

    //增发
    function mint(address account, uint256 amount) external virtual {
        require(owner==msg.sender,"sender error");
        total += amount;
        balances[account] += amount;
        emit Transfer(address(0), account, amount);
    }

    //销毁
    function burn(uint256 amount) external virtual {
        require(owner==msg.sender,"sender error");
        require(balances[owner] >= amount, "burn error");
        balances[owner] = balances[owner] - amount;
        total -= amount;
        emit Transfer(owner, address(0), amount);
    }
}

到了这里,关于web3 solidity 基础 ERC20 大白话搞懂的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 用大白话举例子讲明白云计算

    前几天王坚院士在2023云栖大会上发表了关于云计算的演讲,听得我是热血沸腾,王院士称AI和云计算的结合是“云计算的第三次浪潮”,对此我深表认同。但是身边的很多朋友还不知道云计算是什么意思,有些人还认为百度云和百度云盘是一个东西,下面我用大白话举例说明

    2024年02月04日
    浏览(50)
  • 用大白话举例子讲明白区块链

    什么是区块链?网上这么说: 区块链是一种分布式数据库技术,它以块的形式记录和存储交易数据,并使用密码学算法保证数据的安全性和不可篡改性。每个块都包含了前一个块的哈希值和自身的交易数据,形成了一个不断增长的链条。 区块链的特点包括: 分布式:区块链

    2024年02月04日
    浏览(55)
  • 设计模式大白话——适配器模式

    ​ 适配器其实非常好理解,放到生活中来,我们身边处处都有这样的例子,最常见的是用的比较多的各种转接线(如:USB 转 Type-C),有了这个“适配器”,我们就能够将电脑和手机等设备相进行连接,而不需要改动电脑/手机的原有接口。 ​ 回到编程的世界中,假设我们的

    2024年02月10日
    浏览(47)
  • React底层原理分析(简单大白话版本)

    react包 react-dom包 react-reconciler包 scheduler包 Fiber对象 diff算法 深度优先遍历  堆排序 链表,栈操作 react合成事件

    2024年01月20日
    浏览(44)
  • Lighting Network(闪电网络)大白话解析

    通道(Channel),通过在主网宣布通道建立,而后交易双方转至链下交易,把多次交易在链下完成,不占用主网资源,交易完成后在主网广播最终交易结果,无需更改主网机制即可实现吞吐量的提高。 “通道”是一个逻辑上的概念,实际使用过程中并没有“通道”,即使在数据传

    2024年02月04日
    浏览(42)
  • 大白话理解-微信小程序获取授权

    微信用户授权,才可以操作微信官方的某些接口。 简单来说就是:微信定义了很多接口,然后他们认为有一部分是涉及到用户使用安全的,所以把这一部分划分了出来,然后这一部分按照功能来拆开各种范围。于是有了scope列表的东西,scope翻译为中文是范围的意思。(定位属于

    2024年02月02日
    浏览(36)
  • 别样的git学习--大白话学git

    希望用更加口语化的语言向大家讲述git 的魅力 1、Git-stash (贮存) 想象一下,你正在写一封重要的邮件,但突然你的老板告诉你需要立即处理另一个紧急任务。你还没完成邮件,不想丢失已写的内容,但你也需要一个干净的工作空间来处理新的任务。在这种情况下,Git 的

    2024年01月24日
    浏览(49)
  • 用大白话来讲讲多线程的知识架构

    感觉多线程的知识又多又杂,自从接触java,就在一遍一遍捋脉络和深入学习。现在将这次的学习成果展示如下。 什么是多线程? 操作系统运行一个程序,就是一个线程。同时运行多个程序,就是多线程。即在同一时间,并行做多件事。 “并行”是相对于我们这些用户来说的

    2024年02月11日
    浏览(44)
  • 大白话说说Docker容器默认网络模型工作原理

    Docker的默认网络模型 —— 桥接模式(Bridge) 当你不做任何特殊设置时,Docker会使用一种叫做“桥接模式”的网络设置。这就像是给你的容器小房子安装了一个虚拟的桥接网络。这座桥连接着容器和你的电脑(宿主机),还能与外界通信。 虚拟网络桥 :想象一下,在你的电

    2024年02月21日
    浏览(40)
  • Java【直接插入排序】算法, 大白话式详细图文解析(附代码)

    📕各位读者好, 我是小陈, 这是我的个人主页 📗小陈还在持续努力学习编程, 努力通过博客输出所学知识 📘如果本篇对你有帮助, 烦请点赞关注支持一波, 感激不尽 📙 希望我的专栏能够帮助到你: JavaSE基础: 基础语法, 类和对象, 封装继承多态, 接口, 综合小练习图书管理系统

    2024年02月09日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包