# ERC3525 为什么关注3525 - 为数不多的状态为final的资产协议 --- ## 可计算NFT - Uniswap V2的 提供流动性凭证,为什么不可以拆分? - 非同质化与可计算性是两件事,在 ERC721 里任意两个 NFT 都不能相加,连加法都不能做 - ERC20可以拆分,但在ERC20中,如果想要生产100种商品,需要部署100个ERC20合约 - 在ERC721中,如果想要生产100种商品,只需要部署一个合约,但是它的缺陷在于不可计算, - 换言之,这种忽略与抽象本质上是一种再匀质化的过程,我们将本来非匀质的、彼此不同的一组物品在概念上变得各个相同的匀质物品了。既然是匀质的,在很多场景中就可以进行计算了。其中一个最突出的场景就是价格计算。比如一套邮票价格是 200 元,那么另一套同版的邮票价格就是 200 元。几乎不会有人会因为这两套邮票有几个像素的差别而要求不同的价格。同样的,一张三年期 100 元面值的国库券现价是 90 元,那么10 张同样面值的国库券现价就应该是 900 元,不会有人因为其中有一张国库券有一个折角而给出不同的估值。 - 由此可见,NFT 的不可计算性绝非天经地义。对于很大一类 NFT 来说,可计算将为它们的实际应用带来巨大的便利。 - 标准产品的数字代表 - 各种单据和证书 - 数字金融票证和标准化合约 - 限量发行的数字内容 - 限量发行的虚拟商品 - 可份额化的权利证书 - 基于这个思考,mengyan提出了vNFT的概念,致力于为非匀质化通证 NFT 添加可计算性(computability),即在保留 NFT 强大的个性化描述能力的同时,使“数量”成为其 NFT 的核心属性,使之能够支持数学运算。可计算 NFT 是兼容 ERC721 的“超级 NFT”,同时具备 ERC20 与 ERC721 的特长,是通用的非货币类数字资产描述协议,可以广泛应用于数字艺术品、数字出版物、数字票证、标准化虚拟商品的描述和表示,也可以成为线下实体商品映射上链的理想协议。 - --- ## 什么是ERC3525 ERC3525简单来说是ERC1155 + slot 再1155的基础上增加了一个物品的属性,它抽象出了物品的数量和属性特征,举个例子,在游戏中的某个商品,比如枪支,可以生成一个简单的721合约,每一把枪都是一个NFT,如果再附加数量的特性,就称为了1155代币,例如某个类型的枪有1000把,在1155合约中,就可以使用tokenId + amount的形式来表示,再对tokenid做一层归类,类别属性用slots来表示,就成为了3525代币,例如这把枪带有消音器,另一把枪则有瞄准镜,“瞄准镜”,“消音器”就是slot,一把枪可以用多个slot修饰,也可以有很多个相同slot的枪。 核心在于三个点 - 第一,将“数量”作为 vNFT 的核心属性; - 第二,提供一套标准的抽象与归类的机制,即 SLOT 机制; - 第三,与 ERC721 兼容。 --- ## 核心代码讲解 ```solidity= //SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.0; import "ERC721Enumerable.sol"; import "./interface/IERC3525.sol"; import "./interface/IERC3525Metadata.sol"; import "./interface/IERC3525Receiver.sol"; contract ERC3525 is IERC3525, IERC3525Metadata, ERC721Enumerable { using Address for address; using Strings for uint256; struct ApproveData { address[] approvals; mapping(address => uint256) allowances; } /// @dev tokenId => values mapping(uint256 => uint256) internal _values; /// @dev tokenId => operator => units mapping(uint256 => ApproveData) private _approvedValues; /// @dev tokenId => slot mapping(uint256 => uint256) internal _slots; string private _name; string private _symbol; uint8 private _decimals; constructor( string memory name_, string memory symbol_, uint8 decimals_) ERC721(name_, symbol_) { _decimals = decimals_; } function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721Enumerable) returns (bool) { return interfaceId == type(IERC3525).interfaceId || interfaceId == type(IERC3525Metadata).interfaceId || super.supportsInterface(interfaceId); } function valueDecimals() public view virtual override returns (uint8) { return _decimals; } function balanceOf(uint256 tokenId_) public view virtual override returns (uint256) { require( _exists(tokenId_), "ERC3525: balance query for nonexistent token"); return _values[tokenId_]; } function slotOf(uint256 tokenId_) public view virtual override returns (uint256) { require(_exists(tokenId_), "ERC3525: slot query for nonexistent token"); return _slots[tokenId_]; } function contractURI() public view virtual override returns (string memory) { string memory baseURI = _baseURI(); return bytes(baseURI).length > 0 ? string( abi.encodePacked( baseURI, "contract/", Strings.toHexString(uint256(uint160(address(this)))))) : ""; } function slotURI(uint256 slot_) public view virtual override returns (string memory) { string memory baseURI = _baseURI(); return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, "slot/", slot_.toString())) : ""; } function approve( uint256 tokenId_, address to_, uint256 value_) external payable virtual override { address owner = ERC721.ownerOf(tokenId_); require(to_ != owner, "ERC3525: approval to current owner"); require( ERC721._isApprovedOrOwner(_msgSender(), tokenId_), "ERC3525: approve caller is not owner nor approved for all" ); _approveValue(tokenId_, to_, value_); } function allowance(uint256 tokenId_, address operator_) public view virtual override returns (uint256) { return _approvedValues[tokenId_].allowances[operator_]; } function transferFrom( uint256 fromTokenId_, address to_, uint256 value_) public payable virtual override returns (uint256) { _spendAllowance(_msgSender(), fromTokenId_, value_); uint256 newTokenId = _getNewTokenId(fromTokenId_); _mint(to_, newTokenId, _slots[fromTokenId_]); _transfer(fromTokenId_, newTokenId, value_); return newTokenId; } function transferFrom( uint256 fromTokenId_, uint256 toTokenId_, uint256 value_) public payable virtual override { _spendAllowance(_msgSender(), fromTokenId_, value_); _transfer(fromTokenId_, toTokenId_, value_); } function _mint( address to_, uint256 tokenId_, uint256 slot_) private { ERC721._mint(to_, tokenId_); _slots[tokenId_] = slot_; emit SlotChanged(tokenId_, 0, slot_); } //在使用3525之前,首先要创建一个721的合约 function _mintValue( address to_, uint256 tokenId_, uint256 slot_, uint256 value_) internal virtual { require(to_ != address(0), "ERC3525: mint to the zero address"); require(tokenId_ != 0, "ERC3525: cannot mint zero tokenId"); require(!_exists(tokenId_), "ERC3525: token already minted"); _mint(to_, tokenId_, slot_); _beforeValueTransfer(address(0), to_, 0, tokenId_, slot_, value_); _values[tokenId_] = value_; _afterValueTransfer(address(0), to_, 0, tokenId_, slot_, value_); emit TransferValue(0, tokenId_, value_); } function _burn(uint256 tokenId_) internal virtual override { address owner = ERC721.ownerOf(tokenId_); ERC721._burn(tokenId_); uint256 slot = _slots[tokenId_]; uint256 value = _values[tokenId_]; // 清除与tokenId对应的_slots to _values映射关系 _beforeValueTransfer(owner, address(0), tokenId_, 0, slot, value); delete _slots[tokenId_]; delete _values[tokenId_]; _afterValueTransfer(owner, address(0), tokenId_, 0, slot, value); emit TransferValue(tokenId_, 0, value); emit SlotChanged(tokenId_, slot, 0); } function _transfer( uint256 fromTokenId_, uint256 toTokenId_, uint256 value_) internal virtual { require( _exists(fromTokenId_), "ERC35255: transfer from nonexistent token"); require(_exists(toTokenId_), "ERC35255: transfer to nonexistent token"); require( _values[fromTokenId_] >= value_, "ERC3525: transfer amount exceeds balance"); require( _slots[fromTokenId_] == _slots[toTokenId_], "ERC3535: transfer to token with different slot"); address from = ERC721.ownerOf(fromTokenId_); address to = ERC721.ownerOf(toTokenId_); _beforeValueTransfer(from, to, fromTokenId_, toTokenId_, _slots[fromTokenId_], value_); _values[fromTokenId_] -= value_; _values[toTokenId_] += value_; _afterValueTransfer(from, to, fromTokenId_, toTokenId_, _slots[fromTokenId_], value_); emit TransferValue(fromTokenId_, toTokenId_, value_); } function _spendAllowance( address operator_, uint256 tokenId_, uint256 value_) internal virtual { uint256 currentAllowance = ERC3525.allowance(tokenId_, operator_); if ( !_isApprovedOrOwner(operator_, tokenId_) && currentAllowance != type(uint256).max) { require( currentAllowance >= value_, "ERC3525: insufficient allowance"); _approveValue(tokenId_, operator_, currentAllowance - value_); } } function _approveValue( uint256 tokenId_, address to_, uint256 value_) internal virtual { ApproveData storage approveData = _approvedValues[tokenId_]; approveData.approvals.push(to_); approveData.allowances[to_] = value_; emit ApprovalValue(tokenId_, to_, value_); } function _getNewTokenId(uint256 fromTokenId_) internal virtual returns (uint256) { return ERC721Enumerable.totalSupply() + 1; } function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal virtual override { //clear approve data uint256 length = _approvedValues[tokenId].approvals.length; for (uint256 i = 0; i < length; i++) { address approval = _approvedValues[tokenId].approvals[i]; delete _approvedValues[tokenId].allowances[approval]; } delete _approvedValues[tokenId].approvals; } function _afterTokenTransfer(address from, address to, uint256 tokenId) internal virtual override {} function _checkOnERC3525Received( uint256 fromTokenId_, uint256 toTokenId_, uint256 value_, bytes memory data_) private returns (bool) { address to = ERC721.ownerOf((toTokenId_)); if (to.isContract() && IERC165(to).supportsInterface(type(IERC3525Receiver).interfaceId)) { try IERC3525Receiver(to).onERC3525Received( _msgSender(), fromTokenId_, toTokenId_, value_, data_) returns (bytes4 retval) { return retval == IERC3525Receiver.onERC3525Received.selector; } catch (bytes memory reason) { if (reason.length == 0) { revert( "ERC3525: transfer to non ERC3525Receiver implementer"); } else { // solhint-disable-next-line assembly { revert(add(32, reason), mload(reason)) } } } } else { return true; } } function _beforeValueTransfer( address from_, address to_, uint256 fromTokenId_, uint256 toTokenId_, uint256 slot_, uint256 value_) internal virtual {} function _afterValueTransfer( address from_, address to_, uint256 fromTokenId_, uint256 toTokenId_, uint256 slot_, uint256 value_) internal virtual {} } ``` ### 总结 - 没有重新生成代币,使得3525有了更多潜在的受众,相比1155有更低的使用成本 - slot可以理解为相比1155多抽象了一层,把物品的类别属性也映射到了链上 - token-to-token的转账模式,类似UTXO模型,转账数据都被记录在合约中,合约成为了一个银行账户,有利于数据追溯,对监管友好 - 对基础设施要求苛刻,tokenid-to-tokenid的转账模式不符合用户习惯,在前端设计时,要切换成to address的模式,那么必然要调用类似etherscan这样的基础设施,增加了应用的使用成本。 - 但合约中只记录了最终态,中间的流转过程无法被记录在合约中 - 填补了一部分市场空白,协议的思考和代码实现都值得借鉴。 --- ## 用例 https://github.com/solv-finance/solv-v2-voucher ![](https://i.imgur.com/jpapWgt.png) --- ## 产品构想 - 可以在金融领域之外做一些构想,例如治理,做委托投票,slot可以作为被委托人的编号,Non-transferable 3525 - 以3525为基础的exchange,类似Sudoswap ---