创建 ERC20 供应量

在 Starknet 上构建的代币所实现的标准接口来自 Ethereum 上流行的代币标准,称为 ERC20。 EIP20,ERC20 合约由此派生,但未指定如何创建代币。 本指南将介绍创建固定和动态代币供应量的策略。

固定供应量

假设我们要创建一个名为 MyToken 的代币,它具有固定的代币供应量。 我们可以通过在构造函数中设置代币供应量来实现这一点,构造函数将在部署时执行。

#[starknet::contract]
mod MyToken {
    use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl};
    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,
        fixed_supply: u256,
        recipient: ContractAddress
    ) {
        let name = "MyToken";
        let symbol = "MTK";

        self.erc20.initializer(name, symbol);
        self.erc20.mint(recipient, fixed_supply);
    }
}

在构造函数中,我们首先调用 ERC20 初始化器来设置代币名称和符号。 接下来,我们调用内部 mint 函数,该函数创建 fixed_supply 的代币并将它们分配给 recipient。 由于内部 mint 未在我们的合约中公开,因此不可能创建更多代币。 换句话说,我们已经实现了一个固定的代币供应量!

动态供应量

具有动态供应量的 ERC20 合约包括创建或销毁代币的机制。 让我们对强大的 MyToken 合约进行一些更改,并创建一个铸币机制。

#[starknet::contract]
mod MyToken {
    use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl};
    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) {
        let name = "MyToken";
        let symbol = "MTK";

        self.erc20.initializer(name, symbol);
    }

    #[external(v0)]
    fn mint(
        ref self: ContractState,
        recipient: ContractAddress,
        amount: u256
    ) {
        // 此函数未受保护,这意味着
        // 任何人都可以铸造代币
        self.erc20.mint(recipient, amount);
    }
}

上面公开的 mint 将创建 amount 个代币并将它们分配给 recipient。 我们现在有了铸币机制!

但是,存在一个大问题。 mint 不包括对谁可以调用此函数的任何限制。 为了良好实践,让我们使用 Ownable 实现一个简单的权限机制。

#[starknet::contract]
mod MyToken {

    (...)

    // 集成 Ownable

    #[external(v0)]
    fn mint(
        ref self: ContractState,
        recipient: ContractAddress,
        amount: u256
    ) {
        // 使用 Ownable 设置权限
        self.ownable.assert_only_owner();

        // 如果由合约所有者调用,则铸造代币
        self.erc20.mint(recipient, amount);
    }
}

在构造函数中,我们传递所有者地址以设置 MyToken 合约的所有者。 mint 函数包含 assert_only_owner,这将确保只有合约所有者可以调用此函数。 现在,我们有了一个受保护的 ERC20 铸币机制来创建动态代币供应量。

有关权限机制的更详尽解释,请参见 访问控制