Skip to content

Commit 43b3319

Browse files
arr00Amxxernestognw
authored
Add ERC6909 Implementation along with extensions (#5394)
Co-authored-by: Hadrien Croubois <[email protected]> Co-authored-by: Ernesto García <[email protected]>
1 parent df878c8 commit 43b3319

19 files changed

+1008
-0
lines changed

.changeset/brown-turkeys-marry.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openzeppelin-solidity': minor
3+
---
4+
5+
`ER6909TokenSupply`: Add an extension of ERC6909 which tracks total supply for each token id.

.changeset/dirty-bananas-shake.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openzeppelin-solidity': minor
3+
---
4+
5+
`ERC6909ContentURI`: Add an extension of ERC6909 which adds content URI functionality.

.changeset/proud-cooks-do.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openzeppelin-solidity': minor
3+
---
4+
5+
`ERC6909Metadata`: Add an extension of ERC6909 which adds metadata functionality.

.changeset/ten-hats-begin.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openzeppelin-solidity': minor
3+
---
4+
5+
`ERC6909`: Add a standard implementation of ERC6909.

contracts/interfaces/README.adoc

+12
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ are useful to interact with third party contracts that implement them.
4040
- {IERC5313}
4141
- {IERC5805}
4242
- {IERC6372}
43+
- {IERC6909}
44+
- {IERC6909ContentURI}
45+
- {IERC6909Metadata}
46+
- {IERC6909TokenSupply}
4347
- {IERC7674}
4448

4549
== Detailed ABI
@@ -84,4 +88,12 @@ are useful to interact with third party contracts that implement them.
8488

8589
{{IERC6372}}
8690

91+
{{IERC6909}}
92+
93+
{{IERC6909ContentURI}}
94+
95+
{{IERC6909Metadata}}
96+
97+
{{IERC6909TokenSupply}}
98+
8799
{{IERC7674}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
import {ERC6909Metadata} from "../../../../token/ERC6909/extensions/draft-ERC6909Metadata.sol";
5+
6+
contract ERC6909GameItems is ERC6909Metadata {
7+
uint256 public constant GOLD = 0;
8+
uint256 public constant SILVER = 1;
9+
uint256 public constant THORS_HAMMER = 2;
10+
uint256 public constant SWORD = 3;
11+
uint256 public constant SHIELD = 4;
12+
13+
constructor() {
14+
_setDecimals(GOLD, 18);
15+
_setDecimals(SILVER, 18);
16+
// Default decimals is 0
17+
_setDecimals(SWORD, 9);
18+
_setDecimals(SHIELD, 9);
19+
20+
_mint(msg.sender, GOLD, 10 ** 18);
21+
_mint(msg.sender, SILVER, 10_000 ** 18);
22+
_mint(msg.sender, THORS_HAMMER, 1);
23+
_mint(msg.sender, SWORD, 10 ** 9);
24+
_mint(msg.sender, SHIELD, 10 ** 9);
25+
}
26+
}

contracts/token/ERC6909/README.adoc

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
= ERC-6909
2+
3+
[.readme-notice]
4+
NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/token/erc6909
5+
6+
This set of interfaces and contracts are all related to the https://eips.ethereum.org/EIPS/eip-6909[ERC-6909 Minimal Multi-Token Interface].
7+
8+
The ERC consists of four interfaces which fulfill different roles--the interfaces are as follows:
9+
10+
. {IERC6909}: Base interface for a vanilla ERC6909 token.
11+
. {IERC6909ContentURI}: Extends the base interface and adds content URI (contract and token level) functionality.
12+
. {IERC6909Metadata}: Extends the base interface and adds metadata functionality, which exposes a name, symbol, and decimals for each token id.
13+
. {IERC6909TokenSupply}: Extends the base interface and adds total supply functionality for each token id.
14+
15+
Implementations are provided for each of the 4 interfaces defined in the ERC.
16+
17+
== Core
18+
19+
{{ERC6909}}
20+
21+
== Extensions
22+
23+
{{ERC6909ContentURI}}
24+
25+
{{ERC6909Metadata}}
26+
27+
{{ERC6909TokenSupply}}
+224
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.20;
4+
5+
import {IERC6909} from "../../interfaces/draft-IERC6909.sol";
6+
import {Context} from "../../utils/Context.sol";
7+
import {IERC165, ERC165} from "../../utils/introspection/ERC165.sol";
8+
9+
/**
10+
* @dev Implementation of ERC-6909.
11+
* See https://eips.ethereum.org/EIPS/eip-6909
12+
*/
13+
contract ERC6909 is Context, ERC165, IERC6909 {
14+
mapping(address owner => mapping(uint256 id => uint256)) private _balances;
15+
16+
mapping(address owner => mapping(address operator => bool)) private _operatorApprovals;
17+
18+
mapping(address owner => mapping(address spender => mapping(uint256 id => uint256))) private _allowances;
19+
20+
error ERC6909InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 id);
21+
error ERC6909InsufficientAllowance(address spender, uint256 allowance, uint256 needed, uint256 id);
22+
error ERC6909InvalidApprover(address approver);
23+
error ERC6909InvalidReceiver(address receiver);
24+
error ERC6909InvalidSender(address sender);
25+
error ERC6909InvalidSpender(address spender);
26+
27+
/// @inheritdoc IERC165
28+
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
29+
return interfaceId == type(IERC6909).interfaceId || super.supportsInterface(interfaceId);
30+
}
31+
32+
/// @inheritdoc IERC6909
33+
function balanceOf(address owner, uint256 id) public view virtual override returns (uint256) {
34+
return _balances[owner][id];
35+
}
36+
37+
/// @inheritdoc IERC6909
38+
function allowance(address owner, address spender, uint256 id) public view virtual override returns (uint256) {
39+
return _allowances[owner][spender][id];
40+
}
41+
42+
/// @inheritdoc IERC6909
43+
function isOperator(address owner, address spender) public view virtual override returns (bool) {
44+
return _operatorApprovals[owner][spender];
45+
}
46+
47+
/// @inheritdoc IERC6909
48+
function approve(address spender, uint256 id, uint256 amount) public virtual override returns (bool) {
49+
_approve(_msgSender(), spender, id, amount);
50+
return true;
51+
}
52+
53+
/// @inheritdoc IERC6909
54+
function setOperator(address spender, bool approved) public virtual override returns (bool) {
55+
_setOperator(_msgSender(), spender, approved);
56+
return true;
57+
}
58+
59+
/// @inheritdoc IERC6909
60+
function transfer(address receiver, uint256 id, uint256 amount) public virtual override returns (bool) {
61+
_transfer(_msgSender(), receiver, id, amount);
62+
return true;
63+
}
64+
65+
/// @inheritdoc IERC6909
66+
function transferFrom(
67+
address sender,
68+
address receiver,
69+
uint256 id,
70+
uint256 amount
71+
) public virtual override returns (bool) {
72+
address caller = _msgSender();
73+
if (sender != caller && !isOperator(sender, caller)) {
74+
_spendAllowance(sender, caller, id, amount);
75+
}
76+
_transfer(sender, receiver, id, amount);
77+
return true;
78+
}
79+
80+
/**
81+
* @dev Creates `amount` of token `id` and assigns them to `account`, by transferring it from address(0).
82+
* Relies on the `_update` mechanism
83+
*
84+
* Emits a {Transfer} event with `from` set to the zero address.
85+
*
86+
* NOTE: This function is not virtual, {_update} should be overridden instead.
87+
*/
88+
function _mint(address to, uint256 id, uint256 amount) internal {
89+
if (to == address(0)) {
90+
revert ERC6909InvalidReceiver(address(0));
91+
}
92+
_update(address(0), to, id, amount);
93+
}
94+
95+
/**
96+
* @dev Moves `amount` of token `id` from `from` to `to` without checking for approvals.
97+
*
98+
* This internal function is equivalent to {transfer}, and can be used to
99+
* e.g. implement automatic token fees, slashing mechanisms, etc.
100+
*
101+
* Emits a {Transfer} event.
102+
*
103+
* NOTE: This function is not virtual, {_update} should be overridden instead.
104+
*/
105+
function _transfer(address from, address to, uint256 id, uint256 amount) internal {
106+
if (from == address(0)) {
107+
revert ERC6909InvalidSender(address(0));
108+
}
109+
if (to == address(0)) {
110+
revert ERC6909InvalidReceiver(address(0));
111+
}
112+
_update(from, to, id, amount);
113+
}
114+
115+
/**
116+
* @dev Destroys a `amount` of token `id` from `account`.
117+
* Relies on the `_update` mechanism.
118+
*
119+
* Emits a {Transfer} event with `to` set to the zero address.
120+
*
121+
* NOTE: This function is not virtual, {_update} should be overridden instead
122+
*/
123+
function _burn(address from, uint256 id, uint256 amount) internal {
124+
if (from == address(0)) {
125+
revert ERC6909InvalidSender(address(0));
126+
}
127+
_update(from, address(0), id, amount);
128+
}
129+
130+
/**
131+
* @dev Transfers `amount` of token `id` from `from` to `to`, or alternatively mints (or burns) if `from`
132+
* (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
133+
* this function.
134+
*
135+
* Emits a {Transfer} event.
136+
*/
137+
function _update(address from, address to, uint256 id, uint256 amount) internal virtual {
138+
address caller = _msgSender();
139+
140+
if (from != address(0)) {
141+
uint256 fromBalance = _balances[from][id];
142+
if (fromBalance < amount) {
143+
revert ERC6909InsufficientBalance(from, fromBalance, amount, id);
144+
}
145+
unchecked {
146+
// Overflow not possible: amount <= fromBalance.
147+
_balances[from][id] = fromBalance - amount;
148+
}
149+
}
150+
if (to != address(0)) {
151+
_balances[to][id] += amount;
152+
}
153+
154+
emit Transfer(caller, from, to, id, amount);
155+
}
156+
157+
/**
158+
* @dev Sets `amount` as the allowance of `spender` over the `owner`'s `id` tokens.
159+
*
160+
* This internal function is equivalent to `approve`, and can be used to e.g. set automatic allowances for certain
161+
* subsystems, etc.
162+
*
163+
* Emits an {Approval} event.
164+
*
165+
* Requirements:
166+
*
167+
* - `owner` cannot be the zero address.
168+
* - `spender` cannot be the zero address.
169+
*/
170+
function _approve(address owner, address spender, uint256 id, uint256 amount) internal virtual {
171+
if (owner == address(0)) {
172+
revert ERC6909InvalidApprover(address(0));
173+
}
174+
if (spender == address(0)) {
175+
revert ERC6909InvalidSpender(address(0));
176+
}
177+
_allowances[owner][spender][id] = amount;
178+
emit Approval(owner, spender, id, amount);
179+
}
180+
181+
/**
182+
* @dev Approve `spender` to operate on all of `owner`'s tokens
183+
*
184+
* This internal function is equivalent to `setOperator`, and can be used to e.g. set automatic allowances for
185+
* certain subsystems, etc.
186+
*
187+
* Emits an {OperatorSet} event.
188+
*
189+
* Requirements:
190+
*
191+
* - `owner` cannot be the zero address.
192+
* - `spender` cannot be the zero address.
193+
*/
194+
function _setOperator(address owner, address spender, bool approved) internal virtual {
195+
if (owner == address(0)) {
196+
revert ERC6909InvalidApprover(address(0));
197+
}
198+
if (spender == address(0)) {
199+
revert ERC6909InvalidSpender(address(0));
200+
}
201+
_operatorApprovals[owner][spender] = approved;
202+
emit OperatorSet(owner, spender, approved);
203+
}
204+
205+
/**
206+
* @dev Updates `owner`'s allowance for `spender` based on spent `amount`.
207+
*
208+
* Does not update the allowance value in case of infinite allowance.
209+
* Revert if not enough allowance is available.
210+
*
211+
* Does not emit an {Approval} event.
212+
*/
213+
function _spendAllowance(address owner, address spender, uint256 id, uint256 amount) internal virtual {
214+
uint256 currentAllowance = allowance(owner, spender, id);
215+
if (currentAllowance < type(uint256).max) {
216+
if (currentAllowance < amount) {
217+
revert ERC6909InsufficientAllowance(spender, currentAllowance, amount, id);
218+
}
219+
unchecked {
220+
_allowances[owner][spender][id] = currentAllowance - amount;
221+
}
222+
}
223+
}
224+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.20;
4+
5+
import {ERC6909} from "../draft-ERC6909.sol";
6+
import {IERC6909ContentURI} from "../../../interfaces/draft-IERC6909.sol";
7+
8+
/**
9+
* @dev Implementation of the Content URI extension defined in ERC6909.
10+
*/
11+
contract ERC6909ContentURI is ERC6909, IERC6909ContentURI {
12+
string private _contractURI;
13+
mapping(uint256 id => string) private _tokenURIs;
14+
15+
/// @dev Event emitted when the contract URI is changed. See https://eips.ethereum.org/EIPS/eip-7572[ERC-7572] for details.
16+
event ContractURIUpdated();
17+
18+
/// @dev See {IERC1155-URI}
19+
event URI(string value, uint256 indexed id);
20+
21+
/// @inheritdoc IERC6909ContentURI
22+
function contractURI() public view virtual override returns (string memory) {
23+
return _contractURI;
24+
}
25+
26+
/// @inheritdoc IERC6909ContentURI
27+
function tokenURI(uint256 id) public view virtual override returns (string memory) {
28+
return _tokenURIs[id];
29+
}
30+
31+
/**
32+
* @dev Sets the {contractURI} for the contract.
33+
*
34+
* Emits a {ContractURIUpdated} event.
35+
*/
36+
function _setContractURI(string memory newContractURI) internal virtual {
37+
_contractURI = newContractURI;
38+
39+
emit ContractURIUpdated();
40+
}
41+
42+
/**
43+
* @dev Sets the {tokenURI} for a given token of type `id`.
44+
*
45+
* Emits a {URI} event.
46+
*/
47+
function _setTokenURI(uint256 id, string memory newTokenURI) internal virtual {
48+
_tokenURIs[id] = newTokenURI;
49+
50+
emit URI(newTokenURI, id);
51+
}
52+
}

0 commit comments

Comments
 (0)