From 82b1fda4f9d7c6051968d5691c00cb8ff98a3872 Mon Sep 17 00:00:00 2001 From: Duke tho Date: Fri, 16 Aug 2024 01:08:40 +0700 Subject: [PATCH] Add ERC: Ownership Delegation and Context for ERC-721 Merged by EIP-Bot. --- ERCS/erc-7695.md | 497 +++++++++++++++++++++++++++++++++++ assets/erc-7695/mortgage.svg | 3 + assets/erc-7695/rental.svg | 3 + 3 files changed, 503 insertions(+) create mode 100644 ERCS/erc-7695.md create mode 100644 assets/erc-7695/mortgage.svg create mode 100644 assets/erc-7695/rental.svg diff --git a/ERCS/erc-7695.md b/ERCS/erc-7695.md new file mode 100644 index 0000000000..5e1e652a97 --- /dev/null +++ b/ERCS/erc-7695.md @@ -0,0 +1,497 @@ +--- +eip: 7695 +title: Ownership Delegation and Context for ERC-721 +description: Introduces contexts and ownership delegation for ERC-721 tokens, expanding dApps and financial use cases without transferring ownership +author: Duc Tho Tran (@ducthotran2010) +discussions-to: https://ethereum-magicians.org/t/erc-7695-ownership-delegation-and-context-for-non-fungible-token/19716 +status: Draft +type: Standards Track +category: ERC +created: 2024-04-02 +requires: 165, 721 +--- + +## Abstract + +This standard is an extension for [ERC-721](./eip-721.md), designed to specify users for various contexts with a locking feature and allow temporary ownership delegation without changing the original owner. + +This EIP preserves the benefits and rights of the owner while expanding the utility of NFTs across various dApps by adding the concepts of Ownership Delegation and Contexts, which define specific roles: Controller and User, who can use the NFT within defined contexts. + +## Motivation + +For standard [ERC-721](./eip-721.md) NFTs, there are several use cases in financial applications, including: + +- Staking NFTs to earn rewards. +- Mortgaging an NFT to generate income. +- Granting users for different purposes like rental and token delegation—where someone pays to use tokens and pays another party to use the tokens. + +Traditionally, these applications require ownership transfers to lock the NFT in contracts. However, other decentralized applications (dApps) recognize token ownership as proof that the token owner is entitled to benefits within their reward systems, such as airdrops or tiered rewards. If token owners have their tokens locked in contracts, they are not eligible to receive benefits from holding these tokens, or the reward systems have to support as many contracts as possible to help these owners. + +This is because there is only an Owner role indicating the ownership rights, developing on top of [ERC-721](./eip-721.md) has often posed challenges. This proposal aims to solve these challenges by contextualizing the use case to be handled by controllers and distinguishing ownership rights from other roles at the standard level through an ownership delegation mechanism. Standardizing these measures, dApp developers can more easily construct infrastructure and protocols on top of this standard. + +## Specification + +The keywords “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. + +### Definitions + +This specification encompasses the following components: + +**Token Context** provides a specified use case of a token. It serves as the association relationship between Tokens and Contexts. Within each unique token context, there exists an allocated user who is authorized to utilize the token within that context. In a specified context, there are two distinct roles: + +- **Controller**: This role possesses the authority to control the context. +- **User**: This role signifies the primary token user within the given context. + +**Ownership Rights** of a token are defined to be able to: + +- Transfer that token to a new owner. +- Add token context(s): attaching that token to/from one or many contexts. +- Remove token context(s): detaching that token to/from one or many contexts. + +**Ownership Delegation** involves distinguishing between owner and ownership rights by delegating ownership to other accounts for a specific duration. During this period, owners temporarily cede ownership rights until the delegation expires. + +### Roles + +| Roles | Explanation / Permission | Quantity per Token | +| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | +| Owner | • Has **Ownership Rights** by default
• Delegates an account to hold **Ownership Rights** in a duration | $1$ | +| Ownership Delegatee | • Has **Ownership Rights** in a delegation duration
• Renounces before delegation expires | $1$ | +| Ownership Manager | • Is one who holds **Ownership Rights**
• If not delegated yet, it is referenced to **Owner**, otherwise it is referenced to **Ownership Delegatee** | $1$ | +| **Context Roles** | | $n$ | +| Controller | • Transfers controller
• Sets context user
• (Un)locks token transfer | $1$ per context | +| User | • Authorized to use token in its context | $1$ per context | + +### Interface + +**Smart contracts implementing this standard MUST implement all the functions in the `IERC7695` interface.** + +Smart contracts implementing this standard MUST implement the [ERC-165](./eip-165.md) `supportsInterface` function and MUST return the constant value `true` if `0x486b6fba` is passed through the `interfaceID` argument. + +```solidity +/// Note: the ERC-165 identifier for this interface is 0x486b6fba. +interface IERC7695 /* is IERC721, IERC165 */ { + /// @dev This emits when a context is updated by any mechanism. + event ContextUpdated(bytes32 indexed ctxHash, address indexed controller, uint64 detachingDuration); + /// @dev This emits when a token is attached to a certain context by any mechanism. + event ContextAttached(bytes32 indexed ctxHash, uint256 indexed tokenId); + /// @dev This emits when a token is requested to detach from a certain context by any mechanism. + event ContextDetachmentRequested(bytes32 indexed ctxHash, uint256 indexed tokenId); + /// @dev This emits when a token is detached from a certain context by any mechanism. + event ContextDetached(bytes32 indexed ctxHash, uint256 indexed tokenId); + /// @dev This emits when a user is assigned to a certain context by any mechanism. + event ContextUserAssigned(bytes32 indexed ctxHash, uint256 indexed tokenId, address indexed user); + /// @dev This emits when a token is (un)locked in a certain context by any mechanism. + event ContextLockUpdated(bytes32 indexed ctxHash, uint256 indexed tokenId, bool locked); + /// @dev This emits when the ownership delegation is started by any mechanism. + event OwnershipDelegationStarted(uint256 indexed tokenId, address indexed delegatee, uint64 until); + /// @dev This emits when the ownership delegation is accepted by any mechanism. + event OwnershipDelegationAccepted(uint256 indexed tokenId, address indexed delegatee, uint64 until); + /// @dev This emits when the ownership delegation is stopped by any mechanism. + event OwnershipDelegationStopped(uint256 indexed tokenId, address indexed delegatee); + + /// @notice Gets the longest duration the detaching can happen. + function maxDetachingDuration() external view returns (uint64); + + /// @notice Gets controller address and detachment duration of a context. + /// @dev MUST revert if the context is not existent. + /// @param ctxHash A hash of context to query the controller. + /// @return controller The address of the context controller. + /// @return detachingDuration The duration must be waited for detachment in second(s). + function getContext(bytes32 ctxHash) external view returns (address controller, uint64 detachingDuration); + + /// @notice Creates a new context. + /// @dev MUST revert if the context is already existent. + /// MUST revert if the controller address is zero address. + /// MUST revert if the detaching duration is larger than max detaching duration. + /// MUST emit the event {ContextUpdated} to reflect context created and controller set. + /// @param controller The address that controls the created context. + /// @param detachingDuration The duration must be waited for detachment in second(s). + /// @param ctxMsg The message of new context to be used for hashing. + /// @return ctxHash Hash of the created context. + function createContext(address controller, uint64 detachingDuration, bytes calldata ctxMsg) + external + returns (bytes32 ctxHash); + + /// @notice Updates an existing context. + /// @dev MUST revert if method caller is not the current controller. + /// MUST revert if the context is non-existent. + /// MUST revert if the new controller address is zero address. + /// MUST revert if the detaching duration is larger than max detaching duration. + /// MUST emit the event {ContextUpdated} on success. + /// @param ctxHash Hash of the context to set. + /// @param newController The address of new controller. + /// @param newDetachingDuration The new duration must be waited for detachment in second(s). + function updateContext(bytes32 ctxHash, address newController, uint64 newDetachingDuration) external; + + /// @notice Queries if a token is attached to a certain context. + /// @param ctxHash Hash of a context. + /// @param tokenId The NFT to query. + /// @return True if the token is attached to the context, false if not. + function isAttachedWithContext(bytes32 ctxHash, uint256 tokenId) external view returns (bool); + + /// @notice Attaches a token with a certain context. + /// @dev See "attachContext rules" in "Token (Un)lock Rules". + /// @param ctxHash Hash of a context. + /// @param tokenId The NFT to be attached. + /// @param data Additional data with no specified format, MUST be sent unaltered in call to the {IERC7695ContextCallback} hook(s) on controller. + function attachContext(bytes32 ctxHash, uint256 tokenId, bytes calldata data) external; + + /// @notice Requests to detach a token from a certain context. + /// @dev See "requestDetachContext rules" in "Token (Un)lock Rules". + /// @param ctxHash Hash of a context. + /// @param tokenId The NFT to be detached. + /// @param data Additional data with no specified format, MUST be sent unaltered in call to the {IERC7695ContextCallback} hook(s) on controller. + function requestDetachContext(bytes32 ctxHash, uint256 tokenId, bytes calldata data) external; + + /// @notice Executes context detachment. + /// @dev See "execDetachContext rules" in "Token (Un)lock Rules". + /// @param ctxHash Hash of a context. + /// @param tokenId The NFT to be detached. + /// @param data Additional data with no specified format, MUST be sent unaltered in call to the {IERC7695ContextCallback} hook(s) on controller. + function execDetachContext(bytes32 ctxHash, uint256 tokenId, bytes calldata data) external; + + /// @notice Finds the context user of a token. + /// @param ctxHash Hash of a context. + /// @param tokenId The NFT to be detached. + /// @return user Address of the context user. + function getContextUser(bytes32 ctxHash, uint256 tokenId) external view returns (address user); + + /// @notice Updates the context user of a token. + /// @dev MUST revert if the method caller is not context controller. + /// MUST revert if the context is non-existent. + /// MUST revert if the token is not attached to the context. + /// MUST emit the event {ContextUserAssigned} on success. + /// @param ctxHash Hash of a context. + /// @param tokenId The NFT to be update. + /// @param user Address of the new user. + function setContextUser(bytes32 ctxHash, uint256 tokenId, address user) external; + + /// @notice Queries if the lock a token is locked in a certain context. + /// @param ctxHash Hash of a context. + /// @param tokenId The NFT to be queried. + /// @return True if the token context is locked, false if not. + function isTokenContextLocked(bytes32 ctxHash, uint256 tokenId) external view returns (bool); + + /// @notice (Un)locks a token in a certain context. + /// @dev See "setContextLock rules" in "Token (Un)lock Rules". + /// @param ctxHash Hash of a context. + /// @param tokenId The NFT to be queried. + /// @param lock New status to be (un)locked. + function setContextLock(bytes32 ctxHash, uint256 tokenId, bool lock) external; + + /// @notice Finds the ownership manager of a specified token. + /// @param tokenId The NFT to be queried. + /// @return manager Address of delegatee. + function getOwnershipManager(uint256 tokenId) external view returns(address manager); + + /// @notice Finds the ownership delegatee of a token. + /// @dev MUST revert if there is no (or an expired) ownership delegation. + /// @param tokenId The NFT to be queried. + /// @return delegatee Address of delegatee. + /// @return until The delegation expiry time. + function getOwnershipDelegatee(uint256 tokenId) external view returns (address delegatee, uint64 until); + + /// @notice Finds the pending ownership delegatee of a token. + /// @dev MUST revert if there is no (or an expired) pending ownership delegation. + /// @param tokenId The NFT to be queried. + /// @return delegatee Address of pending delegatee. + /// @return until The delegation expiry time in the future. + function pendingOwnershipDelegatee(uint256 tokenId) external view returns (address delegatee, uint64 until); + + /// @notice Starts ownership delegation and retains ownership until a specific timestamp. + /// @dev Replaces the pending delegation if any. + /// See "startDelegateOwnership rules" in "Ownership Delegation Rules". + /// @param tokenId The NFT to be delegated. + /// @param delegatee Address of new delegatee. + /// @param until The delegation expiry time. + function startDelegateOwnership(uint256 tokenId, address delegatee, uint64 until) external; + + /// @notice Accepts ownership delegation request. + /// @dev See "acceptOwnershipDelegation rules" in "Ownership Delegation Rules". + /// @param tokenId The NFT to be accepted. + function acceptOwnershipDelegation(uint256 tokenId) external; + + /// @notice Stops the current ownership delegation. + /// @dev See "stopOwnershipDelegation rules" in "Ownership Delegation Rules". + /// @param tokenId The NFT to be stopped. + function stopOwnershipDelegation(uint256 tokenId) external; +} +``` + +**Enumerable extension** + +The enumeration extension is OPTIONAL for this standard. This allows your contract to publish its full list of contexts and make them discoverable. When calling the `supportsInterface` function MUST return the constant value `true` if `0xcebf44b7` is passed through the `interfaceID` argument. + +```solidity +/// Note: the ERC-165 identifier for this interface is 0xcebf44b7. +interface IERC7695Enumerable /* is IERC165 */ { + /// @dev Returns a created context in this contract at `index`. + /// An index must be a value between 0 and {getContextCount}, non-inclusive. + /// Note: When using {getContext} and {getContextCount}, make sure you perform all queries on the same block. + function getContext(uint256 index) external view returns(bytes32 ctxHash); + + /// @dev Returns the number of contexts created in the contract. + function getContextCount() external view returns(uint256); + + /// @dev Returns a context attached to a token at `index`. + /// An index must be a value between 0 and {getAttachedContextCount}, non-inclusive. + /// Note: When using {getAttachedContext} and {getAttachedContextCount}, make sure you perform all queries on the same block. + function getAttachedContext(uint256 tokenId, uint256 index) external view returns(bytes32 ctxHash); + + /// @dev Returns the number of contexts attached to the token. + function getAttachedContextCount(uint256 tokenId) external view returns(uint256); +} +``` + +**Controller Interface** + +The controller is RECOMMENDED to be a contract and including callback methods to allow callbacks in cases where there are any attachment or detachment requests. When calling the `supportsInterface` function MUST return the constant value `true` if `0xad0491f1` is passed through the `interfaceID` argument. + +```solidity +/// Note: the ERC-165 identifier for this interface is 0xad0491f1. +interface IERC7695ContextCallback /* is IERC165 */ { + /// @dev This method is called once the token is attached by any mechanism. + /// This function MAY throw to revert and reject the attachment. + /// @param ctxHash The hash of context invoked this call. + /// @param tokenId NFT identifier which is being attached. + /// @param operator The address which called {attachContext} function. + /// @param data Additional data with no specified format. + function onAttached(bytes32 ctxHash, uint256 tokenId, address operator, bytes calldata data) external; + + /// @dev This method is called once the token detachment is requested by any mechanism. + /// @param ctxHash The hash of context invoked this call. + /// @param tokenId NFT identifier which is being requested for detachment. + /// @param operator The address which called {requestDetachContext} function. + /// @param data Additional data with no specified format. + function onDetachRequested(bytes32 ctxHash, uint256 tokenId, address operator, bytes calldata data) external; + + /// @dev This method is called once a token context is detached by any mechanism. + /// @param ctxHash The hash of context invoked this call. + /// @param tokenId NFT identifier which is being detached. + /// @param user The address of the context user which is being detached. + /// @param operator The address which called {execDetachContext} function. + /// @param data Additional data with no specified format. + function onExecDetachContext(bytes32 ctxHash, uint256 tokenId, address user, address operator, bytes calldata data) external; +} +``` + +### Ownership Delegation Rules + +**startDelegateOwnership rules** + +- MUST revert unless there is no delegation. +- MUST revert unless the method caller is the owner, an authorized operator of owner, or the approved address for this NFT. +- MUST revert unless the expiry time is in the future. +- MUST revert if the delegatee address is the owner or zero address. +- MUST revert if the token is not existent. +- MUST emit the event `OwnershipDelegationStarted` on success. +- After the above conditions are met, this function MUST replace the pending delegation if any. + +**acceptOwnershipDelegation rules** + +- MUST revert if there is no delegation. +- MUST revert unless the method caller is the delegatee, or an authorized operator of delegatee. +- MUST revert unless the expiry time is in the future. +- MUST emit the event `OwnershipDelegationAccepted` on success. +- After the above conditions are met, the delegatee MUST be recorded as the ownership manager until the delegation expires. + +**stopDelegateOwnership rules** + +- MUST revert unless the delegation is already accepted. +- MUST revert unless the expiry time is in the future. +- MUST revert unless the method caller is the delegatee, or an authorized operator of delegatee. +- MUST emit the event `OwnershipDelegationStopped` on success. +- After the above conditions are met, the owner MUST be recorded as the ownership manager. + +### **Token (Un)lock Rules** + +To be more explicit about how token (un)locked, these functions: + +- A token can be attached to a context using the `attachContext` method +- The `setContextLock` function MUST be called by the controller to (un)lock +- The `requestDetachContext` and `execDetachContext` functions MUST be called by the ownership manager and MUST operate with respect to the `IERC7695ContextCallback` hook functions + +A list of scenarios and rules follows. + +**Scenarios** + +**_Scenario#1:_** Context controller wants to (un)lock a token that is not requested for detachment. + +- `setContextLock` MUST be called successfully + +**_Scenario#2:_** Context controller wants to (un)lock a token that is requested for detachment. + +- `setContextLock` MUST be reverted + +**_Scenario#3:_** Ownership manager wants to (unlock and) detach a locked token and the callback controller implements `IERC7695ContextCallback`. + +- Caller MUST: + - Call `requestDetachContext` function successfully + - Wait at least context detaching duration (see variable `detachingDuration` in the `getContext` function) + - Call `execDetachContext` function successfully +- `requestDetachContext` MUST call the `onDetachRequested` function despite the call result +- `execDetachContext` MUST call the `onExecDetachContext` function despite the call result + +**_Scenario#4:_** Ownership manager wants to (unlock and) detach a locked token and the callback controller does not implement `IERC7695ContextCallback`. + +- Caller MUST: + - Call `requestDetachContext` function successfully + - Wait at least context detaching duration (see variable `detachingDuration` in the `getContext` function) + - Call `execDetachContext` function successfully + +**_Scenario#5:_** Ownership manager wants to detach an unlocked token and the callback controller implements `IERC7695ContextCallback`. + +- Caller MUST call `requestDetachContext` function successfully +- `requestDetachContext` MUST call the `onExecDetachContext` function despite the result +- `execDetachContext` MUST NOT be called + +**_Scenario#6:_** Ownership manager wants to detach an unlocked token and the callback controller does not implement `IERC7695ContextCallback`. + +- Caller MUST call `requestDetachContext` function successfully +- `execDetachContext` MUST NOT be called + +**Rules** + +**attachContext rules** + +- MUST revert unless the method caller is the ownership manager, an authorized operator of ownership manager, or the approved address for this NFT (if the token is not being delegated). +- MUST revert if the context is non-existent. +- MUST revert if the token is already attached to the context. +- MUST emit the event `ContextAttached`. +- After the above conditions are met, this function MUST check if the controller address is a smart contract (e.g. code size > 0). If so, it MUST call `onAttached` and MUST revert if the call is failed. + - The `data` argument provided by the caller MUST be passed with its contents unaltered to the `onAttached` hook function via its `data` argument. + +**setContextLock rules** + +- MUST revert if the context is non-existent. +- MUST revert if the token is not attached to the context. +- MUST revert if a detachment request has previously been made. +- MUST revert if the method caller is not context controller. +- MUST emit the event `ContextLockUpdated` on success. + +**requestDetachContext rules** + +- MUST revert if a detachment request has previously been made. +- MUST revert unless the method caller is the context controller, the ownership manager, an authorized operator of the ownership manager, or the approved address for this NFT (if the token is not being delegated). +- If the caller is context controller or the token context is not locked, MUST emit the `ContextDetached` event. After the above conditions are met, this function MUST check if the controller address is a smart contract (e.g. code size > 0). If so, it MUST call `onExecDetachContext` and the call result MUST be skipped. + - The `data` argument provided by the caller MUST be passed with its contents unaltered to the `onExecDetachContext` hook function via its `data` argument. +- If the token context is locked, MUST emit the `ContextDetachRequested` event. After the above conditions are met, this function MUST check if the controller address is a smart contract (e.g. code size > 0). If so, it MUST call `onDetachRequested` and the call result MUST be skipped. + - The `data` argument provided by the caller MUST be passed with its contents unaltered to the `onDetachRequested` hook function via its `data` argument. + +**execDetachContext rules** + +- MUST revert unless the method caller is the ownership manager, an authorized operator of ownership manager, or the approved address for this NFT (if the token is not being delegated). +- MUST revert unless a detachment request has previously been made and the specified detaching duration has passed (use variable `detachingDuration` in the `getContext` function when requesting detachment). +- MUST emit the `ContextDetached` event. +- After the above conditions are met, this function MUST check if the controller address is a smart contract (e.g. code size > 0). If so, it MUST call `onExecDetachContext` and the call result MUST be skipped. + - The `data` argument provided by the caller MUST be passed with its contents unaltered to the `onExecDetachContext` hook function via its `data` argument. + +### Additional Transfer Rules + +In addition to extending from [ERC-721](./eip-721.md) for the transfer mechanism when transferring an NFT, the implementation: + +- MUST revert unless the method caller is the ownership manager, an authorized operator of ownership manager, or the approved address for this NFT (if the token is not being delegated). +- MUST revoke ownership delegation if any. +- MUST detach every attached context: + - MUST revert unless a detachment request has previously been made and the specified detaching duration has passed (use variable `detachingDuration` in the `getContext` function when requesting detachment) if the token is locked. + - MUST check if the controller address is a smart contract (e.g. code size > 0). If so, it MUST call the `onExecDetachContext` function (with an empty `data` argument `""`) and the call result MUST be skipped. + +## Rationale + +When designing the proposal, we considered the following concerns. + +### Multiple contexts for multiple use cases + +This proposal is centered around Token Context to allow for the creation of distinct contexts tailored to various decentralized applications (dApps). The context controller assumes the role of facilitating (rental or delegation) dApps, by enabling the granting of usage rights to another user without modifying the NFT's owner record. Besides, this proposal provides the lock feature for contexts to ensure trustlessness in performing these dApps, especially staking cases. + +### Providing an unlock mechanism for owners + +By providing an unlock mechanism for owners, this approach allows owners to unlock their tokens independently, without relying on the context controller to initiate the process. This prevents scenarios where, should the controller lose control, owners would be unable to unlock their tokens. + +### Attachment and detachment callbacks + +The callback results of the `onDetachRequested` and `onExecDetachContext` functions in the **Token (Un)lock Rules** are skipped because we are intentionally removing the controller's ability to stop detachment, ensuring token detachment is independent of the controller's actions. + +Additionally, to retain the permission to reject incoming attachments, the operation reverts if the call to the `onAttach` function fails. + +### Ownership delegation + +This feature provides a new approach by separating the owner and ownership. Primarily designed to facilitate delegating for third parties, it enables delegating another account as the manager of ownership, distinct from the owner. + +Unlike `approve` or `setApprovalForAll` methods, which grant permission to other accounts while maintaining ownership status. Ownership delegation goes beyond simply granting permissions; it involves transferring the owner's rights to the delegatee, with provisions for automatic reversion upon expiration. This mechanism prevents potential abuses, such as requesting mortgages and transfers to alternative accounts if the owner retains ownership rights. + +The **2-step delegation** process is provided to prevent mistakes in assigning delegatees, it must be done through two steps: offer and confirm. In cases the delegation needs to be canceled before its scheduled expiry, the delegatees can invoke `stopOwnershipDelegation` method. + +### Transfer method mechanism + +As part of the integration with the transfer method, we extended its implicit behavior to include token approval: + +- **Reset Ownership Delegation:** Automatically resets ownership delegations. The `OwnershipDelegationStopped` event is intentionally not emitted. +- **Detach All Contexts:** Similarly, all contexts associated with the token are detached if none of them is locked. The `ContextDetached` event is intentionally not emitted. + +These modifications are to ensure trustlessness and gas efficiency during token transfers, providing a seamless experience for users. + +## Backwards Compatibility + +This proposal is backward compatible with [ERC-721](./eip-721.md). + +## Security Considerations + +### Detaching duration + +When developing this token standard to serve multiple contexts: + +- The contract deployer should establish an appropriate upper threshold for detachment delay (by `maxDetachingDuration` method). +- The context owner should anticipate potential use cases and establish an appropriate period not larger than the upper threshold. + +This precaution is essential to mitigate the risk of the owner abusing systems by spamming listings and transferring tokens to another owner in a short time. + +### Duplicated token usage + +When initiating a new context, the context controllers should track all other contexts within the NFT contract to prevent duplicated usage. + +For example, suppose a scenario where a token is locked for rental purposes within a particular game. If that game introduces another context (e.g. supporting delegation in that game), it could lead to duplicated token usage within the game, despite being intended for different contexts. + +In such cases, a shared context for rental and delegation purposes can be considered. Or there must be some restrictions on the new delegation context to prevent reusing that token in the game. + +### Ownership Delegation Buffer Time + +When constructing systems that rely on ownership delegation for product development, it is imperative to incorporate a buffer time (of at least `maxDetachingDuration` seconds) when requesting ownership delegation. This precaution is essential to mitigate the risk of potential abuse, particularly if one of the associated contexts locks the token until the delegation time expires. +For example, consider a scenario where a mortgage contract is built atop this standard, which has a maximum detaching duration of 7 days, while the required delegation period is only 3 days. In such cases, without an adequate buffer time, the owner could exploit the system by withdrawing funds and invoking the relevant context to lock the token, thus preventing its unlock and transfer. + +### Validating Callback Callers + +To enhance security and integrity in interactions between contracts, it is essential to validate the caller of any callback function while implementing the `IERC7695ContextCallback`. This validation ensures that the `msg.sender` of the callback is indeed the expected contract address, typically the token contract or a designated controller contract. Such checks are crucial for preventing unauthorized actions that could be executed by malicious entities pretending to be a legitimate contract. + +### Recommended practices + +**Rental** + +This is a typical use case for rentals, supposing A(owner) owns a token and wants to list his/her token for rent, and B(user) wants to rent the token to play in a certain game. + +![Rental Flow](../assets/eip-7695/rental.svg) + +**Mortgage** + +When constructing collateral systems, it is recommended to support token owners who wish to rent out their tokens while using them for collateral lending. This approach enhances the appeal of mortgage systems, creating a more attractive and versatile financial ecosystem that meets many different needs. + +This is a typical use case for mortgages, supposing A(owner) owns a token and wants to mortgage, and B(lender) wants to earn interest by lending their funds to A. + +![Mortgage Flow](../assets/eip-7695/mortgage.svg) + +### Risk of Token Owner + +**Phishing attacks** + +It is crucial to note that the owner role has the ability to delegate ownership to another account, allowing it to authorize transfers out of the respective wallet. Consequently, some malicious actors could deceive the token owner into delegating them as a delegatee by invoking the `startDelegateOwnership` method. This risk can be considered the same as the `approve` or `setApprovalForAll` methods. + +**Ownership rights loss** + +When interacting with a contract system (e.g. mortgage), where owners have to delegate their ownership rights to the smart contract, it's imperative to: + +- Ensure the timeframe for delegation is reasonable and not excessively distant. If the contract mandates a delegation period that extends too far into the future, make sure it includes a provision to revoke ownership delegation when specific conditions are met. Failing to include such a provision could lead to the loss of ownership rights until the delegation expires. +- Be aware that if the contract owner or their operator is compromised, the token ownership can be altered. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/assets/erc-7695/mortgage.svg b/assets/erc-7695/mortgage.svg new file mode 100644 index 0000000000..609d66f790 --- /dev/null +++ b/assets/erc-7695/mortgage.svg @@ -0,0 +1,3 @@ + + +NFTMortgageNFTMortgagealt[NFT is locked]alt[A kept agreement][A breached agreement]B(Lender)A(Owner)start delegate ownership for [Mortgage]1use NFT to request a loan2fill a loan3accept ownership delegation4pay debt & finish agreement5stop ownership delegation6refund7revoke agreement8request unlock (and detach)9wait delay time10claim NFT11invoke transfer12transfer NFT to B13B(Lender)A(Owner) \ No newline at end of file diff --git a/assets/erc-7695/rental.svg b/assets/erc-7695/rental.svg new file mode 100644 index 0000000000..92ef30dead --- /dev/null +++ b/assets/erc-7695/rental.svg @@ -0,0 +1,3 @@ + + +NFTRentalNFTRentalRental is the context controllerloop[each period]alt[A & B kept agreement][B breached agreement][A breached agreement]B(User)A(Owner)list NFT for rent1fill funds and rent2lock NFT3set B as [User]4request claim funds5transfer funds in the period6finish agreement7revoke agreement8revoke agreement9unlock NFT10set A as [User]11transfer min funds12refund13B(User)A(Owner) \ No newline at end of file