Skip to content

Commit

Permalink
LSP18 Royalties
Browse files Browse the repository at this point in the history
  • Loading branch information
lykhonis committed Nov 29, 2022
1 parent 78e67aa commit 51c1a69
Showing 1 changed file with 270 additions and 0 deletions.
270 changes: 270 additions & 0 deletions LSPs/LSP-18-Royalties.md
@@ -0,0 +1,270 @@
---
lip: 18
title: Royalties
author: Volodymyr Lykhonis <vlad@universal.page>, Jake Prins <jake@universal.page>
discussions-to: https://discord.gg/E2rJPP4
status: Draft
type: LSP
created: 2022-11-23
requires: LSP2, LSP4
---

## Simple Summary
This standard describes a set of [ERC725Y](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-725.md) data key values to store royalties recipient addresses and corresponding percentages in a [ERC725Y](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-725.md) smart contract.

## Abstract
This data key value standard describes a set of data keys that can be added to an [ERC725Y](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-725.md) smart contract to describe royalties:

- `LSP18Royalties[]` is an [LSP2 array](./LSP-2-ERC725YJSONSchema.md) of royalties recipient addresses.
- `LSP18RoyaltiesMap` is a dynamic address mapping, which contains:
- royaties percentage allocation per recipient address.
- and the index in the `LSP18Royalties[]` array.

The data key `LSP18RoyaltiesMap` exists so that smart contracts can detect if an address is present in the array (e.g. as done in the [LSP1-UniversalReceiverDelegate](./LSP-1-UniversalReceiver.md)).

## Motivation
This standard allows to create a decentralised asset royalties allocations by a smart contract.

## Specification
Every contract that supports the LSP18DigitalAssetMetadata SHOULD have the following data keys:

### ERC725Y Data Keys


#### LSP18Royalties[]

An array of royalties recipients addresses (see [LSP-0-ERC725Account](./LSP-0-ERC725Account.md)).


```json
{
"name": "LSP18Royalties[]",
"key": "0xe010b84f60176d2f466b495da33173c1a008950ed855b4b92282e356d009bd76",
"keyType": "Array",
"valueType": "address",
"valueContent": "Address"
}
```

For more info about how to access each index of the `LSP18Royalties[]` array, see [ERC725Y JSON Schema > `keyType`: `Array`](https://github.com/lukso-network/LIPs/blob/master/LSPs/LSP-2-ERC725YJSONSchema.md#array)

#### LSP18RoyaltiesMap

References royalties amounts of each recipient addresses.

The data value MUST be constructed as follows: `bytes8(indexNumber) + bytes2(type) + bytesN`. Where:
- `indexNumber` = the index in the [`LSP18Royalties[]` Array](#lsp18royalties).
- `type` = the type of a payload:
- `0` = percentage
- `bytes4(amountNumber)` = the percentage of royalties allocation in points with `100_000` basis. e.g. `15%` is `15_000` points, and `1.5%` is `1_500` points.
- `1` = fixed amount in `LYX`
- `bytes32(amount)` = wei amount


```json
{
"name": "LSP18RoyaltiesMap:<address>",
"key": "0xe5ea91873c26eca6dec90000<address>",
"keyType": "Mapping",
"valueType": "(bytes8,bytes2,bytes4)",
"valueContent": "(Number,Number,Number)"
}
```

## Rationale

## Implementation

ERC725Y JSON Schema `LSP18Royalties`:
```json
[
{
"name": "LSP18Royalties[]",
"key": "0xe010b84f60176d2f466b495da33173c1a008950ed855b4b92282e356d009bd76",
"keyType": "Array",
"valueType": "address",
"valueContent": "Address"
},
{
"name": "LSP18RoyaltiesMap:<address>",
"key": "0xe5ea91873c26eca6dec90000<address>",
"keyType": "Mapping",
"valueType": "(bytes8,bytes2,bytes4)",
"valueContent": "(Number,Number,Number)"
}
]
```

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
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";
error InvalidLSP18RoyaltiesArrayLength(bytes invalidValue, uint256 invalidValueLength);
error InvalidLSP18RoyaltiesAmount(uint256 amount);
bytes32 constant _LSP18_ROYALTIES_KEY = 0xe010b84f60176d2f466b495da33173c1a008950ed855b4b92282e356d009bd76;
string constant _LSP18_ROYALTIES_MAP_KEY_PREFIX = "LSP18RoyaltiesMap";
uint32 constant _LSP18_ROYALTIES_BASIS = 100_000;
library Royalties {
function setRoyalties(
address asset,
address recipient,
uint256 amount
) internal {
if (amount > _LSP18_ROYALTIES_BASIS) {
revert InvalidLSP18RoyaltiesAmount(amount);
}
bytes32 recipientMapKey = LSP2Utils.generateMappingKey(_LSP18_ROYALTIES_MAP_KEY_PREFIX, recipient);
bytes memory recipientMapValue = IERC725Y(asset).getData(recipientMapKey);
// removing royalties by setting it to 0
if (amount == 0) {
// already removed
if (bytes12(recipientMapValue) == bytes12(0)) {
return;
}
(bytes32[] memory keys, bytes[] memory values) = _removeRoyaltiesEntry(
IERC725Y(asset),
recipientMapKey,
recipientMapValue
);
IERC725Y(asset).setData(keys, values);
} else if (bytes12(recipientMapValue) == bytes12(0)) {
// setting up royalties for the first time
(bytes32[] memory keys, bytes[] memory values) = _addRoyaltiesEntry(IERC725Y(asset), recipient, amount);
IERC725Y(asset).setData(keys, values);
} else {
// updating existing royalties
(bytes32[] memory keys, bytes[] memory values) = _setRoyaltiesEntry(
amount,
recipientMapKey,
recipientMapValue
);
IERC725Y(asset).setData(keys, values);
}
}
function royaltiesOf(address asset, address recipient) internal view returns (uint256) {
bytes32 recipientMapKey = LSP2Utils.generateMappingKey(_LSP18_ROYALTIES_MAP_KEY_PREFIX, recipient);
bytes memory recipientMapValue = IERC725Y(asset).getData(recipientMapKey);
return BytesLib.toUint32(recipientMapValue, 10);
}
function royaltiesRecipients(address asset) internal view returns (address[] memory) {
bytes memory encodedLength = IERC725Y(asset).getData(_LSP18_ROYALTIES_KEY);
// if key is not set or invalid (uint256)
if (encodedLength.length != 32) {
return new address[](0);
}
uint256 arrayLength = abi.decode(encodedLength, (uint256));
bytes32[] memory keys = new bytes32[](arrayLength);
for (uint256 i = 0; i < arrayLength; i++) {
keys[i] = LSP2Utils.generateArrayElementKeyAtIndex(_LSP18_ROYALTIES_KEY, i);
}
bytes[] memory values = IERC725Y(asset).getData(keys);
address[] memory result = new address[](arrayLength);
for (uint256 i = 0; i < arrayLength; i++) {
result[i] = address(bytes20(values[i]));
}
return result;
}
function _setRoyaltiesEntry(
uint256 amount,
bytes32 recipientMapKey,
bytes memory recipientMapValue
) private pure returns (bytes32[] memory keys, bytes[] memory values) {
keys = new bytes32[](1);
values = new bytes[](1);
uint64 index = _extractIndexFromMap(recipientMapValue);
keys[0] = recipientMapKey;
values[0] = bytes.concat(bytes8(index), bytes2(0), bytes4(uint32(amount)));
}
function _addRoyaltiesEntry(
IERC725Y asset,
address recipient,
uint256 amount
) private view returns (bytes32[] memory keys, bytes[] memory values) {
bytes memory encodedArrayLength = asset.getData(_LSP18_ROYALTIES_KEY);
uint256 newArrayLength;
if (encodedArrayLength.length == 0) {
newArrayLength = 1;
} else if (encodedArrayLength.length == 32) {
uint256 arrayLength = abi.decode(encodedArrayLength, (uint256));
newArrayLength = arrayLength + 1;
} else {
revert InvalidLSP18RoyaltiesArrayLength(encodedArrayLength, encodedArrayLength.length);
}
uint256 index = newArrayLength - 1;
keys = new bytes32[](3);
values = new bytes[](3);
keys[0] = _LSP18_ROYALTIES_KEY;
values[0] = bytes.concat(bytes32(newArrayLength));
keys[1] = LSP2Utils.generateArrayElementKeyAtIndex(_LSP18_ROYALTIES_KEY, index);
values[1] = bytes.concat(bytes20(recipient));
keys[2] = LSP2Utils.generateMappingKey(_LSP18_ROYALTIES_MAP_KEY_PREFIX, recipient);
values[2] = bytes.concat(bytes8(uint64(index)), bytes2(0), bytes4(uint32(amount)));
}
function _extractIndexFromMap(bytes memory mapValue) private pure returns (uint64) {
bytes memory val = BytesLib.slice(mapValue, 0, 8);
return BytesLib.toUint64(val, 0);
}
function _removeRoyaltiesEntry(
IERC725Y asset,
bytes32 recipientMapKey,
bytes memory recipientMapValue
) private view returns (bytes32[] memory keys, bytes[] memory values) {
bytes memory encodedArrayLength = asset.getData(_LSP18_ROYALTIES_KEY);
uint256 arrayLength = abi.decode(encodedArrayLength, (uint256));
uint256 newArrayLength = arrayLength - 1;
uint64 index = _extractIndexFromMap(recipientMapValue);
bytes32 recipientKey = LSP2Utils.generateArrayElementKeyAtIndex(_LSP18_ROYALTIES_KEY, index);
if (index == arrayLength - 1) {
keys = new bytes32[](3);
values = new bytes[](3);
keys[0] = _LSP18_ROYALTIES_KEY;
values[0] = bytes.concat(bytes32(newArrayLength));
keys[1] = recipientMapKey;
values[1] = "";
keys[2] = recipientKey;
values[2] = "";
} else {
keys = new bytes32[](5);
values = new bytes[](5);
keys[0] = _LSP18_ROYALTIES_KEY;
values[0] = bytes.concat(bytes32(newArrayLength));
keys[1] = recipientMapKey;
values[1] = "";
bytes32 lastRecipientKey = LSP2Utils.generateArrayElementKeyAtIndex(_LSP18_ROYALTIES_KEY, newArrayLength);
bytes memory lastRecipientValue = asset.getData(lastRecipientKey);
bytes32 lastRecipientMapKey = LSP2Utils.generateMappingKey(
_LSP18_ROYALTIES_MAP_KEY_PREFIX,
address(bytes20(lastRecipientValue))
);
bytes memory lastRecipientMapValue = asset.getData(lastRecipientMapKey);
bytes memory data = BytesLib.slice(lastRecipientMapValue, 8, 6);
keys[2] = recipientKey;
values[2] = lastRecipientValue;
keys[3] = lastRecipientKey;
values[3] = "";
keys[4] = lastRecipientMapKey;
values[4] = bytes.concat(bytes8(index), data);
}
}
}
```

## Copyright
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).

0 comments on commit 51c1a69

Please sign in to comment.