ERC-20

一个 ERC-20 token 合约会跟踪 fungible 代币:任何代币与任何其他代币完全相等;没有代币具有与其相关的特殊权利或行为。 这使得 ERC-20 代币可用于诸如*交换媒介货币*、投票权、*质押*等用途。

OpenZeppelin Contracts 为 Arbitrum Stylus 提供了许多与 ERC20 相关的合约。 在 API reference 中,你可以找到有关其属性和用法的详细信息。

构建 ERC-20 代币合约

使用 Contracts,我们可以轻松创建自己的 ERC-20 代币合约,该合约将用于跟踪 Gold(GLD),这是一种假设游戏中的内部货币。

这是我们的 GLD 代币的样子。

use openzeppelin_stylus::{
    token::erc20::{
        self,
        extensions::{Erc20Metadata, IErc20Metadata},
        Erc20, IErc20,
    },
};

#[entrypoint]
#[storage]
struct GLDToken {
    erc20: Erc20,
    metadata: Erc20Metadata,
}

#[public]
#[implements(IErc20<Error = erc20::Error>, IErc20Metadata, IErc165)]
impl GLDToken {
    #[constructor]
    fn constructor(&mut self, name: String, symbol: String) {
        self.metadata.constructor(name, symbol);
    }

    // ...
}

#[public]
impl IErc20 for GLDToken {
    type Error = erc20::Error;

    fn total_supply(&self) -> U256 {
        self.erc20.total_supply()
    }

    fn balance_of(&self, account: Address) -> U256 {
        self.erc20.balance_of(account)
    }

    fn transfer(
        &mut self,
        to: Address,
        value: U256,
    ) -> Result<bool, Self::Error> {
        self.erc20.transfer(to, value)
    }

    fn allowance(&self, owner: Address, spender: Address) -> U256 {
        self.erc20.allowance(owner, spender)
    }

    fn approve(
        &mut self,
        spender: Address,
        value: U256,
    ) -> Result<bool, Self::Error> {
        self.erc20.approve(spender, value)
    }

    fn transfer_from(
        &mut self,
        from: Address,
        to: Address,
        value: U256,
    ) -> Result<bool, Self::Error> {
        self.erc20.transfer_from(from, to, value)
    }
}

#[public]
impl IErc20Metadata for GLDToken {
    fn name(&self) -> String {
        self.metadata.name()
    }

    fn symbol(&self) -> String {
        self.metadata.symbol()
    }

    fn decimals(&self) -> U8 {
        self.metadata.decimals()
    }
}

#[public]
impl IErc165 for GLDToken {
    fn supports_interface(&self, interface_id: B32) -> bool {
        self.erc20.supports_interface(interface_id)
            || self.metadata.supports_interface(interface_id)
    }
}

我们的合约通常通过 stylus-sdk 继承 使用,并且我们在这里重用 ERC20 用于基本标准实现以及可选扩展。

关于 decimals 的说明

通常,你可能希望能够将你的代币分成任意金额:例如,如果你拥有 5 GLD,你可能希望将 1.5 GLD 发送给朋友,并保留 3.5 GLD 给自己。 不幸的是,Solidity 和 EVM 不支持此行为:只能使用整数(完整)数字,这会带来问题。 你可以发送 12 个代币,但不能发送 1.5 个。

为了解决这个问题,ERC20 提供了一个 decimals 字段,用于指定代币有多少个小数位。 为了能够转移 1.5 GLDdecimals 必须至少为 1,因为该数字具有一位小数。

如何实现这一点? 实际上非常简单:代币合约可以使用更大的整数值,因此余额为 50 将代表 5 GLD,转移 15 将对应于发送 1.5 GLD,依此类推。

重要的是要理解 decimals 仅用于显示目的。 合约内的所有算术运算仍然以整数执行,并且必须由不同的用户界面(钱包、交易所等)根据 decimals 调整显示的值。 GLD 中未指定每个帐户的总代币供应量和余额:你需要除以 10 ** decimals 才能获得实际的 GLD 金额。

你可能想要使用 18decimals 值,就像 Ether 和大多数正在使用的 ERC-20 代币合约一样,除非你有特殊原因不这样做。 当铸造代币或转移它们时,你实际上将发送数字 GLD * (10 ** decimals)

默认情况下,ERC20 使用 18 作为 decimals 的值。

要使用不同的值,你需要覆盖合约中的 decimals() 函数。例如,要使用 16 个小数位,你可以这样做:

fn decimals(&self) -> U8 {
    U8::from(16)
}

因此,如果你想使用具有 18 个小数位的代币合约发送 5 个代币,则实际调用的方法将是:

token.transfer(recipient, 5 * uint!(10_U256).pow(uint!(18_U256)));

扩展

此外,还有多个自定义扩展,包括:

  • ERC-20 Burnable:销毁自有代币。

  • ERC-20 Capped:在铸造代币时强制执行总供应量的上限。

  • ERC-20 Metadata:扩展的 ERC20 接口,包括 name、symbol 和 decimals 函数。

  • ERC-20 Pausable:暂停代币转移的能力。

  • ERC-20 Permit:无 Gas 批准代币(标准化为 EIP-2612)。

  • ERC-4626:代币化的 vault,管理由资产(另一个 ERC-20)支持的股份(表示为 ERC-20)。

  • ERC-20 Flash-Mint:通过临时代币的铸造和销毁,为闪电贷提供代币级别的支持(标准化为 EIP-3156)。

  • ERC-20 Wrapper:包装器,用于创建由另一个 ERC-20 支持的 ERC-20,具有存款和取款方法。