ERC20
ERC20 代币标准是一种针对 同质化代币 的规范,这是一种所有单位彼此完全相等的代币类型。
token::erc20::ERC20Component
提供了在 Cairo 中用于 Starknet 的 EIP-20 的近似实现。
在 Contracts v0.7.0 之前,ERC20 合约存储并从存储中读取 decimals ;但是,此实现返回静态的 18 。
如果升级的小数位数不是 18 的旧 ERC20 合约,则升级后的合约*必须*使用自定义的 decimals 实现。
请参阅 自定义小数位数 指南。
|
用法
使用 Contracts for Cairo 构建 ERC20 合约需要设置构造函数并实例化代币实现。 如下所示:
#[starknet::contract]
mod MyToken {
use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig};
use starknet::ContractAddress;
component!(path: ERC20Component, storage: erc20, event: ERC20Event);
// ERC20 Mixin
#[abi(embed_v0)]
impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl<ContractState>;
impl ERC20InternalImpl = ERC20Component::InternalImpl<ContractState>;
#[storage]
struct Storage {
#[substorage(v0)]
erc20: ERC20Component::Storage
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
#[flat]
ERC20Event: ERC20Component::Event
}
#[constructor]
fn constructor(
ref self: ContractState,
initial_supply: u256,
recipient: ContractAddress
) {
let name = "MyToken";
let symbol = "MTK";
self.erc20.initializer(name, symbol);
self.erc20.mint(recipient, initial_supply);
}
}
MyToken
使用 embed 指令集成了 ERC20Impl
和 ERC20MetadataImpl
,该指令将实现标记为合约中的外部实现。
虽然 ERC20MetadataImpl
是可选的,但通常建议包含它,因为绝大多数 ERC20 代币都提供元数据方法。
上面的示例还包括 ERC20InternalImpl
实例。
这允许合约的构造函数初始化合约并创建代币的初始供应量。
有关 ERC20 代币机制的更完整指南,请参阅 创建 ERC20 供应量。 |
接口
以下接口表示 Contracts for Cairo ERC20Component 的完整 ABI。 该接口包括 IERC20 标准接口以及可选的 IERC20Metadata。
为了支持较早的代币部署,如 双重接口 中所述,该组件还包括以 camelCase 编写的接口的实现。
#[starknet::interface]
pub trait ERC20ABI {
// IERC20
fn total_supply() -> u256;
fn balance_of(account: ContractAddress) -> u256;
fn allowance(owner: ContractAddress, spender: ContractAddress) -> u256;
fn transfer(recipient: ContractAddress, amount: u256) -> bool;
fn transfer_from(
sender: ContractAddress, recipient: ContractAddress, amount: u256
) -> bool;
fn approve(spender: ContractAddress, amount: u256) -> bool;
// IERC20Metadata
fn name() -> ByteArray;
fn symbol() -> ByteArray;
fn decimals() -> u8;
// IERC20Camel
fn totalSupply() -> u256;
fn balanceOf(account: ContractAddress) -> u256;
fn transferFrom(
sender: ContractAddress, recipient: ContractAddress, amount: u256
) -> bool;
}
自定义小数位数
像 Solidity 一样,Cairo 不支持https://en.wikipedia.org//wiki/Floating-point_arithmetic[浮点数]。
为了解决这个限制,ERC20 代币合约可以提供一个 decimals
字段,该字段向外部接口(钱包、交易所等)传达应如何显示代币。
例如,假设一个代币的 decimals
值为 3
,并且代币总供应量为 1234
。
外部接口会将代币供应量显示为 1.234
。
但是,在实际合约中,供应量仍然是整数 1234
。
换句话说,小数位数域绝不会改变实际的算术,因为所有操作仍然在整数上执行。
大多数合约使用 18
位小数,甚至有人提议将其设为强制性(请参阅 EIP 讨论)。
静态方法 (SRC-107)
Contracts for Cairo ERC20
组件利用 SRC-107 来允许使用静态且可配置的小数位数。
要使用默认的 18
位小数,您只需导入 DefaultConfig
实现即可:
#[starknet::contract]
mod MyToken {
// Importing the DefaultConfig implementation would make decimals 18 by default.
// 导入 DefaultConfig 实现将默认使小数位数为 18。
use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig};
use starknet::ContractAddress;
component!(path: ERC20Component, storage: erc20, event: ERC20Event);
#[abi(embed_v0)]
impl ERC20Impl = ERC20Component::ERC20Impl<ContractState>;
#[abi(embed_v0)]
impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl<ContractState>;
impl ERC20InternalImpl = ERC20Component::InternalImpl<ContractState>;
(...)
}
要自定义此值,您可以在合约本地实现 ImmutableConfig 特征。
以下示例展示了如何将小数位数设置为 6
:
mod MyToken {
use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl};
use starknet::ContractAddress;
component!(path: ERC20Component, storage: erc20, event: ERC20Event);
#[abi(embed_v0)]
impl ERC20Impl = ERC20Component::ERC20Impl<ContractState>;
#[abi(embed_v0)]
impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl<ContractState>;
impl ERC20InternalImpl = ERC20Component::InternalImpl<ContractState>;
(...)
// Custom implementation of the ERC20Component ImmutableConfig.
// ERC20Component ImmutableConfig 的自定义实现。
impl ERC20ImmutableConfig of ERC20Component::ImmutableConfig {
const DECIMALS: u8 = 6;
}
}
存储方法
对于更复杂的场景,例如工厂部署具有不同小数位数值的多个代币,灵活的解决方案可能更合适。
请注意,在这种情况下我们没有使用 MixinImpl 或 DefaultConfig,因为我们需要自定义 IERC20Metadata 实现。 |
#[starknet::contract]
mod MyToken {
use openzeppelin_token::erc20::interface;
use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl};
use starknet::ContractAddress;
component!(path: ERC20Component, storage: erc20, event: ERC20Event);
#[abi(embed_v0)]
impl ERC20Impl = ERC20Component::ERC20Impl<ContractState>;
#[abi(embed_v0)]
impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl<ContractState>;
impl ERC20InternalImpl = ERC20Component::InternalImpl<ContractState>;
#[storage]
struct Storage {
#[substorage(v0)]
erc20: ERC20Component::Storage,
// The decimals value is stored locally
// 小数位数的值存储在本地
decimals: u8,
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
#[flat]
ERC20Event: ERC20Component::Event,
}
#[constructor]
fn constructor(
ref self: ContractState, decimals: u8, initial_supply: u256, recipient: ContractAddress,
) {
// Call the internal function that writes decimals to storage
// 调用将小数位数写入存储的内部函数
self._set_decimals(decimals);
// Initialize ERC20
// 初始化 ERC20
let name = "MyToken";
let symbol = "MTK";
self.erc20.initializer(name, symbol);
self.erc20.mint(recipient, initial_supply);
}
#[abi(embed_v0)]
impl ERC20CustomMetadataImpl of interface::IERC20Metadata<ContractState> {
fn name(self: @ContractState) -> ByteArray {
self.erc20.ERC20_name.read()
}
fn symbol(self: @ContractState) -> ByteArray {
self.erc20.ERC20_symbol.read()
}
fn decimals(self: @ContractState) -> u8 {
self.decimals.read()
}
}
#[generate_trait]
impl InternalImpl of InternalTrait {
fn _set_decimals(ref self: ContractState, decimals: u8) {
self.decimals.write(decimals);
}
}
}
此合约需要构造函数中的 decimals
参数,并使用内部函数将小数位数写入存储。
请注意,decimals
状态变量必须在合约的存储中定义,因为此变量不存在于 OpenZeppelin Contracts for Cairo 提供的组件中。
在这种特定情况下,重要的是包括自定义 ERC20 元数据实现,而不是使用 Contracts for Cairo ERC20MetadataImpl
,因为 decimals
方法将始终返回 18
。