|
1 | 1 | // SPDX-License-Identifier: Apache-2.0 |
2 | 2 | pragma solidity ^0.8.20; |
3 | 3 |
|
| 4 | +import {ModularModule} from "../../../ModularModule.sol"; |
| 5 | + |
4 | 6 | import {Role} from "../../../Role.sol"; |
5 | | -import {RoyaltyERC721} from "./RoyaltyERC721.sol"; |
6 | 7 |
|
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 | + //////////////////////////////////////////////////////////////*/ |
8 | 90 |
|
9 | 91 | /// @notice Returns all implemented callback and module functions. |
10 | 92 | 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); |
12 | 98 |
|
13 | 99 | config.fallbackFunctions[0] = FallbackFunction({selector: this.royaltyInfo.selector, permissionBits: 0}); |
14 | 100 | config.fallbackFunctions[1] = |
15 | 101 | FallbackFunction({selector: this.getDefaultRoyaltyInfo.selector, permissionBits: 0}); |
16 | 102 | config.fallbackFunctions[2] = |
17 | 103 | FallbackFunction({selector: this.getRoyaltyInfoForToken.selector, permissionBits: 0}); |
18 | 104 | config.fallbackFunctions[3] = |
19 | | - FallbackFunction({selector: this.setDefaultRoyaltyInfo.selector, permissionBits: Role._MANAGER_ROLE}); |
| 105 | + FallbackFunction({selector: this.getTransferValidator.selector, permissionBits: 0}); |
20 | 106 | 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] = |
21 | 111 | 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}); |
25 | 114 |
|
26 | 115 | config.requiredInterfaces = new bytes4[](1); |
27 | 116 | config.requiredInterfaces[0] = 0xd9b67a26; // ERC1155 |
28 | 117 |
|
| 118 | + config.supportedInterfaces = new bytes4[](1); |
| 119 | + config.supportedInterfaces[0] = 0x2a55205a; // IERC2981. |
| 120 | + |
29 | 121 | config.registerInstallationCallback = true; |
30 | 122 | } |
31 | 123 |
|
| 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 | + |
32 | 301 | } |
0 commit comments