Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ERC: ERC-721 Ownership Shares Extension #266

Merged
merged 42 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
8136d4a
add erc: data access control
Feb 17, 2024
2d60f55
update
bizliaoyuan Feb 17, 2024
975547d
Update erc-7626.md
bizliaoyuan Feb 17, 2024
d23d89e
Update erc-7626.md
chenly Feb 17, 2024
1eac0a2
Update erc-7626.md
chenly Feb 17, 2024
6ce9942
Ownable
bizliaoyuan Feb 18, 2024
0836e71
Update erc-7626.md
chenly Feb 18, 2024
c407c14
change title
Feb 19, 2024
20c7af4
Update ERCS/erc-7626.md
chenly Feb 19, 2024
9c811ea
Update ERCS/erc-7626.md
chenly Feb 19, 2024
22de7f7
rename eip #
Feb 19, 2024
9af4272
rename
Feb 19, 2024
5a3589b
Update erc-7628.md
chenly Feb 20, 2024
1647e89
Update erc-7628.md
chenly Feb 20, 2024
77dfbdf
ERC-721 Balance Extension
bizliaoyuan Feb 23, 2024
8ca460b
update
bizliaoyuan Feb 23, 2024
92cf780
update
bizliaoyuan Feb 23, 2024
db3d4f9
Error: error[preamble-requires-ref-description]: proposals mentioned …
bizliaoyuan Feb 23, 2024
6437253
Error: error[preamble-uint-requires]: preamble header `requires` item…
bizliaoyuan Feb 23, 2024
2020a6d
Update erc-7628.md
bizliaoyuan Feb 23, 2024
c720423
Update erc-7628.md
bizliaoyuan Feb 23, 2024
02281a1
add IERC721
bizliaoyuan Feb 23, 2024
d566d27
Update erc-7628.md
chenly Feb 24, 2024
3b856e4
update
bizliaoyuan Feb 24, 2024
9885664
Merge branch 'erc-7626' of https://github.com/chenly/ERCs into erc-7626
bizliaoyuan Feb 24, 2024
672f523
update Security Considerations
bizliaoyuan Feb 24, 2024
adcf37e
Merge branch 'master' into erc-7626
chenly Feb 24, 2024
5d8c76e
Add Reference Implementation
bizliaoyuan Feb 26, 2024
0134f59
check allowance
bizliaoyuan Feb 26, 2024
5c47ee6
update
bizliaoyuan Feb 26, 2024
48cbcfd
update
bizliaoyuan Feb 26, 2024
18220ef
Update erc-7628.md
chenly Feb 28, 2024
132ed56
Correct some text formatting
Mar 1, 2024
fae628f
change Rationale
bizliaoyuan Mar 2, 2024
50367fa
new version
bizliaoyuan Mar 6, 2024
1c0dafa
change link
bizliaoyuan Mar 6, 2024
63c4333
update
bizliaoyuan Mar 6, 2024
d68a4bb
update
bizliaoyuan Mar 6, 2024
f9f4677
Update erc-7628.md
bizliaoyuan Mar 7, 2024
f93384f
Update erc-7628.md
bizliaoyuan Mar 7, 2024
a3961f4
Update erc-7628.md
bizliaoyuan Mar 7, 2024
a8f5aa5
rm SharesAdded
bizliaoyuan Mar 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions ERCS/erc-7628.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
---
eip: 7628
title: ERC-721 Balance Extension
description: Extend ERC-721 to include balance functionalities similar to ERC-20.
chenly marked this conversation as resolved.
Show resolved Hide resolved
author: Chen Liaoyuan (@chenly) <cly@kip.pro>
discussions-to: https://ethereum-magicians.org/t/erc-7628-erc-721-balance-extension/18744
status: Draft
type: Standards Track
category: ERC
created: 2024-02-20
requires: 20, 721
---

## Abstract

This extension defines an interface that adds balance functionalities to [ERC-721](./eip-721.md) tokens, enabling functions and events for balance querying, transfer, and approval similar to [ERC-20](./eip-20.md) tokens.
chenly marked this conversation as resolved.
Show resolved Hide resolved

## Motivation

Some NFTs represent divisible ownership stakes or shares of associated rights, such as those pertaining to digital assets, real estate, or intellectual property. The ability to accurately query and exchange these shares is crucial for facilitating efficient market operations. However, the absence of built-in support for token balances within the current [ERC-721](./eip-721.md) standard poses challenges and limitations in managing and trading these rights effectively. By introducing functionalities that mirror those of [ERC-20](./eip-20.md) token balances, we can offer a comprehensive solution for [ERC-721](./eip-721.md) tokens. This enhancement empowers holders to effortlessly inquire about and exchange their respective ownership shares, thereby promoting wider acceptance and circulation of NFTs.
chenly marked this conversation as resolved.
Show resolved Hide resolved

## Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.

Implementers of this extension **MUST** have all of the following functions:

```solidity
pragma solidity ^0.8.0;

interface IERC7628 /* is IERC721 */ {
/// @notice Returns the number of decimal places used for token balances.
/// @return The number of decimal places.
function balanceDecimals() external view returns (uint8);

/// @notice Returns the total sum of token balances in existence.
/// @return The total sum of balances for all tokens.
function totalBalances() external view returns (uint256);

/// @notice Returns the balance of the specified token.
/// @param tokenId The identifier of the token.
/// @return The balance of the token.
function balanceOf(uint256 tokenId) external view returns (uint256);
chenly marked this conversation as resolved.
Show resolved Hide resolved

/// @notice Returns the allowance granted to the specified spender by the owner for the specified token.
/// @param tokenId The identifier of the token.
/// @param spender The address of the spender.
/// @return The allowance granted to the spender.
function allowance(uint256 tokenId, address spender) external view returns (uint256);

/// @notice Approves the specified address to spend the specified amount of tokens on behalf of the caller.
/// @param tokenId The identifier of the token.
/// @param to The address of the spender.
/// @param amount The amount of tokens to approve.
function approve(uint256 tokenId, address to, uint256 amount) external;

/// @notice Transfers tokens from the balance of one token to another.
/// @param _fromTokenId The identifier of the sender token.
/// @param _toTokenId The identifier of the recipient token.
/// @param amount The amount of tokens to transfer.
function transferFrom(uint256 _fromTokenId, uint256 _toTokenId, uint256 amount) external;

/// @notice Transfers tokens from the balance of one token to another address.
/// @param _fromTokenId The identifier of the sender token.
/// @param _to to The address of the recipient.
/// @param amount The amount of tokens to transfer.
function transferFrom(uint256 _fromTokenId, address _to, uint256 amount) external;

/// @notice Emitted when tokens are transferred from the balance of one token to another.
/// @param _fromTokenId The identifier of the sender token.
/// @param _toTokenId The identifier of the recipient token.
/// @param value The amount of tokens transferred.
event Transfer(uint256 indexed _fromTokenId, uint256 indexed _toTokenId, uint256 value);

/// @notice Emitted when an approval is granted for a spender to spend tokens on behalf of an owner.
/// @param _tokenId The token identifier.
/// @param spender The address of the spender.
/// @param value The amount of tokens approved.
event Approval(uint256 indexed _tokenId, address indexed spender, uint256 value);
}
```

## Rationale

By adding a balance attribute to each token and adopting mechanisms similar to those found in [ERC-20](./eip-20.md), we enable the seamless transfer of balances between tokens, thereby facilitating the holders to easily query and trade the ownership shares represented by their balances. Additionally, if required, we can opt to mint new NFTs to increase the balance. The removal of the `transfer` function from [ERC-20](./eip-20.md) is due to the necessity of using `tokenId` for transfers. Including it would result in redundancy with the `transferFrom` function.

## Backwards Compatibility

This standard is fully [ERC-721](./eip-721.md) compatible.

## Reference Implementation

A full interface of a smart account can be found in [`ERC7628.sol`](../assets/eip-7628/ERC7628.sol).

## Security Considerations

### 1. Clearing Approvals upon Ownership Transfer
It is crucial to clear any existing approvals when the ownership of a token is transferred. This measure prevents previously approved addresses from retaining access to the tokens once their ownership has changed.

### 2. Guarding Against Reentrancy Attacks
Implementations must protect against reentrancy attacks, especially in functions that modify balances or ownership, such as `transferFrom`. Employing reentrancy guards can help prevent attackers from exploiting vulnerabilities during the execution process.

### 3. Validating Token IDs and Addresses
Ensuring the validity of token IDs and addresses in all transfer or approval operations is essential. This includes verifying that token IDs are legitimate and that addresses are not zero, thereby preventing accidental loss or incorrect handling of tokens.

### 4. Transferring Balances with Ownership
When the ownership of a token is transferred, it is important to also transfer any associated balances to the new owner. This approach ensures that the ownership and any financial value or rights linked to the token remain consistently aligned.

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).
84 changes: 84 additions & 0 deletions assets/erc-7628/ERC7628.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: CC0-1.0

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract ERC7628 is ERC721, Ownable, ReentrancyGuard {
mapping(uint256 => uint256) private _balances;
mapping(uint256 => mapping(address => uint256)) private _allowances;
uint256 private _totalBalance;
uint256 private _nextTokenId;

constructor(address initialOwner)
ERC721("MyToken", "MTK")
Ownable(initialOwner)
{}

function addBalance(uint256 tokenId, uint256 amount) public onlyOwner {
require(tokenId > 0, "ERC7628: tokenId cannot be zero");
_balances[tokenId] += amount;
_totalBalance += amount;
emit Transfer(0, tokenId, amount);
}

function balanceDecimals() external pure returns (uint8) {
return 18;
}

function totalBalances() external view returns (uint256) {
return _totalBalance;
}

function balanceOf(uint256 tokenId) external view returns (uint256) {
return _balances[tokenId];
}

function allowance(uint256 tokenId, address spender) external view returns (uint256) {
return _allowances[tokenId][spender];
}

function approve(uint256 tokenId, address to, uint256 amount) external {
require(to != ownerOf(tokenId), "ERC7628: approval to current owner");
require(msg.sender == ownerOf(tokenId), "ERC7628: approve caller is not owner");

_allowances[tokenId][to] = amount;
emit Approval(tokenId, to, amount);
}

function transferFrom(uint256 _fromTokenId, uint256 _toTokenId, uint256 amount) external nonReentrant {
require(_isApprovedOrOwner(msg.sender, _fromTokenId), "ERC7628: transfer caller is not owner nor approved");
_transfer(_fromTokenId, _toTokenId, amount);
}

function transferFrom(uint256 _fromTokenId, address _to, uint256 amount) external nonReentrant {
require(_isApprovedOrOwner(msg.sender, _fromTokenId), "ERC7628: transfer caller is not owner nor approved");
_nextTokenId++;
_safeMint(_to, _nextTokenId);
_transfer(_fromTokenId, _nextTokenId, amount);
}

function _transfer(uint256 fromTokenId, uint256 toTokenId, uint256 amount) internal nonReentrant {
require(_balances[fromTokenId] >= amount, "ERC7628: transfer amount exceeds balance");

// Check allowance for non-owner transfers
if (msg.sender != ownerOf(fromTokenId)) {
require(_allowances[fromTokenId][msg.sender] >= amount, "ERC7628: transfer amount exceeds allowance");
_allowances[fromTokenId][msg.sender] -= amount;
}

_balances[fromTokenId] -= amount;
_balances[toTokenId] += amount;

emit Transfer(fromTokenId, toTokenId, amount);
}

function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) {
return (spender == ownerOf(tokenId) || _allowances[tokenId][spender] > 0);
}

event Transfer(uint256 indexed fromTokenId, uint256 indexed toTokenId, uint256 value);
event Approval(uint256 indexed tokenId, address indexed spender, uint256 value);
}
Loading