ERC1155
多代币标准
ERC1155 的显著特点是它使用单个智能合约一次性表示多个代币。 这就是为什么它的 balance_of 函数与 ERC20 和 ERC777 的不同:它有一个额外的 ID 参数,用于标识你要查询余额的代币。
这与 ERC721 的工作方式类似,但在该标准中,代币 ID 没有余额的概念:每个代币都是非同质化的,存在或不存在。 ERC721 的 balance_of 函数指的是一个账户有多少不同的代币,而不是每个代币有多少。 另一方面,在 ERC1155 中,账户对每个代币 ID 都有不同的余额,而非同质化代币的实现方式是简单地铸造其中的一个。
这种方法为需要多种代币的项目节省了大量的 Gas。 无需为每种代币类型部署新的合约,单个 ERC1155 代币合约可以持有整个系统状态,从而降低部署成本和复杂性。
用法
使用 Contracts for Cairo,构建 ERC1155 合约需要集成 ERC1155Component
和 SRC5Component
。
该合约还应设置构造函数以初始化代币的 URI 和接口支持。
这是一个基本合约的示例:
#[starknet::contract]
mod MyERC1155 {
use openzeppelin_introspection::src5::SRC5Component;
use openzeppelin_token::erc1155::{ERC1155Component, ERC1155HooksEmptyImpl};
use starknet::ContractAddress;
component!(path: ERC1155Component, storage: erc1155, event: ERC1155Event);
component!(path: SRC5Component, storage: src5, event: SRC5Event);
// ERC1155 Mixin
#[abi(embed_v0)]
impl ERC1155MixinImpl = ERC1155Component::ERC1155MixinImpl<ContractState>;
impl ERC1155InternalImpl = ERC1155Component::InternalImpl<ContractState>;
#[storage]
struct Storage {
#[substorage(v0)]
erc1155: ERC1155Component::Storage,
#[substorage(v0)]
src5: SRC5Component::Storage
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
#[flat]
ERC1155Event: ERC1155Component::Event,
#[flat]
SRC5Event: SRC5Component::Event
}
#[constructor]
fn constructor(
ref self: ContractState,
token_uri: ByteArray,
recipient: ContractAddress,
token_ids: Span<u256>,
values: Span<u256>
) {
self.erc1155.initializer(token_uri);
self
.erc1155
.batch_mint_with_acceptance_check(recipient, token_ids, values, array![].span());
}
}
接口
以下接口表示 Contracts for Cairo ERC1155Component 的完整 ABI。 该接口包括 IERC1155 标准接口和可选的 IERC1155MetadataURI 接口以及 ISRC5。
为了支持较早的代币部署,如 双重接口 中所述,该组件还包括以 camelCase 编写的接口的实现。
#[starknet::interface]
pub trait ERC1155ABI {
// IERC1155
fn balance_of(account: ContractAddress, token_id: u256) -> u256;
fn balance_of_batch(
accounts: Span<ContractAddress>, token_ids: Span<u256>
) -> Span<u256>;
fn safe_transfer_from(
from: ContractAddress,
to: ContractAddress,
token_id: u256,
value: u256,
data: Span<felt252>
);
fn safe_batch_transfer_from(
from: ContractAddress,
to: ContractAddress,
token_ids: Span<u256>,
values: Span<u256>,
data: Span<felt252>
);
fn is_approved_for_all(
owner: ContractAddress, operator: ContractAddress
) -> bool;
fn set_approval_for_all(operator: ContractAddress, approved: bool);
// IERC1155MetadataURI
fn uri(token_id: u256) -> ByteArray;
// ISRC5
fn supports_interface(interface_id: felt252) -> bool;
// IERC1155Camel
fn balanceOf(account: ContractAddress, tokenId: u256) -> u256;
fn balanceOfBatch(
accounts: Span<ContractAddress>, tokenIds: Span<u256>
) -> Span<u256>;
fn safeTransferFrom(
from: ContractAddress,
to: ContractAddress,
tokenId: u256,
value: u256,
data: Span<felt252>
);
fn safeBatchTransferFrom(
from: ContractAddress,
to: ContractAddress,
tokenIds: Span<u256>,
values: Span<u256>,
data: Span<felt252>
);
fn isApprovedForAll(owner: ContractAddress, operator: ContractAddress) -> bool;
fn setApprovalForAll(operator: ContractAddress, approved: bool);
}
ERC1155 兼容性
虽然 Starknet 与 EVM 不兼容,但此实现旨在尽可能接近 ERC1155 标准,但仍然可以发现一些差异,例如:
-
safe_transfer_from
和safe_batch_transfer_from
中的可选data
参数实现为Span<felt252>
。 -
符合
IERC1155Receiver
的合约必须实现 SRC5 并注册IERC1155Receiver
接口 ID。 -
IERC1155Receiver::on_erc1155_received
必须在成功时返回该接口 ID。
批量操作
由于所有状态都保存在单个合约中,因此可以在单个事务中非常有效地对多个代币进行操作。 该标准提供了两个函数 balance_of_batch 和 safe_batch_transfer_from,它们使查询多个余额和转移多个代币更简单且 Gas 消耗更少。 我们还有用于非批量操作的 safe_transfer_from。
本着标准的精神,我们还在非标准函数中包含了批量操作,例如 batch_mint_with_acceptance_check。
虽然 safe_transfer_from 和 safe_batch_transfer_from 通过检查接收者是否可以处理代币来防止损失,但这会将执行权交给接收者,这可能会导致 重入调用。 |
接收代币
为了确保非账户合约可以安全地接受 ERC1155 代币,该合约必须实现 IERC1155Receiver
接口。
接收者合约还必须实现支持接口自省的 SRC5 接口。
IERC1155Receiver
#[starknet::interface]
pub trait IERC1155Receiver {
fn on_erc1155_received(
operator: ContractAddress,
from: ContractAddress,
token_id: u256,
value: u256,
data: Span<felt252>
) -> felt252;
fn on_erc1155_batch_received(
operator: ContractAddress,
from: ContractAddress,
token_ids: Span<u256>,
values: Span<u256>,
data: Span<felt252>
) -> felt252;
}
实现 IERC1155Receiver
接口会公开 on_erc1155_received 和 on_erc1155_batch_received 方法。
当调用 safe_transfer_from 和 safe_batch_transfer_from 时,它们分别调用接收者合约的 on_erc1155_received
或 on_erc1155_batch_received
方法,这些方法*必须*返回 IERC1155Receiver 接口 ID。
否则,事务将失败。
有关如何计算接口 ID 的信息,请参阅 计算接口 ID。 |
创建代币接收器合约
Contracts for Cairo ERC1155ReceiverComponent 已经为安全代币转移返回了正确的接口 ID。
要将 IERC1155Receiver
接口集成到合约中,只需将 ABI 嵌入指令包含到实现中,并在合约的构造函数中添加 initializer
。
这是一个简单的代币接收器合约的示例:
#[starknet::contract]
mod MyTokenReceiver {
use openzeppelin_introspection::src5::SRC5Component;
use openzeppelin_token::erc1155::ERC1155ReceiverComponent;
use starknet::ContractAddress;
component!(path: ERC1155ReceiverComponent, storage: erc1155_receiver, event: ERC1155ReceiverEvent);
component!(path: SRC5Component, storage: src5, event: SRC5Event);
// ERC1155Receiver Mixin
#[abi(embed_v0)]
impl ERC1155ReceiverMixinImpl = ERC1155ReceiverComponent::ERC1155ReceiverMixinImpl<ContractState>;
impl ERC1155ReceiverInternalImpl = ERC1155ReceiverComponent::InternalImpl<ContractState>;
#[storage]
struct Storage {
#[substorage(v0)]
erc1155_receiver: ERC1155ReceiverComponent::Storage,
#[substorage(v0)]
src5: SRC5Component::Storage
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
#[flat]
ERC1155ReceiverEvent: ERC1155ReceiverComponent::Event,
#[flat]
SRC5Event: SRC5Component::Event
}
#[constructor]
fn constructor(ref self: ContractState) {
self.erc1155_receiver.initializer();
}
}