Skip to content

Commit

Permalink
add details for smart contracts on how to create custom permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
CJ42 committed Sep 25, 2024
1 parent 013fe3b commit 36a5b87
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 0 deletions.
4 changes: 4 additions & 0 deletions docs/contracts/overview/DigitalAssets.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
80 changes: 80 additions & 0 deletions docs/contracts/overview/KeyManager.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

0 comments on commit 36a5b87

Please sign in to comment.