-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update royalties to use compact byte array to encode small number of …
…royalties recipients
- Loading branch information
Showing
6 changed files
with
154 additions
and
160 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 |
---|---|---|
@@ -1,174 +1,151 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.17; | ||
|
||
import {BytesLib} from "solidity-bytes-utils/contracts/BytesLib.sol"; | ||
import {IERC725Y} from "@erc725/smart-contracts/contracts/interfaces/IERC725Y.sol"; | ||
import {LSP2Utils} from "@lukso/lsp-smart-contracts/contracts/LSP2ERC725YJSONSchema/LSP2Utils.sol"; | ||
import {Points} from "./Points.sol"; | ||
|
||
error InvalidLSP18RoyaltiesArrayLength(bytes invalidValue, uint256 invalidValueLength); | ||
error InvalidLSP18RoyaltiesPoints(uint32 points); | ||
error InvalidLSP18RoyaltiesData(bytes invalidValue); | ||
|
||
bytes32 constant _LSP18_ROYALTIES_RECIPIENTS_KEY = 0xfdd4e98ba62fdcf79cfde4cfe031a71195ae21ff3d0e29f79db24f2fe9ceb59b; | ||
string constant _LSP18_ROYALTIES_RECIPIENTS_MAP_KEY_PREFIX = "LSP18RoyaltiesRecipientsMap"; | ||
bytes32 constant _LSP18_ROYALTIES_RECIPIENTS_KEY = 0xc0569ca6c9180acc2c3590f36330a36ae19015a19f4e85c28a7631e3317e6b9d; | ||
bytes32 constant _LSP18_ROYALTIES_ENFORCE_PAYMENT = 0x580d62ad353782eca17b89e5900e7df3b13b6f4ca9bbc2f8af8bceb0c3d1ecc6; | ||
uint32 constant _LSP18_ROYALTIES_BASIS = Points.BASIS; | ||
|
||
bytes4 constant _LSP18_DEFAULT_STANDARD_INTERFACE_ID = 0xffffffff; | ||
struct RoyaltiesInfo { | ||
bytes4 interfaceId; | ||
address recipient; | ||
uint32 points; | ||
} | ||
|
||
library Royalties { | ||
function setRoyalties(address asset, bytes4 interfaceId, address recipient, uint32 points) internal { | ||
if (points > _LSP18_ROYALTIES_BASIS) { | ||
revert InvalidLSP18RoyaltiesPoints(points); | ||
function setRoyalties(address asset, RoyaltiesInfo memory info) internal { | ||
if (info.points > _LSP18_ROYALTIES_BASIS) { | ||
revert InvalidLSP18RoyaltiesPoints(info.points); | ||
} | ||
bytes32 recipientMapKey = LSP2Utils.generateMappingKey(_LSP18_ROYALTIES_RECIPIENTS_MAP_KEY_PREFIX, recipient); | ||
bytes memory recipientMapValue = IERC725Y(asset).getData(recipientMapKey); | ||
bytes32[] memory keys; | ||
bytes[] memory values; | ||
// removing royalties by setting it to 0 | ||
if (points == 0) { | ||
// already removed | ||
if (recipientMapValue.length == 0) { | ||
return; | ||
} | ||
(keys, values) = _removeRoyaltiesEntry(IERC725Y(asset), recipientMapKey, recipientMapValue); | ||
} else if (recipientMapValue.length == 0) { | ||
// setting up royalties for the first time | ||
(keys, values) = _addRoyaltiesEntry(IERC725Y(asset), interfaceId, recipient, points); | ||
bytes memory entriesData = IERC725Y(asset).getData(_LSP18_ROYALTIES_RECIPIENTS_KEY); | ||
RoyaltiesInfo[] memory entries = _decodeRoyalties(entriesData); | ||
|
||
// find and update existing entry if any | ||
int256 entryIndex = _indexOfRoyaltiesEntry(entries, info.recipient); | ||
|
||
if (entryIndex < 0) { | ||
// adding new entry | ||
entriesData = bytes.concat(entriesData, _encodeRoyaltiesEntry(info)); | ||
} else { | ||
// updating existing royalties | ||
(keys, values) = _setRoyaltiesEntry(points, interfaceId, recipientMapKey, recipientMapValue); | ||
// updating existing entry | ||
entries[uint256(entryIndex)] = info; | ||
entriesData = _encodeRoyalties(entries); | ||
} | ||
IERC725Y(asset).setDataBatch(keys, values); | ||
|
||
IERC725Y(asset).setData(_LSP18_ROYALTIES_RECIPIENTS_KEY, entriesData); | ||
} | ||
|
||
function royaltiesOf(address asset, address recipient) internal view returns (bytes4 interfaceId, uint32 points) { | ||
bytes32 key = LSP2Utils.generateMappingKey(_LSP18_ROYALTIES_RECIPIENTS_MAP_KEY_PREFIX, recipient); | ||
bytes memory data = IERC725Y(asset).getData(key); | ||
interfaceId = bytes4(BytesLib.slice(data, 0, 4)); | ||
points = BytesLib.toUint32(data, 20); | ||
function royalties(address asset) internal view returns (RoyaltiesInfo[] memory) { | ||
bytes memory value = IERC725Y(asset).getData(_LSP18_ROYALTIES_RECIPIENTS_KEY); | ||
return _decodeRoyalties(value); | ||
} | ||
|
||
function royaltiesRecipients(address asset) | ||
internal | ||
view | ||
returns (address[] memory recipients, uint32[] memory points) | ||
{ | ||
bytes memory encodedLength = IERC725Y(asset).getData(_LSP18_ROYALTIES_RECIPIENTS_KEY); | ||
// if key is not set or invalid (uint128) | ||
if (encodedLength.length != 16) { | ||
return (new address[](0), new uint32[](0)); | ||
} | ||
uint128 arrayLength = BytesLib.toUint128(encodedLength, 0); | ||
bytes32[] memory keys = new bytes32[](arrayLength); | ||
for (uint128 i = 0; i < arrayLength;) { | ||
keys[i] = LSP2Utils.generateArrayElementKeyAtIndex(_LSP18_ROYALTIES_RECIPIENTS_KEY, i); | ||
function royaltiesPaymentEnforced(address asset) internal view returns (bool) { | ||
bytes memory value = IERC725Y(asset).getData(_LSP18_ROYALTIES_ENFORCE_PAYMENT); | ||
return value.length > 0 && value[0] != 0; | ||
} | ||
|
||
function _indexOfRoyaltiesEntry(RoyaltiesInfo[] memory entries, address recipient) private pure returns (int256) { | ||
uint256 entriesCount = entries.length; | ||
for (uint256 i = 0; i < entriesCount;) { | ||
RoyaltiesInfo memory entry = entries[i]; | ||
if (entry.recipient == recipient) { | ||
return int256(i); | ||
} | ||
unchecked { | ||
i++; | ||
} | ||
} | ||
bytes[] memory values = IERC725Y(asset).getDataBatch(keys); | ||
recipients = new address[](arrayLength); | ||
points = new uint32[](arrayLength); | ||
for (uint128 i = 0; i < arrayLength;) { | ||
recipients[i] = BytesLib.toAddress(values[i], 0); | ||
points[i] = BytesLib.toUint32(values[i], 20); | ||
return -1; | ||
} | ||
|
||
function _decodeRoyalties(bytes memory data) private pure returns (RoyaltiesInfo[] memory) { | ||
uint256 dataLength = data.length; | ||
|
||
// count number of entries | ||
uint256 count = 0; | ||
for (uint256 i = 0; i < dataLength;) { | ||
uint16 length; | ||
assembly { | ||
length := mload(add(add(data, 0x2), i)) | ||
} | ||
if (length < 4 /* interfaceId */ + 20 /* recipient */ ) { | ||
revert InvalidLSP18RoyaltiesData(data); | ||
} | ||
unchecked { | ||
i++; | ||
i += 2 /* length */ + length; | ||
count++; | ||
} | ||
} | ||
} | ||
|
||
function royaltiesPaymentEnforced(address asset) internal view returns (bool) { | ||
bytes memory value = IERC725Y(asset).getData(_LSP18_ROYALTIES_ENFORCE_PAYMENT); | ||
return value.length > 0 && value[0] != 0; | ||
} | ||
RoyaltiesInfo[] memory result = new RoyaltiesInfo[](count); | ||
uint256 i = 0; | ||
uint256 j = 0; | ||
|
||
while (i < dataLength) { | ||
uint16 length; | ||
bytes4 interfaceId; | ||
address recipient; | ||
uint32 points; | ||
|
||
assembly { | ||
length := mload(add(add(data, 0x2), i)) | ||
interfaceId := mload(add(add(data, 0x4), add(i, 2))) | ||
recipient := div(mload(add(add(data, 0x20), add(i, 6))), 0x1000000000000000000000000) | ||
} | ||
|
||
// optional points | ||
if (length >= 4 /* interfaceId */ + 20 /* recipient */ + 4 /* points */ ) { | ||
assembly { | ||
points := mload(add(add(data, 0x4), add(i, 26))) | ||
} | ||
} | ||
|
||
// asign entry | ||
result[j] = RoyaltiesInfo(interfaceId, recipient, points); | ||
|
||
// skip any remaining bytes as unsupported | ||
unchecked { | ||
i += 2 /* length */ + length; | ||
j++; | ||
} | ||
} | ||
|
||
function _setRoyaltiesEntry( | ||
uint32 points, | ||
bytes4 interfaceId, | ||
bytes32 recipientMapKey, | ||
bytes memory recipientsMapValue | ||
) private pure returns (bytes32[] memory keys, bytes[] memory values) { | ||
keys = new bytes32[](1); | ||
values = new bytes[](1); | ||
uint128 index = BytesLib.toUint128(recipientsMapValue, 0); | ||
keys[0] = recipientMapKey; | ||
values[0] = bytes.concat(interfaceId, bytes16(index), bytes4(points)); | ||
return result; | ||
} | ||
|
||
function _addRoyaltiesEntry(IERC725Y asset, bytes4 interfaceId, address recipient, uint32 points) | ||
private | ||
view | ||
returns (bytes32[] memory keys, bytes[] memory values) | ||
{ | ||
bytes memory encodedArrayLength = asset.getData(_LSP18_ROYALTIES_RECIPIENTS_KEY); | ||
uint128 newArrayLength = 1; | ||
if (encodedArrayLength.length == 0) { | ||
newArrayLength = 1; | ||
} else if (encodedArrayLength.length == 16) { | ||
uint128 arrayLength = BytesLib.toUint128(encodedArrayLength, 0); | ||
newArrayLength = arrayLength + 1; | ||
} else { | ||
revert InvalidLSP18RoyaltiesArrayLength(encodedArrayLength, encodedArrayLength.length); | ||
function _encodeRoyaltiesEntry(RoyaltiesInfo memory entry) private pure returns (bytes memory result) { | ||
// determine entry length | ||
uint16 length = 4 /* interfaceId */ + 20; /* recipient */ | ||
if (entry.points > 0) { | ||
unchecked { | ||
length += 4; /* points */ | ||
} | ||
} | ||
|
||
// encode entry | ||
result = bytes.concat(bytes2(length), entry.interfaceId, bytes20(entry.recipient)); | ||
|
||
// optional points | ||
if (entry.points > 0) { | ||
result = bytes.concat(result, bytes4(entry.points)); | ||
} | ||
uint128 index = newArrayLength - 1; | ||
keys = new bytes32[](3); | ||
values = new bytes[](3); | ||
keys[0] = _LSP18_ROYALTIES_RECIPIENTS_KEY; | ||
values[0] = bytes.concat(bytes16(newArrayLength)); | ||
keys[1] = LSP2Utils.generateArrayElementKeyAtIndex(_LSP18_ROYALTIES_RECIPIENTS_KEY, index); | ||
values[1] = bytes.concat(bytes20(recipient), bytes4(points)); | ||
keys[2] = LSP2Utils.generateMappingKey(_LSP18_ROYALTIES_RECIPIENTS_MAP_KEY_PREFIX, recipient); | ||
values[2] = bytes.concat(interfaceId, bytes16(index), bytes4(points)); | ||
} | ||
|
||
function _removeRoyaltiesEntry(IERC725Y asset, bytes32 recipientMapKey, bytes memory recipientsMapValue) | ||
private | ||
view | ||
returns (bytes32[] memory keys, bytes[] memory values) | ||
{ | ||
bytes memory encodedArrayLength = asset.getData(_LSP18_ROYALTIES_RECIPIENTS_KEY); | ||
uint128 arrayLength = BytesLib.toUint128(encodedArrayLength, 0); | ||
uint128 newArrayLength = arrayLength - 1; | ||
uint128 index = BytesLib.toUint128(recipientsMapValue, 4); | ||
bytes32 recipientKey = LSP2Utils.generateArrayElementKeyAtIndex(_LSP18_ROYALTIES_RECIPIENTS_KEY, index); | ||
if (index == arrayLength - 1) { | ||
keys = new bytes32[](3); | ||
values = new bytes[](3); | ||
keys[0] = _LSP18_ROYALTIES_RECIPIENTS_KEY; | ||
values[0] = bytes.concat(bytes16(newArrayLength)); | ||
keys[1] = recipientMapKey; | ||
values[1] = ""; | ||
keys[2] = recipientKey; | ||
values[2] = ""; | ||
} else { | ||
keys = new bytes32[](5); | ||
values = new bytes[](5); | ||
keys[0] = _LSP18_ROYALTIES_RECIPIENTS_KEY; | ||
values[0] = bytes.concat(bytes16(newArrayLength)); | ||
keys[1] = recipientMapKey; | ||
values[1] = ""; | ||
|
||
bytes32 lastRecipientKey = | ||
LSP2Utils.generateArrayElementKeyAtIndex(_LSP18_ROYALTIES_RECIPIENTS_KEY, newArrayLength); | ||
bytes memory lastRecipientValue = asset.getData(lastRecipientKey); | ||
|
||
bytes32 lastRecipientMapKey = LSP2Utils.generateMappingKey( | ||
_LSP18_ROYALTIES_RECIPIENTS_MAP_KEY_PREFIX, BytesLib.toAddress(lastRecipientValue, 0) | ||
); | ||
bytes memory lastRecipientsMapValue = asset.getData(lastRecipientMapKey); | ||
|
||
keys[2] = recipientKey; | ||
values[2] = lastRecipientValue; | ||
keys[3] = lastRecipientKey; | ||
values[3] = ""; | ||
keys[4] = lastRecipientMapKey; | ||
values[4] = bytes.concat( | ||
BytesLib.slice(lastRecipientsMapValue, 0, 4), | ||
bytes16(index), | ||
BytesLib.slice(lastRecipientsMapValue, 20, 4) | ||
); | ||
function _encodeRoyalties(RoyaltiesInfo[] memory entries) private pure returns (bytes memory) { | ||
bytes memory result = new bytes(0); | ||
uint256 count = entries.length; | ||
for (uint256 i = 0; i < count;) { | ||
result = bytes.concat(result, _encodeRoyaltiesEntry(entries[i])); | ||
unchecked { | ||
i++; | ||
} | ||
} | ||
return result; | ||
} | ||
} |
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
Oops, something went wrong.