ERC20 Contract
{collapse}
{collapse-item label="一、定义" open}
ERC20
ERC20是以太坊上的代币标准,来自2015年11月V神参与的EIP20。它实现了代币转账的基本逻辑:
- 账户余额(balanceOf())
- 转账(transfer())
- 授权转账(transferFrom())
- 授权(approve())
- 代币总供给(totalSupply())
- 授权转账额度(allowance())
- 代币信息(可选):名称(name()),代号(symbol()),小数位数(decimals())
IERC20
IERC20是ERC20代币标准的接口合约,规定了ERC20代币需要实现的函数和事件。 之所以需要定义接口,是因为有了规范后,就存在所有的ERC20代币都通用的函数名称,输入参数,输出参数。 在接口函数中,只需要定义函数名称,输入参数,输出参数,并不关心函数内部如何实现。 由此,函数就分为内部和外部两个内容,一个重点是实现,另一个是对外接口,约定共同数据。 这就是为什么需要ERC20.sol和IERC20.sol两个文件实现一个合约。
事件
IERC20定义了2个事件:Transfer事件和Approval事件,分别在转账和授权时被释放
/**
* @dev 释放条件:当 `value` 单位的货币从账户 (`from`) 转账到另一账户 (`to`)时.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev 释放条件:当 `value` 单位的货币从账户 (`owner`) 授权给另一账户 (`spender`)时.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);函数
IERC20定义了6个函数,提供了转移代币的基本功能,并允许代币获得批准,以便其他链上第三方使用。
totalSupply()返回代币总供给/** * @dev 返回代币总供给. */ function totalSupply() external view returns (uint256);balanceOf()返回账户余额/** * @dev 返回账户`account`所持有的代币数. */ function balanceOf(address account) external view returns (uint256);transfer()转账/** * @dev 转账 `amount` 单位代币,从调用者账户到另一账户 `to`. * * 如果成功,返回 `true`. * * 释放 {Transfer} 事件. */ function transfer(address to, uint256 amount) external returns (bool);allowance()返回授权额度/** * @dev 返回`owner`账户授权给`spender`账户的额度,默认为0。 * * 当{approve} 或 {transferFrom} 被调用时,`allowance`会改变. */ function allowance(address owner, address spender) external view returns (uint256);approve()授权/** * @dev 调用者账户给`spender`账户授权 `amount`数量代币。 * * 如果成功,返回 `true`. * * 释放 {Approval} 事件. */ function approve(address spender, uint256 amount) external returns (bool);transferFrom()授权转账/** * @dev 通过授权机制,从`from`账户向`to`账户转账`amount`数量代币。转账的部分会从调用者的`allowance`中扣除。 * * 如果成功,返回 `true`. * * 释放 {Transfer} 事件. */ function transferFrom( address from, address to, uint256 amount ) external returns (bool);
实现ERC20
现在我们写一个ERC20,将IERC20规定的函数简单实现。
状态变量
我们需要状态变量来记录账户余额,授权额度和代币信息。其中balanceOf, allowance和totalSupply为public类型,会自动生成一个同名getter函数,实现IERC20规定的balanceOf(), allowance()和totalSupply()。而name, symbol, decimals则对应代币的名称,代号和小数位数。
注意:用override修饰public变量,会重写继承自父合约的与变量同名的getter函数,比如IERC20中的balanceOf()函数。
mapping(address => uint256) public override balanceOf;
mapping(address => mapping(address => uint256)) public override allowance;
uint256 public override totalSupply; // 代币总供给
string public name; // 名称
string public symbol; // 代号
uint8 public decimals = 18; // 小数位数函数
构造函数:初始化代币名称、代号。
constructor(string memory name_, string memory symbol_){ name = name_; symbol = symbol_; }transfer()函数:实现IERC20中的transfer函数,代币转账逻辑。调用方扣除amount数量代币,接收方增加相应代币。土狗币会魔改这个函数,加入税收、分红、抽奖等逻辑。function transfer(address recipient, uint amount) public override returns (bool) { balanceOf[msg.sender] -= amount; balanceOf[recipient] += amount; emit Transfer(msg.sender, recipient, amount); return true; }approve()函数:实现IERC20中的approve函数,代币授权逻辑。被授权方spender可以支配授权方的amount数量的代币。spender可以是EOA账户,也可以是合约账户:当你用uniswap交易代币时,你需要将代币授权给uniswap合约。function approve(address spender, uint amount) public override returns (bool) { allowance[msg.sender][spender] = amount; emit Approval(msg.sender, spender, amount); return true; }transferFrom()函数:实现IERC20中的transferFrom函数,授权转账逻辑。被授权方将授权方sender的amount数量的代币转账给接收方recipient。function transferFrom( address sender, address recipient, uint amount ) public override returns (bool) { allowance[sender][msg.sender] -= amount; balanceOf[sender] -= amount; balanceOf[recipient] += amount; emit Transfer(sender, recipient, amount); return true; }mint()函数:铸造代币函数,不在IERC20标准中。这里为了教程方便,任何人可以铸造任意数量的代币,实际应用中会加权限管理,只有owner可以铸造代币:function mint(uint amount) external { balanceOf[msg.sender] += amount; totalSupply += amount; emit Transfer(address(0), msg.sender, amount); }burn()函数:销毁代币函数,不在IERC20标准中。function burn(uint amount) external { balanceOf[msg.sender] -= amount; totalSupply -= amount; emit Transfer(msg.sender, address(0), amount); }
{/collapse-item}
{collapse-item label="二、应用" open}
1. 代币水龙头
代币水龙头就是让用户免费领代币的网站/应用。
实现一个简版的ERC20水龙头,逻辑非常简单:我们将一些ERC20代币转到水龙头合约里,用户可以通过合约的requestToken()函数来领取100单位的代币,每个地址只能领一次。
状态变量
我们在水龙头合约中定义3个状态变量
amountAllowed设定每次能领取代币数量(默认为100,不是一百枚,因为代币有小数位数)。
tokenContract记录发放的ERC20代币合约地址。
requestedAddress记录领取过代币的地址。
uint256 public amountAllowed = 100; // 每次领 100 单位代币
address public tokenContract; // token合约地址
mapping(address => bool) public requestedAddress; // 记录领取过代币的地址事件
水龙头合约中定义了1个SendToken事件,记录了每次领取代币的地址和数量,在requestTokens()函数被调用时释放。
// SendToken事件
event SendToken(address indexed Receiver, uint256 indexed Amount);
函数合约中只有两个函数:
构造函数:初始化tokenContract状态变量,确定发放的ERC20代币地址。
// 部署时设定ERC20代币合约
constructor(address _tokenContract) {
tokenContract = _tokenContract; // set token contract
}requestTokens()函数,用户调用它可以领取ERC20代币。
// 用户领取代币函数
function requestTokens() external {
require(!requestedAddress[msg.sender], "Can't Request Multiple Times!"); // 每个地址只能领一次
IERC20 token = IERC20(tokenContract); // 创建IERC20合约对象
require(token.balanceOf(address(this)) >= amountAllowed, "Faucet Empty!"); // 水龙头空了
token.transfer(msg.sender, amountAllowed); // 发送token
requestedAddress[msg.sender] = true; // 记录领取地址
emit SendToken(msg.sender, amountAllowed); // 释放SendToken事件
}在Remix上测试
(1).先将自定义的dog合约部署
查看日志部署成功 生成合约地址
(2).使用mint铸造1000个dog到自己钱包中
查看日志触发transfer事件铸造成功 从地址0x0转了100个dog到了自己的钱包地址
(3).选择水龙头合约部署 填写dog代币合约地址作为水龙头合约构造函数参数
查看日志合约部署成功 生成合约地址
(4).调用dog合约的transfer发送200个dog给水龙头合约
查看日志钱包转了200个dog到水龙头合约地址
(5).现在使用一个钱包和水龙头合约进行交互获取dog
钱包0x6......7f2收到了100个dog
查询dog合约看下钱包0x6......7f2拥有100个dog代币
2. Airdrop
由于通常接收空投的用户数量较多,项目方逐一发送转账并不实际。通过使用智能合约批量发送ERC20代币,可以显著提高空投的效率。
空投代币合约
空投合约的逻辑很简单:通过循环,单笔交易将ERC20代币发送到多个地址。合约包含2以下函数:
函数getSum():返回数组的总和uint。
// sum function for arrays
function getSum(uint256[] calldata _arr) public pure returns(uint sum)
{
for(uint i = 0; i < _arr.length; i++)
sum = sum + _arr[i];
}功能multiTransferToken():发送代币空投ERC20,包含3参数:
_token:代币合约地址(address类型)
_addresses:接收空投的用户地址数组(address[]类型)
_amounts_addresses:(uint[]类型)中每个地址对应空投金额数组
该函数包含2以下检查:第一个require检查数组的长度是否_addresses等于数组的长度_amounts。第二个require检查空投合约的授权限额是否大于要空投的代币总量。
/// @notice Transfer ERC20 tokens to multiple addresses, authorization is required before use
///
/// @param _token The address of ERC20 token for transfer
/// @param _addresses The array of airdrop addresses
/// @param _amounts The array of amount of tokens (airdrop amount for each address)
function multiTransferToken(
address _token,
address[] calldata _addresses,
uint256[] calldata _amounts
) external {
// Check: The length of _addresses array should be equal to the length of _amounts array
require(_addresses.length == _amounts.length, "Lengths of Addresses and Amounts NOT EQUAL");
IERC20 token = IERC20(_token); // Declare IERC contract variable
uint _amountSum = getSum(_amounts); // Calculate the total amount of airdropped tokens
// Check: The authorized amount of tokens should be greater than or equal to the total amount of airdropped tokens
require(token.allowance(msg.sender, address(this)) >= _amountSum, "Need Approve ERC20 token");
// for loop, use transferFrom function to send airdrops
for (uint8 i; i < _addresses.length; i++) {
token.transferFrom(msg.sender, _addresses[i], _amounts[i]);
}
}multiTransferETH()功能:发送ETH空投,2参数:
_addresses:接收空投的用户地址数组(address[]类型)
_amounts:空投金额数组,对应_addresses(uint[]类型)中每个地址的数量
/// Transfer ETH to multiple addresses
function multiTransferETH(
address payable[] calldata _addresses,
uint256[] calldata _amounts
) public payable {
// Check: _addresses and _amounts arrays should have the same length
require(_addresses.length == _amounts.length, "Lengths of Addresses and Amounts NOT EQUAL");
// Calculate total amount of ETH to be airdropped
uint _amountSum = getSum(_amounts);
// Check: transferred ETH should equal total amount
require(msg.value == _amountSum, "Transfer amount error");
// Use a for loop to transfer ETH using transfer function
for (uint256 i = 0; i < _addresses.length; i++) {
_addresses[i].transfer(_amounts[i]);
}
}在Remix上测试
(1).先将自定义的dog合约部署
查看日志部署成功 生成合约地址
(2).使用mint铸造1000个dog到自己钱包中
查看日志触发transfer事件铸造成功 从地址0x0转了100个dog到了自己的钱包地址
(3).选择airdrop合约部署
查看日志airdrop合约部署成功 生成合约地址
(4).使用dog合约的approve函数授权airdrop合约拥有其中800个dog的权限
查看日志触发approve事件 授权成功 授权airdrop合约操作钱包地址的800个dog
(5).现在管理员就可以通过和airdrop合约交互 指定要发放空投的钱包地址对应的dog个数
查看日志钱包被空投了dog
(6)拿其中一个钱包去dog合约地址调用balanceof查询钱包的dog个数是100和空投的数量一致
{/collapse-item}
{/collapse}
当前页面是本站的「Google AMP」版。查看和发表评论请点击:完整版 »