diff --git a/EIPS/eip-998.md b/EIPS/eip-998.md index 18bd6b89..e1448797 100644 --- a/EIPS/eip-998.md +++ b/EIPS/eip-998.md @@ -1,7 +1,7 @@ --- eip: 998 title: ERC-998 Composable Non-Fungible Token Standard -author: Matt Lockyer , Nick Mudge +author: Matt Lockyer , Nick Mudge , Jordan Schalm discussions-to: https://github.com/ethereum/EIPs/issues/998 type: Standards Track category: ERC @@ -35,6 +35,7 @@ This specification specifies: 1. [ERC721 top-down composable tokens that receive, hold and transfer ERC721 tokens](#erc721-top-down-composable) 2. [ERC20 top-down composable tokens that receive, hold and transfer ERC20 tokens](#erc20-top-down-composable) 3. [ERC721 bottom-up composable tokens that attach themselves to other ERC721 tokens.](#erc721-bottom-up-composable) +4. [ERC20 bottom-up composable tokens that attach themselves to other ERC20 tokens.](#erc20-bottom-up-composable) ### ERC721 @@ -1029,6 +1030,170 @@ interface ERC998ERC721BottomUpEnumerable { } ``` +### ERC20 Bottom-Up Composable + +ERC20 bottom-up composables are ERC20 tokens that attach themselves to ERC721 tokens, or are owned by a user address like standard ERC20 tokens. + +When owned by an ERC721 token, ERC20 bottom-up composable contracts store the owning address of a token and the parent tokenId. ERC20 bottom-up composables add several methods to the ERC20 and ERC223 interfaces allowing for querying the balance of parent tokens, and transferring tokens to, from, and between parent tokens. + +This functionality can be implemented by adding one additional mapping to track balances of tokens, in addition to the standard mapping for tracking user address balances. +```solidity +/// @dev This mapping tracks standard ERC20/ERC223 ownership, where an address owns +/// a particular amount of tokens. +mapping(address => uint) userBalances; + +/// @dev This additional mapping tracks ERC998 ownership, where an ERC721 token owns +/// a particular amount of tokens. This tracks contractAddres => tokenId => balance +mapping(address => mapping(uint => uint)) nftBalances; +``` + +The complete interface is below. + +```solidity +/// @title ERC998ERC20 Bottom-Up Composable Fungible Token +/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-998.md +/// Note: The ERC-165 identifier for this interface is 0xffafa991 +interface ERC998ERC20BottomUp { + + /// @dev This emits when a token is transferred to an ERC721 token + /// @param _toContract The contract the token is transferred to + /// @param _toTokenId The token the token is transferred to + /// @param _amount The amount of tokens transferred + event TransferToParent( + address indexed _toContract, + uint256 indexed _toTokenId, + uint256 _amount + ); + + /// @dev This emits when a token is transferred from an ERC721 token + /// @param _fromContract The contract the token is transferred from + /// @param _fromTokenId The token the token is transferred from + /// @param _amount The amount of tokens transferred + event TransferFromParent( + address indexed _fromContract, + uint256 indexed _fromTokenId, + uint256 _amount + ); + + /// @notice Get the balance of a non-fungible parent token + /// @param _tokenContract The contract tracking the parent token + /// @param _tokenId The ID of the parent token + /// @return amount The balance of the token + function balanceOfToken(address _tokenContract, uint256 _tokenId) + external + view + returns (uint256 amount); + + /// @notice Transfer tokens from owner address to a token + /// @param _from The owner address + /// @param _toContract The ERC721 contract of the receiving token + /// @param _toToken The receiving token + /// @param _amount The amount of tokens to transfer + function transferToParent(address _from, address _toContract, uint256 _toTokenId, uint256 _amount) + external; + + /// @notice Transfer token from a token to an address + /// @param _fromContract The address of the owning contract + /// @param _fromTokenId The owning token + /// @param _to The address the token is transferred to + /// @param _amount The amount of tokens to transfer + function transferFromParent(address _fromContract, uint256 _fromTokenId, address _to, uint256 _amount) + external; + + /// @notice Transfer token from a token to an address, using ERC223 semantics + /// @param _fromContract The address of the owning contract + /// @param _fromTokenId The owning token + /// @param _to The address the token is transferred to + /// @param _amount The amount of tokens to transfer + /// @param _data Additional data with no specified format, can be used to specify the sender tokenId + function transferFromParentERC223(address _fromContract, uint256 _fromTokenId, address _to, uint256 _amount, bytes _data) + external; + + /// @notice Transfer a token from a token to another token + /// @param _fromContract The address of the owning contract + /// @param _fromTokenId The owning token + /// @param _toContract The ERC721 contract of the receiving token + /// @param _toToken The receiving token + /// @param _amount The amount tokens to transfer + function transferAsChild(address _fromContract, uint256 _fromTokenId, address _toContract, uint256 _toTokenId, uint256 _amount) + external; +} +``` + +#### balanceOfToken +```solidity +/// @notice Get the balance of a non-fungible parent token +/// @param _tokenContract The contract tracking the parent token +/// @param _tokenId The ID of the parent token +/// @return amount The balance of the token +function balanceOfToken(address _tokenContract, uint256 _tokenId) + external + view + returns (uint256 amount); +``` + +This function returns the balance of a non-fungible token. It mirrors the standard ERC20 method `balanceOf`, but accepts the address of the parent token's contract, and the parent token's ID. This method behaves identically to `balanceOf`, but checks for ownership by ERC721 tokens rather than user addresses. + +#### transferToParent +```solidity +/// @notice Transfer tokens from owner address to a token +/// @param _from The owner address +/// @param _toContract The ERC721 contract of the receiving token +/// @param _toToken The receiving token +/// @param _amount The amount of tokens to transfer +function transferToParent(address _from, address _toContract, uint256 _toTokenId, uint256 _amount) + external; +``` + +This function transfers an amount of tokens from a user address to an ERC721 token. This function MUST ensure that the recipient contract implements ERC721 using the ERC165 `supportsInterface` function. This function SHOULD ensure that the recipient token actually exists, by calling `ownerOf` on the recipient token's contract, and ensuring it neither throws nor returns the zero address. This function MUST emit the `TransferToParent` event upon a successful transfer (in addition to the standard ERC20 `Transfer` event!). This function MUST throw if the `_from` account balance does not have enough tokens to spend. + +#### transferFromParent +``` +solidity +/// @notice Transfer token from a token to an address +/// @param _fromContract The address of the owning contract +/// @param _fromTokenId The owning token +/// @param _to The address the token is transferred to +/// @param _amount The amount of tokens to transfer +function transferFromParent(address _fromContract, uint256 _fromTokenId, address _to, uint256 _amount) + external; +``` + +This function transfers an amount of tokens from an ERC721 token to an address. This function MUST emit the `TransferFromParent` event upon a successful transfer (in addition to the standard ERC20 `Transfer` event!). This function MUST throw if the balance of the sender ERC721 token is less than the `_amount` specified. This function MUST verify that the `msg.sender` owns the sender ERC721 token, and MUST throw otherwise. + +#### transferFromParentERC223 +```solidity +/// @notice Transfer token from a token to an address, using ERC223 semantics +/// @param _fromContract The address of the owning contract +/// @param _fromTokenId The owning token +/// @param _to The address the token is transferred to +/// @param _amount The amount of tokens to transfer +/// @param _data Additional data with no specified format, can be used to specify the sender tokenId +function transferFromParentERC223(address _fromContract, uint256 _fromTokenId, address _to, uint256 _amount, bytes _data) + external; +``` + +This function transfers an amount of tokens from an ERC721 token to an address. This function has identical requirements to `transferFromParent`, except that it additionally MUST invoke `tokenFallback` on the recipient address, if the address is a contract, as specified by ERC223. + +### transferAsChild +```solidity +/// @notice Transfer a token from a token to another token +/// @param _fromContract The address of the owning contract +/// @param _fromTokenId The owning token +/// @param _toContract The ERC721 contract of the receiving token +/// @param _toToken The receiving token +/// @param _amount The amount tokens to transfer +function transferAsChild(address _fromContract, uint256 _fromTokenId, address _toContract, uint256 _toTokenId, uint256 _amount) + external; +``` + +This function transfers an amount of tokens from an ERC721 token to another ERC721 token. This function MUST emit BOTH the `TransferFromParent` and `TransferToParent` events (in addition to the standard ERC20 `Transfer` event!). This function MUST throw if the balance of the sender ERC721 token is less than the `_amount` specified. This function MUST verify that the `msg.sender` owns the sender ERC721 token, and MUST throw otherwise. This function MUST ensure that the recipient contract implements ERC721 using the ERC165 `supportsInterface` function. This function SHOULD ensure that the recipient token actually exists, by calling `ownerOf` on the recipient token's contract, and ensuring it neither throws nor returns the zero address. + +### Notes +For backwards-compatibility, implementations MUST emit the standard ERC20 `Transfer` event when a transfer occurs, regardless of whether the sender and recipient are addresses or ERC721 tokens. In the case that either sender or recipient are tokens, the corresponding parameter in the `Transfer` event SHOULD be the contract address of the token. + +Implementations MUST implement all ERC20 and ERC223 functions in addition to the functions specified in this interface. + ## Rationale Two different kinds of composable (top-down and bottom-up) exist to handle different use cases. A regular ERC721 token cannot own a top-down composable, but it can own a bottom-up composable. A bottom-up composable cannot own a regular ERC721 but a top-down composable can own a regular ERC721 token. Having multiple kinds of composables enable different token ownership possibilities.