Skip to content

Commit c5e23c8

Browse files
GWSzetoaadamd-lb
andauthored
Integrate Creator Token Standard into the Royalty Module (#132)
* Initialize CreatorTokenERC721 Extension Signed-off-by: Aadam Debevec <[email protected]> * add basic testing for CreatorTokenExtension Signed-off-by: Aadam Debevec <[email protected]> * add extension creatortoken 20/1155 Signed-off-by: Aadam Debevec <[email protected]> * update extension creatortokenerc1155 Signed-off-by: Aadam Debevec <[email protected]> * test suite for CreatorToker20/1155 Signed-off-by: Aadam Debevec <[email protected]> * add batch transfer testing for 1155 and forge fmt Signed-off-by: Aadam Debevec <[email protected]> * change default evm_version back to london and create profile for cancun Signed-off-by: Aadam Debevec <[email protected]> * check evm version for creatortoken testing Signed-off-by: Aadam Debevec <[email protected]> * optimize loop in batchTransfer hook Signed-off-by: Aadam Debevec <[email protected]> * Integrated Creator Token Standard into Royalty Module - forge install: creator-token-contracts 1.1.2 - forge install: creator-token-standards v3.0.0 - forge install: PermitC v1.0.0 - royalty modules to implement creator token standards - added script to deploy creator token standard NFT - updated foundry toml file - checkpoint: made sure that all of the code has been ported over successfully - all tests pass for ERC-1155 royalty - tests pass for ERC721 and 1155 versions of the updated royalty modules - removed instances of old creator token contracts * updated based on PR feedback * switched from uint96 to uint16 --------- Signed-off-by: Aadam Debevec <[email protected]> Co-authored-by: Aadam Debevec <[email protected]>
1 parent 99e6e5d commit c5e23c8

33 files changed

+1547
-284
lines changed

.gitmodules

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,12 @@
1010
[submodule "lib/ERC721A-Upgradeable"]
1111
path = lib/ERC721A-Upgradeable
1212
url = https://github.com/chiru-labs/ERC721A-Upgradeable
13+
[submodule "lib/creator-token-contracts"]
14+
path = lib/creator-token-contracts
15+
url = https://github.com/limitbreakinc/creator-token-contracts
16+
[submodule "lib/creator-token-standards"]
17+
path = lib/creator-token-standards
18+
url = https://github.com/limitbreakinc/creator-token-standards
19+
[submodule "lib/PermitC"]
20+
path = lib/PermitC
21+
url = https://github.com/limitbreakinc/PermitC

coverage.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Uncovered for src/module/token/royalty/RoyaltyERC1155.sol:
2+
- Function "encodeBytesOnUninstall" (location: source ID 105, line 166, chars 6329-6426, hits: 0)
3+
- Line (location: source ID 105, line 167, chars 6410-6419, hits: 0)
4+
- Statement (location: source ID 105, line 167, chars 6410-6419, hits: 0)
5+
- Branch (branch: 0, path: 1) (location: source ID 105, line 182, chars 6937-7182, hits: 0)
6+
- Branch (branch: 1, path: 1) (location: source ID 105, line 200, chars 7464-7917, hits: 0)
7+
- Line (location: source ID 105, line 211, chars 7871-7874, hits: 0)
8+
- Statement (location: source ID 105, line 211, chars 7871-7874, hits: 0)
9+
- Function "onUninstall" (location: source ID 105, line 229, chars 8450-8503, hits: 0)
10+
- Branch (branch: 2, path: 0) (location: source ID 105, line 286, chars 10597-10670, hits: 0)
11+
- Line (location: source ID 105, line 287, chars 10630-10659, hits: 0)
12+
- Statement (location: source ID 105, line 287, chars 10630-10659, hits: 0)
13+
- Function "getTransferValidationFunction" (location: source ID 105, line 307, chars 11282-11619, hits: 0)
14+
- Line (location: source ID 105, line 312, chars 11431-11581, hits: 0)
15+
- Statement (location: source ID 105, line 312, chars 11431-11581, hits: 0)
16+
- Line (location: source ID 105, line 317, chars 11591-11612, hits: 0)
17+
- Statement (location: source ID 105, line 317, chars 11591-11612, hits: 0)
18+
- Branch (branch: 3, path: 0) (location: source ID 105, line 332, chars 12045-12125, hits: 0)
19+
- Line (location: source ID 105, line 333, chars 12085-12114, hits: 0)
20+
- Statement (location: source ID 105, line 333, chars 12085-12114, hits: 0)
21+
- Statement (location: source ID 105, line 376, chars 13428-13454, hits: 0)

foundry.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ gas_reports = [
1414
"ERC1155Core"
1515
]
1616
ignored_warnings_from = ["node_modules", "lib", "test"]
17+
fs_permissions = [{ access = "read", path = "./"}]
1718

1819
[fmt]
1920
line_length = 120

lib/PermitC

Submodule PermitC added at 65e3a4a

lib/creator-token-contracts

Submodule creator-token-contracts added at 17fb926

lib/creator-token-standards

Submodule creator-token-standards added at 14eb0cb

remappings.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@ ds-test/=lib/forge-std/lib/ds-test/src/
22
forge-std/=lib/forge-std/src/
33
@solady/=lib/solady/src/
44
@erc721a/=lib/erc721a/contracts/
5-
@erc721a-upgradeable/=lib/ERC721A-Upgradeable/contracts/
5+
@erc721a-upgradeable/=lib/ERC721A-Upgradeable/contracts/
6+
@limitbreak/creator-token-standards/=lib/creator-token-standards/src/
7+
@limitbreak/permit-c/=lib/PermitC/src/
Lines changed: 276 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,301 @@
11
// SPDX-License-Identifier: Apache-2.0
22
pragma solidity ^0.8.20;
33

4+
import {ModularModule} from "../../../ModularModule.sol";
5+
46
import {Role} from "../../../Role.sol";
5-
import {RoyaltyERC721} from "./RoyaltyERC721.sol";
67

7-
contract RoyaltyERC1155 is RoyaltyERC721 {
8+
import {BeforeBatchTransferCallbackERC1155} from "../../../callback/BeforeBatchTransferCallbackERC1155.sol";
9+
import {BeforeTransferCallbackERC1155} from "../../../callback/BeforeTransferCallbackERC1155.sol";
10+
import {IInstallationCallback} from "../../../interface/IInstallationCallback.sol";
11+
12+
import {ICreatorToken} from "@limitbreak/creator-token-standards/interfaces/ICreatorToken.sol";
13+
14+
import {ITransferValidator} from "@limitbreak/creator-token-standards/interfaces/ITransferValidator.sol";
15+
import {ITransferValidatorSetTokenType} from
16+
"@limitbreak/creator-token-standards/interfaces/ITransferValidatorSetTokenType.sol";
17+
import {TOKEN_TYPE_ERC1155} from "@limitbreak/permit-c/Constants.sol";
18+
19+
library RoyaltyStorage {
20+
21+
/// @custom:storage-location erc7201:token.royalty.ERC1155
22+
bytes32 public constant ROYALTY_STORAGE_POSITION =
23+
keccak256(abi.encode(uint256(keccak256("token.royalty.erc1155")) - 1)) & ~bytes32(uint256(0xff));
24+
25+
struct Data {
26+
// default royalty info
27+
RoyaltyERC1155.RoyaltyInfo defaultRoyaltyInfo;
28+
// tokenId => royalty info
29+
mapping(uint256 => RoyaltyERC1155.RoyaltyInfo) royaltyInfoForToken;
30+
// the addresss of the transfer validator
31+
address transferValidator;
32+
}
33+
34+
function data() internal pure returns (Data storage data_) {
35+
bytes32 position = ROYALTY_STORAGE_POSITION;
36+
assembly {
37+
data_.slot := position
38+
}
39+
}
40+
41+
}
42+
43+
contract RoyaltyERC1155 is
44+
ModularModule,
45+
IInstallationCallback,
46+
BeforeTransferCallbackERC1155,
47+
BeforeBatchTransferCallbackERC1155
48+
{
49+
50+
/*//////////////////////////////////////////////////////////////
51+
STRUCTS
52+
//////////////////////////////////////////////////////////////*/
53+
54+
/**
55+
* @notice RoyaltyInfo struct to store royalty information.
56+
* @param recipient The address that will receive the royalty payment.
57+
* @param bps The percentage of a secondary sale that will be paid as royalty.
58+
*/
59+
struct RoyaltyInfo {
60+
address recipient;
61+
uint16 bps;
62+
}
63+
64+
/*//////////////////////////////////////////////////////////////
65+
EVENTS
66+
//////////////////////////////////////////////////////////////*/
67+
68+
/// @notice Emitted when the default royalty info for a token is updated.
69+
event DefaultRoyaltyUpdated(address indexed recipient, uint16 bps);
70+
71+
/// @notice Emitted when the royalty info for a specific NFT is updated.
72+
event TokenRoyaltyUpdated(uint256 indexed tokenId, address indexed recipient, uint16 bps);
73+
74+
/// @notice Emitted when the transfer validator is updated.
75+
event TransferValidatorUpdated(address oldValidator, address newValidator);
76+
77+
/*//////////////////////////////////////////////////////////////
78+
ERRORS
79+
//////////////////////////////////////////////////////////////*/
80+
81+
/// @notice Emitted when royalty BPS exceeds 10,000.
82+
error RoyaltyExceedsMaxBps();
83+
84+
/// @notice Revert with an error if the transfer validator is not valid
85+
error InvalidTransferValidatorContract();
86+
87+
/*//////////////////////////////////////////////////////////////
88+
MODULE CONFIG
89+
//////////////////////////////////////////////////////////////*/
890

991
/// @notice Returns all implemented callback and module functions.
1092
function getModuleConfig() external pure virtual override returns (ModuleConfig memory config) {
11-
config.fallbackFunctions = new FallbackFunction[](5);
93+
config.callbackFunctions = new CallbackFunction[](2);
94+
config.fallbackFunctions = new FallbackFunction[](8);
95+
96+
config.callbackFunctions[0] = CallbackFunction(this.beforeTransferERC1155.selector);
97+
config.callbackFunctions[1] = CallbackFunction(this.beforeBatchTransferERC1155.selector);
1298

1399
config.fallbackFunctions[0] = FallbackFunction({selector: this.royaltyInfo.selector, permissionBits: 0});
14100
config.fallbackFunctions[1] =
15101
FallbackFunction({selector: this.getDefaultRoyaltyInfo.selector, permissionBits: 0});
16102
config.fallbackFunctions[2] =
17103
FallbackFunction({selector: this.getRoyaltyInfoForToken.selector, permissionBits: 0});
18104
config.fallbackFunctions[3] =
19-
FallbackFunction({selector: this.setDefaultRoyaltyInfo.selector, permissionBits: Role._MANAGER_ROLE});
105+
FallbackFunction({selector: this.getTransferValidator.selector, permissionBits: 0});
20106
config.fallbackFunctions[4] =
107+
FallbackFunction({selector: this.getTransferValidationFunction.selector, permissionBits: 0});
108+
config.fallbackFunctions[5] =
109+
FallbackFunction({selector: this.setDefaultRoyaltyInfo.selector, permissionBits: Role._MANAGER_ROLE});
110+
config.fallbackFunctions[6] =
21111
FallbackFunction({selector: this.setRoyaltyInfoForToken.selector, permissionBits: Role._MANAGER_ROLE});
22-
23-
config.supportedInterfaces = new bytes4[](1);
24-
config.supportedInterfaces[0] = 0x2a55205a; // IERC2981.
112+
config.fallbackFunctions[7] =
113+
FallbackFunction({selector: this.setTransferValidator.selector, permissionBits: Role._MANAGER_ROLE});
25114

26115
config.requiredInterfaces = new bytes4[](1);
27116
config.requiredInterfaces[0] = 0xd9b67a26; // ERC1155
28117

118+
config.supportedInterfaces = new bytes4[](1);
119+
config.supportedInterfaces[0] = 0x2a55205a; // IERC2981.
120+
29121
config.registerInstallationCallback = true;
30122
}
31123

124+
/*//////////////////////////////////////////////////////////////
125+
Encode install / uninstall data
126+
//////////////////////////////////////////////////////////////*/
127+
128+
/// @dev Returns bytes encoded install params, to be sent to `onInstall` function
129+
function encodeBytesOnInstall(address royaltyRecipient, uint16 royaltyBps, address transferValidator)
130+
external
131+
pure
132+
returns (bytes memory)
133+
{
134+
return abi.encode(royaltyRecipient, royaltyBps, transferValidator);
135+
}
136+
137+
/// @dev Returns bytes encoded uninstall params, to be sent to `onUninstall` function
138+
function encodeBytesOnUninstall() external pure returns (bytes memory) {
139+
return "";
140+
}
141+
142+
/*//////////////////////////////////////////////////////////////
143+
CALLBACK FUNCTIONS
144+
//////////////////////////////////////////////////////////////*/
145+
146+
/// @notice Callback function for ERC1155.transferFrom/safeTransferFrom
147+
function beforeTransferERC1155(address from, address to, uint256 _id, uint256 _value)
148+
external
149+
virtual
150+
override
151+
returns (bytes memory)
152+
{
153+
address transferValidator = getTransferValidator();
154+
if (transferValidator != address(0)) {
155+
ITransferValidator(transferValidator).validateTransfer(msg.sender, from, to, _id, _value);
156+
}
157+
}
158+
159+
function beforeBatchTransferERC1155(address from, address to, uint256[] calldata ids, uint256[] calldata values)
160+
external
161+
virtual
162+
override
163+
returns (bytes memory)
164+
{
165+
address transferValidator = getTransferValidator();
166+
if (transferValidator != address(0)) {
167+
uint256 length = ids.length;
168+
for (uint256 i = 0; i < length; i++) {
169+
ITransferValidator(transferValidator).validateTransfer(msg.sender, from, to, ids[i], values[i]);
170+
}
171+
}
172+
}
173+
174+
/// @dev Called by a Core into an Module during the installation of the Module.
175+
function onInstall(bytes calldata data) external {
176+
(address royaltyRecipient, uint16 royaltyBps, address transferValidator) =
177+
abi.decode(data, (address, uint16, address));
178+
_setDefaultRoyaltyInfo(royaltyRecipient, royaltyBps);
179+
_setTransferValidator(transferValidator);
180+
}
181+
182+
/// @dev Called by a Core into an Module during the uninstallation of the Module.
183+
function onUninstall(bytes calldata data) external {}
184+
185+
/*//////////////////////////////////////////////////////////////
186+
FALLBACK FUNCTIONS
187+
//////////////////////////////////////////////////////////////*/
188+
189+
/// @notice Returns the royalty recipient and amount for a given sale.
190+
function royaltyInfo(uint256 _tokenId, uint256 _salePrice)
191+
external
192+
view
193+
virtual
194+
returns (address receiver, uint256 royaltyAmount)
195+
{
196+
(address overrideRecipient, uint16 overrideBps) = getRoyaltyInfoForToken(_tokenId);
197+
(address defaultRecipient, uint16 defaultBps) = getDefaultRoyaltyInfo();
198+
199+
receiver = overrideRecipient == address(0) ? defaultRecipient : overrideRecipient;
200+
201+
uint16 bps = overrideBps == 0 ? defaultBps : overrideBps;
202+
royaltyAmount = (_salePrice * bps) / 10_000;
203+
}
204+
205+
/// @notice Returns the overriden royalty info for a given token.
206+
function getRoyaltyInfoForToken(uint256 _tokenId) public view returns (address, uint16) {
207+
RoyaltyStorage.Data storage data = RoyaltyStorage.data();
208+
RoyaltyInfo memory royaltyForToken = data.royaltyInfoForToken[_tokenId];
209+
210+
return (royaltyForToken.recipient, uint16(royaltyForToken.bps));
211+
}
212+
213+
/// @notice Returns the default royalty info for a given token.
214+
function getDefaultRoyaltyInfo() public view returns (address, uint16) {
215+
RoyaltyInfo memory defaultRoyaltyInfo = RoyaltyStorage.data().defaultRoyaltyInfo;
216+
return (defaultRoyaltyInfo.recipient, uint16(defaultRoyaltyInfo.bps));
217+
}
218+
219+
/// @notice Sets the default royalty info for a given token.
220+
function setDefaultRoyaltyInfo(address _royaltyRecipient, uint16 _royaltyBps) external {
221+
_setDefaultRoyaltyInfo(_royaltyRecipient, _royaltyBps);
222+
}
223+
224+
/// @notice Sets the royalty info for a specific NFT of a token collection.
225+
function setRoyaltyInfoForToken(uint256 _tokenId, address _recipient, uint16 _bps) external {
226+
if (_bps > 10_000) {
227+
revert RoyaltyExceedsMaxBps();
228+
}
229+
230+
RoyaltyStorage.data().royaltyInfoForToken[_tokenId] = RoyaltyInfo({recipient: _recipient, bps: _bps});
231+
232+
emit TokenRoyaltyUpdated(_tokenId, _recipient, _bps);
233+
}
234+
235+
/// @notice Returns the transfer validator contract address for this token contract.
236+
function getTransferValidator() public view returns (address validator) {
237+
return _royaltyStorage().transferValidator;
238+
}
239+
240+
/**
241+
* @notice Returns the function selector for the transfer validator's validation function to be called
242+
* @notice for transaction simulation.
243+
*/
244+
function getTransferValidationFunction() external pure returns (bytes4 functionSignature, bool isViewFunction) {
245+
functionSignature = bytes4(keccak256("validateTransfer(address,address,address,uint256,uint256)"));
246+
isViewFunction = true;
247+
}
248+
249+
function setTransferValidator(address validator) external {
250+
_setTransferValidator(validator);
251+
}
252+
253+
/*//////////////////////////////////////////////////////////////
254+
INTERNAL FUNCTIONS
255+
//////////////////////////////////////////////////////////////*/
256+
257+
function _setDefaultRoyaltyInfo(address _royaltyRecipient, uint16 _royaltyBps) internal {
258+
if (_royaltyBps > 10_000) {
259+
revert RoyaltyExceedsMaxBps();
260+
}
261+
262+
RoyaltyStorage.data().defaultRoyaltyInfo = RoyaltyInfo({recipient: _royaltyRecipient, bps: _royaltyBps});
263+
264+
emit DefaultRoyaltyUpdated(_royaltyRecipient, _royaltyBps);
265+
}
266+
267+
function _setTransferValidator(address validator) internal {
268+
bool isValidTransferValidator = validator.code.length > 0;
269+
270+
if (validator != address(0) && !isValidTransferValidator) {
271+
revert InvalidTransferValidatorContract();
272+
}
273+
274+
emit TransferValidatorUpdated(address(getTransferValidator()), validator);
275+
276+
_royaltyStorage().transferValidator = validator;
277+
_registerTokenType(validator);
278+
}
279+
280+
function _registerTokenType(address validator) internal {
281+
if (validator != address(0)) {
282+
uint256 validatorCodeSize;
283+
assembly {
284+
validatorCodeSize := extcodesize(validator)
285+
}
286+
if (validatorCodeSize > 0) {
287+
try ITransferValidatorSetTokenType(validator).setTokenTypeOfCollection(address(this), _tokenType()) {}
288+
catch {}
289+
}
290+
}
291+
}
292+
293+
function _tokenType() internal pure virtual returns (uint16 tokenType) {
294+
return uint16(TOKEN_TYPE_ERC1155);
295+
}
296+
297+
function _royaltyStorage() internal pure returns (RoyaltyStorage.Data storage) {
298+
return RoyaltyStorage.data();
299+
}
300+
32301
}

0 commit comments

Comments
 (0)