JavaScript 不可用。

我们检测到浏览器禁用了 JavaScript。请启用 JavaScript 或改用支持的浏览器来继续访问

什么是 ERC-404?

作者:Anban Chu

发表日期:2024年02月19日

所属目录:合约分析

ERC404 想要解决的问题

ERC-404 是一种图币结合的协议试验

  • ERC-20 可以通过 Uniswap 等平台,很容易解决流动性问题。
  • 基于 ERC-721 的 NFT 虽然有 Opensea 等平台,但因为唯一性的特征,一直都没有很好的解决流动性问题,使用类似拍卖的形式进行交易。

ERC-404 是尝试解决 NFT 流动性的实验协议。

解决方案的总结

把 NFT 的集合建一个对应的资金池,交易 NFT 就是交易币,同时交易币也就是交易 NFT。

ERC-404 的实际流转

假设 1 个 NFT 对应发行 1 个 ERC20 代币:这时候拥有 1 枚代币就会铸造生成 1 个 NFT

  • 当用户地址少于 1 个代币的时候: 只有币,没有图
  • 当用户地址大于 1 个代币的时候: 既有币,也有图
  • 当用户买入代币,每凑满 1 个整数,就会铸造 1 个 NFT
    • 当用户在 Uni 上购买 1 枚代币,用户除了得到代币外,还会收到一个生成的 NFT(ERC-721 标准),可以直接在 NFT 市场上交易
  • 当用户地址恰好有一个代币:当卖出一部分的时候,会随机烧毁一个 NFT。(不能指定)
    • 当用户在 Uni 上出售 1 枚代币,所拥有的 NFT 也随即被自动销毁
  • 当用户将持有的 1 枚代币转账到新地址,原地址的 NFT 被销毁,新地址中会随即生成一个全新的随机 NFT
  • 同理当从市场上购买一个 NFT 后,也会收到 1 个代币,出售这个 NFT 后,代币也会随即消失

注意: 只有拥有整数枚代币才会相对应的生成整数个 NFT,拥有 1.5 个代币,只会生成 1 个 NFT,当拥有 2 个代币,出售了 1.5 个代币后,持有的 2 个 NFT 也会随即被销毁。

ERC-404 售卖中的问题

  • 费用问题
    • 用户在 Uni 上购买代币或转账时,需要承担 NFT 的铸造费用
    • 会新生成 NFT,会有稀有度的变化。可能会对已经持有稀有 NFT 的用户造成损失。或许会导致大家无限的转账来刷稀有度,类似卡 BUG 一样的抽奖玩法。
  • NFT 问题
    • 交易无法指定 NFT,如果用户持有 2 个 NFT 2 个代币,当在 Uni 上出售 1 枚代币时,用户没法选择出售其持有的哪一个 NFT(合约也没有办法判定出售的代币是属于哪 1 个,因为代币是同质化的,而规则是卖掉 1 个代币,需要强制销毁一个 NFT) 。
    • 目前只能通过打散的方式,将一个代币对应一个地址的方式来绕开这个问题。
  • 如果判断授权及对应转账的是 NFT ID 还是 Token 金额。
    • 这里是通过数值大小判断的。通过业务层解决,比如授权,不授权 1e18 以下的数字,(同理授权转账的大小最小为 1e18)
      if (amountOrId <= minted && amountOrId > 0) {
          // NFT ID
          address owner = _ownerOf[amountOrId];
          if (msg.sender != owner && !isApprovedForAll[owner][msg.sender]) {
              revert Unauthorized();
          }
          getApproved[amountOrId] = spender;
          emit Approval(owner, spender, amountOrId);
      } else {
          // Token 金额
          allowance[msg.sender][spender] = amountOrId;
          emit Approval(msg.sender, spender, amountOrId);
      }
    

代码分析

abstract contract ERC404 is Ownable {
    // Events ...省略

    // Errors ...省略

    // ERC20
    string public name;
    string public symbol;
    uint8 public immutable decimals;
    uint256 public immutable totalSupply;
    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;

    /// @dev Current mint counter, monotonically increasing to ensure accurate ownership
    uint256 public minted;

    // ERC721
    mapping(uint256 => address) public getApproved;
    mapping(address => mapping(address => bool)) public isApprovedForAll;
    mapping(uint256 => address) internal _ownerOf; // ID 对应的拥有者
    mapping(address => uint256[]) internal _owned; // 地址拥有的ID
    mapping(uint256 => uint256) internal _ownedIndex; // 跟踪 _owned 映射的索引
    mapping(address => bool) public whitelist; // 白名单 (用来设置交易对、路由器等地址,避免不必要的 mint 和 burn)

    // Constructor
    constructor(string memory _name, string memory _symbol, uint8 _decimals, uint256 _totalNativeSupply, address _owner) Ownable(_owner) {
        name = _name;
        symbol = _symbol;
        decimals = _decimals;
        totalSupply = _totalNativeSupply * (10 ** decimals);
    }

    // 设置白名单,白名单的地址不会收到NFT和销毁NFT
    function setWhitelist(address target, bool state) public onlyOwner {
        whitelist[target] = state;
    }

    // ======================== Functions: View ========================
    // 获取指定ID的owner
    function ownerOf(uint256 id) public view virtual returns (address owner) {
        owner = _ownerOf[id];
        if (owner == address(0)) {
            revert NotFound();
        }
    }

    // 获取指定ID的URL
    function tokenURI(uint256 id) public view virtual returns (string memory);

    /// @notice token approvals
    /// @dev 如果小于等于当前ID,则认为是NFT的ID
    function approve(address spender, uint256 amountOrId) public virtual returns (bool) {
        if (amountOrId <= minted && amountOrId > 0) {
            address owner = _ownerOf[amountOrId];
            if (msg.sender != owner && !isApprovedForAll[owner][msg.sender]) {
                revert Unauthorized();
            }
            getApproved[amountOrId] = spender;
            emit Approval(owner, spender, amountOrId);
        } else {
            allowance[msg.sender][spender] = amountOrId;
            emit Approval(msg.sender, spender, amountOrId);
        }

        return true;
    }

    /// @notice Function native approvals
    function setApprovalForAll(address operator, bool approved) public virtual {
        isApprovedForAll[msg.sender][operator] = approved;
        emit ApprovalForAll(msg.sender, operator, approved);
    }

    /// @notice 混合转移
    /// @dev 如果小于等于当前ID,则认为是NFT的ID
    function transferFrom(address from, address to, uint256 amountOrId) public virtual {
        if (amountOrId <= minted) {
            if (from != _ownerOf[amountOrId]) {
                revert InvalidSender();
            }

            if (to == address(0)) {
                revert InvalidRecipient();
            }

            if (msg.sender != from && !isApprovedForAll[from][msg.sender] && msg.sender != getApproved[amountOrId]) {
                revert Unauthorized();
            }

            balanceOf[from] -= _getUnit();

            unchecked {
                balanceOf[to] += _getUnit();
            }

            _ownerOf[amountOrId] = to;
            delete getApproved[amountOrId];

            // update _owned for sender
            uint256 updatedId = _owned[from][_owned[from].length - 1];
            _owned[from][_ownedIndex[amountOrId]] = updatedId;
            // pop
            _owned[from].pop();
            // update index for the moved id
            _ownedIndex[updatedId] = _ownedIndex[amountOrId];
            // push token to to owned
            _owned[to].push(amountOrId);
            // update index for to owned
            _ownedIndex[amountOrId] = _owned[to].length - 1;

            emit Transfer(from, to, amountOrId);
            emit ERC20Transfer(from, to, _getUnit());
        } else {
            uint256 allowed = allowance[from][msg.sender];

            if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amountOrId;

            _transfer(from, to, amountOrId);
        }
    }

    /// @notice Function for fractional transfers
    function transfer(address to, uint256 amount) public virtual returns (bool) {
        return _transfer(msg.sender, to, amount);
    }

    /// @notice Function for native transfers with contract support
    function safeTransferFrom(address from, address to, uint256 id) public virtual {
        transferFrom(from, to, id);

        if (to.code.length != 0 && ERC721Receiver(to).onERC721Received(msg.sender, from, id, '') != ERC721Receiver.onERC721Received.selector) {
            revert UnsafeRecipient();
        }
    }

    /// @notice Function for native transfers with contract support and callback data
    function safeTransferFrom(address from, address to, uint256 id, bytes calldata data) public virtual {
        transferFrom(from, to, id);

        if (to.code.length != 0 && ERC721Receiver(to).onERC721Received(msg.sender, from, id, data) != ERC721Receiver.onERC721Received.selector) {
            revert UnsafeRecipient();
        }
    }

    /// @notice Internal function for fractional transfers
    function _transfer(address from, address to, uint256 amount) internal returns (bool) {
        uint256 unit = _getUnit();
        uint256 balanceBeforeSender = balanceOf[from];
        uint256 balanceBeforeReceiver = balanceOf[to];

        balanceOf[from] -= amount;

        unchecked {
            balanceOf[to] += amount;
        }

        // Skip burn for certain addresses to save gas
        if (!whitelist[from]) {
            uint256 tokens_to_burn = (balanceBeforeSender / unit) - (balanceOf[from] / unit);
            for (uint256 i = 0; i < tokens_to_burn; i++) {
                _burn(from);
            }
        }

        // Skip minting for certain addresses to save gas
        if (!whitelist[to]) {
            uint256 tokens_to_mint = (balanceOf[to] / unit) - (balanceBeforeReceiver / unit);
            for (uint256 i = 0; i < tokens_to_mint; i++) {
                _mint(to);
            }
        }

        emit ERC20Transfer(from, to, amount);
        return true;
    }

    // Internal utility logic
    function _getUnit() internal view returns (uint256) {
        return 10 ** decimals;
    }

    function _mint(address to) internal virtual {
        if (to == address(0)) {
            revert InvalidRecipient();
        }

        unchecked {
            minted++;
        }

        uint256 id = minted;

        if (_ownerOf[id] != address(0)) {
            revert AlreadyExists();
        }

        _ownerOf[id] = to;
        _owned[to].push(id);
        _ownedIndex[id] = _owned[to].length - 1;

        emit Transfer(address(0), to, id);
    }

    function _burn(address from) internal virtual {
        if (from == address(0)) {
            revert InvalidSender();
        }

        uint256 id = _owned[from][_owned[from].length - 1];
        _owned[from].pop();
        delete _ownedIndex[id];
        delete _ownerOf[id];
        delete getApproved[id];

        emit Transfer(from, address(0), id);
    }

    function _setNameSymbol(string memory _name, string memory _symbol) internal {
        name = _name;
        symbol = _symbol;
    }
}

参考资料





以上就是本篇文章的全部内容了,希望对你有帮助。

>> 本站不提供评论服务,技术交流请在 Twitter 上找我 @anbang_account