如何在基于OpenZeppelin的ERC20合约中正确实现自定义getToken转账方法
问题原因
你的猜测完全正确。OpenZeppelin ERC20的transfer()方法的逻辑是:以方法调用者的身份,向收款地址转移对应数量的代币。当你在合约内部调用transfer(msg.sender, _amount)时,调用transfer的主体是合约地址本身,但你在构造函数中铸造的2100万枚代币全部归属部署者钱包地址,合约地址没有任何余额,自然会抛出余额不足的报错。
正确实现方案
根据业务需求可以选择以下两种方案:
方案1:直接给调用用户铸币(无需预存代币到合约)
如果你的业务允许直接给调用用户发放新增代币,直接调用ERC20内置的内部_mint方法即可,不需要依赖合约自身余额:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.2; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract TokenSample is ERC20 { // 可以设置最大发放总量防止无限增发,根据业务需求可选 uint256 public constant MAX_CLAIM_AMOUNT = 1000000 * 10 ** decimals(); uint256 public totalClaimed; constructor() ERC20("TokenSample", "SMPL") { _mint(msg.sender, 21000000 * 10 ** decimals()); } function getToken(uint256 _amount) external returns(bool) { // 你的内部业务逻辑,比如校验用户是否符合领取资格、是否已经领过等 require(totalClaimed + _amount <= MAX_CLAIM_AMOUNT, "Exceed max claim limit"); _mint(msg.sender, _amount); totalClaimed += _amount; return true; } }
方案2:从初始铸造的代币池发放(不新增总供应量)
如果不想增发代币,要从一开始铸造的2100万枚里给用户发,有两种可选实现:
子方案2-1:提前转币到合约地址
- 合约部署完成后,从部署者地址把要用于发放的代币转到合约地址
- 原有的
transfer逻辑不需要修改就可以正常运行
子方案2-2:通过授权转移部署者账户的代币
不需要把代币转到合约,只要部署者提前给合约地址授权足够的代币额度,合约内部调用transferFrom从部署者账户转币给用户即可:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.2; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract TokenSample is ERC20 { address public immutable deployer; constructor() ERC20("TokenSample", "SMPL") { deployer = msg.sender; _mint(deployer, 21000000 * 10 ** decimals()); } function getToken(uint256 _amount) external returns(bool) { // 你的内部业务逻辑 transferFrom(deployer, msg.sender, _amount); return true; } }
注意:部署完成后需要先调用
approve(合约地址, 发放总额度),从部署者地址给合约授权足够的代币额度,否则调用getToken会报错。
内容的提问来源于stack exchange,提问作者Yusnee
相关产品推荐
相关产品推荐

