-
Notifications
You must be signed in to change notification settings - Fork 11.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement Non Fungible Token Royalty (EIP2981) (#3012)
Co-authored-by: Francisco Giordano <frangio.1@gmail.com> Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com>
- Loading branch information
1 parent
1e815f3
commit a65c03b
Showing
11 changed files
with
413 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
import "../token/ERC721/extensions/ERC721Royalty.sol"; | ||
|
||
contract ERC721RoyaltyMock is ERC721Royalty { | ||
constructor(string memory name, string memory symbol) ERC721(name, symbol) {} | ||
|
||
function setTokenRoyalty( | ||
uint256 tokenId, | ||
address recipient, | ||
uint96 fraction | ||
) public { | ||
_setTokenRoyalty(tokenId, recipient, fraction); | ||
} | ||
|
||
function setDefaultRoyalty(address recipient, uint96 fraction) public { | ||
_setDefaultRoyalty(recipient, fraction); | ||
} | ||
|
||
function mint(address to, uint256 tokenId) public { | ||
_mint(to, tokenId); | ||
} | ||
|
||
function burn(uint256 tokenId) public { | ||
_burn(tokenId); | ||
} | ||
|
||
function deleteDefaultRoyalty() public { | ||
_deleteDefaultRoyalty(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
// SPDX-License-Identifier: MIT | ||
// OpenZeppelin Contracts v4.4.0 (token/ERC721/extensions/ERC721Royalty.sol) | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
import "../ERC721.sol"; | ||
import "../../common/ERC2981.sol"; | ||
import "../../../utils/introspection/ERC165.sol"; | ||
|
||
/** | ||
* @dev Extension of ERC721 with the ERC2981 NFT Royalty Standard, a standardized way to retrieve royalty payment | ||
* information. | ||
* | ||
* Royalty information can be specified globally for all token ids via {_setDefaultRoyalty}, and/or individually for | ||
* specific token ids via {_setTokenRoyalty}. The latter takes precedence over the first. | ||
* | ||
* IMPORTANT: ERC-2981 only specifies a way to signal royalty information and does not enforce its payment. See | ||
* https://eips.ethereum.org/EIPS/eip-2981#optional-royalty-payments[Rationale] in the EIP. Marketplaces are expected to | ||
* voluntarily pay royalties together with sales, but note that this standard is not yet widely supported. | ||
* | ||
* _Available since v4.5._ | ||
*/ | ||
abstract contract ERC721Royalty is ERC2981, ERC721 { | ||
/** | ||
* @dev See {IERC165-supportsInterface}. | ||
*/ | ||
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, ERC2981) returns (bool) { | ||
return super.supportsInterface(interfaceId); | ||
} | ||
|
||
/** | ||
* @dev See {ERC721-_burn}. This override additionally clears the royalty information for the token. | ||
*/ | ||
function _burn(uint256 tokenId) internal virtual override { | ||
super._burn(tokenId); | ||
_resetTokenRoyalty(tokenId); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
// SPDX-License-Identifier: MIT | ||
// OpenZeppelin Contracts v4.4.0 (token/common/ERC2981.sol) | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
import "../../interfaces/IERC2981.sol"; | ||
import "../../utils/introspection/ERC165.sol"; | ||
|
||
/** | ||
* @dev Implementation of the NFT Royalty Standard, a standardized way to retrieve royalty payment information. | ||
* | ||
* Royalty information can be specified globally for all token ids via {_setDefaultRoyalty}, and/or individually for | ||
* specific token ids via {_setTokenRoyalty}. The latter takes precedence over the first. | ||
* | ||
* Royalty is specified as a fraction of sale price. {_feeDenominator} is overridable but defaults to 10000, meaning the | ||
* fee is specified in basis points by default. | ||
* | ||
* IMPORTANT: ERC-2981 only specifies a way to signal royalty information and does not enforce its payment. See | ||
* https://eips.ethereum.org/EIPS/eip-2981#optional-royalty-payments[Rationale] in the EIP. Marketplaces are expected to | ||
* voluntarily pay royalties together with sales, but note that this standard is not yet widely supported. | ||
* | ||
* _Available since v4.5._ | ||
*/ | ||
abstract contract ERC2981 is IERC2981, ERC165 { | ||
struct RoyaltyInfo { | ||
address receiver; | ||
uint96 royaltyFraction; | ||
} | ||
|
||
RoyaltyInfo private _defaultRoyaltyInfo; | ||
mapping(uint256 => RoyaltyInfo) private _tokenRoyaltyInfo; | ||
|
||
/** | ||
* @dev See {IERC165-supportsInterface}. | ||
*/ | ||
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) { | ||
return interfaceId == type(IERC2981).interfaceId || super.supportsInterface(interfaceId); | ||
} | ||
|
||
/** | ||
* @inheritdoc IERC2981 | ||
*/ | ||
function royaltyInfo(uint256 _tokenId, uint256 _salePrice) external view override returns (address, uint256) { | ||
RoyaltyInfo memory royalty = _tokenRoyaltyInfo[_tokenId]; | ||
|
||
if (royalty.receiver == address(0)) { | ||
royalty = _defaultRoyaltyInfo; | ||
} | ||
|
||
uint256 royaltyAmount = (_salePrice * royalty.royaltyFraction) / _feeDenominator(); | ||
|
||
return (royalty.receiver, royaltyAmount); | ||
} | ||
|
||
/** | ||
* @dev The denominator with which to interpret the fee set in {_setTokenRoyalty} and {_setDefaultRoyalty} as a | ||
* fraction of the sale price. Defaults to 10000 so fees are expressed in basis points, but may be customized by an | ||
* override. | ||
*/ | ||
function _feeDenominator() internal pure virtual returns (uint96) { | ||
return 10000; | ||
} | ||
|
||
/** | ||
* @dev Sets the royalty information that all ids in this contract will default to. | ||
* | ||
* Requirements: | ||
* | ||
* - `receiver` cannot be the zero address. | ||
* - `feeNumerator` cannot be greater than the fee denominator. | ||
*/ | ||
function _setDefaultRoyalty(address receiver, uint96 feeNumerator) internal virtual { | ||
require(feeNumerator <= _feeDenominator(), "ERC2981: royalty fee will exceed salePrice"); | ||
require(receiver != address(0), "ERC2981: invalid receiver"); | ||
|
||
_defaultRoyaltyInfo = RoyaltyInfo(receiver, feeNumerator); | ||
} | ||
|
||
/** | ||
* @dev Removes default royalty information. | ||
*/ | ||
function _deleteDefaultRoyalty() internal virtual { | ||
delete _defaultRoyaltyInfo; | ||
} | ||
|
||
/** | ||
* @dev Sets the royalty information for a specific token id, overriding the global default. | ||
* | ||
* Requirements: | ||
* | ||
* - `tokenId` must be already minted. | ||
* - `receiver` cannot be the zero address. | ||
* - `feeNumerator` cannot be greater than the fee denominator. | ||
*/ | ||
function _setTokenRoyalty( | ||
uint256 tokenId, | ||
address receiver, | ||
uint96 feeNumerator | ||
) internal virtual { | ||
require(feeNumerator <= _feeDenominator(), "ERC2981: royalty fee will exceed salePrice"); | ||
require(receiver != address(0), "ERC2981: Invalid parameters"); | ||
|
||
_tokenRoyaltyInfo[tokenId] = RoyaltyInfo(receiver, feeNumerator); | ||
} | ||
|
||
/** | ||
* @dev Resets royalty information for the token id back to the global default. | ||
*/ | ||
function _resetTokenRoyalty(uint256 tokenId) internal virtual { | ||
delete _tokenRoyaltyInfo[tokenId]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
= Common (Tokens) | ||
|
||
Functionality that is common to multiple token standards. | ||
|
||
* {ERC2981}: NFT Royalties compatible with both ERC721 and ERC1155. | ||
** For ERC721 consider {ERC721Royalty} which clears the royalty information from storage on burn. | ||
== Contracts | ||
|
||
{{ERC2981}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
const { BN, constants } = require('@openzeppelin/test-helpers'); | ||
const ERC721RoyaltyMock = artifacts.require('ERC721RoyaltyMock'); | ||
const { ZERO_ADDRESS } = constants; | ||
|
||
const { shouldBehaveLikeERC2981 } = require('../../common/ERC2981.behavior'); | ||
|
||
contract('ERC721Royalty', function (accounts) { | ||
const [ account1, account2 ] = accounts; | ||
const tokenId1 = new BN('1'); | ||
const tokenId2 = new BN('2'); | ||
const royalty = new BN('200'); | ||
const salePrice = new BN('1000'); | ||
|
||
beforeEach(async function () { | ||
this.token = await ERC721RoyaltyMock.new('My Token', 'TKN'); | ||
|
||
await this.token.mint(account1, tokenId1); | ||
await this.token.mint(account1, tokenId2); | ||
this.account1 = account1; | ||
this.account2 = account2; | ||
this.tokenId1 = tokenId1; | ||
this.tokenId2 = tokenId2; | ||
this.salePrice = salePrice; | ||
}); | ||
|
||
describe('token specific functions', function () { | ||
beforeEach(async function () { | ||
await this.token.setTokenRoyalty(tokenId1, account1, royalty); | ||
}); | ||
|
||
it('removes royalty information after burn', async function () { | ||
await this.token.burn(tokenId1); | ||
const tokenInfo = await this.token.royaltyInfo(tokenId1, salePrice); | ||
|
||
expect(tokenInfo[0]).to.be.equal(ZERO_ADDRESS); | ||
expect(tokenInfo[1]).to.be.bignumber.equal(new BN('0')); | ||
}); | ||
}); | ||
shouldBehaveLikeERC2981(); | ||
}); |
Oops, something went wrong.