From 57dc1dc0fa1b17d716731fb92c7599bcfee3d374 Mon Sep 17 00:00:00 2001 From: James Save Chives Date: Tue, 26 Nov 2024 03:59:25 +0800 Subject: [PATCH 1/4] Add ERC: Multi-Owner Non-Fungible Tokens (MO-NFT) Merged by EIP-Bot. --- ERCS/erc-7743.md | 109 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 ERCS/erc-7743.md diff --git a/ERCS/erc-7743.md b/ERCS/erc-7743.md new file mode 100644 index 0000000000..6720d1edb0 --- /dev/null +++ b/ERCS/erc-7743.md @@ -0,0 +1,109 @@ +--- +eip: 7743 +title: Multi-Owner Non-Fungible Tokens (MO-NFT) +description: A new type of non-fungible token that supports multiple owners, allowing shared ownership and transferability among users. +author: James Savechives (@jamesavechives) +discussions-to: https://ethereum-magicians.org/t/discussion-on-eip-7743-multi-owner-non-fungible-tokens-mo-nft/20577 +status: Draft +type: Standards Track +category: ERC +created: 2024-07-13 +--- + +## Abstract +This ERC proposes a new standard for non-fungible tokens (NFTs) that supports multiple owners. The MO-NFT standard allows a single NFT to have multiple owners, reflecting the shared and distributable nature of digital assets. This model also incorporates a mechanism for value depreciation as the number of owners increases, maintaining the principle that less ownership translates to more value. + +## Motivation +Traditional NFTs enforce a single-ownership model, which does not align with the inherent duplicability of digital assets. MO-NFTs allow for shared ownership, promoting wider distribution and collaboration while maintaining secure access control. This model supports the principle that some valued information is more valuable if fewer people know it, hence less ownership means higher value. + +## Specification + +### Token Creation and Ownership Model +1. **Minting**: When a digital asset is uploaded, a unique MO-NFT is minted, and the uploader becomes the initial owner. +2. **Ownership List**: The MO-NFT maintains a list of owners. Each transfer adds the new owner to the list while retaining the existing owners. +3. **Transfer Mechanism**: Owners can transfer the token to new owners. The transfer does not remove the original owner from the list but adds the new recipient. + +### Transfer of Ownership +1. **Additive Ownership**: Transferring ownership adds the new owner to the ownership list without removing the current owners. +2. **Ownership Tracking**: The smart contract tracks the list of owners for each MO-NFT. + +### Value Depreciation +1. **Value Model**: As the number of owners increases, the value of the MO-NFT decreases to reflect the reduced exclusivity. +2. **Depreciation Mechanism**: The value depreciation model is defined based on the asset type and distribution strategy. Less ownership equates to more value, following the principle that valued information or assets become less valuable as they become more widely known or accessible. + +### Interface Definitions +The MO-NFT contract implements the following interfaces: + +1. **Minting**: + - `function mintToken() public onlyOwner returns (uint256);` + +2. **Transfer**: + - `function transferFrom(address from, address to, uint256 tokenId) public override;` + - `function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public override;` + - `function safeTransferFrom(address from, address to, uint256 tokenId) public override;` + +3. **Ownership Management**: + - `function isOwner(uint256 tokenId, address account) public view returns (bool);` + - `function getOwnersCount(uint256 tokenId) public view returns (uint256);` + - `function balanceOf(address owner) external view override returns (uint256 balance);` + - `function ownerOf(uint256 tokenId) external view override returns (address owner);` + +### [ERC-721](./eip-721.md) Compliance +The MO-NFT standard is designed to be compatible with the [ERC-721](./eip-721.md) standard. It implements required functions such as `balanceOf`, `ownerOf`, `safeTransferFrom`, and `transferFrom` from the `IERC721` interface. Approval functions are intentionally disabled due to the multi-owner nature of MO-NFTs, which differs from traditional [ERC-721](./eip-721.md) logic. + +The `supportsInterface` function ensures that the MO-NFT declares compatibility with the [ERC-721](./eip-721.md) standard, allowing it to be integrated with existing tools and platforms that support [ERC-721](./eip-721.md). + + +## Rationale +The rationale behind the design choices in the MO-NFT standard is as follows: + +1. **Multi-Ownership Model**: + - We adopted a multi-owner structure to better reflect the nature of digital assets, which can be copied and shared without losing their original form. This model allows for broader distribution and access, enhancing collaboration among multiple owners while retaining a single NFT identity. + +2. **Additive Ownership**: + - The decision to allow ownership transfer to be additive (i.e., not removing existing owners) stems from the concept of shared digital assets. This approach ensures that each transfer does not diminish the original owner's rights, enabling a more inclusive ownership model that suits digital content, where multiple stakeholders may have legitimate claims. + +3. **Value Depreciation Mechanism**: + - We introduced the value depreciation model based on the concept that the value of information decreases as it becomes more widely accessible. This mechanism incentivizes exclusivity and ensures that as more owners are added, the token's value adjusts accordingly, aligning with real-world economic principles. + +4. **[ERC-721](./eip-721.md) Compatibility**: + - We ensured that the MO-NFT standard maintains compatibility with [ERC-721](./eip-721.md) to allow for seamless integration with existing NFT tools, platforms, and marketplaces. This decision was made to leverage the established infrastructure and ecosystem of [ERC-721](./eip-721.md) while extending its capabilities for multi-ownership. + +## Backwards Compatibility +This standard is designed to be backwards compatible with the existing [ERC-721](./eip-721.md) standard. Existing tools and platforms that support [ERC-721](./eip-721.md) should be able to interact with MO-NFTs with minimal modifications. + +## Test Cases +These test cases demonstrate the functionality of MO-NFTs: + +1. **Minting an MO-NFT and Verifying Initial Ownership**: + - **Input**: + - Call `mintToken()` with the sender as the owner. + - **Expected Output**: + - A new `tokenId` is generated. + - The sender becomes the first owner of the token (`isOwner(tokenId, sender)` returns `true`). + - The total supply increases by 1 (`totalSupply()` reflects the new count). + - The `balanceOf(sender)` increases by 1. + +2. **Transferring an MO-NFT and Verifying Additive Ownership**: + - **Input**: + - Call `transferFrom(sender, receiver, tokenId)` where the sender is an existing owner and `receiver` is not an owner. + - **Expected Output**: + - The receiver is added to the list of owners (`isOwner(tokenId, receiver)` returns `true`). + - The sender remains an owner (`isOwner(tokenId, sender)` still returns `true`). + - The total number of owners for the token increases by 1 (`getOwnersCount(tokenId)` reflects the new count). + - The `balanceOf(receiver)` increases by 1, and the `balanceOf(sender)` remains the same. + +### Additional Detailed Test Case (Optional): +3. **Failing to Transfer to an Existing Owner**: + - **Input**: + - Call `transferFrom(sender, existingOwner, tokenId)` where `existingOwner` is already listed as an owner of the token. + - **Expected Output**: + - The transfer is reverted with an error message `"Recipient is already an owner"`. + - The state of the ownership list remains unchanged. + +## Security Considerations +1. **Data Integrity**: Protect the integrity of the ownership list and associated metadata. +2. **Smart Contract Security**: Follow best practices for smart contract development to prevent vulnerabilities and exploits. + +## Copyright +Copyright and related rights waived via [CC0](../LICENSE.md). From 441f79ca99a378fe39831db38763bf0daf71a610 Mon Sep 17 00:00:00 2001 From: James Save Chives Date: Tue, 26 Nov 2024 20:50:00 +0800 Subject: [PATCH 2/4] Update ERC-7743: Include Reference Implementation and Finalize MO-NFT Standard Merged by EIP-Bot. --- ERCS/erc-7743.md | 390 ++++++++++++++++++++++++++---- assets/erc-7743/DigitalAsset.sol | 141 +++++++++++ assets/erc-7743/IDigitalAsset.sol | 18 ++ assets/erc-7743/MONFT.sol | 177 ++++++++++++++ 4 files changed, 674 insertions(+), 52 deletions(-) mode change 100644 => 100755 ERCS/erc-7743.md create mode 100644 assets/erc-7743/DigitalAsset.sol create mode 100644 assets/erc-7743/IDigitalAsset.sol create mode 100644 assets/erc-7743/MONFT.sol diff --git a/ERCS/erc-7743.md b/ERCS/erc-7743.md old mode 100644 new mode 100755 index 6720d1edb0..6917b66b1b --- a/ERCS/erc-7743.md +++ b/ERCS/erc-7743.md @@ -11,99 +11,385 @@ created: 2024-07-13 --- ## Abstract -This ERC proposes a new standard for non-fungible tokens (NFTs) that supports multiple owners. The MO-NFT standard allows a single NFT to have multiple owners, reflecting the shared and distributable nature of digital assets. This model also incorporates a mechanism for value depreciation as the number of owners increases, maintaining the principle that less ownership translates to more value. + +This ERC proposes a new standard for non-fungible tokens (NFTs) that supports multiple owners. The MO-NFT standard allows a single NFT to have multiple owners, reflecting the shared and distributable nature of digital assets. This model incorporates mechanisms for provider-defined transfer fees and ownership burning, enabling flexible and collaborative ownership structures. It maintains compatibility with the existing [ERC-721](./eip-721.md) standard to ensure interoperability with current tools and platforms. ## Motivation -Traditional NFTs enforce a single-ownership model, which does not align with the inherent duplicability of digital assets. MO-NFTs allow for shared ownership, promoting wider distribution and collaboration while maintaining secure access control. This model supports the principle that some valued information is more valuable if fewer people know it, hence less ownership means higher value. + +Traditional NFTs enforce a single-ownership model, which does not align with the inherent duplicability and collaborative potential of digital assets. MO-NFTs allow for shared ownership, promoting wider distribution and collaboration while maintaining secure access control. The inclusion of provider fees and ownership burning enhances the utility and flexibility of NFTs in representing digital assets and services. ## Specification +### Data Structures + +- **Owners Mapping**: A mapping from `tokenId` to an enumerable set of owner addresses. + + ```solidity + mapping(uint256 => EnumerableSet.AddressSet) internal _owners; + ``` + +- **Balances Mapping**: A mapping from owner addresses to the number of tokens they own. + + ```solidity + mapping(address => uint256) private _balances; + ``` + +- **Providers Mapping**: A mapping from `tokenId` to the provider's address. + + ```solidity + mapping(uint256 => address) private _providers; + ``` + +- **Transfer Values Mapping**: A mapping from `tokenId` to the `transferValue` fee. + + ```solidity + mapping(uint256 => uint256) private _transferValues; + ``` + ### Token Creation and Ownership Model -1. **Minting**: When a digital asset is uploaded, a unique MO-NFT is minted, and the uploader becomes the initial owner. -2. **Ownership List**: The MO-NFT maintains a list of owners. Each transfer adds the new owner to the list while retaining the existing owners. -3. **Transfer Mechanism**: Owners can transfer the token to new owners. The transfer does not remove the original owner from the list but adds the new recipient. + +1. **Minting**: + + - The function `mintToken()` allows the creation of a new MO-NFT. The caller becomes both the initial owner and the provider of the token. + + ```solidity + function mintToken() public onlyOwner returns (uint256); + ``` + + - A new `tokenId` is generated, and the caller is added to the owners set and recorded as the provider. The `balanceOf` the caller is incremented. + +2. **Ownership List**: + + - The MO-NFT maintains a list of owners for each token. Owners are stored in an enumerable set to prevent duplicates and allow efficient lookup. + +3. **Provider Role**: + + - The provider is the initial owner who can set and update the `transferValue` fee. Only the provider can modify certain token parameters. + +4. **Transfer Mechanism**: + + - Owners can transfer the token to new owners using `transferFrom`. The transfer adds the new owner to the list without removing existing owners and transfers the `transferValue` fee to the provider. + + ```solidity + function transferFrom(address from, address to, uint256 tokenId) public; + ``` ### Transfer of Ownership -1. **Additive Ownership**: Transferring ownership adds the new owner to the ownership list without removing the current owners. -2. **Ownership Tracking**: The smart contract tracks the list of owners for each MO-NFT. + +1. **Additive Ownership**: + + - Transferring ownership adds the new owner to the ownership list without removing current owners. This approach reflects the shared nature of digital assets. + +2. **Ownership Tracking**: + + - The smart contract tracks the list of owners for each MO-NFT using the `_owners` mapping. + +3. **Provider Fee Handling**: + + - During a transfer, the specified `transferValue` fee is transferred to the provider. The contract must have sufficient balance to cover this fee. + +4. **Burning Ownership**: + + - Owners can remove themselves from the ownership list using the `burn` function. + + ```solidity + function burn(uint256 tokenId) external; + ``` ### Value Depreciation -1. **Value Model**: As the number of owners increases, the value of the MO-NFT decreases to reflect the reduced exclusivity. -2. **Depreciation Mechanism**: The value depreciation model is defined based on the asset type and distribution strategy. Less ownership equates to more value, following the principle that valued information or assets become less valuable as they become more widely known or accessible. + +1. **Value Model**: + + - As the number of owners increases, the value of the MO-NFT may decrease to reflect the reduced exclusivity. This is an optional model and can be implemented at the application level. + +2. **Depreciation Mechanism**: + + - The value depreciation model can be defined based on the asset type and distribution strategy. This standard does not enforce a specific depreciation mechanism but acknowledges its importance. ### Interface Definitions -The MO-NFT contract implements the following interfaces: -1. **Minting**: - - `function mintToken() public onlyOwner returns (uint256);` +**Minting Functions** + +- `function mintToken() public onlyOwner returns (uint256);` -2. **Transfer**: - - `function transferFrom(address from, address to, uint256 tokenId) public override;` - - `function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public override;` - - `function safeTransferFrom(address from, address to, uint256 tokenId) public override;` +- `function provide(string memory assetName, uint256 size, bytes32 fileHash, address provider, uint256 transferValue) external returns (uint256);` -3. **Ownership Management**: - - `function isOwner(uint256 tokenId, address account) public view returns (bool);` - - `function getOwnersCount(uint256 tokenId) public view returns (uint256);` - - `function balanceOf(address owner) external view override returns (uint256 balance);` - - `function ownerOf(uint256 tokenId) external view override returns (address owner);` +**Transfer Functions** + +- `function transferFrom(address from, address to, uint256 tokenId) public;` + +- `function safeTransferFrom(address from, address to, uint256 tokenId) public;` *(Disabled or overridden)* + +- `function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public;` *(Disabled or overridden)* + +**Ownership Management Functions** + +- `function isOwner(uint256 tokenId, address account) public view returns (bool);` + +- `function getOwnersCount(uint256 tokenId) public view returns (uint256);` + +- `function balanceOf(address owner) external view returns (uint256 balance);` + +- `function ownerOf(uint256 tokenId) external view returns (address owner);` + +**Provider Functions** + +- `function setTransferValue(uint256 tokenId, uint256 newTransferValue) external;` + +**Burn Function** + +- `function burn(uint256 tokenId) external;` + +### Events + +- `event TokenMinted(uint256 indexed tokenId, address indexed owner);` + +- `event TokenTransferred(uint256 indexed tokenId, address indexed from, address indexed to);` + +- `event TokenBurned(uint256 indexed tokenId, address indexed owner);` + +- `event TransferValueUpdated(uint256 indexed tokenId, uint256 oldTransferValue, uint256 newTransferValue);` ### [ERC-721](./eip-721.md) Compliance -The MO-NFT standard is designed to be compatible with the [ERC-721](./eip-721.md) standard. It implements required functions such as `balanceOf`, `ownerOf`, `safeTransferFrom`, and `transferFrom` from the `IERC721` interface. Approval functions are intentionally disabled due to the multi-owner nature of MO-NFTs, which differs from traditional [ERC-721](./eip-721.md) logic. -The `supportsInterface` function ensures that the MO-NFT declares compatibility with the [ERC-721](./eip-721.md) standard, allowing it to be integrated with existing tools and platforms that support [ERC-721](./eip-721.md). +The MO-NFT standard is designed to be compatible with the [ERC-721](./eip-721.md) standard. It implements required functions such as `balanceOf`, `ownerOf`, and `transferFrom` from the IERC-721 interface. + +- **Approval Functions**: Functions like `approve`, `getApproved`, `setApprovalForAll`, and `isApprovedForAll` are intentionally disabled or overridden, as they do not align with the MO-NFT multi-owner model. + +- **Safe Transfer Functions**: The `safeTransferFrom` functions are restricted because traditional ERC-721 transfer safety checks are not applicable when ownership is additive rather than exclusive. +- **Supports Interface**: The `supportsInterface` function ensures that the MO-NFT declares compatibility with the ERC-721 standard, allowing it to be integrated with existing tools and platforms. + + ```solidity + function supportsInterface(bytes4 interfaceId) public view returns (bool) { + return interfaceId == type(IERC721).interfaceId || interfaceId == type(IERC165).interfaceId; + } + ``` ## Rationale -The rationale behind the design choices in the MO-NFT standard is as follows: -1. **Multi-Ownership Model**: - - We adopted a multi-owner structure to better reflect the nature of digital assets, which can be copied and shared without losing their original form. This model allows for broader distribution and access, enhancing collaboration among multiple owners while retaining a single NFT identity. +1. **Multi-Ownership Model**: + + - Digital assets are inherently duplicable and can be shared without loss of quality. The multi-owner model allows broader distribution and collaboration while maintaining a unique token identity. + +2. **Additive Ownership**: + + - By adding new owners without removing existing ones, we support shared ownership models common in collaborative environments and digital content distribution. + +3. **Provider Fee Mechanism**: -2. **Additive Ownership**: - - The decision to allow ownership transfer to be additive (i.e., not removing existing owners) stems from the concept of shared digital assets. This approach ensures that each transfer does not diminish the original owner's rights, enabling a more inclusive ownership model that suits digital content, where multiple stakeholders may have legitimate claims. + - Incorporating a provider fee incentivizes creators and providers by rewarding them whenever the asset is transferred. This aligns with models where creators receive royalties or fees for their work. -3. **Value Depreciation Mechanism**: - - We introduced the value depreciation model based on the concept that the value of information decreases as it becomes more widely accessible. This mechanism incentivizes exclusivity and ensures that as more owners are added, the token's value adjusts accordingly, aligning with real-world economic principles. +4. **Ownership Burning**: -4. **[ERC-721](./eip-721.md) Compatibility**: - - We ensured that the MO-NFT standard maintains compatibility with [ERC-721](./eip-721.md) to allow for seamless integration with existing NFT tools, platforms, and marketplaces. This decision was made to leverage the established infrastructure and ecosystem of [ERC-721](./eip-721.md) while extending its capabilities for multi-ownership. + - Allowing owners to remove themselves from the ownership list provides flexibility, enabling owners to relinquish rights or convert their digital ownership into real-world assets. + +5. **ERC-721 Compatibility**: + + - Maintaining compatibility with ERC-721 allows MO-NFTs to leverage existing infrastructure, tools, and platforms, facilitating adoption and interoperability. ## Backwards Compatibility -This standard is designed to be backwards compatible with the existing [ERC-721](./eip-721.md) standard. Existing tools and platforms that support [ERC-721](./eip-721.md) should be able to interact with MO-NFTs with minimal modifications. + +While the MO-NFT standard aims to maintain compatibility with ERC-721, certain deviations are necessary due to the multi-owner model: + +- **Approval Functions**: Disabled or overridden to prevent their use, as they do not align with the MO-NFT's ownership structure. + +- **Safe Transfer Functions**: Restricted because the additive ownership model does not fit the exclusive ownership assumptions in ERC-721. + +- **`ownerOf` Function**: Returns the first owner in the owners list for compatibility, but the concept of a single owner does not fully apply. + +Developers should be aware of these differences when integrating MO-NFTs into systems designed for standard ERC-721 tokens. ## Test Cases -These test cases demonstrate the functionality of MO-NFTs: 1. **Minting an MO-NFT and Verifying Initial Ownership**: + - **Input**: - - Call `mintToken()` with the sender as the owner. + + - Call `mintToken()` as the provider. + - **Expected Output**: + - A new `tokenId` is generated. - - The sender becomes the first owner of the token (`isOwner(tokenId, sender)` returns `true`). - - The total supply increases by 1 (`totalSupply()` reflects the new count). - - The `balanceOf(sender)` increases by 1. -2. **Transferring an MO-NFT and Verifying Additive Ownership**: + - The caller is added as the first owner. + + - The `balanceOf` the caller increases by 1. + + - The provider is recorded for the token. + + - `TokenMinted` event is emitted. + +2. **Transferring an MO-NFT and Verifying Provider Fee Transfer**: + + - **Input**: + + - Call `transferFrom(from, to, tokenId)` where `from` is an existing owner and `to` is a new address. + + - **Expected Output**: + + - The `to` address is added to the owners list. + + - The `transferValue` fee is transferred to the provider. + + - The `balanceOf` of the `to` address increases by 1. + + - `TokenTransferred` event is emitted. + +3. **Burning Ownership**: + + - **Input**: + + - An owner calls `burn(tokenId)`. + + - **Expected Output**: + + - The owner is removed from the owners list. + + - The `balanceOf` of the owner decreases by 1. + + - If the owners list becomes empty, the token is effectively non-existent. + + - `TokenBurned` event is emitted. + +4. **Setting Transfer Value**: + - **Input**: - - Call `transferFrom(sender, receiver, tokenId)` where the sender is an existing owner and `receiver` is not an owner. + + - The provider calls `setTransferValue(tokenId, newTransferValue)`. + - **Expected Output**: - - The receiver is added to the list of owners (`isOwner(tokenId, receiver)` returns `true`). - - The sender remains an owner (`isOwner(tokenId, sender)` still returns `true`). - - The total number of owners for the token increases by 1 (`getOwnersCount(tokenId)` reflects the new count). - - The `balanceOf(receiver)` increases by 1, and the `balanceOf(sender)` remains the same. -### Additional Detailed Test Case (Optional): -3. **Failing to Transfer to an Existing Owner**: + - The `transferValue` is updated in the contract. + + - `TransferValueUpdated` event is emitted. + +5. **Failing Transfer to Existing Owner**: + - **Input**: - - Call `transferFrom(sender, existingOwner, tokenId)` where `existingOwner` is already listed as an owner of the token. + + - Attempt to `transferFrom` to an address that is already an owner. + - **Expected Output**: - - The transfer is reverted with an error message `"Recipient is already an owner"`. - - The state of the ownership list remains unchanged. - + + - The transaction reverts with the error `"MO-NFT: Recipient is already an owner"`. + + - No changes to ownership or balances occur. + + +## Reference Implementation + +The full reference implementation code for the MO-NFT standard is included in the EIPs repository under assets folder. This ensures the code is preserved alongside the EIP and remains accessible. + +- **Contracts**: + + - [`MONFT.sol`](../assets/eip-7743/MONFT.sol): The base implementation of the MO-NFT standard. + + - [`DigitalAsset.sol`](../assets/eip-7743/DigitalAsset.sol): An extended implementation for digital assets with provider fees. + +- **Interfaces**: + + - [`IDigitalAsset.sol`](../assets/eip-7743/IDigitalAsset.sol): Interface defining the functions for digital asset management. + +### Key Functions in Reference Implementation + +**Minting Tokens** + +```solidity +function mintToken() public onlyOwner returns (uint256) { + _nextTokenId++; + + // Add the sender to the set of owners for the new token + _owners[_nextTokenId].add(msg.sender); + + // Increment the balance of the owner + _balances[msg.sender] += 1; + + // Set the provider to the caller + _providers[_nextTokenId] = msg.sender; + + emit TokenMinted(_nextTokenId, msg.sender); + return _nextTokenId; +} +``` + +**Transferring Tokens** + +```solidity +function transferFrom(address from, address to, uint256 tokenId) public override { + require(isOwner(tokenId, msg.sender), "MO-NFT: Caller is not an owner"); + require(to != address(0), "MO-NFT: Transfer to zero address"); + require(!isOwner(tokenId, to), "MO-NFT: Recipient is already an owner"); + + // Add the new owner to the set + _owners[tokenId].add(to); + _balances[to] += 1; + + // Transfer the transferValue to the provider + uint256 transferValue = _transferValues[tokenId]; + address provider = _providers[tokenId]; + require(address(this).balance >= transferValue, "Insufficient contract balance"); + + (bool success, ) = provider.call{value: transferValue}(""); + require(success, "Transfer to provider failed"); + + emit TokenTransferred(tokenId, from, to); +} +``` + +**Burning Ownership** + +```solidity +function burn(uint256 tokenId) external { + require(isOwner(tokenId, msg.sender), "MO-NFT: Caller is not an owner"); + + // Remove the caller from the owners set + _owners[tokenId].remove(msg.sender); + _balances[msg.sender] -= 1; + + emit TokenBurned(tokenId, msg.sender); +} +``` + ## Security Considerations -1. **Data Integrity**: Protect the integrity of the ownership list and associated metadata. -2. **Smart Contract Security**: Follow best practices for smart contract development to prevent vulnerabilities and exploits. + +1. **Reentrancy Attacks**: + + - **Mitigation**: Use the Checks-Effects-Interactions pattern when transferring Ether (e.g., transferring `transferValue` to the provider). + + - **Recommendation**: Consider using `ReentrancyGuard` from OpenZeppelin to prevent reentrant calls. + +2. **Integer Overflow and Underflow**: + + - **Mitigation**: Solidity 0.8.x automatically checks for overflows and underflows, throwing exceptions when they occur. + +3. **Access Control**: + + - **Ensured By**: + + - Only owners can call transfer functions. + + - Only providers can set the `transferValue`. + + - Use of `require` statements to enforce access control. + +4. **Denial of Service (DoS)**: + + - **Consideration**: Functions that iterate over owners could be expensive in terms of gas if the owners list is large. + + - **Mitigation**: Avoid such functions or limit the number of owners. + +5. **Data Integrity**: + + - **Ensured By**: Proper use of Solidity's data types and structures, and by emitting events for all state-changing operations for off-chain verification. + +6. **Ether Handling**: + + - **Consideration**: Ensure the contract can receive Ether to handle provider payments. + + - **Mitigation**: Implement a `receive()` function to accept Ether. ## Copyright + Copyright and related rights waived via [CC0](../LICENSE.md). +``` diff --git a/assets/erc-7743/DigitalAsset.sol b/assets/erc-7743/DigitalAsset.sol new file mode 100644 index 0000000000..7f3d6de140 --- /dev/null +++ b/assets/erc-7743/DigitalAsset.sol @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "./MONFT.sol"; +import "./IDigitalAsset.sol"; + +contract DigitAsset is MultiOwnerNFT, IDigitalAsset { + struct DigitAssetInfo { + uint256 assetId; + string assetName; + uint256 size; + bytes32 fileHash; + address provider; + uint256 transferValue; + } + + mapping(uint256 => DigitAssetInfo) private _assets; + + // Events + event AssetProvided( + uint256 indexed assetId, + uint256 indexed size, + address indexed provider, + bytes32 fileHash, + uint256 transferValue + ); + event AssetTransferred( + address indexed from, + address indexed to, + uint256 indexed assetId, + uint256 transferValue + ); + event TransferValueUpdated( + uint256 indexed assetId, + uint256 oldTransferValue, + uint256 newTransferValue + ); + + constructor() payable MultiOwnerNFT(msg.sender) {} + + function getAssetsTotalSupply() public view override returns (uint256) { + return totalSupply(); + } + + function getAssetName( + uint256 assetId + ) public view override returns (string memory) { + require(_exists(assetId), "Asset: Asset does not exist"); + return _assets[assetId].assetName; + } + + function getAssetDetails( + uint256 assetId + ) public view override returns (AssetDetails memory) { + require(_exists(assetId), "Asset: Asset does not exist"); + DigitAssetInfo storage asset = _assets[assetId]; + return + AssetDetails({ + assetId: asset.assetId, + assetName: asset.assetName, + size: asset.size, + fileHash: asset.fileHash, + provider: asset.provider, + transferValue: asset.transferValue + }); + } + + function provide( + string memory assetName, + uint256 size, + bytes32 fileHash, + address provider, + uint256 transferValue + ) external returns (uint256) { + uint256 assetId = mintToken(); + _assets[assetId] = DigitAssetInfo({ + assetId: assetId, + assetName: assetName, + size: size, + fileHash: fileHash, + provider: provider, + transferValue: transferValue + }); + emit AssetProvided(assetId, size, provider, fileHash, transferValue); + return assetId; + } + + function setTransferValue( + uint256 assetId, + uint256 newTransferValue + ) external { + require(_exists(assetId), "Asset: Asset does not exist"); + DigitAssetInfo storage asset = _assets[assetId]; + require( + msg.sender == asset.provider, + "Only provider can update transfer value" + ); + + uint256 oldTransferValue = asset.transferValue; + asset.transferValue = newTransferValue; + + emit TransferValueUpdated(assetId, oldTransferValue, newTransferValue); + } + + function transferFrom( + address from, + address to, + uint256 tokenId + ) public override { + require( + isOwner(tokenId, msg.sender), // Ensure that `msg.sender` is an owner + "MO-NFT: Transfer from incorrect account" + ); + require(to != address(0), "MO-NFT: Transfer to the zero address"); + + _transferWithProviderPayment(from, to, tokenId); + } + + function _transferWithProviderPayment( + address from, + address to, + uint256 tokenId + ) internal { + DigitAssetInfo storage asset = _assets[tokenId]; + + // Pay the provider the transferValue for this asset + require( + address(this).balance >= asset.transferValue, + "Insufficient contract balance for provider payment" + ); + payable(asset.provider).transfer(asset.transferValue); + + // Call the internal transfer function in MultiOwnerNFT + _transfer(from, to, tokenId); + + emit AssetTransferred(from, to, tokenId, asset.transferValue); + } + + // Allow the contract to receive ETH to fund transfers + receive() external payable {} +} diff --git a/assets/erc-7743/IDigitalAsset.sol b/assets/erc-7743/IDigitalAsset.sol new file mode 100644 index 0000000000..835746e74a --- /dev/null +++ b/assets/erc-7743/IDigitalAsset.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +interface IDigitalAsset { + function getAssetsTotalSupply() external view returns (uint256); + function getAssetName(uint256 assetId) external view returns (string memory); + function getAssetDetails(uint256 assetId) external view returns (AssetDetails memory); + + // Define the structure here as well if you wish to use it as a return type + struct AssetDetails { + uint256 assetId; + string assetName; + uint256 size; + bytes32 fileHash; + address provider; + uint256 transferValue; + } +} \ No newline at end of file diff --git a/assets/erc-7743/MONFT.sol b/assets/erc-7743/MONFT.sol new file mode 100644 index 0000000000..e952994b1a --- /dev/null +++ b/assets/erc-7743/MONFT.sol @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/utils/Context.sol"; +import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; +import "@openzeppelin/contracts/utils/Address.sol"; +import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; // Import EnumerableSet + +contract MultiOwnerNFT is Context, ERC165, IERC721, Ownable { + using Address for address; + using EnumerableSet for EnumerableSet.AddressSet; // Use EnumerableSet for address sets + + uint256 private _nextTokenId; + + // Replace array with EnumerableSet for owners + mapping(uint256 => EnumerableSet.AddressSet) internal _owners; + mapping(uint256 => address) private _tokenApprovals; + mapping(address => mapping(address => bool)) private _operatorApprovals; + mapping(address => uint256) private _balances; + + event TokenMinted(uint256 tokenId, address owner); + event TokenTransferred(uint256 tokenId, address from, address to); + event TokenBurned(uint256 tokenId, address owner); // New burn event + + constructor(address owner) Ownable(owner) {} + + function mintToken() public onlyOwner returns (uint256) { + _nextTokenId++; + + // Add the sender to the set of owners for the new token + _owners[_nextTokenId].add(_msgSender()); + + // Increment the balance of the owner + _balances[_msgSender()] += 1; + + emit TokenMinted(_nextTokenId, _msgSender()); + return _nextTokenId; + } + + function isOwner( + uint256 tokenId, + address account + ) public view returns (bool) { + require(_exists(tokenId), "MO-NFT: Token does not exist"); + + // Check if the account is in the owners set for the token + return _owners[tokenId].contains(account); + } + + function _exists(uint256 tokenId) internal view returns (bool) { + return _owners[tokenId].length() > 0; + } + + // IERC721 Functions + + function totalSupply() public view returns (uint256) { + return _nextTokenId; + } + + function balanceOf( + address owner + ) external view override returns (uint256 balance) { + require( + owner != address(0), + "MO-NFT: Balance query for the zero address" + ); + + // Return the balance from the _balances mapping + return _balances[owner]; + } + + function ownerOf( + uint256 tokenId + ) external view override returns (address owner) { + require(_exists(tokenId), "MO-NFT: Owner query for nonexistent token"); + + // Return the first owner in the set (since this is an EnumerableSet, order is not guaranteed) + return _owners[tokenId].at(0); + } + + function getOwnersCount(uint256 tokenId) public view returns (uint256) { + require(_exists(tokenId), "MO-NFT: Token does not exist"); + + // Return the number of owners for the given tokenId + return EnumerableSet.length(_owners[tokenId]); + } + + function approve(address, uint256) external pure override { + revert("MO-NFT: Approve is forbidden"); + } + + function getApproved(uint256) public pure override returns (address) { + revert("MO-NFT: Approve is forbidden"); + } + + function setApprovalForAll(address, bool) external pure override { + revert("MO-NFT: Approve is forbidden"); + } + + function isApprovedForAll( + address, + address + ) public pure override returns (bool) { + revert("MO-NFT: Approve is forbidden"); + } + + function transferFrom( + address from, + address to, + uint256 tokenId + ) public virtual override { + require( + isOwner(tokenId, _msgSender()), + "MO-NFT: Transfer from incorrect account" + ); + require(to != address(0), "MO-NFT: Transfer to the zero address"); + + _transfer(from, to, tokenId); + } + + function safeTransferFrom(address, address, uint256) public pure override { + revert("MO-NFT: safeTransferFrom is forbidden"); + } + + function safeTransferFrom( + address, + address, + uint256, + bytes memory + ) public pure override { + revert("MO-NFT: safeTransferFrom is forbidden"); + } + + function _transfer(address from, address to, uint256 tokenId) internal { + require( + isOwner(tokenId, from), + "MO-NFT: Transfer from incorrect owner" + ); + require(to != address(0), "MO-NFT: Transfer to the zero address"); + require(!isOwner(tokenId, to), "MO-NFT: Recipient is already an owner"); + + // Add the new owner to the EnumerableSet + _owners[tokenId].add(to); + + // Update balances + _balances[to] += 1; + + emit TokenTransferred(tokenId, from, to); + } + + // New burn function + function burn(uint256 tokenId) external { + require( + isOwner(tokenId, _msgSender()), + "MO-NFT: Only an owner can burn their ownership" + ); + + // Remove the caller from the owners set + _owners[tokenId].remove(_msgSender()); + + // Decrement the balance of the owner + _balances[_msgSender()] -= 1; + + emit TokenBurned(tokenId, _msgSender()); + } + + function supportsInterface( + bytes4 interfaceId + ) public view virtual override(ERC165, IERC165) returns (bool) { + return + interfaceId == type(IERC721).interfaceId || + super.supportsInterface(interfaceId); + } +} From 81a5b28575a6cd3198243fac366fcca879196648 Mon Sep 17 00:00:00 2001 From: Konrad Date: Thu, 28 Nov 2024 12:54:31 +0800 Subject: [PATCH 3/4] Update ERC-7579: specify executeFromExecutor return data type Merged by EIP-Bot. --- ERCS/erc-7579.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ERCS/erc-7579.md b/ERCS/erc-7579.md index 8730fe2276..6b05388a79 100644 --- a/ERCS/erc-7579.md +++ b/ERCS/erc-7579.md @@ -68,6 +68,8 @@ interface IExecution { * @param mode The encoded execution mode of the transaction. * @param executionCalldata The encoded execution call data. * + * @return returnData An array with the returned data of each executed subcall + * * MUST ensure adequate authorization control: i.e. onlyExecutorModule * If a mode is requested that is not supported by the Account, it MUST revert */ @@ -91,9 +93,10 @@ The account MAY also implement the following function in accordance with ERC-433 function executeUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external; ``` -If an account chooses to implement `executeUserOp`, this method SHOULD ensure the account executes `userOp.calldata` except 4 most significant bytes, which are reserved for `executeUserOp.selector` as per ERC-4337. Thus the `userOp.callData[4:]` should represent the calldata for a valid call to the account. It is RECOMMENDED that the account executes a `delegatecall` in order to preserve the original `msg.sender` to the account. +If an account chooses to implement `executeUserOp`, this method SHOULD ensure the account executes `userOp.calldata` except 4 most significant bytes, which are reserved for `executeUserOp.selector` as per ERC-4337. Thus the `userOp.callData[4:]` should represent the calldata for a valid call to the account. It is RECOMMENDED that the account executes a `delegatecall` in order to preserve the original `msg.sender` to the account. Example: + ``` (bool success, bytes memory innerCallRet) = address(this).delegatecall(userOp.callData[4:]); ``` @@ -228,7 +231,7 @@ If the smart account has a fallback handler installed, it: - MUST use `call` or `staticcall` to invoke the fallback handler - MUST utilize [ERC-2771](./eip-2771.md) to add the original `msg.sender` to the `calldata` sent to the fallback handler - MUST route to fallback handlers based on the function selector of the calldata -- MAY implement authorization control, which SHOULD be done via hooks +- MAY implement authorization control, which SHOULD be done via hooks If the account adds features via fallback, these should be considered the same as if the account was implementing those features natively. ERC-165 support (see below) is one example of such an approach. Note, that it is only RECOMMENDED to implement view functions via fallback where this can lead to greater extensibility. It is NOT RECOMMENDED to implement core account logic via a fallback. @@ -280,6 +283,7 @@ interface IModule { Note: A single module that is of multiple types MAY decide to pass `moduleTypeId` inside `data` to `onInstall` and/or `onUninstall` methods, so those methods are able to properly handle installation/uninstallation for various types. Example: + ```solidity // Module.sol function onInstall(bytes calldata data) external { @@ -367,7 +371,7 @@ Our approach has been twofold: ### Extensions -While we want to be minimal, we also want to allow for innovation and opinionated features. Some of these features might also need to be standardized (for similar reasons as the core interfaces) even if not all smart accounts will implement them. To ensure that this is possible, we suggest for future standardization efforts to be done as extensions to this standard. This means that the core interfaces will not change, but that new interfaces can be added as extensions. These should be proposed as separate ERCs, for example with the title `[FEATURE] Extension for ERC-7579`. +While we want to be minimal, we also want to allow for innovation and opinionated features. Some of these features might also need to be standardized (for similar reasons as the core interfaces) even if not all smart accounts will implement them. To ensure that this is possible, we suggest for future standardization efforts to be done as extensions to this standard. This means that the core interfaces will not change, but that new interfaces can be added as extensions. These should be proposed as separate ERCs, for example with the title "[FEATURE] Extension for [ERC-7579](./eip-7579.md)". ### Specification From 1665a4a5d8e41eccf9ff2934a81073214c8dd847 Mon Sep 17 00:00:00 2001 From: James Save Chives Date: Thu, 28 Nov 2024 14:00:32 +0800 Subject: [PATCH 4/4] Update ERC-7743: Remove email & format document Merged by EIP-Bot. --- ERCS/erc-7743.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ERCS/erc-7743.md b/ERCS/erc-7743.md index 6917b66b1b..992b5349bc 100755 --- a/ERCS/erc-7743.md +++ b/ERCS/erc-7743.md @@ -2,7 +2,7 @@ eip: 7743 title: Multi-Owner Non-Fungible Tokens (MO-NFT) description: A new type of non-fungible token that supports multiple owners, allowing shared ownership and transferability among users. -author: James Savechives (@jamesavechives) +author: James Savechives (@jamesavechives) discussions-to: https://ethereum-magicians.org/t/discussion-on-eip-7743-multi-owner-non-fungible-tokens-mo-nft/20577 status: Draft type: Standards Track @@ -392,4 +392,3 @@ function burn(uint256 tokenId) external { ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md). -```