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 ERC6909 Implementation along with extensions #5394

Open
wants to merge 47 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
f16cff1
draft
arr00 Dec 13, 2024
9a3ea2f
add extensions
arr00 Dec 16, 2024
4dbeace
nit
arr00 Dec 16, 2024
f038064
add more setters `ERC6909Metadata`
arr00 Dec 16, 2024
b0b0e3e
rename vars in impl and add tests
arr00 Dec 19, 2024
1b76245
add more tests
arr00 Dec 19, 2024
9ae0429
add more tests
arr00 Dec 20, 2024
f69bdfd
additional testing
arr00 Dec 20, 2024
7d78b5a
await when checking logs
arr00 Dec 20, 2024
6abfd92
address comments and fix tests
arr00 Dec 30, 2024
15ece65
add test for total supply
arr00 Dec 30, 2024
499f79b
format
arr00 Dec 30, 2024
d79198d
add tests
arr00 Dec 30, 2024
85196e7
add internal function test
arr00 Dec 30, 2024
8969005
add changesets
arr00 Jan 2, 2025
607c119
fix spelling
arr00 Jan 2, 2025
5763111
Add tests for `ERC6909Metadata`
arr00 Jan 3, 2025
673124c
test `ERC6909ContentURI`
arr00 Jan 3, 2025
ff3ee75
Add docs
arr00 Jan 10, 2025
1f34734
add content uri docs
arr00 Jan 10, 2025
2d9bc74
start on additional docs
arr00 Jan 14, 2025
14a06a0
add reference contract
arr00 Jan 16, 2025
7089ec5
further documentation
arr00 Jan 16, 2025
a6601c8
Update docs and fix incorrectly named file
arr00 Jan 16, 2025
e711da6
update docs
arr00 Jan 17, 2025
361f812
move interface
arr00 Jan 17, 2025
a2e9c98
add interface reference to interfaces folder
arr00 Jan 17, 2025
78bf584
emit events on uri changes
arr00 Jan 17, 2025
521335c
move game items file for 6909
arr00 Jan 17, 2025
d630d84
check that event is emitted on uri set
arr00 Jan 18, 2025
b3c83c7
fix lint
arr00 Jan 18, 2025
9f6506b
Apply suggestions from code review
arr00 Jan 20, 2025
af6769f
updates
Amxx Jan 21, 2025
d09f7fc
fix total supply tracking during updates from 0 to 0
Amxx Jan 21, 2025
3219c91
typo
Amxx Jan 21, 2025
37515f3
Update draft-ERC6909.sol
Amxx Jan 21, 2025
2eff205
up
Amxx Jan 21, 2025
e864372
reorder `_balances` mapping
arr00 Jan 21, 2025
fa66666
Apply suggestions from code review
arr00 Jan 22, 2025
78d9d92
update docs
arr00 Jan 24, 2025
e81d333
add metadata events and use `URI` instead of `MetadataUpdate`
arr00 Jan 24, 2025
b4eb0bc
formatting
arr00 Jan 26, 2025
a9bb063
add missing dev tag
arr00 Jan 26, 2025
267df51
add docs
arr00 Jan 27, 2025
de3d5dc
add missing internal setters
Amxx Jan 27, 2025
8f239ea
testing
Amxx Jan 27, 2025
d1297b0
fix apostrophe
arr00 Jan 27, 2025
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
123 changes: 123 additions & 0 deletions contracts/token/ERC6909/draft-ERC6909.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {IERC6909} from "../../interfaces/draft-IERC6909.sol";
import {Context} from "../../utils/Context.sol";
import {IERC165, ERC165} from "../../utils/introspection/ERC165.sol";

contract ERC6909 is Context, ERC165, IERC6909 {
error ERC6909InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 id);
error ERC6909InsufficientAllowance(address spender, uint256 allowance, uint256 needed, uint256 id);
error ERC6909InvalidReceiver(address receiver);
error ERC6909InvalidSender(address sender);

mapping(uint256 id => mapping(address owner => uint256)) private _balances;
arr00 marked this conversation as resolved.
Show resolved Hide resolved

mapping(address owner => mapping(address operator => bool)) private _operatorApprovals;

mapping(address owner => mapping(address spender => mapping(uint256 id => uint256))) private _allowances;

function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
return interfaceId == type(IERC6909).interfaceId || super.supportsInterface(interfaceId);
}

function balanceOf(address owner, uint256 id) public view virtual override returns (uint256) {
return _balances[id][owner];
}

function allowance(address owner, address spender, uint256 id) public view virtual override returns (uint256) {
return _allowances[owner][spender][id];
}

function isOperator(address owner, address spender) public view virtual override returns (bool) {
return _operatorApprovals[owner][spender];
}

function approve(address spender, uint256 id, uint256 amount) external virtual override returns (bool) {
arr00 marked this conversation as resolved.
Show resolved Hide resolved
address caller = _msgSender();
_allowances[caller][spender][id] = amount;

emit Approval(caller, spender, id, amount);
return true;
}

function setOperator(address spender, bool approved) external virtual override returns (bool) {
arr00 marked this conversation as resolved.
Show resolved Hide resolved
address caller = _msgSender();
_operatorApprovals[caller][spender] = approved;

emit OperatorSet(caller, spender, approved);
return true;
}

function transfer(address receiver, uint256 id, uint256 amount) external virtual override returns (bool) {
arr00 marked this conversation as resolved.
Show resolved Hide resolved
_update(_msgSender(), receiver, id, amount);
arr00 marked this conversation as resolved.
Show resolved Hide resolved
return true;
}

function transferFrom(
address sender,
address receiver,
uint256 id,
uint256 amount
) external virtual override returns (bool) {
arr00 marked this conversation as resolved.
Show resolved Hide resolved
address caller = _msgSender();
if (caller != sender && !isOperator(sender, caller)) {
uint256 currentAllowance = _allowances[sender][caller][id];
arr00 marked this conversation as resolved.
Show resolved Hide resolved
if (currentAllowance != type(uint256).max) {
if (currentAllowance < amount) {
revert ERC6909InsufficientAllowance(caller, currentAllowance, amount, id);
}
unchecked {
_allowances[sender][_msgSender()][id] = currentAllowance - amount;
arr00 marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

_transfer(sender, receiver, id, amount);
return true;
}

function _transfer(address from, address to, uint256 id, uint256 amount) internal {
if (from == address(0)) {
revert ERC6909InvalidSender(address(0));
}
if (to == address(0)) {
revert ERC6909InvalidReceiver(address(0));
}
_update(from, to, id, amount);
}

function _update(address from, address to, uint256 id, uint256 amount) internal virtual {
address caller = _msgSender();

if (from != address(0)) {
uint256 fromBalance = _balances[id][from];
arr00 marked this conversation as resolved.
Show resolved Hide resolved
if (fromBalance < amount) {
revert ERC6909InsufficientBalance(from, fromBalance, amount, id);
}
unchecked {
_balances[id][from] -= amount;
Amxx marked this conversation as resolved.
Show resolved Hide resolved
}
}
if (to != address(0)) {
_balances[id][to] += amount;
ernestognw marked this conversation as resolved.
Show resolved Hide resolved
}

emit Transfer(caller, from, to, id, amount);
}

function _mint(address to, uint256 id, uint256 amount) internal {
if (to == address(0)) {
revert ERC6909InvalidReceiver(address(0));
}
_update(address(0), to, id, amount);
}

function _burn(address from, uint256 id, uint256 amount) internal {
if (from == address(0)) {
revert ERC6909InvalidSender(address(0));
}
_update(from, address(0), id, amount);
}
}
24 changes: 24 additions & 0 deletions contracts/token/ERC6909/extensions/draft-ER6909TokenSupply.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {ERC6909} from "../draft-ERC6909.sol";
import {IERC6909TokenSupply} from "../../../interfaces/draft-IERC6909.sol";

contract ER6909TokenSupply is ERC6909, IERC6909TokenSupply {
mapping(uint256 id => uint256) private _totalSupplies;

function totalSupply(uint256 id) external view virtual override returns (uint256) {
return _totalSupplies[id];
}

function _update(address from, address to, uint256 id, uint256 amount) internal virtual override {
super._update(from, to, id, amount);

if (from == address(0)) {
_totalSupplies[id] += amount;
} else if (to == address(0)) {
_totalSupplies[id] -= amount;
}
}
}
27 changes: 27 additions & 0 deletions contracts/token/ERC6909/extensions/draft-ERC6909ContentURI.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {ERC6909} from "../draft-ERC6909.sol";
import {IERC6909ContentURI} from "../../../interfaces/draft-IERC6909.sol";

contract ERC6909ContentURI is ERC6909, IERC6909ContentURI {
string private _contractURI;
mapping(uint256 id => string) private _tokenURIs;

function contractURI() external view virtual override returns (string memory) {
arr00 marked this conversation as resolved.
Show resolved Hide resolved
return _contractURI;
}

function tokenURI(uint256 id) external view virtual override returns (string memory) {
arr00 marked this conversation as resolved.
Show resolved Hide resolved
return _tokenURIs[id];
}
ernestognw marked this conversation as resolved.
Show resolved Hide resolved

function _setContractURI(string memory newContractURI) internal {
_contractURI = newContractURI;
}
arr00 marked this conversation as resolved.
Show resolved Hide resolved

function _setTokenURI(uint256 id, string memory newTokenURI) internal {
_tokenURIs[id] = newTokenURI;
}
arr00 marked this conversation as resolved.
Show resolved Hide resolved
}
44 changes: 44 additions & 0 deletions contracts/token/ERC6909/extensions/draft-ERC6909Metadata.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {ERC6909} from "../draft-ERC6909.sol";
import {IERC6909Metadata} from "../../../interfaces/draft-IERC6909.sol";

contract ERC6909Metadata is ERC6909, IERC6909Metadata {
struct TokenMetadata {
string name;
string symbol;
uint8 decimals;
}

mapping(uint256 id => TokenMetadata) private _tokenMetadata;
Amxx marked this conversation as resolved.
Show resolved Hide resolved

function name(uint256 id) external view virtual override returns (string memory) {
Amxx marked this conversation as resolved.
Show resolved Hide resolved
return _tokenMetadata[id].name;
}

function symbol(uint256 id) external view virtual override returns (string memory) {
Amxx marked this conversation as resolved.
Show resolved Hide resolved
return _tokenMetadata[id].symbol;
}

function decimals(uint256 id) external view virtual override returns (uint8) {
Amxx marked this conversation as resolved.
Show resolved Hide resolved
return _tokenMetadata[id].decimals;
}

function _setName(uint256 id, string memory newName) internal {
Amxx marked this conversation as resolved.
Show resolved Hide resolved
_tokenMetadata[id].name = newName;
}

function _setTokenSymbol(uint256 id, string memory newSymbol) internal {
_tokenMetadata[id].symbol = newSymbol;
}

function _setDecimals(uint256 id, uint8 newDecimals) internal {
Amxx marked this conversation as resolved.
Show resolved Hide resolved
_tokenMetadata[id].decimals = newDecimals;
}

function _setTokenMetadata(uint256 id, TokenMetadata memory metadata) internal {
Copy link
Collaborator

@Amxx Amxx Jan 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need that function ? I'd remove it to avoid confuction in case someone overrides _setSymbol and forgets to also override _setTokenMetadata

_tokenMetadata[id] = metadata;
}
}
Loading
Loading