From 36a5b87f8e886f916af598c1fe7d180c9a1d290d Mon Sep 17 00:00:00 2001 From: CJ42 Date: Tue, 24 Sep 2024 00:16:19 +0800 Subject: [PATCH] add details for smart contracts on how to create custom permissions --- docs/contracts/overview/DigitalAssets.md | 4 ++ docs/contracts/overview/KeyManager.md | 80 ++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/docs/contracts/overview/DigitalAssets.md b/docs/contracts/overview/DigitalAssets.md index c5fb0b2da..c587b3103 100644 --- a/docs/contracts/overview/DigitalAssets.md +++ b/docs/contracts/overview/DigitalAssets.md @@ -366,6 +366,10 @@ The smart contracts packages for `@lukso/lsp7-contracts` and `@lukso/lsp8-contra If your token contract uses the proxy pattern with initialize functions, use the `InitAbstract` version of these extension contracts (\_e.g: `LSP7Burnable` -> `LSP7BurnableInitAbstract`). +## Custom logic for transfers + +The LSP7 and LSP8 implementations implement a `_beforeTokenTransfer` and `_afterTokenTransfer` function that enable to specify custom logic that can run before or after the token transfer has happen (= before or after the balances in the contract state have been updated). + ## Note on LSP7 and LSP8 implementations `LSP7DigitalAsset.sol` and `LSP8IdentifiableDigitalAsset.sol` are `abstract` contracts that are not deployable as they are, because they do not contain any public functions by default to manage token supply (_e.g: no public `mint(...)` or `burn(...)` functions_). You can either: diff --git a/docs/contracts/overview/KeyManager.md b/docs/contracts/overview/KeyManager.md index af3d4486c..47d2a0b6d 100644 --- a/docs/contracts/overview/KeyManager.md +++ b/docs/contracts/overview/KeyManager.md @@ -99,6 +99,86 @@ Given the example above, the on-chain nonce is 4 and we are executing the relay - Second relay call: nonce on-chain is 4 -> nonce used to sign was 5 = reverts ❌ with [`InvalidRelayNonce`](../contracts//LSP6KeyManager/LSP6KeyManager.md#invalidrelaynonce) - Third relay call: nonce on-chain is 5 -> nonce used to sign was 6 = reverts ❌ with [`InvalidRelayNonce`](../contracts//LSP6KeyManager/LSP6KeyManager.md#invalidrelaynonce) +## Implement custom permissions + +The permission system of the Key Manager is versatile enough with the permission system so that permissions can be extended (from the default one). The `bytes32` permission range is large enough to enable to create custom permissions, that can be specific for some application use cases. + +For instance, some data keys could be very sensitive for some specific dApp (_e.g: if on a decentralised social media you store the :up: user settings under a specific data key._) and a developer might not necessarily want to use the **Allowed ERC725Y Data Keys** for this specific data key. + +As mentioned in comment in the Key Manager's code, you can extend the verification logic to implement custom permissions. +https://github.com/lukso-network/lsp-smart-contracts/blob/8f0cfb2c573c44702d3155375b2d935b043416b3/contracts/LSP6KeyManager/LSP6Modules/LSP6SetDataModule.sol#L263-L275 + +This can be done simply overriding the function. Below is an example to create a Key Manager that controls a token contract and requires a specific permission to update the `LSP4Metadata`. + +```solidity +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.5; + +// interfaces +import {ILSP6KeyManager} from "@lukso/lsp6-contracts/contracts/ILSP6KeyManager.sol"; + +// modules +import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import {LSP6SetDataModule} from "@lukso/lsp6-contracts/contracts/LSP6SetDataModule.sol"; +import {LSP6OwnershipModule} from "@lukso/lsp6-contracts/contracts/LSP6OwnershipModule.sol"; + +/// @title LSP6 Key Manager implementation to enable multiple owners with different permissions and roles +/// to control an LSP7 or LSP8 Token (instead of having a single `owner()`). +contract LSP6TokenManager is + ILSP6KeyManager, + ERC165, + LSP6SetDataModule, + LSP6OwnershipModule +{ + using Address for *; + using ECDSA for *; + using LSP6Utils for *; + + /// @dev address of the LSP7/8 Token contract this Key Manager controls + address private immutable _linkedToken; + + mapping(address => mapping(uint256 => uint256)) internal _nonceStore; + + constructor(address linkedToken_) { + if (linkedToken_ == address(0)) revert InvalidLSP6Target(); + _linkedToken = linkedToken_; + } + + /// @dev permission required to update the `LSP4Metadata` data key via `setData(...)` on the token contract + bytes32 constant _PERMISSION_UPDATE_TOKEN_METADATA = 0x0000000000000000000000000000000000000000000000000000000000400000; + + /// @dev keccak256('LSP4Metadata') --> from the LSP4 Standard + /// This is defined in `LSP4Constants.sol`, but we write it here for better understanding. + bytes32 constant _LSP4_METADATA_KEY = 0x9afb95cacc9f95858ec44aa8c3b685511002e30ae54415823f406128b85b238e; + + /// @inheritdoc LSP6SetDataModule + /// @dev implement a custom check to verify if the controller + /// has the permission to update the token metadata + function _getPermissionRequiredToSetDataKey( + address controlledContract, + bytes32 controllerPermissions, + bytes32 inputDataKey, + bytes memory inputDataValue + ) internal view virtual override returns (bytes32) { + if (inputDataKey == _LSP4_METADATA_KEY) { + controllerPermissions.hasPermission( + _PERMISSION_UPDATE_TOKEN_METADATA + ); + } + + super._getPermissionRequiredToSetDataKey( + controlledContract, + controllerPermissions, + inputDataKey, + inputDataValue + ); + } + +} +``` + +As you can see from the Solidity code snippet above, since the Key Manager is broken down in multiple modules for each types of permissions (`LSP6SetDataModule`, `LSP6OwnershipModule`), it's relatively easy to create a specific implementation by-reusing the same code and implement custom permissions check based on each implementation on top. + ## Further Reading - [The Bytecode episode #4 (Youtube) - overview of the Solidity code of the `LSP6KeyManagerCore.sol` by Jean Cavallera](https://www.youtube.com/watch?v=2Sm9LsCPjdE)