-
Notifications
You must be signed in to change notification settings - Fork 52
Add ERC-6909 implementations #167
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
base: main
Are you sure you want to change the base?
Changes from all commits
5153017
199c3e1
3be87cb
681c035
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,129 @@ | ||||||||||
| // SPDX-License-Identifier: MIT | ||||||||||
| pragma solidity >=0.8.30; | ||||||||||
|
|
||||||||||
| /// @title ERC-6909 Minimal Multi-Token Interface | ||||||||||
| /// @notice A complete, dependency-free ERC-6909 implementation using the diamond storage pattern. | ||||||||||
| /// @dev Adapted from: https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC6909.sol | ||||||||||
| contract ERC6909Facet { | ||||||||||
| /// @notice Emitted when a transfer occurs. | ||||||||||
| event Transfer( | ||||||||||
| address _caller, address indexed _sender, address indexed _receiver, uint256 indexed _id, uint256 _amount | ||||||||||
| ); | ||||||||||
|
|
||||||||||
| /// @notice Emitted when an operator is set. | ||||||||||
| event OperatorSet(address indexed _owner, address indexed _spender, bool _approved); | ||||||||||
|
|
||||||||||
| /// @notice Emitted when an approval occurs. | ||||||||||
| event Approval(address indexed _owner, address indexed _spender, uint256 indexed _id, uint256 _amount); | ||||||||||
|
|
||||||||||
| /// @dev Storage position determined by the keccak256 hash of the diamond storage identifier. | ||||||||||
| bytes32 constant STORAGE_POSITION = keccak256("compose.erc6909"); | ||||||||||
|
|
||||||||||
| /// @custom:storage-location erc8042:compose.erc6909 | ||||||||||
| struct ERC6909Storage { | ||||||||||
| mapping(address owner => mapping(uint256 id => uint256 amount)) balanceOf; | ||||||||||
| mapping(address owner => mapping(address spender => mapping(uint256 id => uint256 amount))) allowance; | ||||||||||
| mapping(address owner => mapping(address spender => bool)) isOperator; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| /// @notice Returns a pointer to the ERC-6909 storage struct. | ||||||||||
| /// @dev Uses inline assembly to access the storage slot defined by STORAGE_POSITION. | ||||||||||
| /// @return s The ERC6909Storage struct in storage. | ||||||||||
| function getStorage() internal pure returns (ERC6909Storage storage s) { | ||||||||||
| bytes32 position = STORAGE_POSITION; | ||||||||||
| assembly { | ||||||||||
| s.slot := position | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| /// @notice Owner balance of an id. | ||||||||||
| /// @param _owner The address of the owner. | ||||||||||
| /// @param _id The id of the token. | ||||||||||
| /// @return The balance of the token. | ||||||||||
| function balanceOf(address _owner, uint256 _id) external view returns (uint256) { | ||||||||||
| return getStorage().balanceOf[_owner][_id]; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| /// @notice Spender allowance of an id. | ||||||||||
| /// @param _owner The address of the owner. | ||||||||||
| /// @param _spender The address of the spender. | ||||||||||
| /// @param _id The id of the token. | ||||||||||
| /// @return The allowance of the token. | ||||||||||
| function allowance(address _owner, address _spender, uint256 _id) external view returns (uint256) { | ||||||||||
| return getStorage().allowance[_owner][_spender][_id]; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| /// @notice Checks if a spender is approved by an owner as an operator. | ||||||||||
| /// @param _owner The address of the owner. | ||||||||||
| /// @param _spender The address of the spender. | ||||||||||
| /// @return The approval status. | ||||||||||
| function isOperator(address _owner, address _spender) external view returns (bool) { | ||||||||||
| return getStorage().isOperator[_owner][_spender]; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| /// @notice Transfers an amount of an id from the caller to a receiver. | ||||||||||
| /// @param _receiver The address of the receiver. | ||||||||||
| /// @param _id The id of the token. | ||||||||||
| /// @param _amount The amount of the token. | ||||||||||
| /// @return Whether the transfer succeeded. | ||||||||||
| function transfer(address _receiver, uint256 _id, uint256 _amount) external returns (bool) { | ||||||||||
| ERC6909Storage storage s = getStorage(); | ||||||||||
|
|
||||||||||
| s.balanceOf[msg.sender][_id] -= _amount; | ||||||||||
| s.balanceOf[_receiver][_id] += _amount; | ||||||||||
|
|
||||||||||
| emit Transfer(msg.sender, msg.sender, _receiver, _id, _amount); | ||||||||||
|
|
||||||||||
| return true; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| /// @notice Transfers an amount of an id from a sender to a receiver. | ||||||||||
| /// @param _sender The address of the sender. | ||||||||||
| /// @param _receiver The address of the receiver. | ||||||||||
| /// @param _id The id of the token. | ||||||||||
| /// @param _amount The amount of the token. | ||||||||||
| /// @return Whether the transfer succeeded. | ||||||||||
| function transferFrom(address _sender, address _receiver, uint256 _id, uint256 _amount) external returns (bool) { | ||||||||||
| ERC6909Storage storage s = getStorage(); | ||||||||||
| if (msg.sender != _sender && !s.isOperator[_sender][msg.sender]) { | ||||||||||
| uint256 allowed = s.allowance[_sender][msg.sender][_id]; | ||||||||||
| if (allowed != type(uint256).max) s.allowance[_sender][msg.sender][_id] = allowed - _amount; | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use braces here please.
|
||||||||||
| if (allowed != type(uint256).max) s.allowance[_sender][msg.sender][_id] = allowed - _amount; | |
| if (allowed != type(uint256).max) { | |
| s.allowance[_sender][msg.sender][_id] = allowed - _amount; | |
| } |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,111 @@ | ||||||||||
| // SPDX-License-Identifier: MIT | ||||||||||
| pragma solidity >=0.8.30; | ||||||||||
|
|
||||||||||
| /// @title LibERC6909 — ERC-6909 Library | ||||||||||
| /// @notice Provides internal functions and storage layout for ERC-6909 minimal multi-token logic. | ||||||||||
| /// @dev Uses ERC-8042 for storage location standardization and ERC-6093 for error conventions. | ||||||||||
|
||||||||||
| /// @dev Uses ERC-8042 for storage location standardization and ERC-6093 for error conventions. | |
| /// @dev Uses ERC-8042 for storage location standardization. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use braces here, for example:
if (allowed != type(uint256).max)) {
s.allowance[_from][_by][_id] = allowed - _amount;
}
Copilot
AI
Nov 16, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one-line if statement violates the project's style guide (STYLE.md section 5), which requires all if statements to use brackets and a newline. The statement should be reformatted as:\nsolidity\nif (allowed != type(uint256).max) {\n s.allowance[_from][_by][_id] = allowed - _amount;\n}\n
| if (allowed != type(uint256).max) s.allowance[_from][_by][_id] = allowed - _amount; | |
| if (allowed != type(uint256).max) { | |
| s.allowance[_from][_by][_id] = allowed - _amount; | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] There is significant code duplication between ERC6909Facet.sol and LibERC6909.sol. Both files define the same events (Transfer, OperatorSet, Approval), storage struct (ERC6909Storage), storage position constant (STORAGE_POSITION), and getStorage() function. According to the 'No Imports in Facets' rule (STYLE.md section 1), this duplication is intentional for self-containment. However, consider whether LibERC6909 functions could be used by the facet to reduce this duplication, or document why both implementations need to be completely standalone.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot I suggest you read the documentation for the project. Specifically the design of compose here: https://compose.diamonds/docs/design/