Skip to content

Commit

Permalink
Copy files from EIP repo (#1)
Browse files Browse the repository at this point in the history
* initial commit

* forge install: forge-std

v1.5.2

* forge install: openzeppelin-contracts

v4.8.2

* removed unused files and libraries

* format code

* Extend testing

---------

Co-authored-by: Crisgarner <@crisgarner>
Co-authored-by: cxkoda <[email protected]>
  • Loading branch information
crisgarner and cxkoda authored Mar 27, 2023
1 parent edc7d7a commit c0d4b51
Show file tree
Hide file tree
Showing 11 changed files with 888 additions and 1 deletion.
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Compiler files
cache/
out/

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/

# Env file
.env

# Editor files
.gitpod.yml
.DS_Store
/.idea
8 changes: 8 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
branch = v1.5.2
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/Openzeppelin/openzeppelin-contracts
branch = v4.8.2
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,11 @@
# ERC-6464
# ERC6464 Reference Implementations

This is the source code for a reference implementation of ERC6464.

## Build and Test

The repo expects [Foundry](https://github.com/foundry-rs/foundry/tree/master/forge) to be installed:

```bash
forge test
```
13 changes: 13 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[profile.default]
src = 'src'
out = 'out'
libs = ['lib']

[fmt]
line_length = 120
single_line_statement_blocks = "multi"

runs = 1024
max_test_rejects = 16777216

# See more config options https://github.com/foundry-rs/foundry/tree/master/config
1 change: 1 addition & 0 deletions lib/forge-std
Submodule forge-std added at 2b58ec
1 change: 1 addition & 0 deletions lib/openzeppelin-contracts
Submodule openzeppelin-contracts added at d00ace
3 changes: 3 additions & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/
openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/
131 changes: 131 additions & 0 deletions src/ERC6464.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// SPDX-License-Identifier: MIT
// Copyright 2023 PROOF Holdings Inc
pragma solidity ^0.8.0;

import {IERC6464, IERC6464Events, IERC6464AnyApproval} from "./interfaces/IERC6464.sol";
import {IERC721, ERC721} from "openzeppelin-contracts/token/ERC721/ERC721.sol";

abstract contract ERC6464 is ERC721, IERC6464, IERC6464AnyApproval, IERC6464Events {
type TokenNonce is uint256;
type OwnerNonce is uint256;

/**
* @notice Thrown if a caller is not authorized (owner or approved) to perform an action.
*/
error NotAuthorized(address operator, uint256 tokenId);

/**
* @notice Nonce used to efficiently revoke all approvals of a tokenId
*/
mapping(uint256 => TokenNonce) private _tokenNonce;
/**
* @notice Nonce used to efficiently revoke all approvals of an Owner
*/
mapping(address => OwnerNonce) private _ownerNonce;

/**
* @dev tokenId -> tokenNonce -> ownerNounce -> operator -> approval
*/
mapping(uint256 => mapping(TokenNonce => mapping(OwnerNonce => mapping(address => bool)))) private
_isExplicitlyApprovedFor;

/**
* @inheritdoc IERC6464
*/
function setExplicitApproval(address operator, uint256 tokenId, bool approved) public {
if (!_isApprovedOrOwner(_msgSender(), tokenId)) {
revert NotAuthorized(_msgSender(), tokenId);
}
TokenNonce tNonce = _tokenNonce[tokenId];
OwnerNonce oNonce = _ownerNonce[ownerOf(tokenId)];
_isExplicitlyApprovedFor[tokenId][tNonce][oNonce][operator] = approved;
emit ExplicitApprovalFor(operator, tokenId, approved);
}

/**
* @inheritdoc IERC6464
*/
function setExplicitApproval(address operator, uint256[] calldata tokenIds, bool approved) external {
for (uint256 id = 0; id < tokenIds.length; id++) {
setExplicitApproval(operator, tokenIds[id], approved);
}
}

/**
* @inheritdoc IERC6464
*/
function revokeAllExplicitApprovals() external {
_ownerNonce[_msgSender()] = OwnerNonce.wrap(OwnerNonce.unwrap(_ownerNonce[_msgSender()]) + 1);
emit AllExplicitApprovalsRevoked(_msgSender());
}

/**
* @inheritdoc IERC6464
*/
function revokeAllExplicitApprovals(uint256 tokenId) public {
if (!_isApprovedOrOwner(_msgSender(), tokenId)) {
revert NotAuthorized(_msgSender(), tokenId);
}
_revokeAllExplicitApprovals(tokenId);
}

/**
* @inheritdoc IERC6464
*/
function isExplicitlyApprovedFor(address operator, uint256 tokenId) public view returns (bool) {
TokenNonce tNonce = _tokenNonce[tokenId];
OwnerNonce oNonce = _ownerNonce[ownerOf(tokenId)];
return _isExplicitlyApprovedFor[tokenId][tNonce][oNonce][operator];
}

/**
* @inheritdoc IERC6464AnyApproval
*/
function isApprovedFor(address operator, uint256 tokenId) public view returns (bool) {
return isExplicitlyApprovedFor(operator, tokenId) || isApprovedForAll(ownerOf(tokenId), operator)
|| getApproved(tokenId) == operator;
}

/**
* @notice Revoking explicit approvals on token transfer.
*/
function _beforeTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize)
internal
virtual
override
{
super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
if (from == address(0)) {
return;
}

for (uint256 i = 0; i < batchSize; i++) {
_revokeAllExplicitApprovals(firstTokenId + i);
}
}

/**
* @notice Revokes all explicit approvals for a token.
*/
function _revokeAllExplicitApprovals(uint256 tokenId) internal {
_tokenNonce[tokenId] = TokenNonce.wrap(TokenNonce.unwrap(_tokenNonce[tokenId]) + 1);
emit AllExplicitApprovalsRevoked(ownerOf(tokenId), tokenId);
}

/**
* @notice Overriding OZ's `_isApprovedOrOwner` check to grant for explicit approvals the same permissions as standard
* ERC721 approvals.
*/
function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual override returns (bool) {
address owner = ownerOf(tokenId);
return spender == owner || isApprovedFor(spender, tokenId);
}

/**
* @notice OZ's `approve` does only check for `isApprovedForAll`. Overriding to allow all approvals.
*/
function approve(address to, uint256 tokenId) public virtual override(ERC721, IERC721) {
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: approve caller is not token owner or approved");
_approve(to, tokenId);
}
}
84 changes: 84 additions & 0 deletions src/interfaces/IERC6464.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: MIT
// Copyright 2023 PROOF Holdings Inc
pragma solidity ^0.8.0;

// IERC721 is renamed to ERC721 for compatibility with the original EIP defition.
import {IERC721 as ERC721} from "openzeppelin-contracts/token/ERC721/IERC721.sol";

/**
* @notice Extends ERC-721 to include per-token approval for multiple operators.
* @dev Off-chain indexers of approvals SHOULD assume that an operator is approved if either of `ERC721.Approval(…)` or
* `ERC721.ApprovalForAll(…, true)` events are witnessed without the corresponding revocation(s), even if an
* `ExplicitApprovalFor(…, false)` is emitted.
* @dev TODO: the ERC-165 identifier for this interface is TBD.
*/
interface IERC6464 is ERC721 {
/**
* @notice Approves the operator to manage the asset on behalf of its owner.
* @dev Throws if `msg.sender` is not the current NFT owner, or an authorised operator of the current owner.
* @dev Approvals set via this method MUST be revoked upon transfer of the token to a new owner; equivalent to
* calling `revokeAllExplicitApprovals(tokenId)`, including associated events.
* @dev MUST emit `ApprovalFor(operator, tokenId, approved)`.
* @dev MUST NOT have an effect on any standard ERC721 approval setters / getters.
*/
function setExplicitApproval(address operator, uint256 tokenId, bool approved) external;

/**
* @notice Approves the operator to manage the token(s) on behalf of their owner.
* @dev MUST be equivalent to calling `setExplicitApprovalFor(operator, tokenId, approved)` for each `tokenId` in
* the array.
*/
function setExplicitApproval(address operator, uint256[] memory tokenIds, bool approved) external;

/**
* @notice Revokes all explicit approvals granted by `msg.sender`.
* @dev MUST emit `AllExplicitApprovalsRevoked(msg.sender)`.
*/
function revokeAllExplicitApprovals() external;

/**
* @notice Revokes all excplicit approvals granted for the specified token.
* @dev Throws if `msg.sender` is not the current NFT owner, or an authorised operator of the current owner.
* @dev MUST emit `AllExplicitApprovalsRevoked(msg.sender, tokenId)`.
*/
function revokeAllExplicitApprovals(uint256 tokenId) external;

/**
* @notice Query whether an address is an approved operator for a token.
*/
function isExplicitlyApprovedFor(address operator, uint256 tokenId) external view returns (bool);
}

interface IERC6464AnyApproval is ERC721 {
/**
* @notice Returns true if any of the following criteria are met:
* 1. `isExplicitlyApprovedFor(operator, tokenId) == true`; OR
* 2. `isApprovedForAll(ownerOf(tokenId), operator) == true`; OR
* 3. `getApproved(tokenId) == operator`.
* @dev The criteria MUST be extended if other mechanism(s) for approving operators are introduced. The criteria
* MUST include all approval approaches.
*/
function isApprovedFor(address operator, uint256 tokenId) external view returns (bool);
}

interface IERC6464Events {
/**
* @notice Emitted when approval is explicitly granted or revoked for a token.
*/
event ExplicitApprovalFor(address indexed operator, uint256 indexed tokenId, bool approved);

/**
* @notice Emitted when all explicit approvals, as granted by either `setExplicitApprovalFor()` function, are
* revoked for all tokens.
* @dev MUST be emitted upon calls to `revokeAllExplicitApprovals()`.
*/
event AllExplicitApprovalsRevoked(address indexed owner);

/**
* @notice Emitted when all explicit approvals, as granted by either `setExplicitApprovalFor()` function, are
* revoked for the specific token.
* @param owner MUST be `ownerOf(tokenId)` as per ERC721; in the case of revocation due to transfer, this MUST be
* the `from` address expected to be emitted in the respective `ERC721.Transfer()` event.
*/
event AllExplicitApprovalsRevoked(address indexed owner, uint256 indexed tokenId);
}
Loading

0 comments on commit c0d4b51

Please sign in to comment.