Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

draft: LIP-27 - Token Bound Profiles Proposal #304

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 196 additions & 0 deletions LIPs/lip-27.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
---
lip: 27
title: Token Bound Profile
author: Valentine Orga <[email protected]>, Tanto Defi <[email protected]>, SLEEPY SIGN <[email protected]>
status: Draft
type: LSP
category: Core
created: 2024-09-14
requires: LSP0, LSP8
---

## Simple Summary

LIP-27 introduces a standard for creating Token Bound Profiles (TBP) using **LSP8 Identifiable Digital Assets** and **Universal Profiles (LSP0)**. This standard enables LSP8 tokens to be associated with a Universal Profile, facilitating token-based ownership and interaction.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
LIP-27 introduces a standard for creating Token Bound Profiles (TBP) using **LSP8 Identifiable Digital Assets** and **Universal Profiles (LSP0)**. This standard enables LSP8 tokens to be associated with a Universal Profile, facilitating token-based ownership and interaction.
LSP-27 introduces a standard for creating Token Bound Profiles (TBP) using **LSP8 Identifiable Digital Assets** and **Universal Profiles (LSP0)**. This standard enables LSP8 tokens to be associated with a Universal Profile, facilitating token-based ownership and interaction.


## Abstract

This proposal adapts the idea of **EIP-6551 (Token Bound Accounts)** for the Lukso ecosystem. Instead of using ERC721 tokens, LIP-27 leverages **LSP8 tokens** to bind each token to its own Universal Profile.

Key components include:

- A **Registry Contract** that maps LSP8 token identifiers to their respective Universal Profiles.
- A modified **Universal Profile** contract that restricts execution rights to the token owner.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the owner() of a UP is the holder of the NFT? What happen if I want my UP to be owned by a KeyManager?


## Motivation

As NFTs and tokenized assets proliferate, there is a need for tokens to have autonomous profiles capable of holding assets, executing contracts, and interacting with decentralized applications. **LIP-27** facilitates this functionality within the Lukso ecosystem using **LSP8 tokens** and **Universal Profiles**.

This approach enhances the utility of tokens by allowing each token to have its own programmable, self-sovereign profile, while maintaining compatibility with Lukso’s standards for identity (**LSP0**) and digital assets (**LSP8**).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be done with LSP8 using the tokenIdFormat address.

A custom implementation of LSP8 (within the Solidity code, for instance via the _afterTokenTransfer(...) hook could:

  • deploy a new UP contract when minting a tokenId (as standalone or proxy linked to an implementation)
  • renounceOwnership of the UP on burning


## Specification

### Overview

The system consists of two primary components:

1. A **singleton registry** for Token Bound Profiles.
2. A common **Universal Profile implementation** modified to support LSP8 ownership, while adhering to the `LSP0-ERC725Account` interface.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a link to a repo where we can see the Solidity code of this custom implementation?


### Registry Contract

The **Registry Contract** maps each LSP8 token to a unique Universal Profile. This ensures that for each LSP8 token, there is a corresponding Universal Profile.

#### Registry Interface

```solidity
interface ILSP27Registry {
/**
* @dev The registry must emit the LSP27ProfileCreated event upon successful profile creation.
*/
event LSP27ProfileCreated(
address profile,
address indexed implementation,
bytes32 salt,
uint256 chainId,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why having the chainId here? This does not seem necessary. A UP exists only on a specific chain.

address indexed tokenContract,
uint256 indexed tokenId
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if you use LSP8TokenIdFormat = Address + CREATE2, the tokenId would be the address of the UP. So this tokenId parameter would not be necessary. The parameter address profile emitted in the event would act at the same time as the tokenId.

);

/**
* @dev The registry must revert with ProfileCreationFailed error if the CREATE2 operation fails.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be precised in the standard if CREATE2 opcode MUST or COULD be used.

Meaning if it's mandatory to deploy UPs using CREATE2 or not

*/
error ProfileCreationFailed();

/**
* @dev Creates a token-bound profile for a non-fungible token.
* If a profile already exists, returns the address without calling CREATE2.
*
* Emits an LSP27ProfileCreated event.
*
* @return profile The address of the token-bound profile.
*/
function createProfile(
address implementation,
bytes32 salt,
uint256 chainId,
address tokenContract,
uint256 tokenId
) external returns (address profile);

/**
* @dev Returns the computed token-bound profile address for a non-fungible token.
*
* @return profile The address of the token-bound profile.
*/
function profile(
address implementation,
bytes32 salt,
uint256 chainId,
address tokenContract,
uint256 tokenId
) external view returns (address profile);
}
```

### Universal Profile Modifications

The existing **Universal Profile (LSP0)** implementation will remain largely unchanged, but its logic will be modified to support LSP8 token ownership.

#### Constructor

```solidity
constructor(address _tokenContract, bytes32 _id) payable;
```

- **Purpose**: Initializes the Universal Profile, allowing for funding upon deployment and setting the LSP8 token as the profile's owner.

#### Owner Function

```solidity
function owner() public view virtual returns (address);
```

- **Purpose**: The ownership of the Universal Profile is determined by calling the `tokenOwnerOf()` function from the LSP8 contract. Ownership is transferred along with the token transfer.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ownership of the Universal Profile is determined by calling the tokenOwnerOf() function from the LSP8 contract.

This function expects a parameter. What would be the tokenId passed here when calling the owner() function? Where is the tokenId stored in the UP? I see it in the constructor but nothing else is mentioned about that.

Ownership is transferred along with the token transfer.

Then this would mean if you want to have this exact behaviour, you would need to:

  1. Override the _acceptOwnership function to call the LSP8 contract to transfer the tokenId to the new owner (challenging as here the caller would be the UP, so some custom logic would need to be implemented in your LSP8 version).

  2. Override the transfer(...) function in LSP8 to also transfer ownership of the UP to the new owner. This would not work neither, the call might fail as the caller would not be the tokenId owner but the tokenContract. Therefore more custom logic would need to be implemented on the UP side for this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ownership of the Universal Profile is determined by calling the tokenOwnerOf() function from the LSP8 contract.

This function expects a parameter. What would be the tokenId passed here when calling the owner() function? Where is the tokenId stored in the UP? I see it in the constructor but nothing else is mentioned about that.

Ownership is transferred along with the token transfer.

Then this would mean if you want to have this exact behaviour, you would need to:

  1. Override the _acceptOwnership function to call the LSP8 contract to transfer the tokenId to the new owner (challenging as here the caller would be the UP, so some custom logic would need to be implemented in your LSP8 version).

  2. Override the transfer(...) function in LSP8 to also transfer ownership of the UP to the new owner. This would not work neither, the call might fail as the caller would not be the tokenId owner but the tokenContract. Therefore more custom logic would need to be implemented on the UP side for this.


#### Transfer Ownership

```solidity
function transferOwnership(address newOwner) public;
```

- **Purpose**: Ownership of the Universal Profile is transferred by transferring the associated LSP8 token, instead of updating the state within the Universal Profile contract.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- **Purpose**: Ownership of the Universal Profile is transferred by transferring the associated LSP8 token, instead of updating the state within the Universal Profile contract.
- **Purpose**: Ownership of the Universal Profile is transferred by transferring the associated LSP8 tokenId, instead of updating the state within the Universal Profile contract.

But you would still need to update the _owner in the state as well. How would that become different? Otherwise, this would override the standard behaviour (which is from LSP14 Ownable 2 Steps standard that LSP0 uses, so making it less compliant)


#### Renounce Ownership

```solidity
function renounceOwnership() public;
```

- **Purpose**; This function permanently deletes the token contract and ID from storage, leaving the profile without an owner.

## Rationale
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not find this section clear. This is an important one for proposing a new standard.

It does highlight generically the potential benefits (although I find these more generic statements than specific advantages or problems being solved by the standard).

I would suggest to review this section carefully if looking to push this standard, as the rationale is what motivate the developers and projects in the ecosystem to adopt the standard, as it solves their specific needs.


The rationale for this proposal stems from the increasing complexity of digital assets and the need for a flexible, token-bound profile system that enables more nuanced token ownership and interaction capabilities.

By leveraging **LSP8 Identifiable Digital Assets** and **LSP0 Universal Profiles**, we can create a framework for **Token Bound Profiles** that fits seamlessly within the Lukso ecosystem.

This approach offers several advantages:

- **Seamless Integration**: Token Bound Profiles build upon existing Lukso standards (LSP0 and LSP8), making them compatible with the ecosystem's existing infrastructure.
- **Programmable Ownership**: By binding ownership to LSP8 tokens, profiles are automatically controlled by token holders without requiring manual intervention.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a nuance here. By doing so, UP would not be able to be held by custom owner contracts (a Key Manager, a Multi sig, etc...).

This would be very restrictive, as as a result, Token Bound profile would not be able to benefit of gas less transactions.

- **Flexible Interactions**: This system allows tokens to interact with decentralized applications (dApps) and manage assets autonomously via their associated profiles.

## Backwards Compatibility

LIP-27 is fully compatible with existing **LSP0** and **LSP8** standards. It introduces no breaking changes to the Universal Profile or Identifiable Digital Asset contracts.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very strong statement. By the look of the function section above, it seems to break partially LSP14 which is part of LSP0. Although this could be debatable to the extent.

The bigger problem is that this standard proposal breaks composability over "who can be the owner of the UP". In the sense "control the UP". Ownership and control (via the owner() function) are two very different things


- **LSP0 (Universal Profile)**: The modifications to the Universal Profile contract under LIP-27 are backward compatible and do not alter the existing interface. The contract logic is adapted to incorporate LSP8 token ownership without disrupting the standard functionality.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that's correct from the public interface point of view. So this statement nuances my previous comment above.

However, it does disrupt the standard functionalities.

- **LSP8 (Identifiable Digital Assets)**: LIP-27 utilizes LSP8 tokens as the basis for token-bound profiles. The proposal aligns with the LSP8 standard and does not introduce any breaking changes to the existing token mechanics.

Overall, LIP-27 extends the capabilities of existing standards while ensuring full compatibility and continuity within the Lukso ecosystem.

## Security Considerations

### Fraud Prevention

In order to enable trustless sales of token bound accounts, decentralized marketplaces will need to implement safeguards against fraudulent behavior by malicious account owners.

Consider the following potential scam:

- Alice owns an LSP8 token X, which owns token bound account Y.
- Alice deposits 10 LYX into account Y.
- Bob offers to purchase token X for 11 LYX via a decentralized marketplace, assuming he will receive the 10 ETH stored in account Y along with the token.
- Alice withdraws 10 LYX from the token bound account and immediately accepts Bob’s offer.
- Bob receives token X, but account Y is empty.

To mitigate fraudulent behavior by malicious account owners, decentralized marketplaces should implement protection against these sorts of scams. Here are a few mitigation strategies to consider:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting section 👀


- **Attach the current token bound account state to the marketplace order**: If the state of the account has changed since the order was placed, consider the offer void. This functionality would need to be supported at the marketplace level.
- **Attach a list of asset commitments to the marketplace order**: This list should include assets expected to remain in the token bound account when the order is fulfilled. If any of the committed assets have been removed since the order was placed, consider the offer void. This would also need to be implemented by the marketplace.
- **Submit the order to the decentralized market via an external smart contract**: This contract can perform the above checks before validating the order signature, allowing for safe transfers without marketplace support.
- **Implement a locking mechanism on the token bound account**: Prevent malicious owners from extracting assets while the account is locked.

Preventing fraud is outside the scope of this proposal.

### Ownership Cycles

All assets held in a token bound account may become inaccessible if an ownership cycle is created. For example:

- An LSP8 token could be transferred to its own token bound account. If this occurs, both the LSP8 token and all assets stored in the token bound account would be permanently inaccessible, as the token bound account is incapable of executing a transaction that transfers the LSP8 token.

Application clients and account implementations wishing to adopt this proposal are encouraged to implement measures that limit the possibility of ownership cycles.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This must be enforced at the smart contract level. But this my not be applicable as I believe LSP14 Ownable 2 Steps mention in the specs that it cannot transfer ownership to self (to be verified)


## References

1. **EIP-6551: Non-fungible Token Bound Accounts**
A standard on Ethereum that enables ERC-721 tokens to control smart contract accounts. It allows each NFT to own assets and interact with contracts via a token-bound account.
[EIP-6551](https://eips.ethereum.org/EIPS/eip-6551)

2. **LUKSO LSP0 - Universal Profile**
Universal Profile is a key component of the LUKSO ecosystem, enabling individuals and organizations to create a smart contract-based profile for asset management and interaction within the network.
[LSP0 - Universal Profile](https://docs.lukso.tech/contracts/overview/UniversalProfile/)

3. **LUKSO LSP8 - Identifiable Digital Assets**
LSP8 is a token standard for unique, non-fungible assets on LUKSO, analogous to ERC-721. It serves as the foundation for the token-bound account system proposed in LIP-27.
[LSP8 - Identifiable Digital Assets](https://docs.lukso.tech/contracts/overview/DigitalAssets)