首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Solidity学习代码示例-ERC20示例

我们在前面Solidity学习代码示例的两小节文章中,学习到了Solidity的一些小案例。接下来这篇文章中,我将会带着大家手把手去学习ERC20代币合约。如果大家有任何的问题,欢迎大家评论区留言。

我们先来看看代码所要表达的意思是什么。首先打开 https://remix.ethereum.org/ ,我们新建一个文件叫做ERC20.sol,然后我们将代码放进文件中。

然后,我们来到编译到界面,可以自行打开自动编译或者手动编译到选择项。

合约如果没问题的话,编译的选项会有个绿色背景的打勾标志,如果有问题的话,会有红色背景并附上有问题的条数,如果有需要优化的部分,则会提示黄色背景的警告并附上警告的条数。上面我们的代码就没有问题了,编译通过了。通过了之后,我们就开始部署我们的合约了。

部署合约完成后,我们可以看到合约的所有的写入和读取数据的方法。其中,黄色代表的是写入数据,蓝色代表的是读取数据。部署合约完成后,我们也能看到合约的唯一地址,也就是合约地址,是一串哈希值。

讲完合约的编译部署后,我们来分析讲解下源代码。

**ERC20.sol**

```js

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/interfaces/IERC20.sol";

import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

import "@openzeppelin/contracts/utils/Context.sol";

contract ERC20 is Context, IERC20, IERC20Metadata {

mapping(address => uint256) private _balances;

mapping(address => mapping(address => uint256)) private _allowances;

uint256 private _totalSupply;

string private _name;

string private _symbol;

constructor(string memory name_, string memory symbol_) {

_name = name_;

_symbol = symbol_;

}

function name() public view virtual override returns (string memory) {

return _name;

}

function symbol() public view virtual override returns (string memory) {

return _symbol;

}

function decimals() public view virtual override returns (uint8) {

return 18;

}

function totalSupply() public view virtual override returns (uint256) {

return _totalSupply;

}

function balanceOf(address account) public view virtual override returns (uint256) {

return _balances[account];

}

function transfer(address to, uint256 amount) public virtual override returns (bool) {

address owner = _msgSender();

_transfer(owner, to, amount);

return true;

}

function allowance(address owner, address spender) public view virtual override returns (uint256) {

return _allowances[owner][spender];

}

function approve(address spender, uint256 amount) public virtual override returns (bool) {

address owner = _msgSender();

_approve(owner, spender, amount);

return true;

}

function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {

address spender = _msgSender();

_spendAllowance(from, spender, amount);

_transfer(from, to, amount);

return true;

}

function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {

address owner = _msgSender();

_approve(owner, spender, allowance(owner, spender) + addedValue);

return true;

}

function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {

address owner = _msgSender();

uint256 currentAllowance = allowance(owner, spender);

require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");

unchecked {

_approve(owner, spender, currentAllowance - subtractedValue);

}

return true;

}

function _transfer(address from, address to, uint256 amount) internal virtual {

require(from != address(0), "ERC20: transfer from the zero address");

require(to != address(0), "ERC20: transfer to the zero address");

_beforeTokenTransfer(from, to, amount);

uint256 fromBalance = _balances[from];

require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");

unchecked {

_balances[from] = fromBalance - amount;

_balances[to] += amount;

}

emit Transfer(from, to, amount);

_afterTokenTransfer(from, to, amount);

}

function _mint(address account, uint256 amount) internal virtual {

require(account != address(0), "ERC20: mint to the zero address");

_beforeTokenTransfer(address(0), account, amount);

_totalSupply += amount;

unchecked {

// Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.

_balances[account] += amount;

}

emit Transfer(address(0), account, amount);

_afterTokenTransfer(address(0), account, amount);

}

function _burn(address account, uint256 amount) internal virtual {

require(account != address(0), "ERC20: burn from the zero address");

_beforeTokenTransfer(account, address(0), amount);

uint256 accountBalance = _balances[account];

require(accountBalance >= amount, "ERC20: burn amount exceeds balance");

unchecked {

_balances[account] = accountBalance - amount;

// Overflow not possible: amount <= accountbalance <="totalSupply.

_totalSupply -= amount;

}

emit Transfer(account, address(0), amount);

_afterTokenTransfer(account, address(0), amount);

}

function _approve(address owner, address spender, uint256 amount) internal virtual {

require(owner != address(0), "ERC20: approve from the zero address");

require(spender != address(0), "ERC20: approve to the zero address");

_allowances[owner][spender] = amount;

emit Approval(owner, spender, amount);

}

function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {

uint256 currentAllowance = allowance(owner, spender);

if (currentAllowance != type(uint256).max) {

require(currentAllowance >= amount, "ERC20: insufficient allowance");

unchecked {

_approve(owner, spender, currentAllowance - amount);

}

}

}

function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}

function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}

}

contract MyToken is ERC20 {

address public owner;

constructor(string memory name, string memory symbol) ERC20(name, symbol) {

owner = msg.sender;

_mint(msg.sender, 100 * 10 ** uint(decimals()));

}

modifier Manager {

require(owner == msg.sender,"NOT OWNER!");

_;

}

function mint(uint amount) external Manager {

_mint(msg.sender, amount * 10 ** uint(decimals()));

}

}

```

##### ==================== 分割线===================

##### ==================== 分割线===================

```js

import "@openzeppelin/contracts/interfaces/IERC20.sol";

import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

import "@openzeppelin/contracts/utils/Context.sol";

```

OpenZeppelin是用于开发安全的智能合约库,其代码经过社区的审查并有着坚实的社区基础。实现了标准的代币的接口,我们可以放心的使用import功能来讲其导入到我们的代码中,我们就可以直接使用import导入进来的代码的接口,而不需要重复造轮子。当然了,如果我们想要导入自己的合约文件,只要输入正确的路径就可以了。

```js

contract ERC20 is Context, IERC20, IERC20Metadata {}

```

我们想要使用导入进来的合约文件,直接使用is来继承导入的文件即可。

```js

mapping(address => uint256) private _balances;

mapping(address => mapping(address => uint256)) private _allowances;

uint256 private _totalSupply;

string private _name;

string private _symbol;

```

我们把变量声明为private,这样其他人都无法见到这个变量。通常,我们在声明一个变量或者一个方法,我们会把变量名或者方法名前面加一个下划线来表示。

```js

constructor(string memory name_, string memory symbol_) {

_name = name_;

_symbol = symbol_;

}

```

构造器传入了两个参数,这意味着我们在部署合约之前需要传入这两个参数的值。这样这两个值就直接初始化在我们的合约中了。

```js

function name() public view virtual override returns (string memory) {

return _name;

}

function symbol() public view virtual override returns (string memory) {

return _symbol;

}

function decimals() public view virtual override returns (uint8) {

return 18;

}

function totalSupply() public view virtual override returns (uint256) {

return _totalSupply;

}

function balanceOf(address account) public view virtual override returns (uint256) {

return _balances[account];

}

```

合约中的这五个方法都是读取的方法,name返回的是代币的名称,symbol返回的是代币的标志,decimals返回的是代币的精度,totalSupply返回的是代币的总数量,balanceOf返回到是某个账户的余额。

这里有必要补充说明一下decimals精度的概念,通常代币都会有精度,因为以太坊代币的数量是有单位的。以太坊的最小单位位wei,之后是gwei,最大是ether。而1ether等于1000000000000000000wei,也就是1后面18个零。那我们在设置decimal的时候,为了方便做代币的计算,我们通常将decimals设置为18。

```js

function transfer(address to, uint256 amount) public virtual override returns (bool) {

address owner = _msgSender();

_transfer(owner, to, amount);

return true;

}

```

转账方法。参数里的to代表的是对方的账户地址,amount代表的是需要转账的数量。

需要注意的是,区块链中,转账的数量默认单位是wei,所以,如果我们要转账1ether,就必须写成1后面跟18个零。

因为我们需要重写导入合约中的方法,所以函数跟着一个virtual和override关键字。

由于transfer方法是public的,我们通常不把重要的转账逻辑放在public接口来编写,通常我们将具体的逻辑写在私有函数里面,这个也是处于代码合约安全考虑的一部分,也是优化了代码。在这里我们直接使用当前合约里的_transfer方法的逻辑。

注意了,这里的转账者是当前执行这个方法的钱包账户,接收者是to。

```js

function allowance(address owner, address spender) public view virtual override returns (uint256) {

return _allowances[owner][spender];

}

```

allowance方法的意思查询是spender账户还能在owner账户可以使用多少代币。

```js

function approve(address spender, uint256 amount) public virtual override returns (bool) {

address owner = _msgSender();

_approve(owner, spender, amount);

return true;

}

```

approve方法是当前执行这个方法的账户,允许spender账户可以从它的账户里使用多少代币。

比如,当前的账户有10ETH,而spender拥有0ETH。那么当当前的账户执行这个方法时,传入spender的账户地址,传入可以让spender从自己账户可以使用的ETH的数量,比如此时传入amount为6,那么spender本没有ETH,但是它现在可以从给它ETH代币的账户手中花费6ETH的代币了。

```js

function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {

address spender = _msgSender();

_spendAllowance(from, spender, amount);

_transfer(from, to, amount);

return true;

}

```

经过approve方法之后,spender就可以调用transferFrom方法,将from设置为给它ETH代币的账户地址,将to设置为需要转让的对方的地址,传入一个不大于6ETH的amount的值。这样,就可以将代币转出了。

```js

function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {

address owner = _msgSender();

_approve(owner, spender, allowance(owner, spender) + addedValue);

return true;

}

```

increaseAllowance方法,顾名思义,就是增加允许spender从自己账户转出的代币的数量。

```js

function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {

address owner = _msgSender();

uint256 currentAllowance = allowance(owner, spender);

require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");

unchecked {

_approve(owner, spender, currentAllowance - subtractedValue);

}

return true;

}

```

decreaseAllowance方法,就是减少允许spender从自己账户转出的代币的数量。

```js

function _transfer(address from, address to, uint256 amount) internal virtual {

require(from != address(0), "ERC20: transfer from the zero address");

require(to != address(0), "ERC20: transfer to the zero address");

_beforeTokenTransfer(from, to, amount);

uint256 fromBalance = _balances[from];

require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");

unchecked {

_balances[from] = fromBalance - amount;

_balances[to] += amount;

}

emit Transfer(from, to, amount);

_afterTokenTransfer(from, to, amount);

}

```

_transfer方法是转账逻辑实现的具体代码实现。它声明为internal,这就意味着只有内部合约才能够调用次方法,而外部合约则访问不到。

require限制条件中,我们限制发送者的账户地址和接受者的账户地址均不能为0地址,address(0)表示的是0x0地址,也就是黑洞地址,是一个无效的地址。当钱转入到此地址的时候,就意味着再也找不回来了。所以,这里为了用户转到黑洞地址,这里一开始就将参数进行了校验。

_beforeTokenTransfer方法是转账前的状态。

require(fromBalance >= amount),表示发送者的数量要大于填入的amount的值。

unchecked方法不检查算术的溢出

由于我们这个方法是重写的方法,而导入的文件里已经有了Transfer这个event事件,所以我们可以使用emit监听事件。

最后使用_afterTokenTransfer方法来更新是转账后的状态。

```js

function _mint(address account, uint256 amount) internal virtual {

require(account != address(0), "ERC20: mint to the zero address");

_beforeTokenTransfer(address(0), account, amount);

_totalSupply += amount;

unchecked {

// Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.

_balances[account] += amount;

}

emit Transfer(address(0), account, amount);

_afterTokenTransfer(address(0), account, amount);

}

```

_mint方法是铸造代币的方法,我们前面代码中声明了一个_totalSupply代表代币总量的变量,以及_balances这个映射。当我们执行这个方法的逻辑的时候,总量会增加,并且给参数account增加的数量也会对应增加。这个方法是internal的,所以,我们不能直接使用,只能在写一个公共的方法来去调用这个方法实现接口。

```js

function _burn(address account, uint256 amount) internal virtual {

require(account != address(0), "ERC20: burn from the zero address");

_beforeTokenTransfer(account, address(0), amount);

uint256 accountBalance = _balances[account];

require(accountBalance >= amount, "ERC20: burn amount exceeds balance");

unchecked {

_balances[account] = accountBalance - amount;

// Overflow not possible: amount <= accountbalance <="totalSupply.

_totalSupply -= amount;

}

emit Transfer(account, address(0), amount);

_afterTokenTransfer(account, address(0), amount);

}

```

_burn方法与_mint方法则相反,它是将_totalSupply进行减法的操作。当执行这个方法的逻辑时,参数account的amount就会随之减少。同样,它是internal方法,所以,如果我们的需求里有销毁代币的功能,我们也是一样要写一个公共的方法来调用_burn这个接口。

```js

function _approve(address owner, address spender, uint256 amount) internal virtual {

require(owner != address(0), "ERC20: approve from the zero address");

require(spender != address(0), "ERC20: approve to the zero address");

_allowances[owner][spender] = amount;

emit Approval(owner, spender, amount);

}

```

_approve方法是上面approve方法的具体逻辑实现。

```js

function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {

uint256 currentAllowance = allowance(owner, spender);

if (currentAllowance != type(uint256).max) {

require(currentAllowance >= amount, "ERC20: insufficient allowance");

unchecked {

_approve(owner, spender, currentAllowance - amount);

}

}

}

```

_spendAllowance是transfeFrom方法的具体实现。

```js

function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}

function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}

```

这两种方法作为0.8版本写法的存在,是一定要有的。

```js

contract MyToken is ERC20 {}

```

上面介绍的ERC20合约的接口里面没有铸造的公共方法,这里我们又写一份新的合约来继承上面的ERC20合约。那么我们这一份合约里就声明了两个合约,我们就称MyToken合约入口就是我们的主合约,而ERC20合约是我们的从合约。当然,我们也可以将ERC20合约写在一个文件里,然后我们另写一份文件名为MyToken合约,然后我们使用import来导入ERC20合约,这样也是可以的。

```js

address public owner;

constructor(string memory name, string memory symbol) ERC20(name, symbol) {

owner = msg.sender;

_mint(msg.sender, 100 * 10 ** uint(decimals()));

}

```

在这段代码块中,我们声明了一个owner,这个可以看成是我们部署合约的管理者,拥有最高权限。

在构造器里,我们继承了ERC20合约的构造器的接口。在合约部署之前,我们就将部署合约的用户地址赋予owner,并且初始化铸造出100ETH的代币。

```js

modifier Manager {

require(owner == msg.sender,"NOT OWNER!");

_;

}

function mint(uint amount) external Manager {

_mint(msg.sender, amount * 10 ** uint(decimals()));

}

```

在这段代码块中,我们写了一个修改器,要求owner的账户地址必须是部署合约的账户地址。

由于ERC20合约中的_mint方法是internal的,我们无法直接使用_mint方法。所以,我们写了一个mint方法,声明为external,并且加上了修改器的限制条件,说明只有owner用户,也就是部署合约的用户才能够执行mint方法。在mint方法内部,我们直接调用ERC20合约中的_mint方法,参数为代币的数量。这样我们就有了铸造代币的功能了。

至此,我们通过ERC20合约示例,又更全面的学习了Solidity这门语言,也对以太坊的了解更加深入了。以太坊的ERC20代币均是用这种技术实现部署的,ERC20代币也叫做同质化代币,也就意味着所有人持有的代币是没有区别的,这就好比你手中的编号为001的100块人民币跟我手中编号为002的100块人民币的价值是一样的。

下一节,我们来学习ERC721示例,ERC721也就是我们所说的NFT,也就是我们经常提及的元宇宙。如果感兴趣的话,那就赶紧关注我,新的一期内容更加精彩哦!

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20230423A05ST500?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券