You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// SPDX-License-Identifier: MITpragma solidity^0.8.0;
contractBaseERC20 {
// Basic ERC20 token informationstringpublic name;
stringpublic symbol;
uint8public decimals;
// Total supply of tokensuint256public totalSupply;
// Mapping of owner addresses to their token balancesmapping(address=>uint256) balances;
// Mapping of owner addresses to spender addresses and their allowancesmapping(address=>mapping(address=>uint256)) allowances;
// Event emitted when tokens are transferredevent Transfer(addressindexedfrom, addressindexedto, uint256value);
// Event emitted when an allowance for spending tokens is setevent Approval(addressindexedowner, addressindexedspender, uint256value);
// Constructor to initialize token information and mint total supply to deployerconstructor(
stringmemorytokenName,
stringmemorytokenSymbol,
uint8TokenDecimals,
uint256tokenTotalSupply
) {
name = tokenName;
symbol = tokenSymbol;
decimals = TokenDecimals;
totalSupply = tokenTotalSupply * (10** decimals);
balances[msg.sender] = totalSupply;
}
// Returns the token balance of a given addressfunction balanceOf(address_owner) publicviewreturns (uint256balance) {
require(
_owner !=address(0),
"ERC20: balance query for the zero address"
);
return balances[_owner];
}
// Transfers tokens from the message sender to a recipientfunction transfer(address_to, uint256_value) publicreturns (boolsuccess) {
require(_to !=address(0), "ERC20: transfer to the zero address");
require(_value >0, "ERC20: transfer amount must be greater than zero");
uint256 senderBalance = balances[msg.sender];
require(
senderBalance >= _value,
"ERC20: transfer amount exceeds balance"
);
// SafeMath not required for internal transfers in Solidity 0.8+ due to overflow checkingif (_to !=msg.sender) {
balances[msg.sender] = senderBalance - _value;
balances[_to] += _value;
}
emitTransfer(msg.sender, _to, _value);
returntrue;
}
// Transfers tokens on behalf of another address using an allowancefunction transferFrom(
address_from,
address_to,
uint256_value
) publicreturns (boolsuccess) {
require(_to !=address(0), "ERC20: transferFrom to the zero address");
require(
_value >0,
"ERC20: transferFrom amount must be greater than zero"
);
require(
balances[_from] >= _value,
"ERC20: transfer amount exceeds balance"
);
require(
allowances[_from][msg.sender] >= _value,
"ERC20: transfer amount exceeds allowance"
);
balances[_from] -= _value;
balances[_to] += _value;
allowances[_from][msg.sender] -= _value;
emitTransfer(_from, _to, _value);
returntrue;
}
// Approves an address to spend a certain amount of tokens on your behalffunction approve(address_spender, uint256_value) publicreturns (boolsuccess) {
address owner =msg.sender;
require(owner !=address(0), "ERC20: approve from the zero address");
require(_spender !=address(0), "ERC20: approve to the zero address");
allowances[owner][_spender] = _value;
emitApproval(msg.sender, _spender, _value);
returntrue;
}
// Returns the remaining allowance for a spender on a specific owner's tokensfunction allowance(address_owner, address_spender)
publicviewreturns (uint256remaining)
{
require(_owner !=address(0), "ERC20: owner is the zero address");
require(_spender !=address(0), "ERC20: spender is the zero address");
return allowances[_owner][_spender];
}
// SPDX-License-Identifier: MITpragma solidity^0.8.0;
import"@openzeppelin/contracts/token/ERC20/IERC20.sol";
/** * @title TokenBank * @dev A smart contract that allows users to deposit and withdraw ERC20 tokens. */contractTokenBank {
/// @dev A mapping to store balances for each token address and user address.mapping(address=>mapping(address=>uint256)) private balances;
/** * @dev Deposits a specified amount of ERC20 tokens from the caller's address to the contract. * @param token The address of the ERC20 token to deposit. * @param amount The amount of tokens to deposit. * * Emits an event if the deposit is successful. * * Requirements: * - `amount` must be greater than zero. * - The transfer from the caller to the contract must be successful. */function deposit(addresstoken, uint256amount) public {
require(amount >0, "Amount must be greater than zero");
require(
IERC20(token).transferFrom(msg.sender, address(this), amount),
"Transfer failed"
);
balances[token][msg.sender] += amount;
// Emit an event for successful deposits (optional)// emit Deposit(msg.sender, token, amount);
}
/** * @dev Withdraws a specified amount of ERC20 tokens from the contract to the caller's address. * @param token The address of the ERC20 token to withdraw. * @param amount The amount of tokens to withdraw. * * Emits an event if the withdrawal is successful. * * Requirements: * - `amount` must be greater than zero. * - The caller must have sufficient balance of the specified token. * - The transfer from the contract to the caller must be successful. */function withdraw(addresstoken, uint256amount) public {
require(amount >0, "Amount must be greater than zero");
require(balances[token][msg.sender] >= amount, "Insufficient balance");
balances[token][msg.sender] -= amount;
require(IERC20(token).transfer(msg.sender, amount), "Transfer failed");
// Emit an event for successful withdrawals (optional)// emit Withdrawal(msg.sender, token, amount);
}
/** * @dev Returns the balance of a specific ERC20 token for a given user. * @param token The address of the ERC20 token. * @param account The address of the user. * @return The balance of the specified token for the user. */function balanceOf(addresstoken, addressaccount) publicviewreturns (uint256) {
return balances[token][account];
}
}
相关背景
在手把手教你实现BigBank文章中,我们实现了一个稍微复杂点的存款、取款业务。但是聪明的你可能发现了,我们的
BigBank
虽然名字中带有big
,但是有一个明显的缺点:这就好比你去一家银行,只能存取人民币,你想去换点港币去香港旅游,但是银行告诉你不支持港币业务,这就很让人头疼。
这篇文章,我们就从ERC20代币开始分析,逐步实现我们的
TokenBank
合约。功能实现要求
先从ERC20说起
ERC-20标准是用于在以太坊区块链上创建智能合约的代币的技术规范。它定义了一系列规则,所有ERC-20代币都必须遵守这些规则,以便它们能够与以太坊网络上的其他代币和应用程序兼容。
ERC-20标准的主要目的是确保所有ERC-20代币都具有相同的基本功能,使其易于使用和集成。这使得开发人员可以轻松地创建新的代币,并确信它们将能够与现有的以太坊基础设施和工具一起工作。
还记的我们介绍过的接口吗?这里可以把标准认为是接口,我们自己铸造的ERC20 Token必须继承这个接口。并实现它其中的方法。
ERC-20标准定义的基本功能
ERC-20标准定义了代币合约必须实现的一些基本功能,包括:
而可选功能则包括:
一个简单的 ERC20 Token 示例:
在真实的生产环境中,我们一般不会亲自去手动实现,一方面社区已经比较成熟的方案和标准库供我们调用,不需要我们重复造轮子,一方面,我们自己写的如果测试不充分,可能会有很多的漏洞。
但是为了学习,这里提供一份符合
ERC20
标准的Token代码。不太好理解的allowances
从上面代码中可以看出,我们将
allowances
设置为了嵌套映射。这么设计是进过慎重思考的。如果你觉得还是不够直观,这里我们可以用一个图示来说明一下:
让我解释一下这个嵌套映射和可视化图表:
基本结构:
这个结构可以理解为一个两层的映射:
具体例子:
假设我们有以下情况:
在代码中的表示:
可视化解释:
如何使用:
allowances[Alice的地址][Bob的地址]
allowances[Bob的地址][Charlie的地址]
这种结构允许在 ERC20 代币中实现复杂的授权机制,使得用户可以安全地允许其他地址(如智能合约)代表自己使用一定数量的代币,而无需转移代币的所有权。
如何理解approve?
approve
函数。是非常重要的,它在 ERC20 代币的授权机制中扮演着关键角色。我们上面解释的allowances
在这个函数中就有比较重要的应用。让我们逐步深入理解
approve
函数:基本定义:
目的:
approve
函数允许代币持有者(调用者)授权另一个地址(通常是一个合约)代表自己使用一定数量的代币。(这个点非常重要,后续的TokenBank有个坑就是因为这个)参数:
spender
:被授权的地址,可以是另一个用户的地址或一个合约地址。amount
:授权使用的代币数量。返回值:
工作原理:
msg.sender
(调用者)允许spender
使用的代币数量。Approval
事件,记录这次授权操作。使用场景:
重要注意事项:
approve
不会检查用户的余额。用户可以授权超过自己拥有的代币数量。approve
都会覆盖之前的授权值,而不是增加或减少。与其他函数的关系:
transferFrom
:使用approve
设置的授权额度。allowance
:查询当前的授权额度。实际应用示例:
假设 Alice 想要使用一个去中心化交易所(DEX):
理解
approve
函数对于掌握 ERC20 代币的工作机制非常重要。它为代币持有者提供了一种安全的方式来允许其他地址或合约管理他们的部分代币,而无需完全转移所有权。这种机制极大地增加了 ERC20 代币的灵活性和实用性,尤其是在复杂的 DeFi 应用中。transfer 和 transferFrom函数的区别
transfer
和transferFrom
是 ERC20 标准中两个核心函数,它们有着不同的用途和工作方式。基本定义:
transfer
:transferFrom
:主要区别:
a) 调用者:
transfer
: 直接由代币持有者调用。transferFrom
: 可以由第三方调用,前提是该第三方已被授权。b) 资金来源:
transfer
: 资金总是从调用者(msg.sender
)的账户转出。transferFrom
: 资金从sender
参数指定的账户转出,而不一定是调用者的账户。c) 授权机制:
transfer
: 不需要预先授权。transferFrom
: 需要sender
事先通过approve
函数授权给调用者足够的额度。用途:
transfer
:transferFrom
:工作流程:
transfer
:transfer
。transferFrom
:approve
,授权某地址使用一定数量的代币。transferFrom
。使用场景举例:
transfer
: 用户A直接向用户B发送代币。transferFrom
: 去中心化交易所代表用户执行交易,或智能合约自动从用户账户扣除订阅费用。实现TokenBank合约:
细心的朋友可能发现了,我们直接引用了
openzeppelin
接口,这也是上文中提到的,真实的生产环境中,我们不需要自己实现这些能力。如何使用和验证我们的合约是否正确呢?
部署ERC20合约:
我们首先部署ERC20合约,这里为了方便起见,我们还是使用remix, 因为需要钱包授权交互,我们使用真实的测试网络。
我这里直接贴出来我自己部署的合约地址:
部署成功后,可以看到这个样子:
将ERC20代币添加到自己的钱包中:
为了交互方便,我们需要将这个token添加到自己的钱包中:
添加完代币之后,我们可以代币列表中看到我们刚刚添加的代币。
部署TokenBank合约:
我这里直接贴出来我自己部署的合约地址:
用户调用ERC20的approve
为了顺利实现
TokenBank
的功能,我们需要用户授权TokenBank
合约调用ERC20
一部分的份额。授权一定要在存款和取款行为之前,否则交易会失败。
调用deposit方法:
我们调用deposit方法,入参填入ERC20合约地址和需要存入的数量
点击拉起钱包交互,点击确认。交易成功。
调用withdraw方法:
调用withdraw方法,入参填入自己的钱包地址和取款存入的数量;
整个流程跑通。
The text was updated successfully, but these errors were encountered: