一 为什么需要ERC-6960
我们知道同质化代币ERC20和非同质化代币ERC721,他们俩虽然是代币的基础,缺乏足够灵活性。比如你想买NFT,你只能买一整个,不能买0.5个
ERC1155允许在同一个合约中创建同质化代币和非同质化代币,并且允许批量转账,比起ERC20和ERC721,增加灵活性
但是在某些场景下,可能需要对不同资产进行组合,或者想对资产部分进行交易,但是目前的协议做不到。
因此有了ERC-6150和ERC-6960。它们提出了一个多层级的概念,这样可以丰富交易场景、增加代币灵活性
二ERC-6960标准提案
2.1 什么是ERC-6960
ERC-6960也叫作双层代币标准,旨在解决现在代币标准在管理具有多重分类的代币存在的一些限制。这一标准比较适合RWAs实物资产以及支持资产部分所有权,提供了一种更加结构化和高效的代币管理方式。
ERC-6960也叫作双层代币标准,它分为两层:第一层是主资产;第二层是子资产,主资产可以拥有多个子资产。主资产有自己id属性,叫做mainId, 每一个子资产都有自己的id属性叫做subId。如图所示:
这种设计旨在增强代币的组织性和可管理性,特别适用于对房地产或收藏品等实物资产进行代币化和分割。
2.2 ERC-6960有什么特点
2.2.1 可分割性(Divisibility)
ERC-6960代币是可分割的,允许分割现实世界资产的部分所有权。这一特性增强了流动性,并为更多参与者提供了投资机会
2.2.2 身份和合规性(Identity and Compliance)
该标准结合了身份和合规协议,确保代币化资产符合法律和监管要求。这一特性解决了与欺诈、洗钱和其他非法活动相关的担忧
2.2.3 互操作性(Interoperability)
ERC-6960设计为能够与其他代币标准无缝交互,促进了不同区块链平台之间的互操作性
2.2.4 动态代币供应(Dynamic Token Supply)
与固定供应的代币不同,ERC-6960允许根据外部因素动态调整代币供应。这种灵活性使得代币供应能够适应资产估值和市场需求的变化,从而更好地反映资产的实际情况。这对于在更广泛的金融生态系统中整合资产代币化至关重要
三 使用场景
发票:发票的部分所有权让投资者可以以更低的成本进入一种新型资产类别,并且分散他们的投资组合。
公司股票:公司股票的部分所有权让小投资者也能公平地获得公司股权。
数字藏品:收藏家可以前所未有地轻松和安全地交易和拥有稀有的数字资产。
房地产:拥有一部分房地产,即使是很小的一部分,也变得无忧无虑,并且对更广泛的受众更加可及。
四 ERC-6960标准提案规范
4.1 函数
4.1.1 setApprovalForAll
function setApprovalForAll(address operator, bool approved) external;
Owner批准或者撤销operator管理owner所有的token的权限
4.1.2 approve
function approve(
address operator,
uint256 mainId,
uint256 subId,
uint256 amount
) external returns (bool);
Owner批准对operator有权管理amount数量的token
4.1.3 safeTransferFrom
function safeTransferFrom(
address sender,
address recipient,
uint256 mainId,
uint256 subId,
uint256 amount,
bytes calldata data
) external returns (bool);
确保接收地址是合约地址的情况下,必须是实现了ERC6960标准的合约,否则回滚交易
4.1.4 subBalanceOf
function subBalanceOf(
address account,
uint256 mainId,
uint256 subId
) external view returns (uint256);
查询指定账户针对指定mainId和subId的余额
4.1.5 balanceOfBatch
function balanceOfBatch(
address[] calldata accounts,
uint256[] calldata mainIds,
uint256[] calldata subIds
) external view returns (uint256[] calldata);
批量查询给定的账户列表、mainId列表和subId列表对应余额,必须保证accounts、mainIds和subIds三者一一对应,即数量必须要相等
4.1.6 allowance
function allowance(
address owner,
address operator,
uint256 mainId,
uint256 subId
) external view returns (uint256);
查看operator被owner授权的额度
4.1.7 isApprovedForAll
function isApprovedForAll(
address owner,
address operator
) external view returns (bool);
是否owner的代币全部数量都可以被operator管理
4.2 事件
4.2.1 Transfer
event Transfer(
address indexed sender,
address indexed recipient,
uint256 indexed mainId,
uint256 subId,
uint256 amount
);
4.2.2 TransferBatch
event TransferBatch(
address indexed sender,
address indexed recipient,
uint256[] mainIds,
uint256[] subIds,
uint256[] amounts
);
4.3 Approval
event Approval(
address indexed owner,
address indexed operator,
uint256 mainId,
uint256 subId,
uint256 amount
);
4.4 ApprovalForAll
event ApprovalForAll(
address indexed owner,
address indexed operator,
bool approved
);
五 手动实现ERC-6960标准提案
5.1 IERC6960.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
interface IERC6960 {
event Transfer(
address indexed sender,
address indexed recipient,
uint256 indexed mainId,
uint256 subId,
uint256 amount
);
event TransferBatch(
address indexed sender,
address indexed recipient,
uint256[] mainIds,
uint256[] subIds,
uint256[] amounts
);
event Approval(
address indexed owner,
address indexed operator,
uint256 mainId,
uint256 subId,
uint256 amount
);
event ApprovalForAll(
address indexed owner,
address indexed operator,
bool approved
);
event URI(string oldValue, string newValue, uint256 indexed mainId);
/**
* Approve or remove `operator` to manage the tokens for the caller.
*/
function setApprovalForAll(address operator, bool approved) external;
/**
* Allow the operator to move tokens from sender to the recipient within the authorized limit.
* Must emit Transfer event
* @param sender is the address of the previous holder whose balance is decreased
* @param recipient is the address of the new holder whose balance is increased
* @param mainId is the main token type ID to be transferred
* @param subId is the token subtype ID to be transferred
* @param amount is the amount to be transferred of the token subtype
* @param data is additional data with no specified format
* @return True if the operation succeeded, false if operation failed
*/
function safeTransferFrom(
address sender,
address recipient,
uint256 mainId,
uint256 subId,
uint256 amount,
bytes calldata data
) external returns (bool);
/**
* Sets `amount` as the allowance of `spender` over the caller's tokens.
* Must emit an Approval event.
* @param operator is the authorized address to manage tokens for an owner address
* @param mainId is the main token type ID to be approved
* @param subId is the token subtype ID to be approved
* @param amount is the amount to be approved of the token subtype
* @return True if the operation succeeded, false if operation failed
*/
function approve(
address operator,
uint256 mainId,
uint256 subId,
uint256 amount
) external returns (bool);
/**
* Get the token with a particular subId balance of an `account`
* @param account is the address of the token holder
* @param mainId is the main token type ID
* @param subId is the token subtype ID
* @return The amount of tokens owned by `account` in subId
*/
function subBalanceOf(
address account,
uint256 mainId,
uint256 subId
) external view returns (uint256);
/**
* @notice Get the tokens with a particular subIds balance of an `accounts` array
* @param accounts is the address array of the token holder
* @param mainIds is the main token type ID array
* @param subIds is the token subtype ID array
* @return The amount of tokens owned by `accounts` in subIds
*/
function balanceOfBatch(
address[] calldata accounts,
uint256[] calldata mainIds,
uint256[] calldata subIds
) external view returns (uint256[] calldata);
/**
* @notice Get the allowance allocated to an `operator`
* @dev This value changes when {approve} or {transferFrom} are called
* @param owner is the address of the token owner
* @param operator is the authorized address to manage assets for an owner address
* @param mainId is the main token type ID
* @param subId is the token subtype ID
* @return The remaining number of tokens that `operator` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*/
function allowance(
address owner,
address operator,
uint256 mainId,
uint256 subId
) external view returns (uint256);
/**
* @notice Get the approval status of an `operator` to manage assets
* @param owner is the address of the token owner
* @param operator is the authorized address to manage assets for an owner address
* @return True if the `operator` is allowed to manage all of the assets of `owner`, false if approval is revoked
* See {setApprovalForAll}
*/
function isApprovedForAll(
address owner,
address operator
) external view returns (bool);
}
5.2 IERC6960Receiver.sol
nterface IERC6960Receiver {
function onERC6960Received(
address operator,
address from,
uint256 mainId,
uint256 subId,
uint256 amount,
bytes calldata data
) external returns (bytes4);
function onERC6960BatchReceived(
address operator,
address from,
uint256[] calldata mainIds,
uint256[] calldata subIds,
uint256[] calldata amounts,
bytes calldata data
) external returns (bytes4);
}
5.3 IERC6960Enumerable.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "./IERC6960.sol";
interface IERC6960Enumerable is IERC6960 {
/**
* @dev Returns the total number of main ids.
*/
function totalMainIds() external view returns (uint256);
/**
* @dev Returns the total number of sub ids for each main Ids.
*/
function totalSubIds(uint256 mainId) external view returns (uint256);
/**
* @dev Returns the total supply of main ids.
*/
function totalMainSupply(uint256 mainId) external view returns (uint256);
/**
* @dev Returns the total supply of sub ids for each main Ids.
*/
function totalSubSupply(
uint256 mainId,
uint256 subId
) external view returns (uint256);
/**
* @dev Returns array of all sub ids for a main id
*/
function getSubIds(uint256 mainId) external view returns (uint256[] memory);
/**
* @dev Returns total sub id balance of owner for each main id
*/
function subIdBalanceOf(
address owner,
uint256 mainId
) external view returns (uint256);
}
5.4 ERC6960.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "./IERC6960.sol";
import "./IERC6960Receiver.sol";
contract ERC6960 is IERC6960 {
string private _name;
string private _symbol;
mapping(uint256 mainId => mapping(address account => mapping(uint256 subId => uint256))) private _balances;
mapping(address owner => mapping(address operator => bool)) private _operatorApprovals;
mapping(address account => mapping(address operator => mapping(uint256 mainId => mapping(uint256 subId => uint256)))) private _allowances;
constructor(string memory name, string memory symbol) {
_name = name;
_symbol = symbol;
}
/**
* Owner approves or revokes the operator's authority to manage all of the owner's tokens.
*/
function setApprovalForAll(address operator, bool approved) external override {
address owner = msg.sender;
require(owner != operator, "Operator is owner");
_operatorApprovals[owner][operator] = approved;
emit ApprovalForAll(owner, operator, approved);
}
function safeTransferFrom(
address sender,
address recipient,
uint256 mainId,
uint256 subId,
uint256 amount,
bytes calldata data
) external override returns (bool) {
_safeTransferFrom(sender, recipient, mainId, subId, amount, "");
return true;
}
function approve(
address operator,
uint256 mainId,
uint256 subId,
uint256 amount
) external override returns (bool) {
// get token owner, only owner could approve
address owner = msg.sender;
require(operator != owner, "No need make approval to current owner");
_approve(owner, operator, mainId, subId, amount);
return true;
}
function subBalanceOf(
address account,
uint256 mainId,
uint256 subId
) external view override returns (uint256) {
return _balances[mainId][account][subId];
}
function balanceOfBatch(
address[] calldata accounts,
uint256[] calldata mainIds,
uint256[] calldata subIds
) external view override returns (uint256[] calldata) {
require(accounts.length == mainIds.length && accounts.length == subIds.length, "accounts, mainIds and ids length mismatch");
uint256[] memory batchBalances = new uint256[](accounts.length);
for (uint256 i = 0; i < accounts.length; ++i) {
batchBalances[i] = subBalanceOf(accounts[i], mainIds[i], subIds[i]);
}
return batchBalances;
}
function allowance(
address owner,
address operator,
uint256 mainId,
uint256 subId
) external view override returns (uint256) {
return _allowance[owner][spender][mainId][subId];
}
function isApprovedForAll(
address owner,
address operator
) external view override returns (bool) {
return _operatorApprovals[owner][operator];
}
/**
* The owner allows the operator to manage specified amount of tokens.
* @param owner is the holder of the tokens
* @param spender is allowed to manage the token
* @param mainId is main id of token
* @param subId is sub id
* @param amount is the quantity that the operator is allowed to manage
*/
function _approve(
address owner,
address spender,
uint256 mainId,
uint256 subId,
uint256 amount
) internal virtual {
require(owner != address(0), "Invalid owner address");
require(spender != address(0), "Invalid operator address");
_allowances[owner][spender][mainId][subId] = amount;
emit Approval(owner, spender, mainId, subId, amount);
}
/**
* Mint tokens to the specified address
*/
function _mint(address to, uint256 mainId, uint256 subId, uint256 amount) internal virtual {
require(to != address(0), "The to address of minting is invalid");
require(amount != 0, "Mint amount must be greater than 0");
_beforeTokenTransfer(address(0), to, mainId, subId, amount, "");
_balances[mainId][to][subId] += amount;
emit Transfer(address(0), to, mainId, subId, amount);
_afterTokenTransfer(address(0), to, mainId, subId, amount, "");
}
function _safeMint(
address recipient,
uint256 mainId,
uint256 subId,
uint256 amount
) internal virtual {
_safeMint(recipient, mainId, subId, amount, "");
}
function _safeMint(
address recipient,
uint256 mainId,
uint256 subId,
uint256 amount,
bytes memory data
) internal virtual {
_mint(recipient, mainId, subId, amount);
require(
_checkOnERC6960Received(
address(0),
recipient,
mainId,
subId,
amount,
data
),
"Transfer to non ERC6960Receiver implementer"
);
}
/**
* Burn tokens from the specified address
*/
function _burn(address from, uint256 mainId, uint256 subId, uint256 amount) internal virtual {
require(from != address(0), "The from address of burning is invalid");
require(amount != 0, "Brn amount must be greater than 0");
uint256 fromSubBalance = _balances[mainId][from][subId];
require(fromSubBalance >= amount, "Insufficient balance");
_beforeTokenTransfer(from, address(0), mainId, subId, amount, "");
unchecked {
_balances[mainId][from][subId] -= amount;
}
emit Transfer(from, address(0), mainId, subId, amount);
_afterTokenTransfer(from, address(0), mainId, subId, amount, "");
}
/**
* Check spender if has been approved by sender or owner or sender is owner
*/
function _isApprovedOrOwner(
address sender,
address spender
) internal view virtual returns (bool) {
return (sender == spender || isApprovedForAll(sender, spender));
}
/**
* Update the allowance as operator need spend amount allowance
*/
function _spendAllowance(
address owner,
address spender,
uint256 mainId,
uint256 subId,
uint256 amount
) internal virtual {
// Get the remaining allowance of approving to spender by owner
uint256 allowance = _allowances[owner][spender][mainId][subId];
if (allowance >= type(uint256).max) {
return;
}
require(allowance >= amount, "Insufficient allowance");
// Update the allowance as operator need spend amount allowance
unchecked {
_approve(owner,spender,mainId,subId,allowance - amount);
}
}
/**
* Check if operator has been approved by owner
* @param owner token holder
* @param operator caller of this function
*/
function isApprovedForAll(
address owner,
address operator
) public view virtual override returns (bool) {
return _operatorApprovals[owner][operator];
}
/**
* Move tokens from `sender` to `recipient`
*/
function _transfer(
address sender,
address recipient,
uint256 mainId,
uint256 subId,
uint256 amount
) internal virtual {
require(sender != address(0), "Invalid sender address");
require(recipient != address(0), "Invalid receiver address");
require(_balances[mainId][sender][subId] >= amount, "Insufficient balance for transfer");
_beforeTokenTransfer(sender, recipient, mainId, subId, amount, "");
unchecked {
_balances[mainId][sender][subId] -= amount;
}
_balances[mainId][recipient][subId] += amount;
emit Transfer(sender, recipient, mainId, subId, amount);
_afterTokenTransfer(sender, recipient, mainId, subId, amount, "");
}
/**
* Move tokens from `sender` to `recipient` safely
*/
function _safeTransfer(
address sender,
address recipient,
uint256 mainId,
uint256 subId,
uint256 amount,
bytes memory data
) internal virtual {
_transfer(sender, recipient, mainId, subId, amount);
bool checked = _checkOnERC6960Received(sender, recipient, mainId, subId, amount, data);
require(checked, "Recipient is not a contract that implements ERC-6960 ")
}
/**
* Move token from `sender` to `recipient` by operator
*/
function _transferFrom(
address sender,
address recipient,
uint256 mainId,
uint256 subId,
uint256 amount
) internal virtual {
// get the caller address
address operator = msg.sender;
// check if operator has been approved by token owner
if (_isApprovedOrOwner(sender, operator)) {
// the operator's allowance balance needs to be updated,because his allowance will be used by operator
_spendAllowance(sender, operator, mainId, subId, amount);
}
// move token from `sender` to `recipient`
_transfer(sender, recipient, mainId, subId, amount);
}
/**
* Move token from `sender` to `recipient` safely by operator
*/
function _safeTransferFrom(
address sender,
address recipient,
uint256 mainId,
uint256 subId,
uint256 amount,
bytes memory data
) internal virtual {
// get the caller address
address operator = msg.sender;
// check if operator has been approved by token owner
if (!_isApprovedOrOwner(sender, operator)) {
// the operator's allowance balance needs to be updated,because his allowance will be used by operator
_spendAllowance(sender, operator, mainId, subId, amount);
}
// move token from `sender` to `recipient` safely
_safeTransfer(sender, recipient, mainId, subId, amount, data);
}
function _safeBatchTransferFrom(
address sender,
address recipient,
uint256[] memory mainIds,
uint256[] memory subIds,
uint256[] memory amounts,
bytes memory data
) internal virtual {
require(mainIds.length == subIds.length && mainIds.length == amounts.length, "mainIds, subIds and amounts length mismatch");
require(recipient != address(0), "Invalid receiver address");
address operator = msg.sender;
for (uint256 i = 0; i < mainIds.length; ++i) {
uint256 mainId = mainIds[i];
uint256 subId = subIds[i];
uint256 amount = amounts[i];
uint256 senderBalance = _balances[mainId][sender][subId];
require(senderBalance >= amount,"Insufficient balance for transfer");
unchecked {
_balances[mainId][sender][subId] = senderBalance - amount;
}
_balances[mainId][recipient][subId] += amount;
}
emit TransferBatch(operator,sender,recipient,mainIds,subIds,amounts);
require(
_checkOnERC6960BatchReceived(
sender,
recipient,
mainIds,
subIds,
amounts,
data
),"Transfer to non ERC6960Receiver implementer"
);
}
function _checkOnERC6960Received(
address sender,
address recipient,
uint256 mainId,
uint256 subId,
uint256 amount,
bytes memory data
) private returns (bool) {
if (!(recipient.code.length > 0)) {
return true;
}
try IERC6960Receiver(recipient).onERC6960Received(msg.sender, sender, mainId, subId, amount,data) returns (bytes4 retval) {
return retval == IERC6960Receiver.onERC6960Received.selector;
} catch (bytes memory reason) {
assembly {
revert(add(32, reason), mload(reason))
}
}
return false;
}
function _checkOnERC6960BatchReceived(
address sender,
address recipient,
uint256[] memory mainIds,
uint256[] memory subIds,
uint256[] memory amounts,
bytes memory data
) private returns (bool) {
if (!(recipient.code.length > 0)) {
return true;
}
try IERC6960Receiver(recipient).onERC6960BatchReceived(msg.sender, sender, mainIds, subIds, amounts, data) returns (bytes4 retval) {
return retval == IERC6960Receiver.onERC6960BatchReceived.selector;
} catch (bytes memory reason) {
assembly {
revert(add(32, reason), mload(reason))
}
}
return false;
}
/**
* A hook function before transfer token, such as mint, burn
*/
function _beforeTokenTransfer(
address sender,
address recipient,
uint256 mainId,
uint256 subId,
uint256 amount,
bytes memory data
) internal virtual {}
/**
* A hook function after transfer token, such as mint, burn
*/
function _afterTokenTransfer(
address sender,
address recipient,
uint256 mainId,
uint256 subId,
uint256 amount,
bytes memory data
) internal virtual {}
}
5.5 ERC6960Receiver.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "./IERC6960Enumerable.sol";
import "./ERC6960.sol";
abstract contract ERC6960Enumerable is ERC6960, IERC6960Enumerable {
uint256 private _totalMainIds;
mapping(uint256 => uint256[]) private _subIds;
mapping(uint256 mainId => uint256) private _totalSubIds;
mapping(uint256 mainId => uint256) private _totalMainSupply;
mapping(uint256 mainId => mapping(uint256 subId => uint256)) private _totalSubSupply;
function totalMainIds() public view virtual override returns (uint256) {
return _totalMainIds;
}
function totalSubIds(uint256 mainId) public view virtual returns (uint256) {
return _totalSubIds[mainId];
}
function totalMainSupply(uint256 mainId) public view virtual returns (uint256) {
return _totalMainSupply[mainId];
}
function totalSubSupply(uint256 mainId,uint256 subId) public view virtual returns (uint256) {
return _totalSubSupply[mainId][subId];
}
function getSubIds(uint256 mainId) public view virtual returns (uint256[] memory) {
return _subIds[mainId];
}
function subIdBalanceOf(address owner, uint256 mainId) public view virtual returns (uint256 totalBalance) {
for (uint256 i = 0; i < _subIds[mainId].length; ) {
uint256 subId = _subIds[mainId][i];
totalBalance += _balances[mainId][owner][subId];
unchecked {
++i;
}
}
return totalBalance;
}
function _mint(
address recipient,
uint256 mainId,
uint256 subId,
uint256 amount
) internal virtual override(DLT) {
if (_totalMainSupply[mainId] == 0) {
++_totalMainIds;
}
if (_totalSubSupply[mainId][subId] == 0) {
++_totalSubIds[mainId];
uint256[] storage array = _subIds[mainId];
_subIdIndex[mainId][subId] = array.length;
array.push(subId);
}
_totalMainSupply[mainId] += amount;
_totalSubSupply[mainId][subId] += amount;
super._mint(recipient, mainId, subId, amount);
}
function _burn(
address recipient,
uint256 mainId,
uint256 subId,
uint256 amount
) internal virtual override(DLT) {
super._burn(recipient, mainId, subId, amount);
unchecked {
_totalMainSupply[mainId] -= amount;
_totalSubSupply[mainId][subId] -= amount;
// Overflow/Underflow not possible: amount <= fromBalanceMain <= totalSupply
}
if (_totalSubSupply[mainId][subId] == 0) {
--_totalSubIds[mainId];
uint256[] storage array = _subIds[mainId];
uint256 subIdIndex = _subIdIndex[mainId][subId];
uint256 lastSubId = array[array.length - 1];
array[subIdIndex] = lastSubId;
_subIdIndex[mainId][lastSubId] = subIdIndex;
delete _subIdIndex[mainId][subId];
array.pop();
}
if (_totalMainSupply[mainId] == 0) {
--_totalMainIds;
}
}
}
5.6 ERC6960Enumerable.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "./IERC6960Receiver.sol";
import "./IERC6960.sol";
contract ERC6960Receiver is IERC6960Receiver {
function onERC6960Received(
address operator,
address from,
uint256 mainId,
uint256 subId,
uint256 amount,
bytes calldata data
) external override returns (bytes4) {
return IERC6960Receiver.onERC6960Received.selector;
}
function onERC6960BatchReceived(
address operator,
address from,
uint256[] calldata mainIds,
uint256[] calldata subIds,
uint256[] calldata amounts,
bytes calldata data
) external override returns (bytes4) {
return IERC6960Receiver.onERC6960BatchReceived.selector;
}
}