diff --git a/.github/workflows/jekyll-label-bot.yml b/.github/workflows/jekyll-label-bot.yml index 549c28c9b4..73307415ca 100644 --- a/.github/workflows/jekyll-label-bot.yml +++ b/.github/workflows/jekyll-label-bot.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: Pandapip1/jekyll-label-action@d0fd82c3cd118140a50843906845fca8e59a8b9e + - uses: Pandapip1/jekyll-label-action@4b7cce7588a8686f5146a8e12aab7269042057ce with: token: ${{ secrets.GITHUB_TOKEN }} config-path: config/.jekyll-labels.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index a96921d17a..f298015cc7 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -20,16 +20,16 @@ jobs: repo-token: ${{ secrets.GITHUB_TOKEN }} ascending: true # Since we have so many issues, the stale bot finds it hard to keep track. This makes sure that at least the oldest are removed. # Issue config - stale-issue-message: There has been no activity on this issue for 1 week. It will be closed after 3 months of inactivity. + stale-issue-message: There has been no activity on this issue for six months. It will be closed in 7 days if there is no new activity. close-issue-message: This issue was closed due to inactivity. If you are still pursuing it, feel free to reopen it and respond to any feedback. - days-before-issue-stale: 7 - days-before-issue-close: 49 # 49 + 7 weeks = 3 months + days-before-issue-stale: 183 + days-before-issue-close: 190 exempt-issue-labels: discussions-to stale-issue-label: w-stale # PR config - stale-pr-message: There has been no activity on this pull request for 2 weeks. It will be closed after 3 months of inactivity. If you would like to move this PR forward, please respond to any outstanding feedback or add a comment indicating that you have addressed all required feedback and are ready for a review. + stale-pr-message: There has been no activity on this issue for six months. It will be closed in 7 days if there is no new activity. If you would like to move this PR forward, please respond to any outstanding feedback or add a comment indicating that you have addressed all required feedback and are ready for a review. close-pr-message: This pull request was closed due to inactivity. If you are still pursuing it, feel free to reopen it and respond to any feedback or request a review in a comment. - days-before-pr-stale: 14 - days-before-pr-close: 42 # 42 + 14 weeks = 3 months + days-before-pr-stale: 183 + days-before-pr-close: 190 exempt-pr-milestones: "Manual Merge Queue" stale-pr-label: w-stale diff --git a/ERCS/eip-1.md b/ERCS/eip-1.md index 4e683737db..08df84e8ed 100644 --- a/ERCS/eip-1.md +++ b/ERCS/eip-1.md @@ -91,7 +91,7 @@ A PR moving an EIP from Last Call to Final SHOULD contain no changes other than >*EIP Authors are notified of any algorithmic change to the status of their EIP* -**Withdrawn** - The EIP Author(s) have withdrawn the proposed EIP. This state has finality and can no longer be resurrected using this EIP number. If the idea is pursued at later date it is considered a new proposal. +**Withdrawn** - The EIP Author(s) have withdrawn the proposed EIP. This state has finality and can no longer be resurrected using this EIP number. If the idea is pursued at a later date it is considered a new proposal. **Living** - A special status for EIPs that are designed to be continually updated and not reach a state of finality. This includes most notably EIP-1. diff --git a/ERCS/erc-1046.md b/ERCS/erc-1046.md index 82907d56cb..8151c125cb 100644 --- a/ERCS/erc-1046.md +++ b/ERCS/erc-1046.md @@ -58,13 +58,13 @@ interface InteroperabilityMetadata { /** * This MUST be true if this is ERC-721 Token Metadata, otherwise, this MUST be omitted. - * Setting this to true indicates to wallets that the address should be treated as a ERC-721 token. + * Setting this to true indicates to wallets that the address should be treated as an ERC-721 token. **/ erc721?: boolean | undefined; /** * This MUST be true if this is ERC-1155 Token Metadata, otherwise, this MUST be omitted. - * Setting this to true indicates to wallets that the address should be treated as a ERC-1155 token. + * Setting this to true indicates to wallets that the address should be treated as an ERC-1155 token. **/ erc1155?: boolean | undefined; } @@ -97,7 +97,7 @@ The resolved JSON of the `tokenURI` described in the ERC-20 Interface Extension */ interface ERC20TokenMetadata { /** - * Interoperabiliy, to differentiate between different types of tokens and their corresponding URIs. + * Interoperability, to differentiate between different types of tokens and their corresponding URIs. **/ interop: InteroperabilityMetadata; @@ -157,7 +157,7 @@ Contracts that implement ERC-721 and use its token metadata URI SHOULD to use th ```typescript interface ERC721TokenMetadataInterop extends ERC721TokenMetadata { /** - * Interoperabiliy, to avoid confusion between different token URIs + * Interoperability, to avoid confusion between different token URIs **/ interop: InteroperabilityMetadata; } @@ -189,7 +189,7 @@ Contracts that implement ERC-1155 and use its token metadata URI are RECOMMENDED ```typescript interface ERC1155TokenMetadataInterop extends ERC1155TokenMetadata { /** - * Interoperabiliy, to avoid confusion between different token URIs + * Interoperability, to avoid confusion between different token URIs **/ interop: InteroperabilityMetadata; } diff --git a/ERCS/erc-1056.md b/ERCS/erc-1056.md index cb468d8eb1..31c33e298e 100644 --- a/ERCS/erc-1056.md +++ b/ERCS/erc-1056.md @@ -222,7 +222,7 @@ Contract Events are a useful feature for storing data from smart contracts exclu 2. Lookup all events for given identity address using web3, but only for the `previousChange` block -3. Do something with event +3. Do something with the event 4. Find `previousChange` from the event and repeat diff --git a/ERCS/erc-1062.md b/ERCS/erc-1062.md index 0f304b6cb2..2e555d1cdc 100644 --- a/ERCS/erc-1062.md +++ b/ERCS/erc-1062.md @@ -18,9 +18,9 @@ The following standard details the implementation of how to combine the IPFS cry We think that this implementation is not only aim to let more developers and communities to provide more use cases, but also leverage the human-readable features to gain more user adoption accessing decentralized resources. We considered the IPFS ENS resolver mapping standard a cornerstone for building future Web3.0 service. ## Motivation -To build fully decentralized web service, it’s necessary to have a decentralized file storage system. Here comes the IPFS, for three following advantages : +To build a fully decentralized web service, it’s necessary to have a decentralized file storage system. Here comes the IPFS, for three following advantages : - Address large amounts of data, and has unique cryptographic hash for every record. -- Since IPFS is also based on peer to peer network, it can be really helpful to deliver large amounts of data to users, with safer way and lower the millions of cost for the bandwidth. +- Since IPFS is also based on peer to peer network, it can be really helpful to deliver large amounts of data to users, in a safer way and lower the millions of cost for the bandwidth. - IPFS stores files in high efficient way via tracking version history for every file, and removing the duplications across the network. Those features makes perfect match for integrating into ENS, and these make users can easily access content through ENS, and show up in the normal browser. diff --git a/ERCS/erc-1066.md b/ERCS/erc-1066.md index 5b29e2dd32..7f60fe566e 100644 --- a/ERCS/erc-1066.md +++ b/ERCS/erc-1066.md @@ -480,7 +480,7 @@ AwesomeCoin DEX TraderBot Status codes are encoded as a `byte`. Hex values break nicely into high and low nibbles: `category` and `reason`. For instance, `0x01` stands for general success (ie: `true`) and `0x00` for general failure (ie: `false`). -As a general approach, all even numbers are blocking conditions (where the receiver does not have control), and odd numbers are nonblocking (the receiver is free to contrinue as they wish). This aligns both a simple bit check with the common encoding of Booleans. +As a general approach, all even numbers are blocking conditions (where the receiver does not have control), and odd numbers are nonblocking (the receiver is free to continue as they wish). This aligns both a simple bit check with the common encoding of Booleans. `bytes1` is very lightweight, portable, easily interoperable with `uint8`, cast from `enum`s, and so on. diff --git a/ERCS/erc-1077.md b/ERCS/erc-1077.md index 5ada67aea4..4ae82ddc2c 100644 --- a/ERCS/erc-1077.md +++ b/ERCS/erc-1077.md @@ -19,7 +19,7 @@ Allows users to offer [EIP-20] token for paying the gas used in a call. ## Abstract -A main barrier for adoption of DApps is the requirement of multiple tokens for executing in chain actions. Allowing users to sign messages to show intent of execution, but allowing a third party relayer to execute them can circumvent this problem, while ETH will always be required for ethereum transactions, it's possible for smart contract to take [EIP-191] signatures and forward a payment incentive to an untrusted party with ETH for executing the transaction. +A main barrier for the adoption of DApps is the requirement of multiple tokens for executing in chain actions. Allowing users to sign messages to show intent of execution, but allowing a third party relayer to execute them can circumvent this problem, while ETH will always be required for ethereum transactions, it's possible for smart contract to take [EIP-191] signatures and forward a payment incentive to an untrusted party with ETH for executing the transaction. ## Motivation @@ -83,7 +83,7 @@ In order to be compliant, the transaction **MUST** request to sign a "messageHas The fields **MUST** be constructed as this method: -The first and second fields are to make it [EIP-191] compliant. Starting a transaction with `byte(0x19)` ensure the signed data from being a [valid ethereum transaction](https://github.com/ethereum/wiki/wiki/RLP). The second argument is a version control byte. The third being the validator address (the account contract address) according to version 0 of [EIP-191]. The remaining arguments being the application specific data for the gas relay: chainID as per [EIP-1344], execution nonce, execution data, agreed gas Price, gas limit of gas relayed call, gas token to pay back and gas relayer authorized to receive reward. +The first and second fields are to make it [EIP-191] compliant. Starting a transaction with `byte(0x19)` ensure the signed data from being a [valid ethereum transaction](https://github.com/ethereum/wiki/wiki/RLP). The second argument is a version control byte. The third being the validator address (the account contract address) according to version 0 of [EIP-191]. The remaining arguments being the application specific data for the gas relay: chainID as per [EIP-1344], execution nonce, execution data, agreed gas Price, gas limit of gas relayed call, gas token to pay back and gas relayer authorized to receive the reward. The [EIP-191] message must be constructed as following: ```solidity diff --git a/ERCS/erc-1078.md b/ERCS/erc-1078.md index 7990475fbc..ca2e2792c1 100644 --- a/ERCS/erc-1078.md +++ b/ERCS/erc-1078.md @@ -12,12 +12,12 @@ requires: 191, 681, 725, 1077 ## Abstract -This presents a method to replace the usual signup/login design pattern with a minimal ethereum native scheme, that doesn’t require passwords, backing up private keys nor typing seed phrases. From the user point of view it will be very similar to patterns they’re already used to with second factor authentication (without relying in a central server), but for dapp developers it requires a new way to think about ethereum transactions. +This presents a method to replace the usual signup/login design pattern with a minimal ethereum native scheme, that doesn’t require passwords, backing up private keys nor typing seed phrases. From the user's point of view it will be very similar to patterns they’re already used to with second factor authentication (without relying in a central server), but for dapp developers it requires a new way to think about ethereum transactions. ## Simple Summary -The unique identifier of the user is a contract which implements both Identity and the Executable Signed Messages ERCs. The user should not need provide this address directly, only a ens name pointing to it. These types of contracts are indirectly controlled by private keys that can sign messages indicating intents, which are then deployed to the contract by a third party (or a decentralized network of deployers). +The unique identifier of the user is a contract that implements both Identity and the Executable Signed Messages ERCs. The user should not need provide this address directly, only a ens name pointing to it. These types of contracts are indirectly controlled by private keys that can sign messages indicating intents, which are then deployed to the contract by a third party (or a decentralized network of deployers). In this context, therefore, a device "logging into" an app using an identity, means that the device will generate a private key locally and then request an authorization to add that key as one of the signers of that identity, with a given set of permissions. Since that private key is only used for signing messages, it is not required to hold ether, tokens or assets, and if lost, it can be simply be replaced by a new one – the user's funds are kept on the identity contract. @@ -43,7 +43,7 @@ If the user doesn’t have an identity, the app should provide the option to cre All those steps can be designed to be set up in a single ethereum transaction. Since this step is not free, the app reserves the right to charge for registering users, or require the user to be verified in a sybil resistant manner of the app’s choosing (captcha, device ID registration, proof of work, etc) -The user shouldn’t be forced to wait for transaction confirmation times. Instead, have an indicator somewhere on the app the shows the progress and then allow the user to interact with your app normally. It’s unlikely that they’ll need the identity in the first few minutes and if something goes wrong (username gets registered at the same time), you can then ask the user for an action. +The user shouldn’t be forced to wait for transaction confirmation times. Instead, have an indicator somewhere on the app that shows the progress and then allow the user to interact with your app normally. It’s unlikely that they’ll need the identity in the first few minutes and if something goes wrong (username gets registered at the same time), you can then ask the user for an action. **Implementation note:** in order to save gas, some of these steps can be done in advance. The app can automatically deploy a small number of contracts when the gas price is low, and set up all their main variables to be 0xFFFFFF...FFFFF. These should be considered ‘vacant’ and when the user registers one, they will get a gas discount for freeing up space on the chain. This has the added benefit of allowing the user a choice in contract address/icon. diff --git a/ERCS/erc-1129.md b/ERCS/erc-1129.md index ca6acdcd75..d3b1579d1f 100644 --- a/ERCS/erc-1129.md +++ b/ERCS/erc-1129.md @@ -49,9 +49,9 @@ struct Announcement{ ### Methods -#### the number of ammouncements +#### the number of announcements -Returns the number of announcement currently active. +Returns the number of announcements currently active. OPTIONAL - this method can be used to provide quicker information for the UI, but could also be retrieved from `numberOfMessages` variable. diff --git a/ERCS/erc-3770.md b/ERCS/erc-3770.md index 41334680d0..7b3a48ad43 100644 --- a/ERCS/erc-3770.md +++ b/ERCS/erc-3770.md @@ -4,7 +4,7 @@ title: Chain-specific addresses description: Prepending chain-specific addresses with a human-readable chain identifier author: Lukas Schor (@lukasschor), Richard Meissner (@rmeissner), Pedro Gomes (@pedrouid), ligi discussions-to: https://ethereum-magicians.org/t/chain-specific-addresses/6449 -status: Stagnant +status: Draft type: Standards Track category: ERC created: 2021-08-26 @@ -12,8 +12,8 @@ created: 2021-08-26 ## Abstract -[ERC-3770](./eip-3770.md) introduces a new address standard to be adapted by wallets and dApps to display chain-specific addresses by using a human-reacable prefix. - +[ERC-3770](./eip-3770.md) introduces a new address standard to be adapted by wallets and dApps to display chain-specific addresses by using a human-readable prefix. + ## Motivation The need for this proposal emerges from the increasing adoption of non-Ethereum Mainnet chains that use the Ethereum Virtual Machine (EVM). In this context, addresses become ambiguous, as the same address may refer to an EOA on chain X or a smart contract on chain Y. This will eventually lead to Ethereum users losing funds due to human error. For example, users sending funds to a smart contract wallet address which was not deployed on a particular chain. @@ -36,13 +36,8 @@ Chain-specific address = "`shortName`" "`:`" "`address`" ### Semantics -``` - -`shortName` is mandatory and MUST be a valid chain short name from https://github.com/ethereum-lists/chains - -`address` is mandatory and MUST be a [ERC-55](./eip-55.md) compatible hexadecimal address - -``` +* `shortName` is mandatory and MUST be a valid chain short name from https://github.com/ethereum-lists/chains +* `address` is mandatory and MUST be a [ERC-55](./eip-55.md) compatible hexadecimal address ### Examples @@ -58,7 +53,7 @@ Ethereum addresses without the chain specifier will continue to require addition ## Security Considerations -The Ethereum List curators must consider how similar looking chain short names can be used to confuse users. +Similar looking chain short names can be used to confuse users. ## Copyright diff --git a/ERCS/erc-4337.md b/ERCS/erc-4337.md index 53db9e0376..72c0c4ddf2 100644 --- a/ERCS/erc-4337.md +++ b/ERCS/erc-4337.md @@ -76,14 +76,14 @@ To avoid Ethereum consensus changes, we do not attempt to create new transaction | `paymasterData` | `bytes` | Data for paymaster (only if paymaster exists) | | `signature` | `bytes` | Data passed into the account to verify authorization | -Users send `UserOperation` objects to a dedicated user operation mempool. The are not concerned with the packed version. +Users send `UserOperation` objects to a dedicated user operation mempool. They are not concerned with the packed version. A specialized class of actors called **bundlers** (either block builders running special-purpose code, or users that can relay transactions to block builders eg. through a bundle marketplace such as Flashbots that can guarantee next-block-or-never inclusion) listen in on the user operation mempool, and create **bundle transactions**. A bundle transaction packages up multiple `UserOperation` objects into a single `handleOps` call to a pre-published global **entry point contract**. To prevent replay attacks (both cross-chain and multiple `EntryPoint` implementations), the `signature` should depend on `chainid` and the `EntryPoint` address. ### EntryPoint definition -When passed to on-chain contacts (the EntryPoint contract, and then to account and paymaster), a packed version of the above structure is used: +When passed to on-chain contacts (the EntryPoint contract, and then to the account and paymaster), a packed version of the above structure is used: | Field | Type | Description | |----------------------|-----------|------------------------------------------------------------------------| @@ -132,12 +132,12 @@ The `userOpHash` is a hash over the userOp (except signature), entryPoint and ch The account: * MUST validate the caller is a trusted EntryPoint -* If the account does not support signature aggregation, it MUST validate the signature is a valid signature of the `userOpHash`, and +* If the account does not support signature aggregation, it MUST validate that the signature is a valid signature of the `userOpHash`, and SHOULD return SIG_VALIDATION_FAILED (and not revert) on signature mismatch. Any other error MUST revert. -* MUST pay the entryPoint (caller) at least the "missingAccountFunds" (which might be zero, in case current account's deposit is high enough) +* MUST pay the entryPoint (caller) at least the "missingAccountFunds" (which might be zero, in case the current account's deposit is high enough) * The account MAY pay more than this minimum, to cover future transactions (it can always issue `withdrawTo` to retrieve it) * The return value MUST be packed of `authorizer`, `validUntil` and `validAfter` timestamps. - * authorizer - 0 for valid signature, 1 to mark signature failure. Otherwise, an address of an authorizer contract. This ERC defines "signature aggregator" as authorizer. + * authorizer - 0 for valid signature, 1 to mark signature failure. Otherwise, an address of an authorizer contract. This ERC defines a "signature aggregator" as an authorizer. * `validUntil` is 6-byte timestamp value, or zero for "infinite". The UserOp is valid only up to this time. * `validAfter` is 6-byte timestamp. The UserOp is valid only after this time. @@ -208,7 +208,7 @@ this bundler is supposed to track the `key` and `sequence` pair of the UserOpera In some cases, an account may need to have an "administrative" channel of operations running in parallel to normal operations. - In this case, the account may use specific `key` when calling methods on the account itself: + In this case, the account may use a specific `key` when calling methods on the account itself: ```solidity bytes4 sig = bytes4(userOp.callData[0 : 4]); @@ -224,7 +224,7 @@ this bundler is supposed to track the `key` and `sequence` pair of the UserOpera There are 2 separate entry point methods: `handleOps` and `handleAggregatedOps` -* `handleOps` handle userOps of accounts that don't require any signature aggregator. +* `handleOps` handles userOps of accounts that don't require any signature aggregator. * `handleAggregatedOps` can handle a batch that contains userOps of multiple aggregators (and also requests without any aggregator) * `handleAggregatedOps` performs the same logic below as `handleOps`, but it must transfer the correct aggregator to each userOp, and also must call `validateSignatures` on each aggregator before doing all the per-account validation. The entry point's `handleOps` function must perform the following steps (we first describe the simpler non-paymaster case). It must make two loops, the **verification loop** and the **execution loop**. In the verification loop, the `handleOps` call must perform the following steps for each `UserOperation`: @@ -240,7 +240,7 @@ In the execution loop, the `handleOps` call must perform the following steps for * **Call the account with the `UserOperation`'s calldata**. It's up to the account to choose how to parse the calldata; an expected workflow is for the account to have an `execute` function that parses the remaining calldata as a series of one or more calls that the account should make. * If the calldata starts with the methodsig `IAccountExecute.executeUserOp`, then the EntryPoint must build a calldata by encoding `executeUserOp(userOp,userOpHash)` and call the account using that calldata. * After the call, refund the account's deposit with the excess gas cost that was pre-charged.\ - A penalty of `10%` (`UNUSED_GAS_PENALTY_PERCENT`) is applied on the amount of gas that is refunded.\ + A penalty of `10%` (`UNUSED_GAS_PENALTY_PERCENT`) is applied on the amounts of `callGasLimit` and `paymasterPostOpGasLimit` gas that remains **unused**.\ This penalty is necessary to prevent the UserOps from reserving large parts of the gas space in the bundle but leaving it unused and preventing the bundler from including other UserOperations. * After the execution of all calls, pay the collected fees from all UserOperations to the bundler's provided address @@ -279,7 +279,7 @@ enum PostOpMode { } ``` -The EntryPoint must implement the following API to let entities like paymasters to have a stake, and thus have more flexibility in their storage access (see [reputation, throttling and banning section](#reputation-scoring-and-throttlingbanning-for-global-entities) for details.) +The EntryPoint must implement the following API to let entities like paymasters have a stake, and thus have more flexibility in their storage access (see [reputation, throttling and banning section](#reputation-scoring-and-throttlingbanning-for-global-entities) for details.) ```solidity // add a stake to the calling entity @@ -295,7 +295,7 @@ function withdrawStake(address payable withdrawAddress) external The paymaster must also have a deposit, which the entry point will charge UserOperation costs from. The deposit (for paying gas fees) is separate from the stake (which is locked). -The EntryPoint must implement the following interface to allow paymasters (and optionally accounts) manage their deposit: +The EntryPoint must implement the following interface to allow paymasters (and optionally accounts) to manage their deposit: ```c++ // return the deposit of an account @@ -313,9 +313,9 @@ function withdrawTo(address payable withdrawAddress, uint256 withdrawAmount) ext When a client receives a `UserOperation`, it must first run some basic sanity checks, namely that: * Either the `sender` is an existing contract, or the `initCode` is not empty (but not both) -* If `initCode` is not empty, parse its first 20 bytes as a factory address. Record whether the factory is staked, in case the later simulation indicates that it needs to be. If the factory accesses global state, it must be staked - see [reputation, throttling and banning section](#reputation-scoring-and-throttlingbanning-for-global-entities) for details. +* If `initCode` is not empty, parse its first 20 bytes as a factory address. Record whether the factory is staked, in case the later simulation indicates that it needs to be. If the factory accesses the global state, it must be staked - see [reputation, throttling and banning section](#reputation-scoring-and-throttlingbanning-for-global-entities) for details. * The `verificationGasLimit` is sufficiently low (`<= MAX_VERIFICATION_GAS`) and the `preVerificationGas` is sufficiently high (enough to pay for the calldata gas cost of serializing the `UserOperation` plus `PRE_VERIFICATION_OVERHEAD_GAS`) -* The `paymasterAndData` is either empty, or start with the **paymaster** address, which is a contract that (i) currently has nonempty code on chain, (ii) has a sufficient deposit to pay for the UserOperation, and (iii) is not currently banned. During simulation, the paymaster's stake is also checked, depending on its storage usage - see [reputation, throttling and banning section](#reputation-scoring-and-throttlingbanning-for-global-entities) for details. +* The `paymasterAndData` is either empty, or starts with the **paymaster** address, which is a contract that (i) currently has nonempty code on chain, (ii) has a sufficient deposit to pay for the UserOperation, and (iii) is not currently banned. During simulation, the paymaster's stake is also checked, depending on its storage usage - see [reputation, throttling and banning section](#reputation-scoring-and-throttlingbanning-for-global-entities) for details. * The callgas is at least the cost of a `CALL` with non-zero value. * The `maxFeePerGas` and `maxPriorityFeePerGas` are above a configurable minimum value that the client is willing to accept. At the minimum, they are sufficiently high to be included with the current `block.basefee`. * The sender doesn't have another `UserOperation` already present in the pool (or it replaces an existing entry with the same sender and nonce, with a higher `maxPriorityFeePerGas` and an equally increased `maxFeePerGas`). Only one `UserOperation` per sender may be included in a single batch. A sender is exempt from this rule and may have multiple `UserOperations` in the pool and in a batch if it is staked (see [reputation, throttling and banning section](#reputation-scoring-and-throttlingbanning-for-global-entities) below), but this exception is of limited use to normal accounts. @@ -340,12 +340,12 @@ interface IAggregator { * An account signifies it uses signature aggregation returning its address from `validateUserOp`. * During `simulateValidation`, this aggregator is returned to the bundler as part of the `aggregatorInfo` struct. -* The bundler should first accept the aggregator (aggregators must be staked. bundler should verify it is not throttle/banned) +* The bundler should first accept the aggregator (aggregators must be staked. bundler should verify it is not throttled/banned) * To accept the UserOp, the bundler must call **validateUserOpSignature()** to validate the userOp's signature. This method returned an alternate signature (usually empty) that should be used during bundling. * The bundler MUST call `validateUserOp` a second time on the account with the UserOperation using that returned signature, and make sure it returns the same value. * **aggregateSignatures()** must aggregate all UserOp signatures into a single value. -* Note that the above methods are helper method for the bundler. The bundler MAY use a native library to perform the same validation and aggregation logic. +* Note that the above methods are helper methods for the bundler. The bundler MAY use a native library to perform the same validation and aggregation logic. * **validateSignatures()** MUST validate the aggregated signature matches for all UserOperations in the array, and revert otherwise. This method is called on-chain by `handleOps()` @@ -353,11 +353,11 @@ interface IAggregator { #### Simulation Rationale -In order to add a UserOperation into the mempool (and later to add it into a bundle) we need to "simulate" its validation to make sure it is valid, and that it is capable of paying for its own execution. +To add a UserOperation into the mempool (and later to add it into a bundle) we need to "simulate" its validation to make sure it is valid, and that it pays for its own execution. In addition, we need to verify that the same will hold true when executed on-chain. For this purpose, a UserOperation is not allowed to access any information that might change between simulation and execution, such as current block time, number, hash etc. -In addition, a UserOperation is only allowed to access data related to this sender address: Multiple UserOperations should not access the same storage, so that it is impossible to invalidate a large number of UserOperations with a single state change. -There are 3 special contracts that interact with the account: the factory (initCode) that deploys the contract, the paymaster that can pay for the gas, and signature aggregator (described later) +In addition, a UserOperation is only allowed to access data related to this sender address: Multiple UserOperations should not access the same storage, so it is impossible to invalidate a large number of UserOperations with a single state change. +There are 3 special contracts that interact with the account: the factory (initCode) that deploys the contract, the paymaster that can pay for the gas, and a signature aggregator (described later) Each of these contracts is also restricted in its storage access, to make sure UserOperation validations are isolated. #### Simulation Specification: @@ -419,15 +419,15 @@ Either return value may contain a "validAfter" and "validUntil" timestamps, whic A node MAY drop a UserOperation if it expires too soon (e.g. wouldn't make it to the next block) by either the account or paymaster. If the `ValidationResult` includes `sigFail`, the client SHOULD drop the `UserOperation`. -In order to prevent DoS attack on bundlers, they must make sure the validation methods above pass the validation rules, which constraint their usage of opcodes and storage. +To prevent DoS attacks on bundlers, they must make sure the validation methods above pass the validation rules, which constrain their usage of opcodes and storage. For the complete procedure see [ERC-7562](./eip-7562.md) ### Alternative Mempools The simulation rules above are strict and prevent the ability of paymasters and signature aggregators to grief the system. -However, there might be use-cases where specific paymasters (and signature aggregators) can be validated +However, there might be use cases where specific paymasters (and signature aggregators) can be validated (through manual auditing) and verified that they cannot cause any problem, while still require relaxing of the opcode rules. -A bundler cannot simply "whitelist" request from a specific paymaster: if that paymaster is not accepted by all +A bundler cannot simply "whitelist" a request from a specific paymaster: if that paymaster is not accepted by all bundlers, then its support will be sporadic at best. Instead, we introduce the term "alternate mempool": a modified validation rules, and procedure of propagating them to other bundlers. @@ -435,7 +435,7 @@ The procedure of using alternate mempools is defined in [ERC-7562](./eip-7562.md ### Bundling -Bundling is the process where a node/bundler collects multiple UserOperations and create a single transaction to submit on-chain. +Bundling is the process where a node/bundler collects multiple UserOperations and creates a single transaction to submit on-chain. During bundling, the bundler should: @@ -475,42 +475,42 @@ When a bundler includes a bundle in a block it must ensure that earlier transact ### Error codes. While performing validation, the EntryPoint must revert on failures. During simulation, the calling bundler MUST be able to determine which entity (factory, account or paymaster) caused the failure. -The attribution of revert to entity is done using the call-tracing: the last entity called by the EntryPoint prior the revert is the entity that caused the revert. +The attribution of a revert to an entity is done using call-tracing: the last entity called by the EntryPoint prior to the revert is the entity that caused the revert. * For diagnostic purposes, the EntryPoint must only revert with explicit FailedOp() or FailedOpWithRevert() errors. * The message of the error starts with event code, AA## -* Event code starting with "AA1" signify an error during account creation -* Event code starting with "AA2" signify an error during account validation (validateUserOp) -* Event code starting with "AA3" signify an error during paymaster validation (validatePaymasterUserOp) +* Event code starting with "AA1" signifies an error during account creation +* Event code starting with "AA2" signifies an error during account validation (validateUserOp) +* Event code starting with "AA3" signifies an error during paymaster validation (validatePaymasterUserOp) ## Rationale -The main challenge with a purely smart contract wallet based account abstraction system is DoS safety: how can a block builder including an operation make sure that it will actually pay fees, without having to first execute the entire operation? +The main challenge with a purely smart contract wallet-based account abstraction system is DoS safety: how can a block builder including an operation make sure that it will actually pay fees, without having to first execute the entire operation? Requiring the block builder to execute the entire operation opens a DoS attack vector, as an attacker could easily send many operations that pretend to pay a fee but then revert at the last moment after a long execution. Similarly, to prevent attackers from cheaply clogging the mempool, nodes in the P2P network need to check if an operation will pay a fee before they are willing to forward it. -The first step is clean separation between validation (acceptance of UserOperation, and acceptance to pay) and execution. -In this proposal, we expect accounts to have a `validateUserOp` method that takes as input a `UserOperation`, and verify the signature and pay the fee. +The first step is a clean separation between validation (acceptance of UserOperation, and acceptance to pay) and execution. +In this proposal, we expect accounts to have a `validateUserOp` method that takes as input a `UserOperation`, verifies the signature and pays the fee. Only if this method returns successfully, the execution will happen. The entry point-based approach allows for a clean separation between verification and execution, and keeps accounts' logic simple. It enforces the simple rule that only after validation is successful (and the UserOp can pay), the execution is done, and also guarantees the fee payment. ### Validation Rules Rationale -The next step is protecting the bundlers from denial-of-service attacks by a mass number of UserOperation that appear to be valid (and pay) but that eventually revert, and thus block the bundler from processing valid UserOperations. +The next step is protecting the bundlers from denial-of-service attacks by a mass number of UserOperations that appear to be valid (and pay) but that eventually revert, and thus block the bundler from processing valid UserOperations. There are two types of UserOperations that can fail validation: -1. UserOperations that succeed in initial validation (and accepted into the mempool), but relay on environment state to fail later when attempting to include them in a block. +1. UserOperations that succeed in initial validation (and accepted into the mempool), but rely on the environment state to fail later when attempting to include them in a block. 2. UserOperations that are valid when checked independently, by fail when bundled together to be put on-chain. To prevent such rogue UserOperations, the bundler is required to follow a set of [restrictions on the validation function](./eip-7562.md), to prevent such denial-of-service attacks. ### Reputation Rationale. -UserOperation's storage access rules prevent them from interfere with each other. -But "global" entities - paymasters, factories and aggregators are accessed by multiple UserOperations, and thus might invalidate multiple previously-valid UserOperations. +UserOperation's storage access rules prevent them from interfering with each other. +But "global" entities - paymasters, factories and aggregators are accessed by multiple UserOperations, and thus might invalidate multiple previously valid UserOperations. -To prevent abuse, we throttle down (or completely ban for a period of time) an entity that causes invalidation of large number of UserOperations in the mempool. -To prevent such entities from "sybil-attack", we require them to stake with the system, and thus make such DoS attack very expensive. -Note that this stake is never slashed, and can be withdrawn any time (after unstake delay) +To prevent abuse, we throttle down (or completely ban for a period of time) an entity that causes invalidation of a large number of UserOperations in the mempool. +To prevent such entities from "Sybil-attack", we require them to stake with the system, and thus make such DoS attack very expensive. +Note that this stake is never slashed, and can be withdrawn at any time (after unstake delay) Unstaked entities are allowed, under the rules below. @@ -521,11 +521,11 @@ The stake value is not enforced on-chain, but specifically by each node while si ### Reputation scoring and throttling/banning for global entities [ERC-7562] defines a set of rules a bundler must follow when accepting UserOperations into the mempool. -It also descrbies the "reputation|" +It also descrbies the "reputation" ### Paymasters -Paymaster contracts allow abstraction of gas: having a contract, that is not the sender of the transaction, pay for the transaction fees. +Paymaster contracts allow the abstraction of gas: having a contract, that is not the sender of the transaction, to pay for the transaction fees. Paymaster architecture allows them to follow the model of "pre-charge, and later refund". E.g. a token-paymaster may pre-charge the user with the max possible price of the transaction, and refund the user with the excess afterwards. @@ -538,7 +538,7 @@ The wallet creation itself is done by a "factory" contract, with wallet-specific The factory is expected to use CREATE2 (not CREATE) to create the wallet, so that the order of creation of wallets doesn't interfere with the generated addresses. The `initCode` field (if non-zero length) is parsed as a 20-byte address, followed by "calldata" to pass to this address. This method call is expected to create a wallet and return its address. -If the factory does use CREATE2 or some other deterministic method to create the wallet, it's expected to return the wallet address even if the wallet has already been created. This is to make it easier for clients to query the address without knowing if the wallet has already been deployed, by simulating a call to `entryPoint.getSenderAddress()`, which calls the factory under the hood. +If the factory does use CREATE2 or some other deterministic method to create the wallet, it's expected to return the wallet address even if the wallet has already been created. This comes to make it easier for clients to query the address without knowing if the wallet has already been deployed, by simulating a call to `entryPoint.getSenderAddress()`, which calls the factory under the hood. When `initCode` is specified, if either the `sender` address points to an existing contract, or (after calling the initCode) the `sender` address still does not exist, then the operation is aborted. The `initCode` MUST NOT be called directly from the entryPoint, but from another address. @@ -547,7 +547,7 @@ For security reasons, it is important that the generated contract address will d This way, even if someone can create a wallet at that address, he can't set different credentials to control it. The factory has to be staked if it accesses global storage - see [reputation, throttling and banning section](#reputation-scoring-and-throttlingbanning-for-global-entities) for details. -NOTE: In order for the wallet to determine the "counterfactual" address of the wallet (prior its creation), +NOTE: In order for the wallet to determine the "counterfactual" address of the wallet (prior to its creation), it should make a static call to the `entryPoint.getSenderAddress()` ### Entry point upgrading @@ -580,7 +580,7 @@ The result `SHOULD` be set to the **userOpHash** if and only if the request pass * The `message` field SHOULD be set to the revert message from the paymaster * The `data` field MUST contain a `paymaster` value * **code: -32502** - transaction rejected because of opcode validation - * **code: -32503** - UserOperation out of time-range: either wallet or paymaster returned a time-range, and it is already expired (or will expire soon) + * **code: -32503** - UserOperation out of time-range: either wallet or paymaster returned a time-range, and it has already expired (or will expire soon) * The `data` field SHOULD contain the `validUntil` and `validAfter` values * The `data` field SHOULD contain a `paymaster` value, if this error was triggered by the paymaster * **code: -32504** - transaction rejected because paymaster (or signature aggregator) is throttled/banned @@ -591,6 +591,7 @@ The result `SHOULD` be set to the **userOpHash** if and only if the request pass * **code: -32506** - transaction rejected because wallet specified unsupported signature aggregator * The `data` field SHOULD contain an `aggregator` value * **code: -32507** - transaction rejected because of wallet signature check failed (or paymaster signature, if the paymaster uses its data as signature) + * **code: -32508** - transaction rejected because paymaster balance can't cover all pending UserOperations. ##### Example: @@ -669,7 +670,7 @@ Response: Estimate the gas values for a UserOperation. Given UserOperation optionally without gas limits and gas prices, return the needed gas limits. -The signature field is ignored by the wallet, so that the operation will not require user's approval. +The signature field is ignored by the wallet, so that the operation will not require the user's approval. Still, it might require putting a "semi-valid" signature (e.g. a signature in the right length) **Parameters**: @@ -733,9 +734,9 @@ Return a UserOperation receipt based on a hash (userOpHash) returned by `eth_sen * **sender** * **nonce** * **paymaster** the paymaster used for this userOp (or empty) -* **actualGasCost** - actual amount paid (by account or paymaster) for this UserOperation +* **actualGasCost** - the actual amount paid (by account or paymaster) for this UserOperation * **actualGasUsed** - total gas used by this UserOperation (including preVerification, creation, validation and execution) -* **success** boolean - did this execution completed without revert +* **success** boolean - did this execution completed without a revert * **reason** in case of revert, this is the revert reason * **logs** the logs generated by this UserOperation (not including logs of other UserOperations in the same bundle) * **receipt** the TransactionReceipt object. @@ -788,7 +789,7 @@ Returns [EIP-155](./eip-155.md) Chain ID. ### RPC methods (debug Namespace) -This api must only be available on testing mode and is required by the compatibility test suite. In production, any `debug_*` rpc calls should be blocked. +This api must only be available in testing mode and is required by the compatibility test suite. In production, any `debug_*` rpc calls should be blocked. #### * debug_bundler_clearState @@ -906,7 +907,7 @@ After setting mode to "manual", an explicit call to debug_bundler_sendBundleNow #### * debug_bundler_setReputation -Sets reputation of given addresses. parameters: +Sets the reputation of given addresses. parameters: **Parameters:** @@ -914,7 +915,7 @@ Sets reputation of given addresses. parameters: * `address` - The address to set the reputation for. * `opsSeen` - number of times a user operations with that entity was seen and added to the mempool - * `opsIncluded` - number of times a user operations that uses this entity was included on-chain + * `opsIncluded` - number of times user operations that use this entity was included on-chain * **EntryPoint** the entrypoint used by eth_sendUserOperation @@ -961,7 +962,7 @@ An array of reputation entries with the fields: * `address` - The address to set the reputation for. * `opsSeen` - number of times a user operations with that entity was seen and added to the mempool -* `opsIncluded` - number of times a user operations that uses this entity was included on-chain +* `opsIncluded` - number of times user operation that use this entity was included on-chain * `status` - (string) The status of the address in the bundler 'ok' | 'throttled' | 'banned'. ```json= @@ -990,7 +991,7 @@ An array of reputation entries with the fields: #### * debug_bundler_addUserOps Accept UserOperations into the mempool. -Assume the given UserOperations all pass validation (without actually validating them), and accept them directly into th mempool +Assume the given UserOperations all pass validation (without actually validating them), and accept them directly into the mempool **Parameters:** diff --git a/ERCS/erc-4824.md b/ERCS/erc-4824.md index c9dd0459c4..d2021546fc 100644 --- a/ERCS/erc-4824.md +++ b/ERCS/erc-4824.md @@ -4,7 +4,7 @@ title: Common Interfaces for DAOs description: An API for decentralized autonomous organizations (DAOs). author: Joshua Tan (@thelastjosh), Isaac Patka (@ipatka), Ido Gershtein , Eyal Eithcowich , Michael Zargham (@mzargham), Sam Furter (@nivida) discussions-to: https://ethereum-magicians.org/t/eip-4824-decentralized-autonomous-organizations/8362 -status: Draft +status: Review type: Standards Track category: ERC created: 2022-02-17 @@ -22,17 +22,17 @@ DAOs, since being invoked in the Ethereum whitepaper, have been vaguely defined. The key words “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. -Every contract implementing this EIP MUST implement the [ERC-4824](./eip-4824) interface below: +Every contract implementing this EIP MUST implement the `IERC4824` interface below: ```solidity pragma solidity ^0.8.1; /// @title ERC-4824 DAOs /// @dev See -interface IERC-4824 { +interface IERC4824 { event DAOURIUpdate(address daoAddress, string daoURI); - /// @notice A distinct Uniform Resource Identifier (URI) pointing to a JSON object following the "ERC-4824 DAO JSON-LD Schema". This JSON file splits into four URIs: membersURI, proposalsURI, activityLogURI, and governanceURI. The membersURI should point to a JSON file that conforms to the "ERC-4824 Members JSON-LD Schema". The proposalsURI should point to a JSON file that conforms to the "ERC-4824 Proposals JSON-LD Schema". The activityLogURI should point to a JSON file that conforms to the "ERC-4824 Activity Log JSON-LD Schema". The governanceURI should point to a flatfile, normatively a .md file. Each of the JSON files named above can be statically-hosted or dynamically-generated. + /// @notice A distinct Uniform Resource Identifier (URI) pointing to a JSON object following the "ERC-4824 DAO JSON-LD Schema". This JSON file splits into four subsidiary URIs: membersURI, proposalsURI, activityLogURI, and governanceURI. The membersURI SHOULD point to a JSON file that conforms to the "ERC-4824 Members JSON-LD Schema". The proposalsURI SHOULD point to a JSON file that conforms to the "ERC-4824 Proposals JSON-LD Schema". The activityLogURI SHOULD point to a JSON file that conforms to the "ERC-4824 Activity Log JSON-LD Schema". The governanceURI SHOULD point to a flatfile, normatively a .md file. Each of the JSON files named above MAY be statically-hosted or dynamically-generated. The content of subsidiary JSON files MAY be directly embedded as a JSON object directly within the top-level DAO JSON, in which case the relevant field MUST be renamed to remove the "URI" suffix. For example, "membersURI" would be renamed to "members", "proposalsURI" would be renamed to "proposals", and so on. function daoURI() external view returns (string memory _daoURI); } ``` @@ -53,97 +53,20 @@ The DAO JSON-LD Schema mentioned above: } ``` -A DAO MAY inherit the above interface above or it MAY create an external registration contract that is compliant with this EIP. If a DAO creates an external registration contract, the registration contract MUST store the DAO’s primary address. +A DAO MAY inherit the `IERC4824` interface above or it MAY create an external registration contract that is compliant with this EIP. Whether the DAO inherits the above interface or it uses an external registration contract, the DAO SHOULD define a method for and implement some access control logic to enable efficient updating for daoURI. If a DAO creates an external registration contract, the registration contract MUST store the DAO’s primary address, typically the address of the primary governance contract. See the reference implementation of external registration contract in the attached assets folder to this EIP. -If the DAO inherits the above interface, it SHOULD define a method for updating daoURI. If the DAO uses an external registration contract, the registration contract SHOULD contain some access control logic to enable efficient updating for daoURI. - -```solidity -pragma solidity ^0.8.1; - -/// @title ERC-4824 Common Interfaces for DAOs -/// @dev See -/// @title ERC-4824: DAO Registration -contract ERC-4824Registration is IERC-4824, AccessControl { - bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE"); - - string private _daoURI; - - address daoAddress; - - constructor() { - daoAddress = address(0xdead); - } - - /// @notice Set the initial DAO URI and offer manager role to an address - /// @dev Throws if initialized already - /// @param _daoAddress The primary address for a DAO - /// @param _manager The address of the URI manager - /// @param daoURI_ The URI which will resolve to the governance docs - function initialize( - address _daoAddress, - address _manager, - string memory daoURI_, - address _ERC-4824Index - ) external { - initialize(_daoAddress, daoURI_, _ERC-4824Index); - _grantRole(MANAGER_ROLE, _manager); - } - - /// @notice Set the initial DAO URI - /// @dev Throws if initialized already - /// @param _daoAddress The primary address for a DAO - /// @param daoURI_ The URI which will resolve to the governance docs - function initialize( - address _daoAddress, - string memory daoURI_, - address _ERC-4824Index - ) public { - if (daoAddress != address(0)) revert AlreadyInitialized(); - daoAddress = _daoAddress; - _setURI(daoURI_); - - _grantRole(DEFAULT_ADMIN_ROLE, _daoAddress); - _grantRole(MANAGER_ROLE, _daoAddress); - - ERC-4824Index(_ERC-4824Index).logRegistration(address(this)); - } - - /// @notice Update the URI for a DAO - /// @dev Throws if not called by dao or manager - /// @param daoURI_ The URI which will resolve to the governance docs - function setURI(string memory daoURI_) public onlyRole(MANAGER_ROLE) { - _setURI(daoURI_); - } - - function _setURI(string memory daoURI_) internal { - _daoURI = daoURI_; - emit DAOURIUpdate(daoAddress, daoURI_); - } - - function daoURI() external view returns (string memory daoURI_) { - return _daoURI; - } - - function supportsInterface( - bytes4 interfaceId - ) public view virtual override returns (bool) { - return - interfaceId == type(IERC-4824).interfaceId || - super.supportsInterface(interfaceId); - } -} -``` +When reporting information in the DAO JSON-LD Schema, if a given field has no value (for example, `description`), it SHOULD be removed rather than left with an empty or `null` value. ### Indexing -If a DAO inherits the ERC-4824 interface from a 4824-compliant DAO factory, then the DAO factory SHOULD incorporate a call to an indexer contract as part of the DAO's initialization to enable efficient network indexing. If the DAO is [ERC-165](./eip-165)-compliant, the factory can do this without additional permissions. If the DAO is _not_ compliant with ERC-165, the factory SHOULD first obtain access control rights to the indexer contract and then call logRegistration directly with the address of the new DAO and the daoURI of the new DAO. Note that any user, including the DAO itself, MAY call logRegistration and submit a registration for a DAO which inherits the ERC-4824 interface and which is also ERC-165-compliant. +If a DAO inherits the `IERC4824` interface from a 4824-compliant DAO factory, then the DAO factory SHOULD incorporate a call to an indexer contract as part of the DAO's initialization to enable efficient network indexing. If the DAO is [ERC-165](./eip-165)-compliant, the factory can do this without additional permissions. If the DAO is _not_ compliant with ERC-165, the factory SHOULD first obtain access control rights to the indexer contract and then call `logRegistration` directly with the address of the new DAO and the daoURI of the new DAO. Note that any user, including the DAO itself, MAY call `logRegistration` and submit a registration for a DAO which inherits the `IERC4824` interface and which is also ERC-165-compliant. ```solidity pragma solidity ^0.8.1; -error ERC-4824InterfaceNotSupported(); +error ERC4824InterfaceNotSupported(); -contract ERC-4824Index is AccessControl { +contract ERC4824Index is AccessControl { using ERC165Checker for address; bytes32 public constant REGISTRATION_ROLE = keccak256("REGISTRATION_ROLE"); @@ -162,123 +85,39 @@ contract ERC-4824Index is AccessControl { } function logRegistration(address daoAddress) external { - if (!daoAddress.supportsInterface(type(IERC-4824).interfaceId)) - revert ERC-4824InterfaceNotSupported(); + if (!daoAddress.supportsInterface(type(IERC4824).interfaceId)) + revert ERC4824InterfaceNotSupported(); emit DAOURIRegistered(daoAddress); } } ``` -If a DAO uses an external registration contract, the DAO SHOULD use a common registration factory contract linked to a common indexer to enable efficient network indexing. +If a DAO uses an external registration contract, the DAO SHOULD use a common registration factory contract linked to a common indexer to enable efficient network indexing. See the reference implementation of the factory contract in the attached assets folder to this EIP. -```solidity -pragma solidity ^0.8.1; - -/// @title ERC-4824 Common Interfaces for DAOs -/// @dev See - -contract CloneFactory { - // implementation of eip-1167 - see https://eips.ethereum.org/EIPS/eip-1167 - function createClone(address target) internal returns (address result) { - bytes20 targetBytes = bytes20(target); - assembly { - let clone := mload(0x40) - mstore( - clone, - 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000 - ) - mstore(add(clone, 0x14), targetBytes) - mstore( - add(clone, 0x28), - 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000 - ) - result := create(0, clone, 0x37) - } - } -} - -contract ERC-4824RegistrationSummoner { - event NewRegistration( - address indexed daoAddress, - string daoURI, - address registration - ); - - address public ERC-4824Index; - address public template; /*Template contract to clone*/ - - constructor(address _template, address _ERC-4824Index) { - template = _template; - ERC-4824Index = _ERC-4824Index; - } - - function registrationAddress( - address by, - bytes32 salt - ) external view returns (address addr, bool exists) { - addr = Clones.predictDeterministicAddress( - template, - _saltedSalt(by, salt), - address(this) - ); - exists = addr.code.length > 0; - } - - function summonRegistration( - bytes32 salt, - string calldata daoURI_, - address manager, - address[] calldata contracts, - bytes[] calldata data - ) external returns (address registration, bytes[] memory results) { - registration = Clones.cloneDeterministic( - template, - _saltedSalt(msg.sender, salt) - ); - - if (manager == address(0)) { - ERC-4824Registration(registration).initialize( - msg.sender, - daoURI_, - ERC-4824Index - ); - } else { - ERC-4824Registration(registration).initialize( - msg.sender, - manager, - daoURI_, - ERC-4824Index - ); - } - - results = _callContracts(contracts, data); - - emit NewRegistration(msg.sender, daoURI_, registration); - } -``` +#### Indexing priority +daoURIs may be published directly in the DAO's contract or through a call to a common registration factory contract. In cases where both occur, the daoURI (and all sub-URIs) published through a call to a registration factory contract SHOULD take precedence. If there are multiple registrations, the most recent registration SHOULD take precedence. ### Members -Members JSON-LD Schema. Every contract implementing this EIP SHOULD implement a membersuRI pointing to a JSON object satisfying this schema. +Members JSON-LD Schema. Every contract implementing this EIP SHOULD implement a membersURI pointing to a JSON object satisfying this schema. Below, DID refers to [Decentralized Identifiers](https://www.w3.org/TR/2022/REC-did-core-20220719/). ```json { - "@context": "", + "@context": "https://www.daostar.org/schemas", "type": "DAO", - "name": "", "members": [ { - "type": "EthereumAddress", - "id": "
" + "id": "" }, { - "type": "EthereumAddress", - "id": "
" + "id": "" } ] } ``` +For example, for an address on Ethereum Mainnet, the [CAIP-10](https://github.com/ChainAgnostic/CAIPs/blob/ad0cfebc45a4b8368628340bf22aefb2a5edcab7/CAIPs/caip-10.md) address would be of the form `eip155:1:0x1234abcd`, while the DID address would be of the form `did:ethr:0x1234abcd`. + ### Proposals Proposals JSON-LD Schema. Every contract implementing this EIP SHOULD implement a proposalsURI pointing to a JSON object satisfying this schema. @@ -287,15 +126,14 @@ In particular, any on-chain proposal MUST be associated to an id of the form CAI ```json { - "@context": "http://www.daostar.org/schemas", - "type": "DAO", - "name": "", + "@context": "https://www.daostar.org/schemas", "proposals": [ { "type": "proposal", "id": "", "name": "", - "contentURI": "", + "contentURI": "", + "discussionURI": "", "status": "", "calls": [ { @@ -312,29 +150,15 @@ In particular, any on-chain proposal MUST be associated to an id of the form CAI } ``` +When deferenced, contentURI should return the content (i.e. the text) of the proposal. Similarly, discussionURI should return a discussion link, whether a forum post, Discord channel, or Twitter thread. + ### Activity Log Activity Log JSON-LD Schema. Every contract implementing this EIP SHOULD implement a activityLogURI pointing to a JSON object satisfying this schema. ```json { - "@context": "", - "type": "DAO", - "name": "", - "activities": [ - { - "id": "", - "type": "activity", - "proposal": { - "type": "proposal" - "id": "", - }, - "member": { - "type": "EthereumAddress", - "id": "
" - } - }, - ], + "@context": "https://www.daostar.org/schemas", "activities": [ { "id": "", @@ -344,10 +168,9 @@ Activity Log JSON-LD Schema. Every contract implementing this EIP SHOULD impleme "id": "", }, "member": { - "type": "EthereumAddress", - "id": "
" + "id": "" } - } + } ] } ``` @@ -356,36 +179,68 @@ Activity Log JSON-LD Schema. Every contract implementing this EIP SHOULD impleme Contracts JSON-LD Schema. Every contract implementing this EIP SHOULD implement a contractsURI pointing to a JSON object satisfying this schema. -Further, every contractsURI SHOULD include at least the contract inheriting the ERC-4824 interface. +contractsURI is especially important for DAOs with distinct or decentralized governance occurring across multiple different contracts, possibly across several chains. Multiple addresses may report the same daoURI. -``` +To prevent spam or spoofing, all DAOs adopting this specification SHOULD publish through contractsURI the address of every contract associated to the DAO, including but not limited to those that inherit the `IERC4824` interface or those that interact with a registration factory contract. Note that this includes the contract address(es) of any actual registration contracts deployed through a registration factory. + +```json { - "@context": "", - "type": "DAO", - "name": "", + "@context": "https://www.daostar.org/schemas", "contracts": [ { - "type": "EthereumAddress", - "id": "
", + "id": "" "name": "", "description": "" }, { - "type": "EthereumAddress", - "id": "
", + "id": "" "name": "", "description": "" - } + }, { - "type": "EthereumAddress", - "id": "
", - "name": "", + "id": "" + "name": "", "description": "" } ] } ``` +### URI fields +The content of subsidiary JSON files MAY be directly embedded as a JSON object directly within the top-level DAO JSON, in which case the relevant field MUST be renamed to remove the "URI" suffix. For example, `membersURI` would be renamed to `members`, `proposalsURI` would be renamed to `proposals`, and so on. In all cases, the embedded JSON object MUST conform to the relevant schema. A given field and a URI-suffixed field (e.g. `membersURI` and `members`) SHOULD NOT appear in the same JSON-LD; if they do, the field without the URI suffix MUST take precedence. + +Fields which are not appended with URI MAY be appended with a URI, for example `name` and `description` may be renamed to `nameURI` and `descriptionURI`, in which case the dereferenced URI MUST return a JSON-LD object containing the `"@context": "https://www.daostar.org/schemas"` field and the original key-value pair. + +For example, descriptionURI should return: +```json +{ + "@context": "https://www.daostar.org/schemas", + "description": "" +} +``` + +### Entities which are not DAOs + +Entities which are not DAOs or which do not wish to identify as DAOs MAY still publish daoURIs. If so, they SHOULD use a different value for the `type` field than "DAO", for example "Organization", "Foundation", "Person", or, most broadly, "Entity". + +Entities which are not DAOs or which do not wish to identify as DAOs MAY also publish metadata information through an off-chain orgURI or entityURI, which are aliases of daoURI. If these entities are reporting their URI through an on-chain smart contract or registration, however, they MUST retain `IERC4824`'s daoURI in order to enable network indexing. + +The Entity JSON-LD Schema: + +```json +{ + "@context": "https://www.daostar.org/schemas", + "type": "", + "name": "", + "description": "", + "membersURI": "", + "proposalsURI": "", + "activityLogURI": "", + "governanceURI": "", + "contractsURI": "" +} +``` + ## Rationale In this standard, we assume that all DAOs possess at least two primitives: _membership_ and _behavior_. _Membership_ is defined by a set of addresses. _Behavior_ is defined by a set of possible contract actions, including calls to external contracts and calls to internal functions. _Proposals_ relate membership and behavior; they are objects that members can interact with and which, if and when executed, become behaviors of the DAO. @@ -396,19 +251,23 @@ DAOs themselves have a number of existing and emerging use-cases. But almost all While we considered standardizing on-chain aspects of DAOs in this standard, particularly on-chain proposal objects and proposal IDs, we felt that this level of standardization was premature given (1) the relative immaturity of use-cases, such as multi-DAO proposals or master-minion contracts, that would benefit from such standardization, (2) the close linkage between proposal systems and governance, which we did not want to standardize (see “governanceURI”, below), and (3) the prevalence of off-chain and L2 voting and proposal systems in DAOs (see “proposalsURI”, below). Further, a standard URI interface is relatively easy to adopt and has been actively demanded by frameworks (see “Community Consensus”, below). +We added the ability to append or remove the URI suffix to make dereferenced daoURIs easier to parse, especially in certain applications that did not want to maintain several services or flatfiles. Where there is a conflict, we decided that fields without the URI suffix should take precedence since they are more directly connected to the initial publication of daoURI. + +In terms of indexing: we believe that the most trustworthy way of publishing a daoURI is through an on-chain registration contract, as it is the clearest reflection of the active will of a DAO. It is also the primary way a DAO may “overwrite” any other daoURI that has previously been published, through any means. If a DAO inherits daoURI directly through its contract, then this information is also trustworthy, though slightly less so as it often reflects the decisions of a DAO framework rather than the DAO directly. + ### membersURI -Approaches to membership vary widely in DAOs. Some DAOs and DAO frameworks (e.g. Gnosis Safe, Tribute), maintain an explicit, on-chain set of members, sometimes called owners or stewards. But many DAOs are structured so that membership status is based on the ownership of a token or tokens (e.g. Moloch, Compound, DAOstack, 1Hive Gardens). In these DAOs, computing the list of current members typically requires some form of off-chain indexing of events. +Approaches to membership vary widely in DAOs. Some DAOs and DAO frameworks (e.g. Gnosis Safe, Tribute), maintain an explicit, on-chain set of members, sometimes called owners or stewards. But many DAOs are structured so that membership status is based on the ownership of a token or tokens (e.g. Moloch, Compound, DAOstack, 1Hive Gardens). In these DAOs, computing the list of current members typically requires some form of off-chain indexing of events. -In choosing to ask only for an (off-chain) JSON schema of members, we are trading off some on-chain functionality for more flexibility and efficiency. We expect different DAOs to use membersURI in different ways: to serve a static copy of on-chain membership data, to contextualize the on-chain data (e.g. many Gnosis Safe stewards would not say that they are the only members of the DAO), to serve consistent membership for a DAO composed of multiple contracts, or to point at an external service that computes the list, among many other possibilities. We also expect many DAO frameworks to offer a standard endpoint that computes this JSON file, and we provide a few examples of such endpoints in the implementation section. +In choosing to ask only for an (off-chain) JSON schema of members, we are trading off some on-chain functionality for more flexibility and efficiency. We expect different DAOs to use membersURI in different ways: to serve a static copy of on-chain membership data, to contextualize the on-chain data (e.g. many Gnosis Safe stewards would not say that they are the only members of the DAO), to serve consistent membership for a DAO composed of multiple contracts, or to point at an external service that computes the list, among many other possibilities. We also expect many DAO frameworks to offer a standard endpoint that computes this JSON file, and we provide a few examples of such endpoints in the implementation section. -We encourage extensions of the Membership JSON-LD Schema, e.g. for DAOs that wish to create a state variable that captures active/inactive status or different membership levels. +We encourage extensions of the Membership JSON-LD Schema, e.g. for DAOs that wish to create a state variable that captures active/inactive status or different membership levels. ### proposalsURI -Proposals have become a standard way for the members of a DAO to trigger on-chain actions, e.g. sending out tokens as part of grant or executing arbitrary code in an external contract. In practice, however, many DAOs are governed by off-chain decision-making systems on platforms such as Discourse, Discord, or Snapshot, where off-chain proposals may function as signaling mechanisms for an administrator or as a prerequisite for a later on-chain vote. (To be clear, on-chain votes may also serve as non-binding signaling mechanisms or as “binding” signals leading to some sort of off-chain execution.) The schema we propose is intended to support both on-chain and off-chain proposals, though DAOs themselves may choose to report only on-chain, only off-chain, or some custom mix of proposal types. +Proposals have become a standard way for the members of a DAO to trigger on-chain actions, e.g. sending out tokens as part of a grant or executing arbitrary code in an external contract. In practice, however, many DAOs are governed by off-chain decision-making systems on platforms such as Discourse, Discord, or Snapshot, where off-chain proposals may function as signaling mechanisms for an administrator or as a prerequisite for a later on-chain vote. (To be clear, on-chain votes may also serve as non-binding signaling mechanisms or as “binding” signals leading to some sort of off-chain execution.) The schema we propose is intended to support both on-chain and off-chain proposals, though DAOs themselves may choose to report only on-chain, only off-chain, or some custom mix of proposal types. -**Proposal ID**. Every unique on-chain proposal MUST be associated to a proposal ID of the form CAIP10_ADDRESS + “?proposalId=” + PROPOSAL_COUNTER, where PROPOSAL_COUNTER is an arbitrary string which is unique per CAIP10_ADDRESS. Note that PROPOSAL_COUNTER may not be the same as the on-chain representation of the proposal; however, each PROPOSAL_COUNTER should be unique per CAIP10_ADDRESS, such that the proposal ID is a globally unique identifier. We endorse the CAIP-10 standard to support multi-chain / layer 2 proposals and the “?proposalId=” query syntax to suggest off-chain usage. +**Proposal ID**. In the specification, we state that every unique on-chain proposal must be associated to a proposal ID of the form CAIP10_ADDRESS + “?proposalId=” + PROPOSAL_COUNTER, where PROPOSAL_COUNTER is an arbitrary string which is unique per CAIP10_ADDRESS. Note that PROPOSAL_COUNTER may not be the same as the on-chain representation of the proposal; however, each PROPOSAL_COUNTER should be unique per CAIP10_ADDRESS, such that the proposal ID is a globally unique identifier. We endorse the CAIP-10 standard to support multi-chain / layer 2 proposals and the “?proposalId=” query syntax to suggest off-chain usage. **ContentURI**. In many cases, a proposal will have some (off-chain) content such as a forum post or a description on a voting platform which predates or accompanies the actual proposal. @@ -433,7 +292,7 @@ _Alternatives we considered: history, interactions_ ### governanceURI -Membership, to be meaningful, usually implies rights and affordances of some sort, e.g. the right to vote on proposals, the right to ragequit, the right to veto proposals, and so on. But many rights and affordances of membership are realized off-chain (e.g. right to vote on a Snapshot, gated access to a Discord). Instead of trying to standardize these wide-ranging practices or forcing DAOs to locate descriptions of those rights on-chain, we believe that a flatfile represents the easiest and most widely-acceptable mechanism for communicating what membership means and how proposals work. These flatfiles can then be consumed by services such as Etherscan, supporting DAO discoverability and legibility. +Membership, to be meaningful, usually implies rights and affordances of some sort, e.g. the right to vote on proposals, the right to ragequit, the right to veto proposals, and so on. But many rights and affordances of membership are realized off-chain (e.g. right to vote on a Snapshot, gated access to a Discord). Instead of trying to standardize these wide-ranging practices or forcing DAOs to locate descriptions of those rights on-chain, we believe that a flatfile represents the easiest and most widely-acceptable mechanism for communicating what membership means and how proposals work. These flatfiles can then be consumed by services such as Etherscan, supporting DAO discoverability and legibility. We chose the word “governance” as an appropriate word that reflects (1) the widespread use of the word in the DAO ecosystem and (2) the common practice of emitting a governance.md file in open-source software projects. @@ -441,7 +300,9 @@ _Alternative names considered: description, readme, constitution_ ### contractsURI -Over the course of community conversations, multiple parties raised the need to report on, audit, and index the different contracts belonging to a given DAO. Some of these contracts are deployed as part of the modular design of a single DAO framework, e.g. the core, voting, and timelock contracts within Open Zeppelin / Compound Governor. In other cases, a DAO might deploy multiple multsigs as treasuries and/or multiple subDAOs that are effectively controlled by the DAO. ContractsURI offers a generic way of declaring these many instruments. +Over the course of community conversations, multiple parties raised the need to report on, audit, and index the different contracts belonging to a given DAO. Some of these contracts are deployed as part of the modular design of a single DAO framework, e.g. the core, voting, and timelock contracts within Open Zeppelin / Compound Governor. In other cases, a DAO might deploy multiple multsigs as treasuries and/or multiple subDAOs that are effectively controlled by the DAO. contractsURI offers a generic way of declaring these many instruments so that they can be efficiently aggregated by an indexer. + +contractsURI is also important for spam prevention or spoofing. Some DAOs may spread governance power and control across multiple different governance contracts, possibly across several chains. To capture this reality, multiple addresses may wish to report the same daoURI, or different daoURIs with the same name. However, unauthorized addresses may try to report the same daoURI or name. Additional contract information can prevent attacks of this sort by allowing indexers to weed out spam information. _Alternative names considered: contractsRegistry, contractsList_ @@ -453,9 +314,9 @@ Further, given the emergence of patterns such as subDAOs and DAOs of DAOs in lar ### **Community Consensus** -The initial draft standard was developed as part of the DAOstar One roundtable series, which included representatives from all major EVM-based DAO frameworks (Aragon, Compound, DAOstack, Gnosis, Moloch, OpenZeppelin, and Tribute), a wide selection of DAO tooling developers, as well as several major DAOs. Thank you to all the participants of the roundtable. We would especially like to thank Auryn Macmillan, Fabien of Snapshot, Selim Imoberdorf, Lucia Korpas, and Mehdi Salehi for their contributions. +The initial draft standard was developed as part of the DAOstar roundtable series, which included representatives from all major EVM-based DAO frameworks (Aragon, Compound, DAOstack, Gnosis, Moloch, OpenZeppelin, and Tribute), a wide selection of DAO tooling developers, as well as several major DAOs. Thank you to all the participants of the roundtable. We would especially like to thank Fabien of Snapshot, Jake Hartnell, Auryn Macmillan, Selim Imoberdorf, Lucia Korpas, and Mehdi Salehi for their contributions. -In-person events will be held at Schelling Point 2022 and at ETHDenver 2022, where we hope to receive more comments from the community. We also plan to schedule a series of community calls through early 2022. +In-person events for community comment were held at Schelling Point 2022, ETHDenver 2022, ETHDenver 2023, DAO Harvard 2023, DAO Stanford 2023 (also known as the Science of Blockchains Conference DAO Workshop). The team also hosted over 50 biweekly community calls as part of the DAOstar project. ## Backwards Compatibility @@ -470,4 +331,3 @@ Indexers that rely on the data returned by the URI should take caution if DAOs r ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md). - diff --git a/ERCS/erc-5173.md b/ERCS/erc-5173.md index cd97e51bbc..61eb9e4547 100644 --- a/ERCS/erc-5173.md +++ b/ERCS/erc-5173.md @@ -13,21 +13,21 @@ requires: 165, 721 ## Abstract -This EIP introduces the NFT Future Rewards (nFR) extension for [EIP-721](./eip-721.md) tokens (NFTs). nFR allows owners to benefit from future price appreciation even after selling their tokens. This establishes a Provenance Value Amplification (PVA) framework where creators, buyers, and sellers collaborate to collectively increase value. This innovative approach disrupts the current zero-sum trading paradigm by creating a fairer and more rewarding system for all participants. +This ERC introduces the NFT Future Rewards (nFR) extension for [ERC-721](./eip-721.md) tokens (NFTs). nFR allows owners to benefit from future price appreciation even after selling their tokens, without the need for market prediction. This establishes a Provenance Value Amplification (PVA) framework where creators, buyers, and sellers collaborate to collectively increase value. This innovative approach disrupts the current zero-sum trading paradigm by creating a fairer and more rewarding system for all participants. -[ERC-5173](./eip-5173.md) fosters a sustainable and collaborative trading environment by aligning the interests of service providers and users. Compliant token owners enjoy price increases during holding and continue receiving nFRs after selling. By eliminating competition and promoting shared prosperity, nFR fosters strong bonds within the NFT ecosystem. The framework ensures equitable profit distribution across all historical owners, including the original minter. +[ERC-5173](./eip-5173.md) fosters a sustainable and collaborative trading environment by aligning the interests of service providers and users. Compliant token owners enjoy price increases during holding and continue receiving nFRs after selling. By eliminating competition and promoting shared prosperity, nFR fosters strong bonds within the NFT and crypto token ecosystems. The framework ensures equitable profit distribution across all historical owners, including the original minter. ## Motivation The current trading landscape is often marred by unfair practices like spoofing, insider trading, and wash trading. These activities disadvantage average traders caught in cycles of fear and greed. However, the rise of NFTs and their inherent transaction tracking capability presents an opportunity to disrupt this unequal value distribution. -ERC-5173 introduces a standardized profit-sharing model across the entire ownership history of an NFT, benefiting all market participants. It creates a Flow of provenance where buyers and owners are rewarded for their contributions to price discovery. This model fosters aligned interests and establishes a mutually beneficial economic structure for both buyers and sellers. +ERC-5173 introduces a standardized profit-sharing model across the entire ownership history of an NFT, benefiting all market participants. It creates a "Flow of Provenance" where buyers and owners are rewarded for their contributions to price discovery. This model fosters aligned interests and establishes a mutually beneficial economic structure for both buyers and sellers. -NFTs can accurately reflect the contributions of their owners to their value. By recording every price change of each ERC-5173 token, we can establish a Future Rewards program that fairly compensates owners. This program aims to level the playing field and provide average traders with a better chance at success. +NFTs can accurately reflect the contributions of their owners to their value. By recording every price change of each ERC-5173 token, we can establish a Future Rewards program that fairly compensates owners. This program aims to level the playing field and provide average traders with a better chance at success, without the need for complex market predictions. -In addition to promoting this novel gift economic model, the nFR framework discourages illicit activities that circumvent artist and marketplace rules. This fosters a transparent and trustworthy trading ecosystem. +In addition to promoting this novel "gift economy" model, the nFR framework discourages illicit activities that circumvent artist and marketplace rules. This fosters a transparent and trustworthy trading ecosystem. -Applied to the exchange of wrapped [EIP-20](./eip-20.md) tokens, this value-amplification construct has the potential to transform the asset transaction sector by integrating identities within the time and sales (T&S) data. This inclusive attribute affords a holistic perspective of each trade, infusing an additional layer of depth into the trading experience. +Applied to the exchange of wrapped [ERC-20](./eip-20.md) tokens, this value-amplification construct has the potential to transform the asset transaction sector by integrating identities within the time and sales (T&S) data. This inclusive attribute affords a holistic perspective of each trade, infusing an additional layer of depth into the trading experience. ## Specification diff --git a/ERCS/erc-5521.md b/ERCS/erc-5521.md index 74cddf2fd7..6c08946e13 100644 --- a/ERCS/erc-5521.md +++ b/ERCS/erc-5521.md @@ -4,8 +4,7 @@ title: Referable NFT description: An ERC-721 extension to construct reference relationships among NFTs author: Saber Yu (@OniReimu), Qin Wang , Shange Fu , Yilin Sai , Shiping Chen , Sherry Xu , Jiangshan Yu discussions-to: https://ethereum-magicians.org/t/eip-x-erc-721-referable-nft/10310 -status: Last Call -last-call-deadline: 2024-06-07 +status: Final type: Standards Track category: ERC created: 2022-08-10 @@ -128,7 +127,7 @@ interface TargetContract is IERC165 { ### Is this event informative enough? `UpdateNode`: This event disseminates crucial information, including the rNFT ID, its owner, and lists of contract addresses/IDs with rNFTs referring to or referred by the subject rNFT. This data set enables stakeholders to efficiently manage and navigate the complex web of relationships inherent in the rNFT ecosystem. -Implementers are free to choose to use a struct (a **RECOMMENDED** struct is given in the Reference Implementation), or several separate mappings, or whatever other storage mechanism. Whichever mechanism chosen has no observable effect on the behaviour of the contract, as long as its output can fulfill the `UpdateNode` event. +Implementers are free to choose to use a struct (a recommended struct is given in the Reference Implementation), or several separate mappings, or whatever other storage mechanism. Whichever mechanism chosen has no observable effect on the behaviour of the contract, as long as its output can fulfill the `UpdateNode` event. ### Why `createdTimestampOf`? `createdTimestamp`: A key principle of this standard is that an rNFT should reference content already accepted by the community (a time-based sequence known by participants). Global timestamps for rNFTs are thus essential, serving to prevent conflicting states (akin to concurrency issues in transaction processing and block organization). We define a block-level timestamp where `createdTimestamp = block.timestamp` Note that, given that the granularity of references is tied to the block timestamp, it is impractical to discern the order of two rNFTs within the same block. @@ -149,14 +148,14 @@ Test cases are included in [ERC_5521.test.js](../assets/eip-5521/ERC_5521.test.j ## Reference Implementation -The **RECOMMENDED** implementation is demonstrated as follows: +The recommended implementation is demonstrated as follows: -- `Relationship`: a structure that contains `referring`, `referred`, `referringKeys`, `referredKeys`, `createdTimestamp`, and other customized and **OPTIONAL** attributes (i.e., not necessarily included in the standard) such as `privityOfAgreement` recording the ownerships of referred NFTs at the time the Referable NFTs (rNFTs) were being created or `profitSharing` recording the profit sharing of `referring`. +- `Relationship`: a structure that contains `referring`, `referred`, `referringKeys`, `referredKeys`, `createdTimestamp`, and other customized and optional attributes (i.e., not necessarily included in the standard) such as `privityOfAgreement` recording the ownerships of referred NFTs at the time the Referable NFTs (rNFTs) were being created or `profitSharing` recording the profit sharing of `referring`. - `referring`: an out-degree indicator, used to show the users this NFT refers to; - `referred`: an in-degree indicator, used to show the users who have refereed this NFT; - `referringKeys`: a helper for mapping conversion of out-degree indicators, used for events; - `referredKeys`: a helper for mapping conversion of in-degree indicators, used for events; -- `createdTimestamp`: a time-based indicator, used to compare the timestamp of mint, which **MUST NOT** be editable anyhow by callers. +- `createdTimestamp`: a time-based indicator, used to compare the timestamp of mint, which should not be editable anyhow by callers. - `referringOf` and `referredOf`: First, the current `referringOf` and `referredOf` allow cross-contract looking up, while this cannot be done by directly accessing `_relationship`. Secondly, only if privacy is not a concern, making `_relationship` public simplifies the contract by relying on Solidity’s automatically generated getters. However, if you need to control the visibility of the data, keeping the state variable private and providing specific getter functions would be the best approach. For example, if `_relationship` includes details about specific users’ interactions or transactions or some private extensible parameters (in the updated version, we specifically highlight the `Relationship` can be extended to meet different requirements), always making this data public could reveal users’ behavior patterns or preferences, leading to potential privacy breaches. - `convertMap`: This function is essential for retrieving the full mapping contents within a struct. Even if `_relationship` is public, The getters only allow retrieval of individual values for specific keys. Since we need comprehensive access to all stored addresses, `convertMap` is necessary to fulfill our event emission requirements. diff --git a/ERCS/erc-5625.md b/ERCS/erc-5625.md index d12d6cae8a..dc480c8b37 100644 --- a/ERCS/erc-5625.md +++ b/ERCS/erc-5625.md @@ -4,7 +4,7 @@ title: NFT Metadata JSON Schema dStorage Extension description: Add a dStorage property to non-fungible tokens (NFTs) metadata JSON schema to provide decentralized storage information of NFT assets author: Gavin Fu (@gavfu) discussions-to: https://ethereum-magicians.org/t/eip-5625-nft-metadata-json-schema-dstorage-extension/10754 -status: Stagnant +status: Review type: Standards Track category: ERC created: 2022-09-08 diff --git a/ERCS/erc-5630.md b/ERCS/erc-5630.md index 70003c83c2..45cea4e9d3 100644 --- a/ERCS/erc-5630.md +++ b/ERCS/erc-5630.md @@ -4,7 +4,7 @@ title: New approach for encryption / decryption description: defines a specification for encryption and decryption using Ethereum wallets. author: Firn Protocol (@firnprotocol), Fried L. Trout, Weiji Guo (@weijiguo) discussions-to: https://ethereum-magicians.org/t/eip-5630-encryption-and-decryption/10761 -status: Stagnant +status: Draft type: Standards Track category: ERC created: 2022-09-07 diff --git a/ERCS/erc-6123.md b/ERCS/erc-6123.md index 33fd88b5c8..eeedbc2bb2 100644 --- a/ERCS/erc-6123.md +++ b/ERCS/erc-6123.md @@ -65,15 +65,28 @@ The following methods specify a Smart Derivative Contract's trade initiation and A party can initiate a trade by providing the party address to trade with, trade data, trade position, payment amount for the trade and initial settlement data. Only registered counterparties are allowed to use that function. ```solidity -function inceptTrade(address _withParty, string memory _tradeData, int _position, int256 _paymentAmount, string memory _initialSettlementData) external; +function inceptTrade(address withParty, string memory tradeData, int position, int256 paymentAmount, string memory initialSettlementData) external returns (string memory); ``` +The position and the paymentAmount are viewed from the incepter. +The function will return a generated unique `tradeId`. The trade id will also be emitted by an event. + #### Trade Initiation Phase: `confirmTrade` A counterparty can confirm a trade by providing its trade specification data, which then gets matched against the data stored from `inceptTrade` call. ```solidity -function confirmTrade(address _withParty, string memory _tradeData, int _position, int256 _paymentAmount, string memory _initialSettlementData) external; +function confirmTrade(address withParty, string memory tradeData, int position, int256 paymentAmount, string memory initialSettlementData) external; +``` + +Here, the position and the paymentAmount is viewed from the confimer (opposite sign compared to the call to `inceptTrade`). + +#### Trade Initiation Phase: `cancelTrade` + +The counterparty that called `inceptTrade` has the option to cancel the trade, e.g., in the case where the trade is not confirmed in a timely manner. + +```solidity +function cancelTrade(address withParty, string memory tradeData, int position, int256 paymentAmount, string memory initialSettlementData) external; ``` #### Trade Settlement Phase: `initiateSettlement` @@ -98,28 +111,37 @@ function performSettlement(int256 settlementAmount, string memory settlementData This method - either called back from the provided settlement token directly or from an eligible address - completes the settlement transfer. This might result in a termination or start of the next settlement phase, depending on the provided success flag. +The transactionData is emitted as part of the corresponding event: `TradeSettled` or `TradeTerminated` ```solidity -function afterTransfer(uint256 transactionHash, bool success) external; +function afterTransfer(bool success, string memory transactionData) external; ``` - #### Trade Termination: `requestTermination` -Allows an eligible party to request a mutual termination with a termination amount she is willing to pay +Allows an eligible party to request a mutual termination of the trade with the correspondig `tradeId` with a termination amount she is willing to pay and provide further termination terms (e.g. an XML) ```solidity -function requestTradeTermination(string memory tradeId, int256 _terminationPayment) external; +function requestTradeTermination(string memory tradeId, int256 terminationPayment, string memory terminationTerms) external; ``` #### Trade Termination: `confirmTradeTermination` -Allows an eligible party to confirm a previously requested (mutual) trade termination, including termination payment value +Allows an eligible party to confirm a previously requested (mutual) trade termination, including termination payment value and termination terms + +```solidity +function confirmTradeTermination(string memory tradeId, int256 terminationPayment, string memory terminationTerms) external; +``` + +#### Trade Termination: `cancelTradeTermination` + +The party that initiated `requestTradeTermination` has the option to withdraw the request, e.g., in the case where the termination is not confirmed in a timely manner. ```solidity -function confirmTradeTermination(string memory tradeId, int256 _terminationPayment) external; +function cancelTradeTermination(string memory tradeId, int256 terminationPayment, string memory terminationTerms) external; ``` + ### Trade Events The following events are emitted during an SDC Trade life-cycle. @@ -140,6 +162,14 @@ Emitted on trade confirmation - method 'confirmTrade' event TradeConfirmed(address confirmer, string tradeId); ``` +#### TradeCanceled + +Emitted on trade cancellation - method 'cancelTrade' + +```solidity +event TradeCanceled(address initiator, string tradeId); +``` + #### TradeActivated Emitted when a Trade is activated @@ -148,56 +178,76 @@ Emitted when a Trade is activated event TradeActivated(string tradeId); ``` -### TradeSettlementRequest +#### TradeTerminationRequest -Emitted when a settlement is requested. May trigger the settlement phase. +Emitted when termination request is initiated by a counterparty ```solidity -event TradeSettlementRequest(string tradeData, string lastSettlementData); +event TradeTerminationRequest(address initiator, string tradeId, int256 terminationPayment, string terminationTerms); ``` +#### TradeTerminationConfirmed -### TradeSettlementPhase +Emitted when termination request is confirmed by a counterparty -Emitted when the settlement phase is started. +```solidity +event TradeTerminationConfirmed(address confirmer, string tradeId, int256 terminationPayment, string terminationTerms); +``` + +#### TradeTerminationCanceled + +Emitted when termination request is canceled by the requesting counterparty ```solidity -event TradeSettlementPhase(); +event TradeTerminationCanceled(address initiator, string tradeId, string terminationTerms); ``` +#### TradeTerminated -#### TradeTerminationRequest +Emitted when trade is terminated -Emitted when termination request is initiated by a counterparty +```solidity +event TradeTerminated(string cause); +``` + + +### Settlement Events + +The following events are emitted during the settlement phases. + +#### SettlementRequested + +Emitted when a settlement is requested. May trigger the settlement phase. ```solidity -event TradeTerminationRequest(address cpAddress, string tradeId); +event SettlementRequested(address initiator, string tradeData, string lastSettlementData); ``` -#### TradeTerminationConfirmed +#### SettlementEvaluated -Emitted when termination request is confirmed by a counterparty +Emitted when the settlement phase is started. ```solidity -event TradeTerminationConfirmed(address cpAddress, string tradeId); +event SettlementEvaluated(address initiator, int256 settlementAmount, string settlementData); ``` -#### TradeTerminated +#### SettlementTransferred -Emitted when trade is terminated +Emitted when the settlement succeeded. ```solidity -event TradeTerminated(string cause); +event SettlementTransferred(string transactionData); ``` -#### ProcessHalted +#### SettlementFailed -Emitted when trade processing stops. +Emitted when the settlement failed. ```solidity -event ProcessHalted(); +event SettlementFailed(string transactionData); ``` + ## Rationale The interface design and reference implementation are based on the following considerations: @@ -207,15 +257,22 @@ The interface design and reference implementation are based on the following con - The interface specification is generic enough to handle the case that parties process one or even multiple financial transactions (on a netted base) - Usually, the valuation of financial trades (e.g. OTC Derivatives) will require advanced valuation methodology to determine the market value. This is why the concept might rely on an external market data source and hosted valuation algorithms - A pull-based valuation-based oracle pattern can be implemented by using the provided callback pattern (methods: `initiateSettlement`, `performSettlement`) -- The reference implementation `SDC.sol` is based on a state-machine pattern where the states also serve as guards (via modifiers) to check which method is allowed to be called at a particular given process and trade state +- The reference implementation `SDCSingleTrade.sol` considers a single trade and is based on a state-machine pattern where the states also serve as guards (via modifiers) to check which method is allowed to be called at a particular given process and trade state +- The interface allows the extension to multiple trades with common (netted) settlement. ### State diagram of trade and process states -![image info](../assets/eip-6123/doc/sdc_trade_states.png) +![image info](../assets/eip-6123/doc/sdc_trade_states.svg) + +The diagram shows the trade states of a single trade SDC as in `SDCSingleTrade.sol`. ### Sequence diagram of reference implementation 'SDCPledgedBalance.sol' + ![image info](../assets/eip-6123/doc/sequence.svg) +The sequence diagram show the function calls that create the trade and stellement state transitions +and the emitted events. + ## Test Cases Life-cycle unit tests based on the sample implementation and usage of [ERC-20](./eip-20.md) token is provided. See file [test/SDCTests.js](../assets/eip-6123/test/SDCTests.js) @@ -223,7 +280,7 @@ Life-cycle unit tests based on the sample implementation and usage of [ERC-20](. ## Reference Implementation -An abstract contract class SDC.sol as well as a full reference implementation SDCPledgedBalance.sol for an OTC-Derivative is provided and is based on the [ERC-20](./eip-20.md) token standard. +An abstract contract class `SDCSingleTrade.sol` for single trade SDCs as well as a full reference implementation SDCPledgedBalance.sol for an OTC-Derivative is provided and is based on the [ERC-20](./eip-20.md) token standard. See folder `/assets/contracts`, more explanation on the implementation is provided inline. ### Trade Data Specification (suggestion) diff --git a/ERCS/erc-6229.md b/ERCS/erc-6229.md new file mode 100644 index 0000000000..c06fcd35f7 --- /dev/null +++ b/ERCS/erc-6229.md @@ -0,0 +1,321 @@ +--- +eip: 6229 +title: Tokenized Vaults with Lock-in Period +description: ERC-4626 Tokenized Vaults with Lock-in Period. +author: Anderson Chen (@Ankarrr), Martinet Lee , Anton Cheng +discussions-to: https://ethereum-magicians.org/t/eip-tokenized-vaults-with-lock-in-period/12298 +status: Draft +type: Standards Track +category: ERC +created: 2022-12-21 +requires: 4626 +--- + +## Abstract + +This standard extends [EIP-4626](./eip-4626.md) to support lock-in periods. + +## Motivation + +The [EIP-4626](./eip-4626.md) standard defines a tokenized vault allowing users (contracts or EOAs) to deposit and withdraw underlying tokens at any time. However, there exist cases where the vault needs to lock the underlying tokens (perhaps to execute certain strategies). During the lock-in period, neither withdrawals nor deposits should be allowed. This standard extends the EIP-4626 to support lock-in periods and handle scheduled deposits and withdrawals during them. + +## Specification + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. + +All vaults that follow this EIP MUST implement [EIP-4626](./eip-4626.md) to provide basic vault functions and [EIP-20](./eip-20.md) to represent shares. + +### Definitions + +- asset: The underlying [EIP-20](./eip-20.md) token that the vault accepts and manages. +- share: The EIP-20 token that the vault issued. +- locked: A status of the vault. When the vault is locked, user can’t withdraw or deposit assets from the vault. +- unlocked: A status of the vault. When the vault is unlocked, user can withdraw or deposit assets from the vault. +- round: The period that the vault is locked. + +### View Methods + +#### isLocked + +The current state of the vault. + +`true` represents a vault is in the locked state, and `false` represents a vault is in the unlocked state. + +```yaml +- name: isLocked + type: bool + stateMutability: view + + inputs: [] + + outputs: + - name: isLocked + type: bool + +``` + +#### vaultRound + +The current round of the vault. + +MUST start with `0`. + +MUST add `1` each time a new round starts, that is, when the `isLocked` becomes true. MUST NOT be modified in any other circumstances. + +```yaml +- name: vaultRound + type: uint256 + stateMutability: view + + inputs: [] + + outputs: + - name: vaultRound + type: uint256 +``` + +### Methods + +#### scheduleDeposit + +Schedule the intent to deposit `assets` when the `isLocked` is true. + +MUST only be callable when the `isLocked` is true. + +MUST transfer the `assets` from the caller to the vault. MUST not issue new shares. + +MUST revert if `assets` cannot be deposited. + +MUST revert if the `isLocked` is false. + +```yaml +- name: scheduleDeposit + type: function + stateMutability: nonpayable + + inputs: + - name: assets + type: uint256 +``` + +#### scheduleRedeem + +Schedule the intent to redeem `shares` from the vault when the `isLocked` is true. + +MUST only be callable when the `isLocked` is true. + +MUST transfer the `shares` from the caller to the vault. MUST not transfer assets to caller. + +MUST revert if `shares` cannot be redeemed. + +MUST revert if the `isLocked` is false. + +```yaml +- name: scheduleRedeem + type: function + stateMutability: nonpayable + + inputs: + - name: shares + type: uint256 +``` + +#### settleDeposits + +Process all scheduled deposits for `depositor` and minting `newShares`. + +MUST only be callable when the `isLocked` is false. + +MUST issue `newShares` according to the current share price for the scheduled `depositor`. + +MUST revert if there is no scheduled deposit for `depositor`. + +```yaml +- name: settleDeposits + type: function + stateMutability: nonpayable + + inputs: + - name: depositor + - type: address + + outputs: + - name: newShares + - type: uint256 +``` + +#### settleRedemptions + +Process all scheduled redemptions for `redeemer` by burning `burnShares` and transferring `redeemAssets` to the `redeemer`. + +MUST only be callable when the `isLocked` is false. + +MUST burn the `burnShares` and transfer `redeemAssets` back to the `redeemer` according to the current share price. + +MUST revert if no scheduled redemption for `redeemer`. + +```yaml +- name: settleRedemptions + type: function + stateMutability: nonpayable + + inputs: + - name: redeemer + - type: address + + outputs: + - name: burnShares + - type: uint256 + - name: redeemAssets + - type: uint256 +``` + +#### getScheduledDeposits + +Get the `totalAssets` of scheduled deposits for `depositor`. + +MUST NOT revert. + +```yaml +- name: getScheduledDeposits + type: function + stateMutability: view + + inputs: + - name: depositor + - type: address + + outputs: + - name: totalAssets + - type: uint256 +``` + +#### getScheduledRedemptions + +Get the `totalShares` of scheduled redemptions for `redeemer`. + +MUST NOT revert. + +```yaml +- name: getScheduledRedemptions + type: function + stateMutability: view + + inputs: + - name: redeemer + - type: address + + outputs: + - name: totalShares + - type: uint256 +``` + +### Events + +#### ScheduleDeposit + +`sender` schedules a deposit with `assets` in this `round`. + +MUST be emitted via `scheduleDeposit` method. + +```yaml +- name: ScheduleDeposit + type: event + + inputs: + - name: sender + indexed: true + type: address + - name: assets + indexed: false + type: uint256 + - name: round + indexed: false + type: uint256 +``` + +#### ScheduleRedeem + +`sender` schedules a redemption with `shares` in this `round`. + +MUST be emitted via `scheduleRedeem` method. + +```yaml +- name: ScheduleRedeem + type: event + + inputs: + - name: sender + indexed: true + type: address + - name: shares + indexed: false + type: uint256 + - name: round + indexed: false + type: uint2 +``` + +#### SettleDeposits + +Settle scheduled deposits for `depositor` in this `round`. Issue `newShares` and transfer them to the `depositor`. + +MUST be emitted via `settleDeposits` method. + +```yaml +- name: SettleDeposits + type: event + + inputs: + - name: depositor + indexed: true + type: address + - name: newShares + type: uint256 + - name: round + type: uint256 +``` + +#### SettleRedemptions + +Settle scheduled redemptions for `redeemer` in this `round`. Burn `burnShares` and transfer `redeemAssets` back to the `redeemer`. + +MUST be emitted via `settleRedemptions` method. + +```yaml +- name: SettleRedemptions + type: event + + inputs: + - name: redeemer + indexed: true + type: address + - name: burnShares + type: uint256 + - name: redeemAssets + type: uint256 + - name: round + type: uint256 +``` + +## Rationale + +The standard is designed to be a minimal interface. Details such as the start and end of a lock-in period, and how the underlying tokens are being used during the lock-in period are not specified. + +There is no function for scheduling a withdrawal, since during the lock-in period, the share price is undetermined, so it is impossible to determine how many underlying tokens can be withdrawn. + +## Backwards Compatibility + +The `deposit`, `mint`, `withdraw`, `redeem` methods for [EIP-4626](./eip-4626.md) should revert when the `isLocked` is true to prevent issuing or burning shares with an undefined share price. + +## Security Considerations + +Implementors need to be aware of unsettled scheduled deposits and redemptions. If a user has scheduled a deposit or redemption but does not settle when the `isLocked` is false, and then settles it after several rounds, the vault will process it with an incorrect share price. We didn’t specify the solution in the standard since there are many possible ways to solve this issue and we think implementors should decide the solution according to their use cases. For example: + +- Not allow the `isLocked` to become true if there is any unsettled scheduled deposit or redemption +- Force settling the scheduled deposits or redemptions when the `isLocked` becomes true +- Memorize the ending share price for each round and let the users settle according to the share prices + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/ERCS/erc-6538.md b/ERCS/erc-6538.md index 3b25731dc0..fcb1ffc88b 100644 --- a/ERCS/erc-6538.md +++ b/ERCS/erc-6538.md @@ -4,8 +4,7 @@ title: Stealth Meta-Address Registry description: A canonical contract for entities to register stealth meta-addresses directly or through a third party using signatures. author: Matt Solomon (@mds1), Toni Wahrstätter (@nerolation), Ben DiFrancesco (@apbendi), Vitalik Buterin (@vbuterin), Gary Ghayrat (@garyghayrat) discussions-to: https://ethereum-magicians.org/t/stealth-meta-address-registry/12888 -status: Last Call -last-call-deadline: 2024-06-11 +status: Final type: Standards Track category: ERC created: 2023-01-24 diff --git a/ERCS/erc-6860.md b/ERCS/erc-6860.md index 092e988adf..a79ddac86e 100644 --- a/ERCS/erc-6860.md +++ b/ERCS/erc-6860.md @@ -38,7 +38,7 @@ This specification uses the Augmented Backus-Naur Form (ABNF) notation of [RFC 2 A Web3 URL is an ASCII string in the following form : ``` -web3URL = schema "://" [ userinfo "@" ] contractName [ ":" chainid ] pathQuery +web3URL = schema "://" [ userinfo "@" ] contractName [ ":" chainid ] pathQuery [ "#" fragment ] schema = "w3" / "web3" userinfo = address ``` @@ -69,6 +69,12 @@ pathQuery = mPathQuery ; path+query for manual mode **pathQuery**, made of the path and optional query, will have a different structure whether the resolve mode is "manual" or "auto". +``` +fragment = *VCHAR +``` + +**fragment**, like in HTTP URLs, is a string of characters meant to refer to a resource, and is not transmitted to the smart contract. + ``` web3UrlRef = web3URL / relativeWeb3URL @@ -286,7 +292,7 @@ The protocol will find the address of **vitalik.eth** from ENS on chainid 1 (Mai ### Appendix A: Complete ABNF for Web3 URLs ``` -web3URL = schema "://" [ userinfo "@" ] contractName [ ":" chainid ] pathQuery +web3URL = schema "://" [ userinfo "@" ] contractName [ ":" chainid ] pathQuery [ "#" fragment ] schema = "w3" / "web3" userinfo = address contractName = address @@ -295,6 +301,7 @@ chainid = %x31-39 *DIGIT pathQuery = mPathQuery ; path+query for manual mode / aPathQuery ; path+query for auto mode +fragment = *VCHAR web3UrlRef = web3URL / relativeWeb3URL diff --git a/ERCS/erc-6900.md b/ERCS/erc-6900.md index a854fbbca8..6cd2d28886 100644 --- a/ERCS/erc-6900.md +++ b/ERCS/erc-6900.md @@ -434,7 +434,7 @@ struct PluginManifest { ### Expected behavior -#### Responsibilties of `StandardExecutor` and `PluginExecutor` +#### Responsibilities of `StandardExecutor` and `PluginExecutor` `StandardExecutor` functions are used for open-ended calls to external addresses. @@ -550,9 +550,9 @@ See `https://github.com/erc6900/reference-implementation` ## Security Considerations -As much as the modular smart contract accounts themselves, installed plugins are trusted components, as a modular smart contract account can be almost arbitrarily configured through installed plugins. A malicious plugin could add always reverting hooks to native function selectors, bricking the account, as well as add function selectors that end up draining the funds of the account. Users should therefore be very careful in what plugins to add to their account. +The modular smart contract accounts themselves are trusted components. Installed plugins are trusted to varying degrees, as plugins can interact with an arbitrarily large or small set of resources on an account. For example, a wide-reaching malicious plugin could add reverting hooks to native function selectors, bricking the account, or add execution functions that may drain the funds of the account. However, it is also possible to install a plugin with a very narrow domain, and depend on the correctness of the account behavior to enforce its limited access. Users should therefore be careful in what plugins to add to their account. -Users should therefore perform careful due diligence before installing a plugin and should be mindful of the fact that plugins are potentially dangerous. The plugin's manifest can give users an understanding of the domain of the plugin, i.e., the requested permissions to install certain validation functions and/or hooks on certain execution selectors. Generally, plugins that include native function selectors in their domain, e.g., plugins that add a validation hook to the native `uninstallPlugin()` function, can potentially introduce significantly more harm than plugins that simply add validation hooks to function selectors that the plugin itself is adding to the account. +Users should perform careful due diligence before installing a plugin and should be mindful of the fact that plugins are potentially dangerous. The plugin's manifest can give users an understanding of the domain of the plugin, i.e., the requested permissions to install certain validation functions and/or hooks on certain execution selectors. Generally, plugins that include native function selectors in their domain, e.g., plugins that add a validation hook to the native `uninstallPlugin()` function, can introduce significantly more harm than plugins that simply add validation hooks to function selectors that the plugin itself is adding to the account. Plugins can also add validation hooks to function selectors installed by other plugins. While usually, such a plugin would, e.g., add additional pre-validation hooks, it can also cause the previously installed plugin to be executed in an unintended context. For example, if a plugin were to only be intended to operate in the user operation context, its plugin manifest might only define user operation validation functions. However, another plugin might add a passing runtime validation function to that function selector, causing, for example, a session key plugin to suddenly be executed in a runtime validation context, circumventing all the parameter-validation that would have happened during user operation validation and granting unrestricted access to all session keys. Therefore, it is strongly recommended to always add reverting validation hooks to the context the plugin is not intended to be executed in. This recommendation may change in the next iteration of the standard. diff --git a/ERCS/erc-6909.md b/ERCS/erc-6909.md index ee0b569418..29a5011354 100644 --- a/ERCS/erc-6909.md +++ b/ERCS/erc-6909.md @@ -134,7 +134,7 @@ MUST return True. Transfers an `amount` of a token `id` from a `sender` to a `receiver` by the caller. -MUST revert when the caller is not an operator for the `sender` and the caller's allowance for the token `id` for the `sender` is insufficient. +MUST revert when the caller is neither the `sender` nor an operator for the `sender` and the caller's allowance for the token `id` for the `sender` is insufficient. MUST revert when the `sender`'s balance for the token id is insufficient. @@ -144,7 +144,7 @@ MUST decrease the caller's `allowance` by the same `amount` of the `sender`'s ba SHOULD NOT decrease the caller's `allowance` for the token `id` for the `sender` if the `allowance` is infinite. -SHOULD NOT decrease the caller's `allowance` for the token `id` for the `sender` if the caller is an operator. +SHOULD NOT decrease the caller's `allowance` for the token `id` for the `sender` if the caller is an operator or the `sender`. MUST return True. diff --git a/ERCS/erc-7007.md b/ERCS/erc-7007.md index 34801db404..caac5be17b 100644 --- a/ERCS/erc-7007.md +++ b/ERCS/erc-7007.md @@ -4,7 +4,8 @@ title: Verifiable AI-Generated Content Token description: An ERC-721 extension for verifiable AI-generated content tokens using Zero-Knowledge and Optimistic Machine Learning techniques author: Cathie So (@socathie), Xiaohang Yu (@xhyumiracle), Conway (@0x1cc), Lee Ting Ting (@tina1998612), Kartin discussions-to: https://ethereum-magicians.org/t/eip-7007-zkml-aigc-nfts-an-erc-721-extension-interface-for-zkml-based-aigc-nfts/14216 -status: Review +status: Last Call +last-call-deadline: 2024-09-30 type: Standards Track category: ERC created: 2023-05-10 @@ -13,17 +14,17 @@ requires: 165, 721 ## Abstract -The verifiable AI-generated content (AIGC) non-fungible token (NFT) standard is an extension of the [ERC-721](./eip-721.md) token standard for AIGC. It proposes a set of interfaces for basic interactions and enumerable interactions for AIGC-NFTs. The standard includes a `mint` and `verify` function interface, a new `Mint` event, optional `Enumerable` and `Updatable` extensions, and a JSON schema for AIGC-NFT metadata. Additionally, it incorporates Zero-Knowledge Machine Learning (zkML) and Optimistic Machine Learning (opML) capabilities to enable verification of AIGC data correctness. In this standard, the `tokenId` is indexed by the `prompt`. +The verifiable AI-generated content (AIGC) non-fungible token (NFT) standard is an extension of the [ERC-721](./eip-721.md) token standard for AIGC. It proposes a set of interfaces for basic interactions and enumerable interactions for AIGC-NFTs. The standard includes an `addAigcData` and `verify` function interface, a new `AigcData` event, optional `Enumerable` and `Updatable` extensions, and a JSON schema for AIGC-NFT metadata. Additionally, it incorporates Zero-Knowledge Machine Learning (zkML) and Optimistic Machine Learning (opML) capabilities to enable verification of AIGC data correctness. In this standard, the `tokenId` is indexed by the `prompt`. ## Motivation -The verifiable AIGC-NFT standard aims to extend the existing [ERC-721](./eip-721.md) token standard to accommodate the unique requirements of AI-generated content NFTs representing models in a collection. This standard provides interfaces to use zkML or opML to verify whether or not the AIGC data for an NFT is generated from a certain ML model with a certain input (prompt). The proposed interfaces allow for additional functionality related to minting, verifying, and enumerating AIGC-NFTs. Additionally, the metadata schema provides a structured format for storing information related to AIGC-NFTs, such as the prompt used to generate the content and the proof of ownership. +The verifiable AIGC-NFT standard aims to extend the existing [ERC-721](./eip-721.md) token standard to accommodate the unique requirements of AI-generated content NFTs representing models in a collection. This standard provides interfaces to use zkML or opML to verify whether or not the AIGC data for an NFT is generated from a certain ML model with a certain input (prompt). The proposed interfaces allow for additional functionality related to adding AIGC data, verifying, and enumerating AIGC-NFTs. Additionally, the metadata schema provides a structured format for storing information related to AIGC-NFTs, such as the prompt used to generate the content and the proof of ownership. This standard supports two primary types of proofs: validity proofs and fraud proofs. In practice, zkML and opML are commonly employed as the prevailing instances for these types of proofs. Developers can choose their preferred ones. In the zkML scenario, this standard enables model owners to publish their trained model and its ZKP verifier to Ethereum. Any user can claim an input (prompt) and publish the inference task. Any node that maintains the model and the proving circuit can perform the inference and proving, and submit the output of inference and the ZK proof for the inference trace to the verifier. The user that initiates the inference task will own the output for the inference of that model and input (prompt). -In the opML scenario, this standard enables model owners to publish their trained model to Ethereum. Any user can claim an input (prompt) and publish the inference task. Any node that maintains the model can perform the inference and submit the inference output. Other nodes can challenge this result within a predefined challenge period. At the end of the challenge period, the user can verify that they own the output for the inference of that model and prompt. +In the opML scenario, this standard enables model owners to publish their trained model to Ethereum. Any user can claim an input (prompt) and publish the inference task. Any node that maintains the model can perform the inference and submit the inference output. Other nodes can challenge this result within a predefined challenge period. At the end of the challenge period, the user can verify that they own the output for the inference of that model and prompt, and update the AIGC data as needed. This capability is especially beneficial for AI model authors and AI content creators seeking to capitalize on their creations. With this standard, every input prompt and its resulting content can be securely verified on the blockchain. This opens up opportunities for implementing revenue-sharing mechanisms for all AI-generated content (AIGC) NFT sales. AI model authors can now share their models without concerns that open-sourcing will diminish their financial value. @@ -32,10 +33,11 @@ An example workflow of a zkML AIGC NFT project compliant with this proposal is a ![zkML Suggested Workflow](../assets/eip-7007/workflow.png) There are 4 components in this workflow: -* ML model - contains weights of a pre-trained model; given an inference input, generates the output -* zkML prover - given an inference task with input and output, generates a ZK proof -* AIGC-NFT smart contract - contract compliant with this proposal, with full [ERC-721](./eip-721.md) functionalities -* Verifier smart contract - implements a `verify` function, given an inference task and its ZK proof, returns the verification result as a boolean + +- ML model - contains weights of a pre-trained model; given an inference input, generates the output +- zkML prover - given an inference task with input and output, generates a ZK proof +- AIGC-NFT smart contract - contract compliant with this proposal, with full [ERC-721](./eip-721.md) functionalities +- Verifier smart contract - implements a `verify` function, given an inference task and its ZK proof, returns the verification result as a boolean ## Specification @@ -43,47 +45,37 @@ The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SH **Every compliant contract must implement the `IERC7007`, [`ERC721`](./eip-721.md), and [`ERC165`](./eip-165.md) interfaces.** -The verifiable AIGC-NFT standard includes the following interfaces: +The verifiable AIGC-NFT standard includes the following interfaces: -`IERC7007`: Defines a `mint` function and a `Mint` event for minting AIGC-NFTs. Defines a `verify` function to check the validity of the combination of prompt and aigcData using zkML/opML techniques. +`IERC7007`: Defines an `addAigcData` function and an `AigcData` event for adding AIGC data to AIGC-NFTs. Defines a `verify` function to check the validity of the combination of prompt and aigcData using zkML/opML techniques. ```solidity pragma solidity ^0.8.18; /** * @dev Required interface of an ERC7007 compliant contract. - * Note: the ERC-165 identifier for this interface is 0x7e52e423. + * Note: the ERC-165 identifier for this interface is 0x702c55a6. */ interface IERC7007 is IERC165, IERC721 { /** - * @dev Emitted when `tokenId` token is minted. + * @dev Emitted when `tokenId` token's AIGC data is added. */ - event Mint( - address indexed to, + event AigcData( uint256 indexed tokenId, bytes indexed prompt, - bytes aigcData, - string uri, + bytes indexed aigcData, bytes proof ); /** - * @dev Mint token at `tokenId` given `to`, `prompt`, `aigcData`, `uri`, and `proof`. `proof` means that we input the ZK proof when using zkML and byte zero when using opML as the verification method. - * - * Requirements: - * - `tokenId` must not exist.' - * - verify(`prompt`, `aigcData`, `proof`) must return true. - * - * Optional: - * - `proof` should not include `aigcData` to save gas. + * @dev Add AIGC data to token at `tokenId` given `prompt`, `aigcData`, and `proof`. */ - function mint( - address to, + function addAigcData( + uint256 tokenId, bytes calldata prompt, bytes calldata aigcData, - string calldata uri, bytes calldata proof - ) external returns (uint256 tokenId); + ) external; /** * @dev Verify the `prompt`, `aigcData`, and `proof`. @@ -129,6 +121,7 @@ pragma solidity ^0.8.18; /** * @title ERC7007 Token Standard, optional updatable extension + * Note: the ERC-165 identifier for this interface is 0x3f37dce2. */ interface IERC7007Updatable is IERC7007 { /** @@ -136,8 +129,7 @@ interface IERC7007Updatable is IERC7007 { */ function update( bytes calldata prompt, - bytes calldata aigcData, - string calldata uri + bytes calldata aigcData ) external; /** @@ -146,8 +138,7 @@ interface IERC7007Updatable is IERC7007 { event Update( uint256 indexed tokenId, bytes indexed prompt, - bytes indexed aigcData, - string uri + bytes indexed aigcData ); } ``` @@ -156,44 +147,44 @@ interface IERC7007Updatable is IERC7007 { ```json { - "title": "AIGC Metadata", - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Identifies the asset to which this NFT represents" - }, - "description": { - "type": "string", - "description": "Describes the asset to which this NFT represents" - }, - "image": { - "type": "string", - "description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive." - }, - "prompt": { - "type": "string", - "description": "Identifies the prompt from which this AIGC NFT generated" - }, - "aigc_type": { - "type": "string", - "description": "image/video/audio..." - }, - "aigc_data": { - "type": "string", - "description": "A URI pointing to a resource with mime type image/* representing the asset to which this AIGC NFT represents." - }, - "proof_type": { - "type": "string", - "description": "validity (zkML) or fraud (opML)" - } + "title": "AIGC Metadata", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Identifies the asset to which this NFT represents" + }, + "description": { + "type": "string", + "description": "Describes the asset to which this NFT represents" + }, + "image": { + "type": "string", + "description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive." + }, + "prompt": { + "type": "string", + "description": "Identifies the prompt from which this AIGC NFT generated" + }, + "aigc_type": { + "type": "string", + "description": "image/video/audio..." + }, + "aigc_data": { + "type": "string", + "description": "A URI pointing to a resource with mime type image/* representing the asset to which this AIGC NFT represents." + }, + "proof_type": { + "type": "string", + "description": "validity (zkML) or fraud (opML)" } + } } ``` ### ML Model Publication -While this standard does not describe the Machine Learning model publication stage, it is natural and recommended to publish the commitment of the Model to Ethereum separately, before any actual `mint` actions. The model commitment schema choice lies on the AIGC-NFT project issuer party. The commitment should be checked inside the implementation of the `verify` function. +While this standard does not describe the Machine Learning model publication stage, it is natural and recommended to publish the commitment of the Model to Ethereum separately, before any actual `addAigcData` actions. The model commitment schema choice lies on the AIGC-NFT project issuer party. The commitment should be checked inside the implementation of the `verify` function. ## Rationale @@ -203,15 +194,15 @@ This specification sets the `tokenId` to be the hash of its corresponding `promp ### Generalization to Different Proof Types -This specification accommodates two proof types: validity proofs for zkML and fraud proofs for opML. Function arguments in `mint` and `verify` are designed for generality, allowing for compatibility with both proof systems. Moreover, the specification includes an updatable extension that specifically serves the requirements of opML. +This specification accommodates two proof types: validity proofs for zkML and fraud proofs for opML. Function arguments in `addAigcData` and `verify` are designed for generality, allowing for compatibility with both proof systems. Moreover, the specification includes an updatable extension that specifically serves the requirements of opML. ### `verify` interface -We specify a `verify` interface to enforce the correctness of `aigcData`. It is defined as a view function to reduce gas cost. `verify` should return true if and only if `aigcData` is finalized in both zkML and opML. In zkML, it must verify the ZK proof, i.e. `proof`; in opML, it must make sure that the challenging period is finalized, and that the `aigcData` is up-to-date, i.e. has been updated after finalization. Additionally, `proof` can be *empty* in opML. +We specify a `verify` interface to enforce the correctness of `aigcData`. It is defined as a view function to reduce gas cost. `verify` should return true if and only if `aigcData` is finalized in both zkML and opML. In zkML, it must verify the ZK proof, i.e. `proof`; in opML, it must make sure that the challenging period is finalized, and that the `aigcData` is up-to-date, i.e. has been updated after finalization. Additionally, `proof` can be _empty_ in opML. -### `mint` interface +### `addAigcData` interface -We specify a `mint` interface to bind the prompt and `aigcData` with `tokenId`. Notably, it acts differently in zkML and opML cases. In zkML, `mint` should make sure `verify` returns `true`. While in opML, it can be called before finalization. The consideration here is that, limited by the proving difficulty, zkML usually targets simple model inference tasks in practice, making it possible to provide a proof within an acceptable time frame. On the other hand, opML enables large model inference tasks, with a cost of longer confirmation time to achieve the approximate same security level. Mint until opML finalization may not be the best practice considering the existing optimistic protocols. +We specify an `addAigcData` interface to bind the prompt and `aigcData` with `tokenId`. This function provides flexibility for different minting implementations. Notably, it acts differently in zkML and opML cases. In zkML, `addAigcData` should make sure `verify` returns `true`. While in opML, it can be called before finalization. The consideration here is that, limited by the proving difficulty, zkML usually targets simple model inference tasks in practice, making it possible to provide a proof within an acceptable time frame. On the other hand, opML enables large model inference tasks, with a cost of longer confirmation time to achieve the approximate same security level. Mint until opML finalization may not be the best practice considering the existing optimistic protocols. ### Naming Choice on `update` @@ -227,8 +218,8 @@ The reference implementation includes sample implementations of the [ERC-7007](. ## Reference Implementation -* ERC-7007 for [zkML](../assets/eip-7007/contracts/ERC7007Zkml.sol) and [opML](../assets/eip-7007/contracts/ERC7007Opml.sol) -* [ERC-7007 Enumerable Extension](../assets/eip-7007/contracts/ERC7007Enumerable.sol) +- ERC-7007 for [zkML](../assets/eip-7007/contracts/ERC7007Zkml.sol) and [opML](../assets/eip-7007/contracts/ERC7007Opml.sol) +- [ERC-7007 Enumerable Extension](../assets/eip-7007/contracts/ERC7007Enumerable.sol) ## Security Considerations @@ -242,4 +233,4 @@ In the opML scenario, it is important to consider that the `aigcData` might chan ## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +Copyright and related rights waived via [CC0](../LICENSE.md). \ No newline at end of file diff --git a/ERCS/erc-7015.md b/ERCS/erc-7015.md index 3cb425dfb9..ac21a71b1e 100644 --- a/ERCS/erc-7015.md +++ b/ERCS/erc-7015.md @@ -2,9 +2,9 @@ eip: 7015 title: NFT Creator Attribution description: Extending NFTs with cryptographically secured creator attribution. -author: Carlos Flores (@strollinghome) +author: indreams (@strollinghome) discussions-to: https://ethereum-magicians.org/t/eip-authorship-attribution-for-erc721/14244 -status: Draft +status: Review type: Standards Track category: ERC created: 2023-05-11 @@ -29,7 +29,7 @@ This EIP takes advantage of the fact that contract addresses can be precomputed **Signing Mechanism** -Creator consent is given by signing an [EIP-712](./eip-712.md) compatible message; all signatures compliant with this EIP MUST include all fields defined. The struct signed can be any arbitrary data that defines how to create the token; it must hashed in an EIP-712 compatible format with a proper EIP-712 domain. +Creator consent is given by signing an [EIP-712](./eip-712.md) compatible message; all signatures compliant with this EIP MUST include all fields defined. The struct signed can be any arbitrary data that defines how to create the token; it must hashed in an EIP-712 compatible format with a proper EIP-712 domain. The following shows some examples of structs that could be encoded into `structHash` (defined below): @@ -48,7 +48,7 @@ struct TokenCreation { Creator attribution is given through a signature verification that MUST be verified by the NFT contract being deployed and an event that MUST be emitted by the NFT contract during the deployment transaction. The event includes all the necessary fields for reconstructing the signed digest and validating the signature to ensure it matches the specified creator. The event name is `CreatorAttribution` and includes the following fields: - `structHash`: hashed information for deploying the NFT contract (e.g. name, symbol, admins etc). This corresponds to the value `hashStruct` as defined in the [EIP-712 definition of hashStruct](./eip-712.md#definition-of-hashstruct) standard. -- `domainName`: the domain name of the contract verifying the singature (for EIP-712 signature validation). +- `domainName`: the domain name of the contract verifying the singature (for EIP-712 signature validation). - `version`: the version of the contract verifying the signature (for EIP-712 signature validation) - `creator`: the creator's account - `signature`: the creator’s signature @@ -57,11 +57,11 @@ The event is defined as follows: ```solidity event CreatorAttribution( - bytes32 structHash, - string domainName, - string version, + bytes32 structHash, + string domainName, + string version, address creator, - bytes signature + bytes signature ); ``` diff --git a/ERCS/erc-7208.md b/ERCS/erc-7208.md index 738348c309..8b4d1d3f23 100644 --- a/ERCS/erc-7208.md +++ b/ERCS/erc-7208.md @@ -1,789 +1,286 @@ --- eip: 7208 title: On-Chain Data Container -description: Abstracting logic away from storage +description: ERC interoperability by abstracting logic away from storage author: Rachid Ajaja , Alexandros Athanasopulos (@Xaleee), Pavel Rubin (@pash7ka), Sebastian Galimberti Romano (@galimba) discussions-to: https://ethereum-magicians.org/t/erc-7208-on-chain-data-container/14778 status: Draft type: Standards Track category: ERC created: 2023-06-09 -requires: 165, 721 +requires: 165 --- ## Abstract -On-chain Data Containers are smart contracts that inherit from [ERC-721](./eip-721.md) to store on-chain data in structures called "Properties". Information stored in Properties can be accessed and modified by the implementation of smart contracts called "Property Managers". This ERC defines a series of interfaces for the separation of the storage from the interface implementing the functions that govern the data. We introduce the interface for "Restrictions", structures associated with Properties that apply limitations in the capabilities of Property Managers to access or modify the data stored within Properties. +"On-chain Data Containers" (ODCs) are a series of interfaces used for indexing and managing data in Smart Contracts called "Data Object" (DO). Information stored in Data Objects can be accessed and modified by implementing smart contracts called "Data Manager" (DM). This ERC defines a series of interfaces for the separation of the storage of data from the implementation of the logic functions that govern such data. We introduce the interfaces for access management through "Data Index" (DI) implementations, the structures associated with "Data Points" (DP) for abstracting storage, the Data Managers to access or modify the data, and finally the "Data Point Registries" (DPR) interfaces for compatibility that enable data portability (horizontal mobility) between different implementations of this ERC. -## Motivation - -As the Ethereum ecosystem continues to grow, it is becoming increasingly important to enable more flexible and sophisticated on-chain data management solutions, as well as off-chain assets and their on-chain representation. We have seen too many times where the market hype has driven an explosion of new standard token proposals, most of them targeting a specific business use-case rather than increasing interoperability. - -This ERC defines several interface for ODCs (On-Chain Data Containers) that together allow for the storage and retrieval of additional on-chain data, referred to as "Properties". This provides an interoperable solution for dynamic data management across various token standards, enabling them to hold mutable states and reflect changes over time. - -ODCs aim to address the limitations of both previous and future ERCs by enabling on-chain data aggregation, providing an interface for standardized, interoperable, and composable data management solutions. -ODCs address some of the existing limitations with previous ERC token contracts by separating the logic from the storage. +## Motivation -This ERC defines a standard interface for interacting with the concept of "Restrictions" for data stored within an ODC ("Properties"). This enables greater flexibility, interoperability, and utility for multiple ERCs to work together. +As the Ethereum ecosystem grows, so does the demand for on-chain functionalities. The market encourages a desire for broader adoption through more complex systems and there is a constant need for improved efficiency. We have seen times where the market hype has driven an explosion of new standard token proposals. While each standard serves its purpose, most often requires more flexibility to manage interoperability with other standards. -This proposal is motivated by the need to extend the capabilities of on-chain stored data beyond the static nature of each ERC, enabling complex logic to be abstracted away from the stored variables. This is particularly relevant for use cases where the state of the NFT needs to change in response to certain events or conditions, as well as when the storage and the logic must be separated. For instance, NFTs representing Account Abstraction contracts, Smart Wallets, or the digital representation of Real World Assets, all of which require dynamic and secure storage. -NFTs conforming to standards such as [ERC-721](./eip-721.md) have often faced limitations when representing complex digital assets. The Ethereum ecosystem hosts a rich diversity of token standards, each designed to cater to specific use cases. While such diversity spurs innovation, it also results in a highly fragmented landscape, especially for Non-Fungible Tokens (NFTs). Different projects might implement their own ways of managing mutable states, incurring further fragmentation and interoperability issues. While each standard serves its purpose, they often lack the flexibility needed to manage additional on-chain data associated with the utility of these tokens. +The diversity of standards spurs innovation. Different projects will implement their bespoke solutions for interoperability. The absence of a unified adapter mechanism driving the interactions between assets issued under different ERC standards is causing interoperability issues. This, in turn, is leading to fragmentation. -Real-world assets have multiple ways in which they can be represented as on-chain tokens by utilizing different standard interfaces. However, for those assets to be exchanged, traded or interacted with, the marketplace is required to implement each of those standards in order to be able to access and modify the on-chain data. -Therefore, there is a need for standardization to manage these mutable states for tokenization in a manner that abstracts the on-chain data handling from the logical accounting. Such standard would provide all ERCs, regardless of their specific use case, with the mechanisms for interacting with each other in a consistent and predictable way. +We recognize there is no “one size fits all” solution to solve the standardization and interoperability challenges. Most assets - Fungible, Non-Fungible, Digital Twins, Real-world Assets, DePin, etc - have multiple mechanisms for representing them as on-chain tokens through the use of different standard interfaces. However, for those assets to be exchanged, traded, or interacted with, protocols must implement compatibility with those standards before accessing and modifying the on-chain data. Moreover, the immutability of smart contracts plays a role in future-proofing their implementations by supporting new tokenization standards. A collaborative effort must be made to enable interaction between assets tokenized under different standards. The current ERC provides the tools for developing such on-chain adapters. -This EIP proposes a series of interfaces for storing and accessing data on-chain, codifying information as generic Properties associated with Restrictions specific to use cases. This enhancement is designed to work by extending existing token standards, providing a flexible, efficient, and coherent way to manage the data associated with: -- **Standard Neutrality**: The standard aims to separate the data logic from the token standard. This neutral approach would allow ERCs to transition seamlessly between different token standards, promoting interactions with platforms or marketplaces designed for those standards. This could significantly improve interoperability among different standards, reducing fragmentation in the landscape. +We aim to abstract the on-chain data handling from the logical implementation and the ERC interfaces exposing the underlying data. The current ERC proposes a series of interfaces for storing and accessing data on-chain, codifying the underlying assets as generic "Data Points" that may be associated with multiple interoperable and even concurrent ERC interfaces. This proposal is designed to work by coexisting with previous and future token standards, providing a flexible, efficient, and coherent mechanism to manage asset interoperability. -- **Consistent Interface**: A uniform interface abstracts the data storage from the use case, irrespective of the underlying token standard. This consistent interface simplifies interoperability, enabling platforms and marketplaces to interact with a uniform data interface, regardless of individual token standards. This common ground for all tokenization could reduce fragmentation in the ecosystem. +- **Data Abstraction**: We propose a standardized interface for enabling developers to separate the data storage code from the underlying token utility logic, reducing the need for supporting and implementing multiple inherited -and often clashing- interfaces to achieve asset compatibility. The data (and therefore the assets) can be stored independently of the logic that governs such data. -- **Simplified Upgrades**: A standard interface for representing the utility of the on-chain data would simplify the process of integrating new token standards. This could help to reduce fragmentation caused by outdated standards, facilitating easier transition to new, more efficient, or feature-rich implementations. +- **Standard Neutrality**: A neutral approach must enable the underlying data of any tokenized asset to transition seamlessly between different token standards. This will significantly improve interoperability among different standards, reducing fragmentation in the landscape. Our proposal aims to separate the storage of data representing an underlying asset from the standard interface used for representing the token. -- **Data Abstraction**: A standardized interface would allow developers to separate the data storage code from the underlying token utility logic, reducing the need for off-chain services to implement multiple interfaces and promoting greater unity in the ecosystem. +- **Consistent Interface**: A uniform interface of primitive functions abstracts the data storage from the use case, irrespective of the underlying token's standard or the interface used for exposing such data. Both data as well as metadata can be stored on-chain, and exposed through the same functions. -- **Actionable data**: Current practices often store token metadata off-chain, rendering it inaccessible for smart contracts without the use of oracles. Moreover, metadata is often used to store information that could otherwise be considered data relevant to the token's inherent identity. This ERC seeks to rectify this issue by introducing a standardized interface for reading and storing additional on-chain data related to ODC. +- **Data Portability**: We provide a mechanism for the Horizontal Mobility of data between implementations of this standard, incentivizing the implementation of interoperable solutions and standard adapters. -A case-by-case limited analysis is provided in the compatibility appendix. ## Specification -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. ### Terms -**ODC**: A uniquely identifiable non-fungible token. An ODC MAY store information within Properties. - -**Property**: A modifiable information unit stored within an ODC. Properties SHOULD be capable of undergoing modifications and MUST be able to act as an indexed data container. - -**Restriction**: A configuration stored within the **ODC**, that SHOULD describe under which conditions a certain **Property Manager** is allowed to modify the information stored within a certain **Property**. +**Data Index Implementation**: One or many Smart Contracts implementing the Data Index interface, used for data access management through the indexing of Data Objects. -**Property Manager**: A type of Smart Contract that MUST implement a **PM interface** in order to manage data stored within the **ODC**. +**Data Point**: A uniquely identifiable unit of information indexed by a Data Index, managed by a Data Manager through a Data Object, and provided by a Data Point Registry. -**Category**: **Property Managers** MUST be grouped in Categories that SHOULD represent access to **Properties**. Each **Property Manager** MAY be part of one or more **Categories**. The assignment of categories SHOULD be managed by Governance. +**Data Object**: One or many Smart Contracts implementing the low-level storage management of stored Data Points. -### ODC Interface +**Data Manager**: One or many Smart Contracts implementing the high-level logic and end-user interfaces for managing Data Points. -An ODC MUST extend the functionality of [ERC-721](./eip-721.md) through the incorporation of **Properties** in its internal storage. The **Properties** MAY have **Restrictions**. - -### Properties - -**Properties** are modifiable information units stored within an ODC. Properties SHOULD be capable of undergoing modifications and MUST be able to act as an indexed data container. - -```solidity -/** - * @notice Gets a Property Data point of the ODC. - * @dev This function allows anyone to get a property of the ODC. - * @param ouid The unique ID of the ODC. - * @param propertyKey The key of the property to be retrieved. - * @param dataKey The key of the data inside of Property. - * @return The value of the data point within the Property. - */ - function getPropertyData( - uint256 ouid, - bytes32 propertyKey, - bytes32 dataKey - ) external view returns (bytes32); -``` +**Data Point Registry**: One or many Smart Contracts that define a space of compatible or interoperable Data Points. -```solidity -/** - * @notice Sets a Property Data point within the ODC. - * @dev This function allows the owner or an authorized operator to set a property of the ODC. - * @param ouid The unique ID of the ODC. - * @param propertyKey The key of the property to be set. - * @param dataKey The Key of the property to be set. - * @param dataValue The value of the data point to be set within the Property. - */ - function setPropertyData( - uint256 ouid, - bytes32 propertyKey, - bytes32 dataKey, - bytes32 dataValue - ) external; -``` - -- **getProperty**: This function MUST retrieve a specific `dataValue` of an ODC, identifiable through the `tokenId`, `propertyKey`, and `dataKey` parameters. - - -- **setProperty**: This function MUST set or update a specific Property data point within an ODC. This operation is REQUIRED to be executed solely by the owner of the ODC or an approved Smart Contract. This function MUST set the `dataValue` within the `dataKey` of `propertyKey` for the `tokenId`. - -```solidity -/** - * @title Onchain Data Container (ODC) Interface for Properties Data - * Provides functions to manipulate data stored in ODC token - */ -interface IODCProperties { - /** - * @notice Returns data stored in a property - * @param odcid ODC token id - * @param prop Property key - * @param dataType Defines which data is returned (implementation-specific) - * @param hint Data-type dependent, can be zero-length, can be used to specify part of the data to get (for example, if `dataType` specifies a Map, `hint` can be a key in this Map) - * @return Data previously stored. SHOULD be empty array if nothing was stored. - */ - function propertyDataOf( - uint256 odcid, - bytes32 prop, - bytes32 dataType, - bytes calldata hint - ) external view returns (bytes memory); - - /** - * @notice Returns data stored in a property - * @dev Access to this function SHOULD be allowed only to PropertyManagers approved for this property, see IODCPermissions - * @param odcid ODC token id - * @param prop Property key - * @param dataType Defines which data is returned (implementation-specific) - * @param data Data to store (how the data is stored is implementation-specifiec and can be different for different dataType) - */ - function setPropertyData( - uint256 odcid, - bytes32 prop, - bytes32 dataType, - bytes calldata data - ) external; -} -``` +The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. +### Data Index Interface -```solidity -/** - * @title Onchain Data Container (ODC) Interface for Restrictions - * Provides functions to restrict certain actions with ODC token - */ -interface IODCRestrictions { - /** - * @notice Retrievs list of restrictions associated with specified ODC token - * @param odcid ODC token id - * @return rids List of restriction ids - */ - function restrictionsOf(uint256 odcid) external view returns(uint256[] memory rids); - - /** - * @notice Retrieves detalis of restriction - * @param rid Id of restriction to read - * @return odcid ODC token id - * @return prop property the restriction is associated with - * @return restrictionType Type of restriction - * @return data Data of restriction (may be different from the `initData` used to add restriction) - */ - function restriction(uint256 rid) external view returns(uint256 odcid, bytes32 prop, bytes32 restrictionType, bytes memory data); - - /** - * @notice Adds a restriction associated with `odcid` and a property - * @dev Access to this function SHOULD be allowed only to PropertyManagers approved for this property, see IODCPermissions - * @param odcid ODC token id - * @param prop Property key - * @param restrictionType Defines type of Restriction. The implementation should manage list of supported Restriction Types - * @param initData Data to initialize the Restriction - * @return rid ID of added restriction (unique for all `odcid` in this contract, will never change untill removed and can't be reused) - */ - function addRestriction( - uint256 odcid, - bytes32 prop, - bytes32 restrictionType, - bytes calldata initData - ) external returns(uint256 rid); - - /** - * @notice Removes restriction from the ODC - * @param odcid Id of Data Container - * @param prop Property key - * @param rid ID of restriction to remove - * @return If a restriction was deleted - returns true, if it does not exist - returns false, if it exists but can't be deleted - throws an exception - */ - function removeRestriction( - uint256 odcid, - bytes32 prop, - uint256 rid - ) external returns(bool); -} -``` + * DataIndex SHOULD manage the access of Data Managers to Data Objects. + * DataIndex SHOULD manage internal IDs for each user. + * DataIndex SHOULD use the IDataIndex interface: ```solidity -interface IODCMetadata { - function metadataOf(uint256 odcid) external view returns(string calldata); -} -interface IODCTypes { +interface IDataIndex { /** - * @notice Enum of operations which PropertyManager can initiate on ODC + * @notice Verifies if DataManager is allowed to write specific DataPoint on specific DataObject + * @param dp Identifier of the DataPoint + * @param dm Address of DataManager + * @return if write access is allowed */ - enum PMOperation { - MODIFY_PROPERTY_DATA, - ADD_RESTRICTION, - REMOVE_RESTRICTION - } - /** - * - */ - enum Mutatation { - SPLIT, - MERGE, - ATTACH, - DETACH - } -} -``` + function isApprovedDataManager(DataPoint dp, address dm) external view returns(bool); -```solidity -/** - * @title Onchain Data Container (ODC) Interface for the registry of properties and categories - */ -interface IODCRegistry is IODCTypes { - /** - * @notice Returns address responsible for category management: add/remove properties, approve PropertyManagers etc - */ - function maintenerOf(bytes32 category) external view returns(address); - /** - * @notice Returns category of specified property - */ - function categoryOf(bytes32 property) external view returns(bytes32 category); - /** - * @notice Returns all properties of specified category - */ - function propertiesOf(bytes32 categoy) external view returns(bytes32[] memory properties); /** - * @notice Returns metadata provider for specific property + * @notice Defines if DataManager is allowed to write specific DataPoint + * @param dp Identifier of the DataPoint + * @param dm Address of DataManager + * @param approved if DataManager should be approved for the DataPoint + * @dev Function should be restricted to DataPoint maintainer only */ - function metadataProviderOf(bytes32 property) external view returns(IMetadataProvider); - /** - * @notice Returns if mutation is allowed for the category - */ - function isAllowed(Mutatation mutation, bytes32 category) external view returns(bool); -} -``` + function allowDataManager(DataPoint dp, address dm, bool approved) external; -```solidity -/** - * @title Onchain Data Container (ODC) Interface for mutations between several ODCs - */ -interface IODCMutations is IODCTypes { - function merge( - uint256 from, - uint256 to, - bytes32[] calldata categories - ) external; - function split(uint256 from, bytes32[] calldata categories) external returns (uint256); -} -interface IODCPermissions is IODCTypes { /** - * @notice Verifies if PropertyManager is allowed to execute an operation on the ODC - * @param pm Address of PropertyManager - * @param op Operation to execute - * @param odcid Id of Data Container - * @param prop Property key - * @return if PropertyManager is allowed to execute the operation + * @notice Reads stored data + * @param dobj Identifier of DataObject + * @param dp Identifier of the datapoint + * @param operation Read operation to execute on the data + * @param data Operation-specific data + * @return Operation-specific data */ - function isPropertyManagerAllowed(address pm, PMOperation op, uint256 odcid, bytes32 prop) external returns(bool); -} -``` + function read(address dobj, DataPoint dp, bytes4 operation, bytes calldata data) external view returns(bytes memory); -```solidity -interface IMetadataProvider { - function metadataOf() view; -} -``` - -```solidity -interface IODCErrors is IODCTypes { /** - * @param odcid Id of Data Container - * @param rid Id of restriction wich triggered the error - * @param restrictionReason Restriction-specific explanaition of the reason to revert the call + * @notice Store data + * @param dobj Identifier of DataObject + * @param dp Identifier of the datapoint + * @param operation Read operation to execute on the data + * @param data Operation-specific data + * @return Operation-specific data (can be empty) + * @dev Function should be restricted to allowed DMs only */ - error Restricted(uint256 odcid, uint rid, string restrictionReason); - error OperationNotAllowedForPropertyManager(address pm, PMOperation op, uint256 odcid, bytes32 prop); - error MutationNotAllowed(Mutatation mutation, bytes32 category); + function write(address dobj, DataPoint dp, bytes4 operation, bytes calldata data) external returns(bytes memory); } ``` +The **Data Index** is a smart contract entrusted with access control. It is a gating mechanism for **Data Managers** to access **Data Objects**. If a **Data Manager** intends to access a **Data Point** (either by `read()`, `write()`, or any other method), the **Data Index** should be used for validating access to the data. +The mechanism for ID managamenent determines a space of compatibility between implementations. -```solidity -/** - * @notice Queries whether a given ODC token has a specific property - * @param ouid ODC unique ID - * @param prop property ID (property to inquire) - * @return bool true if the token has the property, false if not - */ - function hasProperty(uint256 ouid, bytes32 prop) external view returns (bool) -``` - -```solidity -/** - * @notice Adds a given property to an existing ODC token - * @param ouid ODC unique ID - * @param prop property ID (property to add) - * @param restrictions An array of restrictions to be associated with the property - * @return An array with the respective restriction indexes - */ - function addProperty( - uint256 ouid, - bytes32 prop, - IMetaRestrictions.Restriction[] calldata restrictions - ) external returns (uint256[] memory) -``` -```solidity -/** - * @notice Removes an existing property from an existing ODC - * @param ouid ODC unique ID - * @param prop property ID (property to remove) - */ - function removeProperty(uint256 ouid, bytes32 prop) external -``` +### Data Object Interface -```solidity -/** - * @notice Retrieves all the properties of a given category - * @param category category ID to consult - * @return An array with all the respective property IDs - */ - function propertiesOfCategory(bytes32 category) external view returns (bytes32[] memory) -``` + * Data Object SHOULD implement the logic directly related to handling the data stored on Data Points. + * Data Object SHOULD implement the logic for transfering management of its Data Points to a different Data Index Implementation. + * Data Object SHOULD use the IDataObject interface: ```solidity -/** - * @notice Retrieves all enabled properties of a given ODC - * @param ouid ODC unique ID - * @return An array with all the enabled property IDs - */ - function propertiesOf(uint256 ouid) external view returns (bytes32[] memory) -``` +interface IDataObject { + /** + * @notice Reads stored data + * @param dp Identifier of the DataPoint + * @param operation Read operation to execute on the data + * @param data Operation-specific data + * @return Operation-specific data + */ + function read(DataPoint dp, bytes4 operation, bytes calldata data) external view returns(bytes memory); -```solidity -/** - * @notice Retrieves a given user's ODC unique ID that has a specific property attached - * @param account user's account address - * @param prop property ID - * @return uint256 the ODC token ID, or 0 if such a user's token doesn't exist - */ - function getToken(address account, bytes32 prop) external view returns (uint256) -``` + /** + * @notice Store data + * @param dp Identifier of the DataPoint + * @param operation Read operation to execute on the data + * @param data Operation-specific data + * @return Operation-specific data (can be empty) + */ + function write(DataPoint dp, bytes4 operation, bytes calldata data) external returns(bytes memory); -```solidity -/** - * @notice Retrieves all the ODC unique IDs owned by a given user that have a specific property attached - * @param account user's account address - * @param prop property ID to inquire - * @return An array with all the ODC token IDs that have the property attached - */ - function tokensWithProperty(address account, bytes32 prop) external view returns (uint256[] memory) + /** + * @notice Sets DataIndex Implementation + * @param dp Identifier of the DataPoint + * @param newImpl address of the new DataIndex implementation + */ + function setDIImplementation(DataPoint dp, address newImpl) external; +} ``` +**Data Objects** are entrusted with the management of transactions that affect the storage of **Data Points**. -```solidity -/** - * @notice Checks if a specific property exists in a given ODC token - * @param ouid ODC unique ID - * @param prop property ID - * @return bool true if the property is attached to the given token, false if not - */ - function exists(uint256 ouid, bytes32 prop) external view returns (bool) -``` +**Data Objects** can receive `read()`, `write()`, or any other custom requests from a **Data Manager** requesting access to a **Data Point**. -```solidity -/** - * @notice Merges the given categories' related properties from one ODC token to another - * @param from origin ODC unique ID - * @param to target ODC unique ID - * @param categories An array with all the category IDs to merge - */ - function merge( - uint256 from, uint256 to, bytes32[] calldata categories) external -``` +As such, **Data Objects** respond to a gating mechanism given by a single **Data Index**. The function `setDIImplementation()` SHOULD enable the delegation of the the management function to an `IDataIndex` implementation. -```solidity -/** - * @notice Splits an ODC token from its specific categories and mints a new one with the related properties attached - * @param ouid origin ODC unique ID - * @param categories category IDs to split - * @return newOuid the resulting (newly minted) ODC token ID - */ - function split(uint256 ouid, bytes32[] calldata categories) external returns (uint256 newOuid) -``` -```solidity -/** - * @notice Adds a new restriction to a given ODC property - * @param ouid ODC unique ID - * @param prop property ID - * @param restr the restriction to add - * @return uint256 The restriction's id - */ - function addRestriction( - uint256 ouid, - bytes32 prop, - Restriction calldata restr - ) external returns (uint256) { -``` +### Data Point Structure -```solidity -/** - * @notice Removes a restriction identified by its index from a given ODC's property - * @param ouid ODC unique ID - * @param prop property ID - * @param ridx restriction index - */ - function removeRestriction( - uint256 ouid, - bytes32 prop, - uint256 ridx - ) external -``` +* Data Point SHOULD be `bytes32` storage units. +* Data Point SHOULD use a 4 bytes prefix for storing information relevant to the compatibility with other Data Points. +* Data Point SHOULD use the last 20 bytes for storage identifying which Registry allocated them. +* The RECOMMENDED internal structure of the Data Point is as follows: ```solidity /** - * @notice Retrieves all restrictions attached to a given ODC's property - * @param ouid ODC unique ID - * @param prop property ID - * @return An array with all the requested restrictions (of type Restriction) - */ - function restrictionsOf(uint256 ouid, bytes32 prop) external view returns (Restriction[] memory) + * RECOMMENDED internal DataPoint structure on the Reference Implementation: + * 0xPPPPVVRRIIIIIIIIHHHHHHHHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + * - Prefix (bytes4) + * -- PPPP - Type prefix (i.e. 0x4450 - ASCII representation of letters "DP") + * -- VV - Verison of DataPoint specification (i.e. 0x00 for the reference implementation) + * -- RR - Reserved + * - Registry-local identifier + * -- IIIIIIII - 32 bit implementation-specific id of the DataPoint + * - Chain ID (bytes4) + * -- HHHHHHHH - 32 bit of chain identifier + * - REGISTRY Address (bytes20) + * -- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - Address of Registry which allocated the DataPoint +**/ ``` +**Data Points** are the low-level structure abstracting information. **Data Points** are allocated by a **Data Point Registry**, and this information should be stored within its internal structure. Each **Data Point** should have a unique identifier provided by the **Data Point Registry** when instantiated. +### Data Point Registry Interface -### Restrictions -**Restrictions** serve as a protective measure, ensuring that changes to **Properties** adhere to predefined rules, thereby maintaining the integrity and intended use of the information stored within the ODC. The **Restrictions** structure provides a layer of governance over the mutable **Properties**. **Property Managers** can check **Restrictions** applied to **Properties** before modifying the data stored within them. This further abstract the logic away from the storage, ensuring that mutability can be achieved and preserving the overall stability and reliability of the ODC. -```solidity -/** - * @dev ridxCounter Utilized to give continuous indices (ridxs) to restrictions - * @dev restrictions Mapping of restrictions' ridxs to the respective restriction data - * @dev byProperty Mapping of properties' unique identifiers to the respective set of ridxs (restrictions' indices) - * @dev byType Mapping of restrictions' unique types to the respective set of ridxs (restrictions' indices) - */ - struct TokenRestrictions { - uint256 ridxCounter; - mapping(uint256 => IMetaRestrictions.Restriction) restrictions; - mapping(bytes32 => EnumerableSet.UintSet) byProperty; - mapping(bytes32 => EnumerableSet.UintSet) byType; - } -``` + * Data Point Registry SHOULD store Data Point access management data for Data Managers and Data Objects + * Data Point Registry SHOULD use the IDataPointRegistry interface: ```solidity -/** - * @dev Adds a restriction to a given ODC's property - * @param l storage layout - * @param ouid ODC unique ID - * @param property identifier for the property - * @param r the restriction to add - * @return uint256 The index of the newly added restriction - */ - function addRestriction( - Layout storage l, - uint256 ouid, - bytes32 property, - IMetaRestrictions.Restriction memory r - ) internal returns (uint256) -``` - -```solidity -/** - * @dev Removes a restriction identified by its index from a given ODC's property - * @param l storage layout - * @param ouid ODC unique ID - * @param property identifier for the property - * @param ridx restriction index - */ - function removeRestriction( - Layout storage l, - uint256 ouid, - bytes32 property, - uint256 ridx - ) internal -``` +interface IDataPointRegistry { + /** + * @notice Verifies if an address has an Admin role for a DataPoint + * @param dp DataPoint + * @param account Account to verify + */ + function isAdmin(DataPoint dp, address account) external view returns (bool); + /** + * @notice Allocates a DataPoint to an owner + * @param owner Owner of the new DataPoint + * @dev Owner SHOULD be granted Admin role during allocation + */ + function allocate(address owner) external payable returns (DataPoint); -### PropertyManagers -Both **Properties** and **Restrictions** within the **ODC** SHALL be stored on-chain and made accessible though **Property Managers**. The interface defining this interaction is as follows: - - -### Categories and Registry - -Although the owner of ODCs can decide to implement an `allow list` for *Property Managers* that are enabled for interacting with the *Properties* and *Restrictions* stored within it, there are security considerations to be had regarding which *Property Managers* are allowed to interact with the ODCs internal storage. - -The *Registry* is a smart contract for managing the internal governance by managing roles and permissions for *Property Managers*. This component is the single reference point for organizing *Property Managers* in *Categories*. As such, this system increases security by defining who has access to what, mitigating the possibility of unauthorized transactions or modifications. - -The *Registry* keeps track of all the *Categories* as well as the *Properties* and *Restrictions* that the *Property Managers* have access to within those *Categories*. - -#### Registry Management Functions + /** + * @notice Transfers a DataPoint to an owner + * @param dp Data Point to be transferred + * @param owner Owner of the new DataPoint + */ + function transferOwnership(DataPoint dp, address newOwner) external; -```solidity -/** - * @notice Retrieves the category info of a given property - * @param property property ID - * @return category The category ID of the property - * @return splitAllowed true if splitting is allowed, false if not - */ - function getCategoryInfoForProperty(bytes32 property) external view returns (bytes32 category, bool splitAllowed); -``` + /** + * @notice Grant permission to grant/revoke other roles on the DataPoint inside a Data Index Implementation + * This is useful if DataManagers are deployed during lifecycle of the application. + * @param dp DataPoint + * @param account New admin + * @return If the role was granted (otherwise account already had the role) + */ + function grantAdminRole(DataPoint dp, address account) external returns (bool); -```solidity -/** - * @notice Retrieves the info of a given category - * @param category category ID - * @return properties Array of property IDs included within the category - * @return splitAllowed true if splitting is allowed, false if not - */ - function getCategoryInfo(bytes32 category) external view returns (bytes32[] memory properties, bool splitAllowed); + /** + * @notice Revoke permission to grant/revoke other roles on the DataPoint inside a Data Index Implementation + * @param dp DataPoint + * @param account Old admin + * @dev If an owner revokes Admin role from himself, he can add it again + * @return If the role was revoked (otherwise account didn't had the role) + */ + function revokeAdminRole(DataPoint dp, address account) external returns (bool); +} ``` +The **Data Point Registry** is a smart contract entrusted with **Data Point** access control. **Data Managers** may request the allocation of **Data Points** to the **Data Point Registry**. Access-control to those **Data Points** is also managed by the **Data Point Registry**. -```solidity -/** - * @notice Consults if a given address is a manager for a given category - * @param category category ID - * @param manager the address to inquire - * @return bool true if the address is manager for the category, false if not - */ - function isCategoryManager(bytes32 category, address manager) external view returns (bool); -``` -```solidity -/** - * @notice Consults if a given address is a registered manager for a given property - * @param property property ID - * @param manager the address to inquire - * @return bool true if the address is manager for the property, false if not - */ - function isPropertyManager(bytes32 property, address manager) external view returns (bool); -``` -```solidity -/** - * @notice Consults if a given address has been granted ODC minter role - * @param manager the address to inquire - * @return bool true if the address has been granted minter role, false if not - */ - function isMinter(address manager) external view returns (bool); -``` +### Data Manager Contract -### Metadata structure -Non-fungible tokens (NFTs) have rapidly gained prominence in the Ethereum ecosystem, serving as a foundation for various digital assets, ranging from art pieces to real estate tokens, to Identity-based systems. These tokens require metadata: information describing the properties of the token, which provides context and enriches the token's functionality within its ecosystem. -More often than not, developers manually generate NFT metadata for their respective projects, often leading to inconsistent structures and formats across different projects. This inconsistency hampers interoperability between NFT platforms and applications, slightly impeding the growth and development of the Ethereum NFT ecosystem. -Moreover, many protocols and standards rely on Metadata to store actual information that is not actionable by Smart Contracts. This generates a segregated model where NFTs as data-containers are not a self-contained unit, but a digital entity that lives fragmented between on-chain and off-chain storage. -The current EIP introduces a Metadata library that is designed to standardize the generation and handling of ODC metadata, promoting consistency, interoperability, and upgradeability. -The Metadata library includes base properties for [ERC-721](./eip-721.md) tokens and allows for the addition of extra **Properties**. These are flexible and extendable, covering `string`, `date`, `integer`, and `decimal` **Properties**. This broad range of property types caters to the diverse metadata needs across different use cases. -The Metadata library includes functions to generate metadata, add extra **Properties** to the metadata, merge two sets of **Properties**, and encode the metadata in a format compatible with popular NFT platforms like OpenSea. The library promotes reusability and reduces the amount of boilerplate code developers need to write. It is backwards compatible so that previous metadata models can also be implemented by generating a constant metadata link that always points to the same URI, as regular NFTs. + * Data Manager MAY use read() or DataObject.read() to read data form Data Objects + * Data Manager MAY use write() to write data to Data Objects + * Data Manager MAY share Data Point with other Data Managers + * Data Manager MAY use multiple Data Points + * Data Manager MAY implement the logic for requesting Data Points from a Data Point Registry. -#### ODC Metadata Functions +**Data Managers** are independent smart contracts that implement the business logic or "high-level" data management. They can either `read()` from a **Data Object** address and `write()` through a **Data Index** Implementation managing the delegated storage of the **Data Points**. -```solidity -/** - * @notice Generates metadata for a given property of a ODC token - * @param prop property ID of ODC token - * @param ouid ODC unique ID - * @return The generated metadata - */ - function getMetadata(bytes32 prop, uint256 ouid) external view returns (Metadata.ExtraProperties memory); -``` ## Rationale -The decision to encode Properties as bytes32 data containers in the ODC Interface is primarily driven by flexibility and future-proofing. Encoding as bytes32 allows for a wide range of data types to be stored, including but not limited to strings, integers, addresses, and more complex data structures, providing the developer with flexibility to accommodate diverse use cases. Furthermore, as Ethereum and its standards continue to evolve, encoding as bytes32 ensures that the ODC standard can support future data types or structures without requiring significant changes to the standard itself. - -Having a 2-layer data structure of `propertyKey` => `dataKey` => `dataValue` allows different applications to have their own address space. Implementations can manage access to this space using different `propertyKey` for different applications. -A case-by-case example on potential Properties encodings was performed and summarized is provided in the appendix. +The decision to encode Data Points as bytes32 data containers is primarily driven by flexibility and future-proofing. Using bytes32 allows for a wide range of data encodings. This provides the developer with many options to accommodate diverse use cases. Furthermore, as Ethereum and its standards continue to evolve, encoding as bytes32 ensures that the Standard Adapters can support future data types or structures without requiring significant changes to the standard adapter itself. The Data Point encoding should have a prefix so that the Data Object can efficiently identify compatibility issues when accessing the data storage. Additionally, the prefix should be used to find the Data Point Registry and verify admin access of the Data Point. The use of a suffix for identifying the Data Point Registry is also required, for the Data Object to quickly discard badly formed transactions that aim to use a Data Point from an unmatching Data Point Registry. -The inclusion of Properties within an ODC provides the capability to associate a richer set of on-chain accessible information within the storage. This enables a wide array of complex, dynamic, and interactive use cases to be implemented with multiple ERCs as well as other smart contracts. -Properties in an ODC offer flexibility in storing mutable on-chain data that can be modified as per the requirements of the token's use case. This allows the ODC to hold mutable states and reflect changes over time, providing a dynamic layer to the otherwise static nature of storage through a standardized interface. +Data Manager implementations decide which Data Points they will be using. Their allocation is managed through a Data Point Registry, and the access to the Data Point is managed by passing through the Data Index Implementation. -By leveraging Properties within the ODC, this standard delivers a powerful framework that amplifies the potential of all ERCs (present and future). In particular, ODCs can be leveraged to represent Account Abstraction contracts, abstracting the data-storage from the logic that consumes it, enabling for a single data-point to have multiple representations depending on the implementation. +Data Objects being independent separate Smart Contracts that implement the same `read`/`write` interface for communicating with Data Managers is a decision mainly driven by the scalability of the system. Offering a simple interface for this 2-layer structure enables different applications to have their addresses for storage of data as well as assets. It is up to each implementation to manage access to this Data Point storage space. This enables a wide array of complex, dynamic, and interactive use cases to be implemented with multiple ERCs as well as other smart contracts. +Data Objects offer flexibility in storing mutable on-chain data that can be modified as per the requirements of each specific use case. This enables the Data Managers to hold mutable states in delegated storage and reflect changes over time, providing a dynamic layer to the otherwise static nature of storage through most other standardized interfaces. -## Backwards Compatibility +As the Data Points can be set to respond to a specific Data Index implementation, Data Managers can decide to migrate the complete storage of a Data Object from one Data Index implementation to another. +By leveraging multiple implementations of the `IDataIndex` interface, this standard delivers a powerful framework that amplifies the potential of all ERCs (present and future). -This ERC is intended to augment the functionality of existing token standards without introducing breaking changes. As such, it does not present any backwards compatibility issues. Already deployed ERCs can be wrapped as Properties, with the application of on-chain data relevant to each use-case. -It offers an extension that allows for the storage and retrieval of Properties within an ODC while maintaining compatibility with existing ERCs related to tokenization. +## Backwards Compatibility +This ERC is intended to augment the functionality of existing token standards without introducing breaking changes. As such, it does not present any backward compatibility issues. Already deployed tokens under other ERCs can be wrapped as Data Points and managed by Data Objects, and later exposed through any implementation of Data Managers. Each interoperability integration will require a compatibility analysis, depending on the use case. ## Reference Implementation +We present an **educational example** implementation showcasing two types of tokens (Fungible and Semi-Fungible) sharing the same storage. The abstraction of the storage from the logic is achieved through the use of **Data Objects**. A factory is used for deploying fungible token contracts that share storage with each semi-fungible NFT representing a collection of fractions. Note that if a `transfer()` is called by either interface (Fungible or Semi-Fungible), both interfaces are emitting an event. -### The DataStorage Library (ODC storage example) -This library implementation allows the creation of On-chain Data Containers that can store various data types, handle versions of data, and efficiently manage stored data. The `DataStorageLib` provides a system for handling data that is both efficient and flexible. The `struct DataStorageInternal` includes mappings that enable the storage of different data types. -These are: - * `keyValueData` for `bytes32` key-value pairs, - * `keyBytesData` for storing `bytes`, - * `keySetData` for sets of `bytes32` values, - * `keyMapData` for mappings of `bytes32 => bytes32` values. - -Dynamic Data Versions: -The library handles versioning of data. The `DataStorage` struct contains a 'current' version and a mapping that links versions to specific indexes in the storage. This allows the smart contract to maintain a historical record of state changes, as well as revert to previous versions if necessary. -Clearing and Relocation: -Several functions, such as `clear()`, `wipe()`, and `moveData()` are dedicated to clearing and relocating stored data. This functionality allows efficient management of stored data. +**This example has not been audited and should not be used in production environments.** -Addition and Removal of Data: -The library includes functions to set and get values of different data types. Functions such as `setValue()` and `getBytes32Value()` facilitate this functionality. The addition or removal of data is reflected in the respective set (e.g., `kvKeys`, `kbKeys`, `ksKeys`, `kmKeys`) to ensure that the library correctly keeps track of all existing keys. - -Efficient Data Retrieval: -There are several getter functions to facilitate data retrieval from these storage structures. These include getting all keys (`getAllKeys()`), checking if a set contains a value (`getSetContainsValue()`), and getting all entries from a mapping (`getMapAllEntries()`). - -Data Deletion: -The library provides efficient ways to delete data, like `deleteAllFromEnumerableSet()` and `deleteAllFromEnumerableMap()`. - -#### Examples of Properties and Restrictions - -**On-chain Metadata**: This could include the name, description, image URL, and other metadata associated with the ODC. For example, in the case of an art NFT, the `setProperty` function could be used to set the artist's name, the creation date, the medium, and other relevant information. Afterwards, the implementation could include a `tokenUri()` function that procedurally exposes the `Property Data`, directly rendered from within the ODC. - -**Locking Restrictions**: They are utilized when an asset needs to be prevented from `transfer()` events or activity that would potentially change its internal storage. For example, when staking an asset or when locking for Fractionalization. - -**Ownership History**: The `setProperty` function could be used to record the ownership history of the ODC. For example, each time the ODC is transferred, a new entry could be added to the ownership history property. - -**Royalties**: The `setProperty` function could be used to set a royalties property for the ODC. This could specify a percentage of future sales that should be paid to the original creator. - -**Zero-Knowledge Proofs**: The `setProperty` function could be used to store Identity information related to the ODCs owner and signed by KYC provider. This is combined with *Transfer Restrictions* to achieve identity-based Recovery of assets. - -**Oracle Subscription**: An oracle system can stream data periodically into the Property (i.e. asset price, weather condition, metrics, etc) - -**Storage Abstraction**: Multiple ERCs can make use of the same ODC for storing variables. For example, in a DeFi protocol, a Property with a stored value of `100` could signify `either USDT or USDC`, provided that the logic is connected to both USDT and USDC protocols. - - -### Wrapping of Assets (Example Property Manager) - -The Wrapper addresses challenges and requirements that have emerged in the Ethereum ecosystem, specifically regarding the handling and manipulation of assets from different standards. The Wrapper component provides Backwards Compatibility by: - -Standardization: With the proliferation of various token standards on Ethereum, there is a need for a universal interface that can handle these token types uniformly. The Wrapper provides standardization, enabling a consistent approach to dealing with various token types, regardless of their implementation. -By allowing different token types to be bundled together into a single entity (ODC), this approach increases the composability and interoperability between various protocols and platforms. A more efficient and flexible asset management is achieved, allowing users to bundle multiple assets into a single ODC and reducing the complexity of handling individual assets. Furthermore, the capability to 'unwrap' these bundled assets as needed, provides users with granular control over their digital assets. -The transferring or interaction with multiple individual tokens often leads to a high accumulation of gas fees. However, by wrapping these assets into a single entity, users can perform operations in a more cost-effective manner. - - -```solidity -/** - * @notice This struct is used to receive all parameter for wrap function - * @param tokens The token addresses of the assets. - * @param amounts The amount of each asset to wrap (if applicable). - * @param ids The id of each asset to wrap (if applicable). - * @param types The type of each asset to wrap. - * @param unlockTimestamps The unlocking timestamps of wrapped assets. - * @param existingOuidToUse If different than 0, it represents the ODC to wrap assets on. If 0, new mNFT is minted. - */ - struct WrapData { - address[] tokens; - uint256[] amounts; - uint256[] ids; - uint256[] types; - uint256[] unlockTimestamps; - uint256 existingOuidToUse; - } -``` - -```solidity -/** - * @notice This function is called by the users to wrap assets inside an ODC - * @param data The data used when wrapping. - */ - function wrap(WrapData memory data) external returns (uint256, uint256[] memory); -``` - -```solidity -/** - * @notice Rewrap - * @dev This function is called by the users to be able to extend fungible assets' amounts. - * @param data The data used when rewrapping. - */ - function rewrap(RewrapData memory data) external returns (uint256, uint256[] memory); -``` - -```solidity -/** - * @notice Unwrap - * @dev This function is called by the users to unwrap assets from a ODC. - * @dev This function is called by the users to unwrap assets from a ODC. - * @param ouid The ODC id associated. - * @param restrictionIds The restriction ids of the properties. - * @param tokens The token addresses of the assets. - * @param types The type of each asset to unwrap. - * @param ids The ids of each asset to unwrap if applicable - */ - function unwrap(uint256 ouid, uint256[] calldata restrictionIds, address[] calldata tokens, uint256[] calldata types, uint256[] calldata ids) external; -``` - -```solidity -/** - * @notice RegisterNewType - * @dev This function is called by the owner to register a new type of asset. - * @param propertyManager The property manager address that is handling this specific type. - * @param isFungible true if the asset is fungible and false otherwise. - */ - function registerNewType(IExternalTokenPropertyManager propertyManager, bool isFungible) external -``` - -### Fractionalization (Example Property Manager) - -Fractionalizer is a Property Manager that enables the creation of fraction tokens for a specific ODC. As an ODC can be the repository of information for multiple types of assets, the Fractionalization process may require of a special contract ruling the governance of said assets. For example, if the ODC represents a piece of art, and the fractions represent a percentage of the ownership over it, a governor Property Manager contract would be required to implement the logic detailing under which conditions the full ownership of the ODC is transferable. - - -```solidity -/** - * @notice createNewErc20Fractions - * @param name The name of the erc20 token. - * @param symbol The symbol of the erc20 token. - * @param amountToMint Amount of erc20 fractions to mint to the msg.sender. - * @param ouid The id of ODC to lock. - * @param governor The governor of the fractions, if it's empty, governor is created. - * @param data The governance data to use if the governor is empty. - */ - function createNewErc20Fractions( - string calldata name, - string calldata symbol, - uint256 amountToMint, - uint256 ouid, - address governor, - GovernanceDeployer.GovernanceData calldata data - ) external returns (address) -``` - -```solidity -/** - * @notice createNewErc721Fractions - * @param name The name of the erc721 token. - * @param symbol The symbol of the erc721 token. - * @param baseUri The baseUri of the erc721 token. - * @param idsToMint ids of erc721 fractions to mint to the msg.sender. - * @param ouid The id of ODC to lock. - * @param governor The governor of the fractions, if it's empty, msg.sender is used. - */ - function createNewErc721Fractions( - string calldata name, - string calldata symbol, - string calldata baseUri, - uint256[] calldata idsToMint, - uint256 ouid, - address governor - ) external returns (address) -``` ## Security Considerations -1. The management of Properties should be handled securely, with appropriate access control mechanisms in place to prevent unauthorized modifications. -2. Storing enriched metadata on-chain could potentially lead to higher gas costs. This should be considered during the design and implementation of ODCs. -3. Increased on-chain data storage could also lead to potential privacy concerns. It's important to ensure that no sensitive or personally identifiable information is stored within ODC metadata. -4. Ensuring decentralized control over the selection of Property Managers is critical to maintain the decentralization ethos of Ethereum. -5. Developers must also be cautious of potential interoperability and compatibility issues with systems that have not yet adapted to this new standard. - -The presence of mutable Properties can be used to implement security measures. In the context of preventing unauthorized access and modifications, an ODC-based system could implement the following strategies, adapted to each use-case: - -**Role-Based Access Control (RBAC)**: Only accounts assigned to specific roles at a Property level can perform certain actions on a Property. For instance, only an 'owner' might be able to call setProperty functions. - -**Time Locks**: Time locks can be used to delay certain actions, giving the community or a governance mechanism time to react if something malicious is happening. For instance, changes to Properties could be delayed depending on the use-case. +The access control is separated into three layers: -**Multi-Signature (Multisig) Properties**: Multisig Properties could be implemented in a way that require more than one account to approve an action performed on the Property. This could be used as an additional layer of security for critical functions. For instance, changing certain properties might require approval from multiple trusted signers. +* **Layer 1**: The Data Point Registry allocates for Data Managers and manages ownership (admin/write rights) of Data Points. +* **Layer 2**: The Data Index smart contract implements Access Control by managing Approvals of Data Managers to Data Points. It uses the Data Point Registry to verify who can grant/revoke this access. +* **Layer 3**: The Data Manager exposes functions that can perform `write` operations on the Data Point by calling the Data Index implementation. +No further security considerations are derived specifically from this ERC. ## Copyright diff --git a/ERCS/erc-7280.md b/ERCS/erc-7280.md new file mode 100644 index 0000000000..d736d2e9be --- /dev/null +++ b/ERCS/erc-7280.md @@ -0,0 +1,124 @@ +--- +eip: 7280 +title: NFT Metadata Extension like JSON-LD +description: Let NFT metadata have a feature equivalent to JSON-LD to be semantic. +author: Yohei Nishikubo (@yoheinishikubo) +discussions-to: https://ethereum-magicians.org/t/eip-7280-nft-metadata-extension-like-json-ld/14935 +status: Draft +type: Standards Track +category: ERC +created: 2023-07-04 +requires: 721, 1155, 3525 +--- + +## Abstract + +This proposal expands the metadata format for Non-Fungible Tokens ([ERC-721](./eip-721.md), [ERC-1155](./eip-1155.md), [ERC-3525](./eip-3525.md), and others), adding support for linked data like JSON-LD format. The additional data is stored under the linked_data key in the metadata JSON. + +## Motivation + +The existing metadata format for Non-Fungible Tokens is limited and doesn't support the inclusion of structured and semantically meaningful data. By integrating JSON-LD (Linked Data), we can enhance the richness and interoperability of the metadata associated with NFTs. + +This allows for complex metadata structures that can link to external schemas and data, improving the contextual relevance and usability of NFTs across various applications. + +## Specification + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. + +The JSON-LD based metadata is stored under a new `linked_data` key in the metadata JSON. The `linked_data` key is an array of objects, where each object contains two keys: `schema` and `data`. + +| name | compliance level | type | description | +| ------ | ---------------- | ------ | ------------------------------ | +| schema | MUST | object | The schema of the linked data. | +| data | MUST | object | The data of the linked data. | + +### Schema + +| name | compliance level | type | description | +| ----------- | ---------------- | ------ | ------------------------------ | +| uri | MUST | string | The URI of the schema. | +| name | MUST | string | The name of the schema. | +| description | OPTIONAL | string | The description of the schema. | + +### Data + +| name | compliance level | type | description | +| ----------- | ---------------- | ------ | --------------------------------------------------------- | +| uri | MUST | string | The URI of the data. | +| lang | OPTIONAL | string | The language of the data. IETF language tag like `en-US`. | +| name | OPTIONAL | string | The name of the data. | +| description | OPTIONAL | string | The description of the data. | + +## Rationale + +For providing typical webpage for an NFT, it's much simple to include JSON-LD in HTML header tag with this extension. Just looking for JSON-LD compliant value's uri from `linked_data` array, fetch it and embed its content in HTML header tag. +This means the minter of NFT can control the appearance in the search result of Google, for example. +In more common case for interoperability, the NFT metadata can include any schema and data with this extension. This means the NFT metadata can be used as a data source for any application. With the schema, the implementation is much easier. + +## Backwards Compatibility + +The proposed expansion to the NFT metadata format is backward compatible with existing implementations. NFTs that do not include the `linked_data` key will continue to function as before, and existing applications consuming NFT metadata will not be affected. + +## Reference Implementation + +Here is an example metadata JSON demonstrating the new linked_data structure: + +```json +{ + "name": "NFT Name", + "description": "This NFT represents...", + "image": "https://example.org/images/nft.png", + "linked_data": [ + { + "schema": { + "name": "VideoObject", + "uri": "https://example.org/schemas/VideoObject.json" + }, + "data": { + "uri": "https://example.org/data/video1.json" + } + }, + { + "schema": { + "name": "MusicRecording", + "uri": "https://example.org/schemas/MusicRecording.json" + }, + "data": { + "uri": "https://example.org/data/music1.json" + } + }, + { + "schema": { + "name": "GoogleTravelImpactModel", + "uri": "https://example.org/schemas/GoogleTravelImpactModel.json" + }, + "data": { + "uri": "https://example.org/data/gtim1.json" + } + } + ] +} +``` + +In the example above, the NFT metadata contains three linked data objects, each with a different schema and data: +First one. VideoObject data can be used as JSON-LD in HTML header tag and realize rich snippet in Google search result. +Second one. MusicRecording data is based on a schema from `schema.org`. However this one cannot realize rich snippet. +Third one. GoogleTravelImpactModel data is a dedicated schema for Google Travel Impact Model. +The most important point is that any schema and data can be included with this standard like above. + +### Sample files + +- [VideoObject.json](../assets/eip-7280/samples/schemas/VideoObject.json) +- [MusicRecording.json](../assets/eip-7280/samples/schemas/MusicRecording.json) +- [GoogleTravelImpactModel.json](../assets/eip-7280/samples/schemas/GoogleTravelImpactModel.json) +- [video1.json](../assets/eip-7280/samples/data/video1.json) +- [music1.json](../assets/eip-7280/samples/data/music1.json) +- [gtim1.json](../assets/eip-7280/samples/data/gtim1.json) + +## Security Considerations + +The proposed expansion does not introduce any additional security considerations beyond those already associated with NFTs and linked data. Implementations should adhere to best practices for secure handling and validation of metadata from external sources. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/ERCS/erc-7291.md b/ERCS/erc-7291.md index 18d46ca41b..8aaa7220de 100644 --- a/ERCS/erc-7291.md +++ b/ERCS/erc-7291.md @@ -619,7 +619,7 @@ Why is there a need for destination? ### Why was a push transaction model chosen? -- This standard sticks to the push transaction model where the transfer of PBM is initiated on the senders side. Modern wallets can support the required PBM logic by embedding the unwrapping logic within the ERC-1155 `safeTransfer` function. +- This standard sticks to the push transaction model where the transfer of PBM is initiated on the sender's side. Modern wallets can support the required PBM logic by embedding the unwrapping logic within the ERC-1155 `safeTransfer` function. ### Customisability diff --git a/ERCS/erc-7303.md b/ERCS/erc-7303.md index ab2a23c5cb..109800737d 100644 --- a/ERCS/erc-7303.md +++ b/ERCS/erc-7303.md @@ -154,7 +154,7 @@ contract MyToken is ERC721, ERC7303 { _grantRoleByERC1155(BURNER_ROLE, 0x..., ...); } - function safeMint(address to, uint256 tokenId, string memory uri) + function safeMint(address to, uint256 tokenId) public onlyHasToken(MINTER_ROLE, msg.sender) { _safeMint(to, tokenId); diff --git a/ERCS/erc-7399.md b/ERCS/erc-7399.md index e97b1a0a1c..94f0c841d2 100644 --- a/ERCS/erc-7399.md +++ b/ERCS/erc-7399.md @@ -1,8 +1,8 @@ --- eip: 7399 -title: Flash Loans +title: ⚡ Flash Loans ⚡ description: Interfaces and processes for flash loans -author: Alberto Cuesta Cañada (@alcueca), Ultrasecr.eth (@ultrasecreth), Devtooligan (@devtooligan), Michael Amadi (@AmadiMichaels) +author: Alberto Cuesta Cañada (@alcueca), Michael Amadi (@AmadiMichaels), Devtooligan (@devtooligan), Ultrasecr.eth (@ultrasecreth), Sam Bacha (@sambacha) discussions-to: https://ethereum-magicians.org/t/erc7400-flash-loans/15211 status: Draft type: Standards Track @@ -106,9 +106,9 @@ interface IERC7399 { ``` -The `maxFlashLoan` function MUST return the maximum available loan for `asset`. The `maxFlashLoan` function MUST NOT revert. If the asset is not supported `maxFlashLoan` the value returned MUST be zero. +The `maxFlashLoan` function MUST return the maximum available loan for `asset`. The `maxFlashLoan` function MUST NOT revert. If no flash loans for the specified `asset` are possible, the value returned MUST be zero. -The `flashFee` function MUST return the fee charged for a loan of `amount` `asset`. If the asset is not supported `flashFee` MUST revert. If the lender doesn't have enough liquidity to loan `amount` the fee returned MUST be `type(uint256).max`. If an `asset`is supported but the available liquidity for it is exactly zero, the fee returned still MUST be `type(uint256).max`. +The `flashFee` function MUST return the fee charged for a loan of `amount` `asset`. The `flashFee` function MUST NOT revert. If a flash loan for the specified `asset` and `amount` is not possible, the value returned MUST be `type(uint256).max`. The `flash` function MUST execute the callback passed on as an argument. @@ -148,9 +148,11 @@ function(address, address, IERC20, uint256, uint256, bytes memory) external retu The interfaces described in this ERC have been chosen as to cover the known flash lending use cases, while allowing for safe and gas efficient implementations. -`flashFee` reverts on unsupported assets, because returning a numerical value would be incorrect. +`maxFlashLoan` and `flashFee` return numerical values on impossible loans to allow sorting lenders without having to deal with reverts. -`flashFee` returns a value that is consistent with an impossible loan when the `lender` doesn't have enough liquidity to serve the loan. +`maxFlashLoan` returns a value that is consistent with an impossible loan when the `lender` is not able to serve the loan. + +`flashFee` returns a value that is consistent with an impossible loan when the `lender` is not able to serve the loan. `flash` has been chosen as a function name as a verb which is descriptive enough, unlikely to clash with other functions in the `lender`, and including both the use cases in which the assets lent are held or minted by the `lender`. diff --git a/ERCS/erc-7432.md b/ERCS/erc-7432.md index 47c2041e4b..0708c4c36a 100644 --- a/ERCS/erc-7432.md +++ b/ERCS/erc-7432.md @@ -4,7 +4,8 @@ title: Non-Fungible Token Roles description: Role Management for NFTs. Enables accounts to share the utility of NFTs via expirable role assignments. author: Ernani São Thiago (@ernanirst), Daniel Lima (@karacurt) discussions-to: https://ethereum-magicians.org/t/eip-7432-non-fungible-token-roles/15298 -status: Review +status: Last Call +last-call-deadline: 2024-09-17 type: Standards Track category: ERC created: 2023-07-14 @@ -337,7 +338,7 @@ Automatic expiration is implemented via the `grantRole` and `roleExpirationDate` for setting the expiration date, and `roleExpirationDate` allow developers to check whether the role is expired. Since `uint256` is not natively supported by most programming languages, dates are represented as `uint64` on this standard. The maximum UNIX timestamp represented by a `uint64` is about the year `584,942,417,355`, which should be enough to be -considered "permanent". For this reason, it's RECOMMENDED using `type(uint64).max` to support use cases that require a +considered "permanent". For this reason, it's recommended using `type(uint64).max` to support use cases that require a role never to expire. ### Revocable Roles @@ -347,8 +348,8 @@ others, the recipient may require assurance that the role cannot be revoked. The to the `grantRole` function to specify whether a role can be revoked prematurely, enabling the standard to support both use cases. -Regardless of the value of `revocable`, it's RECOMMENDED always to enable the `recipient` to revoke roles, allowing -them to eliminate undesirable assignments. +Regardless of the value of `revocable`, it's recommended always to enable the `recipient` to revoke roles, allowing them +to eliminate undesirable assignments. ### Custom Data diff --git a/ERCS/erc-7484.md b/ERCS/erc-7484.md index f5ff17e70b..80c07471e7 100644 --- a/ERCS/erc-7484.md +++ b/ERCS/erc-7484.md @@ -107,6 +107,7 @@ The Registry SHOULD also implement the following additional functionality: - The Registry MUST revert if the number of `attesters` that have made an attestation on the `module` is smaller than the `threshold`. - The Registry MUST revert if any `attester` has revoked their attestation on the `module`. +- The `attesters` provided MUST be unique and sorted and the Registry MUST revert if they are not. #### `check` functions with moduleType @@ -120,6 +121,7 @@ The Registry SHOULD also implement the following additional functionality: #### `trustAttesters` - The Registry MUST store the `threshold` and `attesters` for the `msg.sender`. +- The `attesters` provided MUST be unique and sorted and the Registry MUST revert if they are not. ### Adapter behavior diff --git a/ERCS/erc-7518.md b/ERCS/erc-7518.md new file mode 100644 index 0000000000..707647c32f --- /dev/null +++ b/ERCS/erc-7518.md @@ -0,0 +1,573 @@ +--- +eip: 7518 +title: Dynamic Compliant Interop Security Token +description: Security token framework with semi-fungible partitions for dynamic regulatory compliance management and cross-chain interoperability +author: Abhinav (@abhinav-d3v) , Prithvish Baidya (@d4mr) , Rajat Kumar (@rajatwasan) , Prasanth Kalangi +discussions-to: https://ethereum-magicians.org/t/eip-7518-dynamic-compliant-interop-security-token-dycist/15822 +status: Draft +type: Standards Track +category: ERC +created: 2023-09-14 +requires: 165, 1155 +--- +## Abstract + +This proposal is a security token standard that extends [ERC-1155](./eip-1155.md) to provide a flexible framework for managing compliant real-asset security tokens. It introduces the concept of partitions, where each `tokenId` represents a distinct partition with its own set of rights and privileges. This makes it suitable for various use cases, particularly semi-fungible asset management. The standard also includes features like token locking, forced transfers for recovery, address freezing, payouts, and dynamic compliance management using off-chain vouchers. + +## Motivation + +The growing demand for tokenized real-world assets necessitates a token standard that can accommodate the unique requirements of security tokens. Existing standards, while powerful, do not fully address the need for flexible partitioning and comprehensive compliance management. + +Build upon of [ERC-1155](./eip-1155.md) to introduce partitions, allowing for the creation of semi-fungible tokens representing fractional ownership, different share classes, or other distinct units within a single token contract. This flexibility is crucial for tokenizing complex real-world assets like real estate or funds. + +Furthermore, it includes features essential for security tokens, such as token locking for vesting or holding periods, forced transfers for recovery in case of lost keys, address freezing for regulatory compliance, efficient payout mechanisms, and dynamic compliance management using off-chain vouchers. + +By providing a standardized interface for these features, this proposal aims to facilitate the development of interoperable and compliant security token ecosystems. + +## Specification + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. + +### Interface + +```solidity +pragma solidity ^0.8.0; + +interface IERC7518 is IERC1155, IERC165{ + event TokensLocked(address indexed account, uint indexed id, uint256 amount, uint256 releaseTime); + + event TokenUnlocked(address indexed account, uint indexed id); + + event TokensForceTransferred(address indexed from, address indexed to, uint indexed id, uint256 amount); + + event AddressFrozen(address indexed account, bytes data); + + event AddressUnfrozen(address indexed account, bytes data); + + // Emitted when the transferability of tokens with a specific ID is restricted. + event TransferRestricted(uint indexed id); + + // Emitted when the transferability restriction of tokens with a specific ID is removed. + event TransferRestrictionRemoved(uint indexed id); + + event PayoutDelivered(address indexed from, address indexed to, uint256 amount); + + /** + * @dev Retrieves the transferable balance of tokens for the specified account and ID. + * @param account The address of the account. + * @param id The token ID. + * @return The transferable balance of tokens. + */ + function transferableBalance(address account, uint id) external view returns (uint); + + /** + * @dev Retrieves the locked balance of tokens for the specified account and ID. + * @param account The address of the account. + * @param id The token ID. + * @return The locked balance of tokens. + */ + function lockedBalanceOf(address account, uint256 id) external view returns (uint256); + + /** + * @dev Restricts the transferability of tokens with the specified ID. + * @param id The token ID. + * @return A boolean value indicating whether the operation was successful. + */ + function restrictTransfer(uint id) external returns (bool); + + /** + * @dev Removes the transferability restriction of tokens with the specified ID. + * @param id The token ID. + * @return A boolean value indicating whether the operation was successful. + */ + function removeRestriction(uint id) external returns (bool); + + /** + * @notice Transfers `_value` amount of an `_id` from the `_from` address to the `_to` address specified (with safety call). + * @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see "Approval" section of the standard). + + * After the above conditions are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call `onERC1155Received` on `_to` and act appropriately (see "Safe Transfer Rules" section of the standard). + * @param _from Source address + * @param _to Target address + * @param _id ID of the token type + * @param _value Transfer amount + * @param _data Additional data with no specified format, MUST be sent unaltered in call to `onERC1155Received` on `_to` + */ + function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) override external; + + /** + * @dev Checks if a transfer is allowed. + * @param from The address to transfer tokens from. + * @param to The address to transfer tokens to. + * @param id The token ID. + * @param amount The amount of tokens to transfer. + * @param data Additional data related to the transfer. + * @return status A boolean value indicating whether the transfer is allowed. + */ + function canTransfer(address from, address to, uint id, uint amount, bytes calldata data) external view returns (bool status); + + /** + * @dev lock token till a particular block time. + * @param account The address of the account for which tokens will be locked. + * @param id The token ID. + * @param amount The amount of tokens to be locked for the account. + * @param releaseTime The timestamp indicating when the locked tokens can be released. + * @return bool Returns true if the tokens are successfully locked, otherwise false. + */ + function lockTokens(address account, uint id, uint256 amount, uint256 releaseTime) external returns (bool); + + /** + * @dev Unlocks tokens that have crossed the release time for a specific account and id. + * @param account The address of the account to unlock tokens for. + * @param id The token ID. + */ + function unlockToken(address account, uint256 id) external; + + /** + * @dev Force transfer in cases like recovery of tokens. + * @param from The address to transfer tokens from. + * @param to The address to transfer tokens to. + * @param id The token ID. + * @param amount The amount of tokens to transfer. + * @param data Additional data related to the transfer. + * @return A boolean value indicating whether the operation was successful. + */ + function forceTransfer(address from, address to, uint256 id, uint256 amount, bytes memory data) external returns (bool); + + /** + * @dev Freezes specified address. + * @param account The address to be frozen. + * @param data Additional data related to the freeze operation. + * @return A boolean value indicating whether the operation was successful. + */ + function freezeAddress(address account, bytes calldata data) external returns (bool); + + /** + * @dev Unfreezes specified address. + * @param account The address to be unfrozen. + * @param data Additional data related to the unfreeze operation. + * @return A boolean value indicating whether the operation was successful. + */ + function unFreeze(address account, bytes memory data) external returns (bool); + + /** + * @dev Sends payout to single address with corresponding amounts. + * @param to address to send the payouts to. + * @param amount amount representing the payouts to be sent. + * @return A boolean indicating whether the batch payouts were successful. + */* + function payout(address calldata to, uint256 calldata amount) public returns (bool); + + /** + * @dev Sends batch payouts to multiple addresses with corresponding amounts. + * @param to An array of addresses to send the payouts to. + * @param amount An array of amounts representing the payouts to be sent. + * @return A boolean indicating whether the batch payouts were successful. + */ + function batchPayout(address[] calldata to, uint256[] calldata amount) public returns (bool); +} +``` + +### Methods for token + +### `transferableBalance` + +Retrieves the transferable balance of tokens for the specified account and ID. + +```solidity +function transferableBalance(address account,uint id) external view returns (uint) +``` + +- MUST calculate and return the transferable balance of tokens for the specified account and ID ie current `balanceOf(account, id) - lockedBalanceOf(account, id)`. + +### `lockedBalanceOf` + +Retrieves the locked balance of tokens for the specified account and ID. + +```solidity +function lockedBalanceOf(address account,uint256 id) external view returns (uint256) +``` + +- MUST retrieve and return the locked balance of tokens for the specified `account` and `id`. + +### `restrictTransfer` + +Restricts the transferability of tokens with the specified ID. + +```solidity +function restrictTransfer(uint id) external returns (bool) +``` + +- MUST restrict the transferability of tokens with the specified `id`. +- SHOULD emit `TransferRestricted`. + +### `removeRestriction` + +Removes the transferability restriction of tokens with the specified ID. + +```solidity +function removeRestriction(uint id) external returns (bool) +``` + +- MUST remove the transferability restriction of tokens with the specified `id`. MUST check if `id` is previously restricted. +- SHOULD emit `TransferRestrictionRemoved`. + +### `safeTransferFrom` + +```solidi +function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) override external; +``` + +- MUST revert if `_to` is the zero address. +- MUST revert if balance of holder for token `_id` is lower than the `_value` sent. +- MUST revert on any other error. +- MUST emit the `TransferSingle` event to reflect the balance change (see "Safe Transfer Rules" section of the standard). +- MUST call `canTransfer` function to check if the transfer can proceed + +### `canTransfer` + +Determine transferring a specified amount of a token from one address to another. + +```solidity +function canTransfer(address from,address to,uint id,uint amount,bytes calldata data) external view returns (bool status); +``` + +- Accurately determine whether the transfer of tokens is allowed. +- MUST validate `to` and `from` are not frozen address. +- MUST validate `id` of the transfer should not be restricted +- MUST check if `amount` is a transferable balance. +- MAY call external contract to verify the transfer. +- SHOULD NOT modify any state or perform any side effects. + +### `lockTokens` + +Locks a specified amount of tokens from an account for a specified duration. + +```solidity +function lockTokens(address account,uint id,uint256 amount,uint256 releaseTime) external returns (bool); +``` + +- MUST enforce time-based restrictions on the transfer or use of tokens. +- MUST revert if balance of holder is less than amount. +- SHOULD use proper access control measures to ensure that only authorized entities can lock tokens. +- MUST perform input validation prevent potential vulnerabilities and unauthorized locking of tokens. +- SHOULD record release time securely and ensure that locked tokens are only released after the designated time has passed. +- SHOULD emit `TokensLocked`. + +### `unlockToken` + +Unlocks tokens that have crossed the release time for a specific account and id. + +```solidity +function unlockToken(address account,uint256 id) external; +``` + +- MUST unlock the tokens for the specified `account` address and `id`. +- MUST unlock all the token which has release time > `block.time` +- SHOULD revert if no token are unlocked to save gas. +- SHOULD emit `TokenUnlocked`. + +### `forceTransfer` + +Force transfer in cases like recovery of tokens + +```solidity +function forceTransfer(address from,address to,uint256 id,uint256 amount,bytes memory data) external returns (bool); +``` + +- MUST bypass normal transfer restrictions and authorization checks. +- MUST revert if the `from` address is not Frozen. +- MUST revert if `to` address is Frozen. +- MUST ensure that only authorized entities have the capability to call this function. +- Additional data related to the freeze operation. +- SHOULD emit `TokensForceTransferred`. + +### `freeze` + +Freezes specified address. The Freeze function takes in the `account address` to be frozen and additional data, and returns a `boolean` value indicating whether the operation was successful. + +```solidity +function freezeAddress(address account,bytes data) external returns (bool); +``` + +- MUST prevent `account` to transfer and payout. +- SHOULD implement appropriate access control measures to ensure that only authorized addresses can be unfrozen. +- SHOULD emit `AddressFrozen`. + +### `unFreeze` + +The Unfreeze function takes in the `account address` to be unfrozen and additional data, and returns a `boolean` value indicating whether the operation was successful. + +```solidity +function unFreeze(address account,bytes memory data) external returns (bool); +``` + +- MUST consider implications of unfreezing an address, as it grants unrestricted transfer and operation capabilities. +- MUST unfreeze the specified `account` +- SHOULD implement appropriate access control measures to ensure that only authorized addresses can be unfrozen. +- SHOULD emit `AddressUnfrozen`. + +### `payout` + +Send payouts to single address, receiver will be receiving a specific amount of tokens. + +```solidity +function payout(address calldata to,uint256 calldata amount) public returns (bool) +``` + +- MUST revert if `to` address is frozen address. +- SHOULD have sufficient balance to transfer token from issuer address. +- SHOULD emit `PayoutDelivered`. + +### `batchPayout` + +Send payouts to multiple addresses at once, with each address receiving a specific amount of tokens. It can be used for various purposes such as distributing rewards, dividends, or interest payment. + +```solidity +function batchPayout(address[] calldata to,uint256[] calldata amount) public returns (bool) +``` + +- MUST revert if `to` address is frozen address. +- SHOULD have sufficient balance to transfer token from issuer address. +- SHOULD emit `PayoutDelivered`. + +### Interoperability + +This proposal facilitates interoperability with [ERC-3643](./eip-3643.md) tokens through a token wrapping method. The process involves two key components: the [ERC-3643](./eip-3643.md) token contracts representing the original and the proposed token contract for the wrapped version. Users seeking to wrap their tokens interact with the wrapping contract, which securely locks their original tokens and mints an equivalent amount of the proposed tokens to their address. Conversely, unwrapping is achieved by calling the contract's withdraw function, resulting in the burning of the proposed tokens and the release of the corresponding original tokens. Events are emitted for transparency, and robust security measures are implemented to safeguard user assets and address any potential vulnerabilities in the contract code. With this design, this proposal ensures the seamless conversion and compatibility with [ERC-3643](./eip-3643.md) tokens, promoting greater utility and usability across the Ethereum ecosystem. + +### Interface for Interoperability + +```solidity +interface IERC1155Wrapper is IERC7518 { + +/** +@dev Emitted when a new wrapped token address is added to the set. +@param wrappedTokenAddress The address of the wrapped token that was added. +*/ +event WrappedTokenAddressSet(address wrappedTokenAddress); + +/** +@dev Emitted when tokens are wrapped. +@param The ERC1155 token ID of the wrapped tokens. +@param amount The amount of tokens that were wrapped. +*/ +event TokensWrapped(uint indexed id, uint256 amount); + +/** +@dev Emitted when tokens are unwrapped. +@param wrappedTokenId Is the ERC1155 token ID of the wrapped tokens. +@param amount The amount of tokens that were unwrapped. +*/ +event TokensUnwrapped(uint indexed wrappedTokenId, uint256 amount); + +/** +* @dev Sets the wrapped token address and logic for deciding partitions. +* @param wrappedTokenAddress The address of the wrapped token contract. +* @return A boolean value indicating whether the operation was successful. +*/ +function setWrappedToken(address token) external returns (bool); + +/** +* @dev Wraps the specified amount of tokens by depositing the original tokens and receiving new standard tokens. +* @param amount The amount of tokens to wrap. +* @param data Additional data for partition. +* @return A boolean value indicating whether the operation was successful. +*/ +function wrapToken(uint256 amount, bytes calldata data) external returns (bool); + +/** +* @notice Wraps a specified amount of tokens from a given partition into the main balance. +* @dev This function allows users to convert tokens from a specific partition back to the main balance,making them fungible with tokens from other partitions. +* @param partitionId The unique identifier of the partition from which tokens will be wrapped. +* @param id The unique identifier of the token. +* @param amount The amount of tokens to be wrapped from the specified partition. +* @param data Additional data that may be used to handle the wrap process (optional). +* @return success A boolean indicating whether the wrapping operation was successful or not. +*/ + +function wrapTokenFromPartition(bytes32 partitionId, uint256 id, uint256 amount, bytes calldata data) external returns (bool); +/** +* @dev Unwraps the specified amount of wrapped tokens by depositing the current tokens and receiving the original tokens. +* @param wrappedTokenId internal partition id. +* @param amount The amount of wrapped tokens to unwrap. +* @param data Additional data for partition. +* @return A boolean value indicating whether the operation was successful. +*/ +function unwrapToken(uint256 wrappedTokenId, uint256 amount, bytes calldata data) external returns (bool); + +/** +* @dev Retrieves the balance of wrapped tokens for the specified account and ID. +* @param account The address of the account. +* @param id The token ID. +* @param data Additional data for partition. +* @return The balance of wrapped tokens. +*/ +function wrappedBalanceOf(address account, uint256 id, bytes calldata data) external view returns (uint256); + +/** +* @dev Retrieves the balance of original tokens for the specified account and ID. +* @param account The address of the account. +* @param id The token ID. +* @param data Additional data for partition. +* @return The balance of original tokens. +*/ +function originalBalanceOf(address account, uint256 id, bytes calldata data) external view returns (uint256); +} +``` + +### Methods for Interoperability + +### `setWrappedTokenAddress` + +```solidity +function setWrappedTokenAddress(address token) external returns (bool); +``` + +- `token` address could be any security token standard i.e [ERC-3643](./eip-3643.md). + +### `wrapToken` + +```solidity +function wrapToken(uint256 amount, bytes calldata data) external returns (bool); +``` + +- MUST lock token in an on-chain vault type smart contract. +- MUST mint an equivalent amount of the proposed token. +- MUST verify mapping of [ERC-1155](./eip-1155.md) `id` with the corresponding [ERC-20](./eip-20.md) compatible security token. + +### `wrapTokenFromPartition` + +```solidity +function wrapTokenFromPartition(bytes32 partitionId, uint256 id, uint256 amount, bytes calldata data) external returns (bool); +``` + +- MUST lock the token amount from source standard and mint an equivalent amount of the proposed token. +- SHOULD lock token in smart contract to achieve one to one mapping with the investor. +- MUST verify mapping of `id` with the corresponding partially fungible security token `partitionId`. + +### `unwrapToken` + +```solidity +function unwrapToken(uint256 wrappedTokenId, uint256 amount, bytes calldata data) external returns (bool); +``` + +- MUST burn the proposed token and release the original token. +- MUST verify that the token is not subject to any of the proposal's locking functionality. + +### Partition Management + +The proposal leverages the `tokenId` feature of [ERC-1155](./eip-1155.md) to represent distinct partitions within a token contract. Each `tokenId` corresponds to a unique partition with its own set of rights, privileges, and compliance rules. This enables the creation of semi-fungible tokens representing fractional ownership, different share classes, or other granular units. + +The partition paradigm offers significant flexibility and power in managing security tokens: + +1. Dynamic Allocation : Partitions allow for dynamic allocation of tokens between different classes or categories. For example, in a real estate tokenization scenario, an issuer can initially allocate tokens to a Reg D partition for accredited U.S. investors and a "Reg S" partition for non-U.S. investors. As the offering progresses and demand shifts, the issuer can dynamically mint tokens into the appropriate partition based on the investor's eligibility, ensuring optimal distribution and compliance. +2. Temporary Non-Fungibility : Partitions enable temporary non-fungibility of tokens. In some cases, securities may need to be treated as non-fungible for a certain period, such as tokens of the same underlying asset sold at different offerings. By assigning tokens to specific partitions, issuers can enforce these restrictions and maintain the necessary segregation between them, but merge them at a later point to prevent liquidity fragmentation. Merger occurs by creating a new joint partition, a deploying a merger contract where users can deposit old partitioned tokens to receive new joint partition token. +3. Granular Compliance : Each partition can have its own set of compliance rules and transfer restrictions. This allows for more granular control over token transfers based on the specific characteristics of each partition. For instance, a partition representing a particular share class may have different transfer restrictions or payout rights compared to other partitions. +4. Efficient Asset Management : Partitions streamline the management of complex asset structures. Instead of deploying separate contracts for each share class or asset category, issuers can manage multiple partitions within a single proposed contract, reducing deployment costs and simplifying overall asset management. + +### Compliance Management + +![image](../assets/eip-7518/sequentialDiagram.png) + +This proposal includes functions for managing token transfers in accordance with regulatory requirements and issuer-defined rules. The `canTransfer` function checks whether a transfer is allowed based on factors such as token restrictions, frozen addresses, transferable balances, and token locking. + +To facilitate dynamic compliance management, it introduces the concept of off-chain vouchers. These vouchers are signed messages generated by an authorized entity (e.g., the issuer or a designated compliance service) that attest to the compliance of a specific transfer. The `canTransfer` function can verify these vouchers to determine the eligibility of a transfer. + +Here's an example of how off-chain vouchers can be used with the proposal: + +1. The token issuer defines a set of compliance rules and requirements for token transfers. +2. When a user initiates a transfer, they submit a request to a designated compliance service with the necessary details (sender, recipient, amount, etc.). +3. The compliance service evaluates the transfer request against the predefined rules and requirements, considering factors such as investor eligibility, transfer restrictions, and regulatory compliance. +4. If the transfer is deemed compliant, the compliance service generates a signed voucher containing the relevant details and returns it to the user. +5. The user includes the signed voucher as an additional parameter when calling the `safeTransferFrom` function on the proposed contract. +6. The `canTransfer` function verifies the authenticity and validity of the voucher by checking the signature and ensuring that the voucher details match the transfer parameters. +7. If the voucher is valid and the transfer meets all other requirements, the transfer is allowed to proceed. + +By leveraging off-chain vouchers, the proposal enables dynamic compliance management, allowing issuers to enforce complex and evolving compliance rules without the need to update the token contract itself. This approach provides flexibility and adaptability in the face of changing regulatory requirements. + +### Token Recovery + +In case of lost or compromised wallets, the proposal includes a `forceTransfer` function that allows authorized entities (e.g., the issuer or a designated recovery agent) to transfer tokens from one address to another. This function bypasses the usual transfer restrictions and can be used as a recovery mechanism. + +### Payout Management + +Provides functions for efficient payout distribution to token holders. The `payout` function allows sending payouts to a single address, while `batchPayout` enables sending payouts to multiple addresses in a single transaction. These functions streamline the process of distributing dividends, interest, or other payments to token holders. + +### Real World Example + +![image](../assets/eip-7518/exampleUsecase.png) + +#### Use Case 1: Tokenization of Commercial Real Estate + +In this use case, a commercial real estate property with 100 floors is being tokenized using this proposal. Each floor is represented as a unique non-fungible token (NFT) partition, allowing for fractional ownership and separate management of individual floors. + +1. Property Representation: The entire commercial property is tokenized using the proposed contract, with each floor being assigned a unique tokenId representing an NFT partition. + +2. Fractional Ownership: Each floor's NFT partition can be divided into multiple fungible tokens, enabling fractional ownership. For instance, if a floor is divided into 100 tokens, multiple investors can own portions of that floor. + +3. Dynamic Pricing: Since each floor is a separate partition, the pricing of tokens within a partition can be adjusted dynamically based on factors such as floor level, amenities, or market demand. This flexibility allows for accurate representation of the varying values of different floors. + +4. Transfer of Ownership: The ownership of each floor's NFT partition can be transferred seamlessly to token holders using the safeTransferFrom function. This enables the seamless transfer of ownership rights for specific floors. + +5. Compliance Management: Different compliance rules and transfer restrictions can be applied to each partition (floor) based on regulatory requirements or issuer-defined rules. The canTransfer function can be used to enforce these rules before allowing transfers. + +6. Payouts: The payout and batchPayout functions can be used to distribute rental income, dividends, or other payouts to token holders of specific floor partitions efficiently. + +By leveraging proposal, this use case demonstrates the ability to tokenize complex real estate assets while maintaining granular control over ownership, pricing, compliance, and payouts for individual units within the property. + +#### Use Case 2: Tokenization of Securities with Reg S and Reg D Partitions + +In this use case, a company is tokenizing its securities and wants to comply with different regulations for U.S. accredited investors (Reg D) and non-U.S. investors (Reg S). + +1. Initial Partitions: The company deploys an proposed standard and creates two partitions: one for Reg D investors (accredited U.S. investors) and another for Reg S investors (non-U.S. investors). + +2. Dynamic Allocation: As the offering progresses, the company can dynamically mint tokens into the appropriate partition based on investor eligibility. For example, if a U.S. accredited investor wants to participate, tokens can be minted in the Reg D partition, while tokens for non-U.S. investors are minted in the Reg S partition. + +3. Compliance Management: Each partition can have its own set of compliance rules and transfer restrictions. The canTransfer function can be integrated with off-chain compliance services to verify the eligibility of a transfer based on the specific rules for each partition. + +4. Temporary Non-Fungibility: During the initial offering period, tokens in the Reg D and Reg S partitions may need to be treated as non-fungible due to different regulatory requirements. However, after the holding period, the company can create a new joint partition and allow token holders to deposit their old partitioned tokens to receive the new joint partition tokens, merging the two classes. + +5. Payouts: The payout and batchPayout functions can be used to distribute dividends, interest payments, or other payouts to token holders in each partition based on their respective rights and privileges. + +By utilizing the proposal, this use case demonstrates the ability to tokenize securities while maintaining compliance with different regulatory regimes, dynamically allocating tokens based on investor eligibility, and efficiently managing payouts and potential mergers of different share classes. + +#### Use Case 3: Force Transfer for AML/KYC/Compliance Violations + +In the world of tokenized securities, maintaining compliance with regulatory requirements is of utmost importance. This proposal provides a robust mechanism to handle situations where an investor's tokens need to be forcibly transferred due to violations of Anti-Money Laundering (AML), Know Your Customer (KYC), or other compliance-related regulations. + +Let's consider the scenario of Alice, an investor who holds tokens in the proposed token compliant security token contract. During the regular compliance checks conducted by the token issuer or a designated compliance service, it is discovered that Alice's wallet address is associated with suspicious activities related to money laundering or other financial crimes. + +In such a situation, the regulatory authorities or the contract administrators may decide to freeze Alice's account and initiate a forced transfer of her tokens to a designated address controlled by the issuer or a recovery agent. The `forceTransfer` function in this proposal enables this process. + +## Rationale + +### Enhancing Compliance Management + +The `canTransfer` function facilitates compliance checks during token transfers, offering adaptability through diverse implementation methods such as on-chain storage, oracle utilization, or any off-chain methodologies. This versatility ensures seamless integration with existing compliance frameworks, particularly in enforcing regulatory standards like KYC/AML. Additionally, functionalities like `freezeAddress`, `restrictTransfer`, `lockToken` and `forceTransfer` empower entities to regulate token movements based on specified conditions or regulatory requirements. Complementing these, the `unlockToken` function enhances transparency and accountability by facilitating the release of tokens post-compliance actions. + +### Interoperability with other standard + +The functions `wrapToken` and `wrapTokenFromPartition` are essential for simplifying conversions within the token system. `wrapToken` is specifically designed for wrapping ERC-20-like tokens to this protocol, on the other hand, `wrapTokenFromPartition` is used when we want to convert tokens from non-fungible tokens or any multi-standard token into proposed protocol. It allows for more specialized conversions, ensuring tokens from different standards can work together smoothly. + +The `unwrapToken` function is used to reverse the process of wrapping tokens. When tokens are wrapped, they're usually locked or held in a special way to ensure they're used correctly. users can unlock or release these tokens, returning them to their original standard, essentially, frees up tokens that were previously locked, giving users more control over their assets in the ecosystem. + +### Payment distribution + +The `payout` function enables direct payments to individual token holders for one-off or event-triggered distributions, facilitating targeted disbursements. Meanwhile, the `batchPayout` function processes multiple payments in a single transaction, optimizing efficiency for larger-scale or regular payouts on the blockchain + +## Backwards Compatibility + +The proposal is fully compatible with [ERC-1155](./eip-1155.md) , and any [ERC-1155](./eip-1155.md) compliant wallet or marketplace can interact with the proposal's tokens. The additional functions introduced by this proposal do not conflict with the [ERC-1155](./eip-1155.md) interface, ensuring seamless integration with existing ecosystem tools and infrastructure. + +## Security Considerations + +1. Access Control: The proposal includes functions that can significantly impact token transfers and balances, such as `forceTransfer`, `freezeAddress`, and `lockTokens`. It is crucial to implement proper access control mechanisms, such as role-based permissions, to ensure that only authorized entities can execute these functions. +2. Parameter Validation: Functions like `safeTransferFrom`, `lockTokens`, and `forceTransfer` should validate input parameters to prevent unauthorized or unintended actions. This includes checking for valid addresses, sufficient balances, and appropriate permissions. +3. Reentrancy Protection: The contract should implement reentrancy guards to prevent potential vulnerabilities arising from external calls, especially in functions that transfer tokens or update balances. +4. Overflow/Underflow Protection: The contract should use safe math libraries or built-in overflow protection to prevent integer overflow and underflow vulnerabilities. +5. Payout Security: The `payout` and `batchPayout` functions should ensure that only authorized entities can initiate payouts and that the total payout amount does not exceed the available balance. Proper access control and input validation are essential to prevent unauthorized or fraudulent payouts. +6. Off-Chain Voucher Security: When using off-chain vouchers for dynamic compliance management, it is crucial to ensure the security and integrity of the voucher generation process. The compliance service responsible for generating vouchers should have robust security measures in place to prevent unauthorized voucher creation or tampering. Additionally, the proposed contract should thoroughly validate the authenticity and validity of vouchers before allowing transfers to proceed. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). \ No newline at end of file diff --git a/ERCS/erc-7527.md b/ERCS/erc-7527.md index 6fa3d8ffda..042dcbce31 100644 --- a/ERCS/erc-7527.md +++ b/ERCS/erc-7527.md @@ -46,6 +46,8 @@ Three interfaces are included here: `Agency`, `App`, and `Factory`. `Agency` and `App` MAY be implemented by the same contract or MAY be separately implemented. If separately implemented, they SHALL be mutually bounded and not upgradable after initialization. +`Agency` and `App` should implement `iconstructor` interface to initialize the parameters within the contract and validate the configuration parameters. If factory is used to deploy `Agency` and `App`, factory will automatically call the two functions when deploying. + `App` SHALL implement `onlyAgency()` modifier and `mint` and `burn` SHALL apply `onlyAgency()` as a modifier, which restricts calls to `Mint` and `Burn` only have effect if they are called through the corresponding `Agency`. `Agency` is OPTIONAL to implement `onlyApp()`. @@ -66,14 +68,15 @@ pragma solidity ^0.8.20; /** * @dev The settings of the agency. * @param currency The address of the currency. If `currency` is 0, the currency is Ether. - * @param premium The base premium of the currency. + * @param basePremium The base premium of the currency. * @param feeRecipient The address of the fee recipient. * @param mintFeePercent The fee of minting. * @param burnFeePercent The fee of burning. */ + struct Asset { address currency; - uint256 premium; + uint256 basePremium; address feeRecipient; uint16 mintFeePercent; uint16 burnFeePercent; @@ -94,22 +97,27 @@ interface IERC7527Agency { * @dev Emitted when `tokenId` token is wrapped. * @param to The address of the recipient of the newly created non-fungible token. * @param tokenId The identifier of the newly created non-fungible token. - * @param price The price of wrapping. + * @param premium The premium of wrapping. * @param fee The fee of wrapping. */ - event Wrap(address indexed to, uint256 indexed tokenId, uint256 price, uint256 fee); + event Wrap(address indexed to, uint256 indexed tokenId, uint256 premium, uint256 fee); /** * @dev Emitted when `tokenId` token is unwrapped. * @param to The address of the recipient of the currency. * @param tokenId The identifier of the non-fungible token to unwrap. - * @param price The price of unwrapping. + * @param premium The premium of unwrapping. * @param fee The fee of unwrapping. */ - event Unwrap(address indexed to, uint256 indexed tokenId, uint256 price, uint256 fee); + event Unwrap(address indexed to, uint256 indexed tokenId, uint256 premium, uint256 fee); + + /** + * @dev Constructor of the instance contract. + */ + function iconstructor() external; /** - * @dev Wrap currency premium into a non-fungible token. + * @dev Wrap some amount of currency into a non-fungible token. * @param to The address of the recipient of the newly created non-fungible token. * @param data The data to encode into ifself and the newly created non-fungible token. * @return The identifier of the newly created non-fungible token. @@ -117,7 +125,10 @@ interface IERC7527Agency { function wrap(address to, bytes calldata data) external payable returns (uint256); /** - * @dev Unwrap a non-fungible token into currency premium. + * @dev Unwrap a non-fungible token into some amount of currency. + * + * Todo: event + * * @param to The address of the recipient of the currency. * @param tokenId The identifier of the non-fungible token to unwrap. * @param data The data to encode into ifself and the non-fungible token with identifier `tokenId`. @@ -133,20 +144,20 @@ interface IERC7527Agency { function getStrategy() external view returns (address app, Asset memory asset, bytes memory attributeData); /** - * @dev Returns the price and fee of unwrapping. - * @param data The data to encode to calculate the price and fee of unwrapping. - * @return price The price of wrapping. + * @dev Returns the premium and fee of unwrapping. + * @param data The data to encode to calculate the premium and fee of unwrapping. + * @return premium The premium of wrapping. * @return fee The fee of wrapping. */ - function getUnwrapOracle(bytes memory data) external view returns (uint256 price, uint256 fee); + function getUnwrapOracle(bytes memory data) external view returns (uint256 premium, uint256 fee); /** - * @dev Returns the price and fee of wrapping. - * @param data The data to encode to calculate the price and fee of wrapping. - * @return price The price of wrapping. + * @dev Returns the premium and fee of wrapping. + * @param data The data to encode to calculate the premium and fee of wrapping. + * @return premium The premium of wrapping. * @return fee The fee of wrapping. */ - function getWrapOracle(bytes memory data) external view returns (uint256 price, uint256 fee); + function getWrapOracle(bytes memory data) external view returns (uint256 premium, uint256 fee); } ``` @@ -154,7 +165,7 @@ interface IERC7527Agency { `ERC7527App` SHALL inherit `name` from interface `ERC721Metadata`. -``` +``` pragma solidity ^0.8.20; interface IERC7527App { @@ -174,6 +185,11 @@ interface IERC7527App { */ function getAgency() external view returns (address payable); + /** + * @dev Constructor of the instance contract. + */ + function iconstructor() external; + /** * @dev Sets the agency of the non-fungible token. * @param agency The agency of the non-fungible token. @@ -299,6 +315,11 @@ When executing the `unwrap` function, predetermined strategy parameters are pass They can support function oracle based on on-chain and off-chain parameters, but on-chain parameters are suggested only for consensus of on-chain reality. +### `initData` and `iconstructor` + +During the deployment of `App` and `Agency` by the Factory, the Factory uses `initData` as Calldata to call the `Agency` and `App` contracts and also invokes the `iconstructor` functions within `App` and `Agency`. + +The initData is mainly used to call the parameterized initialization functions, while `iconstructor` is often used to validate configuration parameters and non-parameterized initialization functions. ## Backwards Compatibility @@ -327,6 +348,11 @@ contract ERC7527Agency is IERC7527Agency { receive() external payable {} + function iconstructor() external override pure { + (, Asset memory _asset,) = getStrategy(); + require(_asset.basePremium != 0, "LnModule: zero basePremium"); + } + function unwrap(address to, uint256 tokenId, bytes calldata data) external payable override { (address _app, Asset memory _asset,) = getStrategy(); require(_isApprovedOrOwner(_app, msg.sender, tokenId), "LnModule: not owner"); @@ -356,33 +382,33 @@ contract ERC7527Agency is IERC7527Agency { function getStrategy() public pure override returns (address app, Asset memory asset, bytes memory attributeData) { uint256 offset = _getImmutableArgsOffset(); address currency; - uint256 premium; - address payable awardFeeRecipient; + uint256 basePremium; + address payable feeRecipient; uint16 mintFeePercent; uint16 burnFeePercent; assembly { app := shr(0x60, calldataload(add(offset, 0))) currency := shr(0x60, calldataload(add(offset, 20))) - premium := calldataload(add(offset, 40)) - awardFeeRecipient := shr(0x60, calldataload(add(offset, 72))) + basePremium := calldataload(add(offset, 40)) + feeRecipient := shr(0x60, calldataload(add(offset, 72))) mintFeePercent := shr(0xf0, calldataload(add(offset, 92))) burnFeePercent := shr(0xf0, calldataload(add(offset, 94))) } - asset = Asset(currency, premium, awardFeeRecipient, mintFeePercent, burnFeePercent); + asset = Asset(currency, basePremium, feeRecipient, mintFeePercent, burnFeePercent); attributeData = ""; } function getUnwrapOracle(bytes memory data) public pure override returns (uint256 premium, uint256 fee) { uint256 input = abi.decode(data, (uint256)); (, Asset memory _asset,) = getStrategy(); - premium = _asset.premium + input * _asset.premium / 100; + premium = _asset.basePremium + input * _asset.basePremium / 100; fee = premium * _asset.burnFeePercent / 10000; } function getWrapOracle(bytes memory data) public pure override returns (uint256 premium, uint256 fee) { uint256 input = abi.decode(data, (uint256)); (, Asset memory _asset,) = getStrategy(); - premium = _asset.premium + input * _asset.premium / 100; + premium = _asset.basePremium + input * _asset.basePremium / 100; fee = premium * _asset.mintFeePercent / 10000; } @@ -419,6 +445,8 @@ contract ERC7527App is ERC721Enumerable, IERC7527App { _; } + function iconstructor() external {} + function getName(uint256) external pure returns (string memory) { return "App"; } @@ -465,7 +493,7 @@ contract ERC7527Factory is IERC7527Factory { abi.encodePacked( appInstance, agencySettings.asset.currency, - agencySettings.asset.premium, + agencySettings.asset.basePremium, agencySettings.asset.feeRecipient, agencySettings.asset.mintFeePercent, agencySettings.asset.burnFeePercent, @@ -475,6 +503,10 @@ contract ERC7527Factory is IERC7527Factory { } IERC7527App(appInstance).setAgency(payable(agencyInstance)); + + IERC7527Agency(payable(agencyInstance)).iconstructor(); + IERC7527App(appInstance).iconstructor(); + if (agencySettings.initData.length != 0) { (bool success, bytes memory result) = agencyInstance.call(agencySettings.initData); diff --git a/ERCS/erc-7540.md b/ERCS/erc-7540.md index b59cbf6f33..f072211639 100644 --- a/ERCS/erc-7540.md +++ b/ERCS/erc-7540.md @@ -4,7 +4,7 @@ title: Asynchronous ERC-4626 Tokenized Vaults description: Extension of ERC-4626 with asynchronous deposit and redemption support author: Jeroen Offerijns (@hieronx), Alina Sinelnikova (@ilinzweilin), Vikram Arun (@vikramarun), Joey Santoro (@joeysantoro), Farhaan Ali (@0xfarhaan), João Martins (@0xTimepunk) discussions-to: https://ethereum-magicians.org/t/eip-7540-asynchronous-erc-4626-tokenized-vaults/16153 -status: Review +status: Final type: Standards Track category: ERC created: 2023-10-18 @@ -73,8 +73,6 @@ After submission, Requests go through Pending, Claimable, and Claimed stages. An Note that `maxDeposit` increases and decreases in sync with `claimableDepositRequest`. -An important Vault inequality is that following a Request(s), the cumulative requested quantity MUST be more than `pendingDepositRequest + maxDeposit - claimed`. The inequality may come from fees or other state transitions outside implemented by Vault logic such as cancellation of a Request, otherwise, this would be a strict equality. - Requests MUST NOT skip or otherwise short-circuit the Claim state. In other words, to initiate and claim a Request, a user MUST call both request* and the corresponding claim* function separately, even in the same block. Vaults MUST NOT "push" tokens onto the user after a Request, users MUST "pull" the tokens via the Claim function. For asynchronous Vaults, the exchange rate between `shares` and `assets` including fees and yield is up to the Vault implementation. In other words, pending redemption Requests MAY NOT be yield-bearing and MAY NOT have a fixed exchange rate. @@ -82,11 +80,9 @@ For asynchronous Vaults, the exchange rate between `shares` and `assets` includi ### Request Ids The request ID (`requestId`) of a request is returned by the corresponding `requestDeposit` and `requestRedeem` functions. -Multiple requests may have the same `requestId`, so a given Request is discriminated by both the `requestId` and the `owner`. - -Requests of the same `requestId` MUST be fungible with each other (except in the special case `requestId == 0` described below). I.e. all Requests with the same `requestId` MUST transition from Pending to Claimable at the same time and receive the same exchange rate between `assets` and `shares`. +Multiple requests may have the same `requestId`, so a given Request is discriminated by both the `requestId` and the `controller`. -If a Request becomes partially claimable, all requests of the same `requestId` MUST become claimable at the same pro-rata rate. +Requests of the same `requestId` MUST be fungible with each other (except in the special case `requestId == 0` described below). I.e. all Requests with the same `requestId` MUST transition from Pending to Claimable at the same time and receive the same exchange rate between `assets` and `shares`. If a Request with `requestId != 0` becomes partially claimable, all requests of the same `requestId` MUST become claimable at the same pro-rata rate. There are no assumptions or requirements of requests with different `requestId`. I.e. they MAY transition to Claimable at different times and exchange rates with no ordering or correlation enforced in any way. @@ -187,13 +183,13 @@ MUST NOT revert unless due to integer overflow caused by an unreasonably large i Assumes control of `shares` from `owner` and submits a Request for asynchronous `redeem`. This places the Request in Pending state, with a corresponding increase in `pendingRedeemRequest` for the amount `shares`. -The output `requestId` is used to partially discriminate the request along with the `controller`. See [Request Ids](#request-ids) section for more info. +The output `requestId` is used to discriminate the request along with the `controller`. See [Request Ids](#request-ids) section for more info. -MAY support either a locking or a burning mechanism for `shares` depending on the Vault implementation. +`shares` MAY be temporarily locked in the Vault until the Claimable or Claimed state for accounting purposes, or they MAY be burned immediately upon `requestRedeem`. -If a Vault uses a locking mechanism for `shares`, those `shares` MUST be burned from the Vault balance before or upon claiming the Request. +In either case, the `shares` MUST be removed from the custody of `owner` upon `requestRedeem` and burned by the time the request is Claimed. -MUST support a redeem Request flow where the control of `shares` is taken from `owner` directly where `msg.sender` has ERC-20 approval over the `shares` of `owner`, or the `owner` has approved the `msg.sender` as an operator. +Redeem Request approval of `shares` for a `msg.sender` NOT equal to `owner` may come either from ERC-20 approval over the `shares` of `owner` or if the `owner` has approved the `msg.sender` as an operator. This MUST be consistent with similar behaviour pointed out in [ERC-6909](./eip-6909.md), within "Approvals and Operators" section: "In accordance with the transferFrom method, spenders with operator permission are not subject to allowance restrictions, spenders with infinite approvals SHOULD NOT have their allowance deducted on delegated transfers, but spenders with non-infinite approvals MUST have their balance deducted on delegated transfers." When the Request is Claimable, `claimableRedeemRequest` will be increased for the `controller`. `redeem` or `withdraw` can subsequently be called by `controller` to receive `assets`. A Request MAY transition straight to Claimable state but MUST NOT skip the Claimable state. @@ -325,10 +321,10 @@ Implementations MUST support an additional overloaded `deposit` and `mint` metho - `deposit(uint256 assets, address receiver, address controller)` - `mint(uint256 shares, address receiver, address controller)` -The `controller` field is used to look up the Request for which the `assets` should be claimed. - Calls MUST revert unless `msg.sender` is either equal to `controller` or an operator approved by `controller`. +The `controller` field is used to discriminate the Request for which the `assets` should be claimed in the case where `msg.sender` is NOT `controller`. + When the `Deposit` event is emitted, the first parameter MUST be the `controller`, and the second parameter MUST be the `receiver`. ### Events @@ -417,7 +413,9 @@ MAY be logged when the operator status is set to the same status it was before t Smart contracts implementing this Vault standard MUST implement the [ERC-165](./eip-165.md) `supportsInterface` function. -Asynchronous deposit Vaults MUST return the constant value `true` if `0x3a2f2433` is passed through the `interfaceID` argument. +All asynchronous Vaults MUST return the constant value `true` if either `0xe3bc4e65` (representing the operator methods that all ERC-7540 Vaults implement) or `0x2f0a18c5` (representing the [ERC-7575](./eip-7575.md) interface) is passed through the `interfaceID` argument. + +Asynchronous deposit Vaults MUST return the constant value `true` if `0xce3bbe50` is passed through the `interfaceID` argument. Asynchronous redemption Vaults MUST return the constant value `true` if `0x620ee8e4` is passed through the `interfaceID` argument. @@ -490,7 +488,7 @@ It reduces code and implementation complexity at little to no cost to simply man ### Mandated Support for [ERC-165](./eip-165.md) -Implementing support for [ERC-165](./eip-165.md) is mandated because of the [optionality of flows](#optionality-of-flows). Integrations can use the `supportsInterface` method to check whether a vault is fully asynchronous, partially asynchronous, or fully synchronous, and use a single contract to support all cases. +Implementing support for [ERC-165](./eip-165.md) is mandated because of the [optionality of flows](#optionality-of-flows). Integrations can use the `supportsInterface` method to check whether a vault is fully asynchronous, partially asynchronous, or fully synchronous (for which it is just following the [ERC-4626](./eip-4626)), and use a single contract to support all cases. ### Not Allowing Pending Claims to be Fungible The async pending claims represent a sort of semi-fungible intermediate share class. Vaults can elect to wrap these claims in any token standard they like, for example, ERC-20, [ERC-1155](./eip-1155.md), or ERC-721 depending on the use case. This is intentionally left out of the spec to provide flexibility to implementers. @@ -556,6 +554,12 @@ In general, asynchronicity concerns make state transitions in the Vault much mor * The view methods for viewing Pending and Claimable request states (e.g. pendingDepositRequest) are estimates useful for display purposes but can be outdated. The inability to know the final exchange rate on any Request requires users to trust the implementation of the asynchronous Vault in the computation of the exchange rate and fulfillment of their Request. * Shares or assets locked for Requests can be stuck in the Pending state. Vaults may elect to allow for the fungibility of pending claims or implement some cancellation functionality to protect users. +### Operators + +An operator has the ability to transfer the `asset` of the vault from the approver to any address, and simultaneously grants control over the `share` of the vault. + +Any user approving an operator must trust that operator with both the `asset` and `share` of the Vault. + ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md). \ No newline at end of file diff --git a/ERCS/erc-7555.md b/ERCS/erc-7555.md new file mode 100644 index 0000000000..88d666f1fa --- /dev/null +++ b/ERCS/erc-7555.md @@ -0,0 +1,211 @@ +--- +eip: 7555 +title: Single Sign-On for Account Discovery +description: Discover accounts using a signing key that do not use the secp256k1 curve. +author: Alexander Müller (@alexmmueller), Gregory Markou (@GregTheGreek), Willem Olding (@Wollum), Belma Gutlic (@morrigan), Marin Petrunić (@mpetrunic), Pedro Gomes (@pedrouid) +discussions-to: https://ethereum-magicians.org/t/erc-7555-single-sign-on-for-account-discovery/16536 +status: Draft +type: Standards Track +category: ERC +created: 2023-11-10 +requires: 4337 +--- + +## Abstract +This proposal establishes a standardized interface and functionality for applications to discover user accounts besides the readily available EOA. Specifically discovering normal accounts and smart accounts that may have been deployed or configured using a signing key that is not the standard Ethereum secp256k1 curve. The objective is to ensure uniformity of address retrieval across applications, and domains. + +## Motivation +The recent progress in account abstraction has led to significantly increased flexibility enabling use cases such as multi-signature transactions, social recovery, contract/account whitelisting, session keys and much more. However, with increased flexibility there comes an increased complexity. One area of increased complexity is account fragmentation -both at the EOA and smart account level - following from the inability to correctly identify all existing addresses by a user. In this EIP we present a potential solution that aims to unify the discovery and handling of such accounts. + +Prior to [ERC-4337](./eip-4337.md), the standard approach to interacting with a smart contract account required a valid signature from a keypair using secp256k1. Since ERC-4337, alternative signing options have become popular, such as passkey, yubikey or ios/android secure enclaves, which do not conform to the secp256k1 curve, and require a paymaster to submit the transaction on the users behalf. Since providers implement additional logic into the key generation process (shamir, mpc, secure enclave, etc) alternative signers have no uniform way for a user to produce the same externally-owned account adresses, or smart account addresses across different applications. + +Secure hardware devices such as native passkeys, or yubikeys generate a unique keypair per domain. The implication is for application developers that natively integrate authentication methods such as those, will never be able to recover a uniform keypair. Practically, if we have the following scenario where there are two applications: a mobile app (App A), and a web based application (App B). If both implement a solution such as passkey, App A and App B would recover two different keys. This poses a hurdle to the user who would expect to have the same address across services (much like they would using a hardware wallet, or other wallets). + +With the introduction of 4337, this problem is amplified. An application that wants its users to leverage 4337 (to abstract keys away, and generally improve the onboarding experience) will not be able to detect if a user has an existing smart account deployed. This will lead to the developer (or third party service providing the onboarding experience) to deploy a smart account on behalf of the user at the given address scoped to the apps domain. + +Not being able to correctly identify existing accounts owned by a user will lead to account fragmentation. The fragmentation, as described early, exists because applications will identify them as a new user, and not one whom may already have an account. Leading to a single user having many unassociated accounts, with assets scattered amongst them, and no way to unify them. + +This standard aims to achieve: +1. Standard way for applications to request a users signing address. +2. Standard way for applications to provide single sign-on (SSO) functionality for alternative signing methods. +3. Standard way for applications to disclose smart accounts that have been created through their own service. + +This standard **does not** aim to achieve: +1. How a user can sign messages across domains. +2. How a provider generates a keypair for a user. +3. How an application handles the user interface logic. + + +## Specification +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. + +### Definitions +- **Smart account** - An ERC-4337 compliant smart contract account that has a modular architecture. +- **Domain** - A string of text acting as an identification to a server or wesbite (eg: `ethereum.org` or `ABCDE12345.com.example.app`). +- **EOA** - Accounts that are controlled by a single private key. +- **Provider** - A third party service provider that is able to authenticate a user and produce a keypair for the user. + +### Redirects +An application looking to authenticate a user must navigate the user to a given provider's URI based on the `URI Request Syntax`. The application must implement a valid redirect URI for the callback in order to receive a valid response. + +#### Available Routes +- `/auth/`: The route used to authenticate a user, and request credentials. +- `/sendTransaction/`: The route used to send a transaction payload for a user to sign. This is more of a convenient method to allow applications to do both authentication, and plugin registration within a single redirect, instead of requiring the user to perform two redirects. + +### Schema +The `smart_account_address` should be returned in the CAIP-10 format. + +#### Auth Route +##### Request Schema +```= swagger + parameters: + - in: query + name: redirect_uri + schema: + type: string + description: The uri that the provider should redirect back to. + - in: query + name: chain_id + schema: + type: string + description: The chain_id of a given network. +``` +##### Response Schema +```= swagger + parameters: + - in: query + name: smart_account_address + schema: + type: string + description: The on-chain address for a given smart account, formatted using CAIP-10 +``` + +##### Request Syntax +```= swagger +https:///auth/? + redirect_uri= + &chain_id= +``` +##### Response Syntax +```= swagger +https:///auth/? + smart_account_address= +``` + +#### sendTransaction Route +##### Request Schema +```= swagger + parameters: + - in: query + name: redirect_uri + schema: + type: string + description: The uri that the provider should redirect back to. + - in: query + name: chain_id + schema: + type: string + description: The chain_id of a given network. + - in: query + name: transaction + schema: + type: string + description: The RLP encoded transaction that needs to be signed +``` +##### Response Schema +```= swagger + parameters: + - in: query + name: smart_account_address + schema: + type: string + description: The on-chain address for a given smart account, formatted using CAIP-10 + - in: query + name: tx_hash + schema: + type: string + description: The hash of the transaction +``` + +##### Request Syntax +```= swagger +https:///sendTransaction/? + redirect_uri= + &chain_id= + &transaction= +``` +##### Response Syntax +```= swagger +https:///sendTransaction/? + smart_account_address= + &tx_hash= +``` + +## Rationale +### Redirects +Taking inspiration from how SSO functions in the web today. We implement a similar redirect pattern, consisting of a simple request/response. + +#### Application +##### Initial Request +An application would redirect a user to a specified provider, only passing along the callback url information. This is to ensure the providers website can remain stateless, and not rely on web requests. +##### Response from provider +When a user is redirected to the application, it can parse the response for a signer address, and associated smart account address. + +#### Provider +Upon a user navigating to the provider website, the provider would parse the redirect url and authenticate the user. The authentication method does not matter, such that it can produce a valid public address, and recover any smart accounts that may have been deployed through the provider. + +## Backwards Compatibility + +No backward compatibility issues found. + +## Reference Implementation +Using `location.replace()` vs `location.href` is up to the application to decide how they wish the experience to be handled. + +Sample URI Request +```= +https://eth-sso.ethereum.org/auth?redirect_uri=http://myapp.com/eth-sso/callback/&chain_id=1 +``` +Sample Response +```= +http://myapp.com/callback/?smart_account_address=0xb...c +``` + +Application logic +```javascript= +// https://myapp.com +// User triggered authentication function +function auth() { + window.location.replace("https://eth-sso.ethereum.org/auth?redirect_uri=myapp.com&chain_id=1/eth-sso/callback/"); +}; + +// App level routing logic (generic router) +route("/eth-sso/callback/", function() { + let params = (new URL(document.location)).searchParams; + let smartAccountAddress = params.get("smart_account_address"); +}); +``` + +Provider Logic +```javascript= +// eg: https://eth-sso.ethereum.org/auth +route("/eth-sso/callback/", function("/auth") { + let params = (new URL(document.location)).searchParams; + let redirectUrl = params.get("redirect_uri"); + // Authenticate the user (eg: with passkeys) + let address = "..."; + // Get smart account if available + let smartAccountAddress = getSmartAccount(address); + window.location.replace(`http://${redirectUrl}/?smart_account_address=${smartAccountAddress}`); +}); +``` + +## Security Considerations + + +- Is there a concern that a user can spoof another persons address, and that could be malicious? For example, circumventing the provider, and manually calling the redirect_url with a chosen address. A way around this would be having the user actually sign a challenge message, perhaps leveraging SIWE. + +The absence of wildcard support in the redirect URI is intended to protect users from nested open redirect vulnerabilities. Allowing wildcards could enable attackers to redirect users to different pages under the supported wildcard, creating a vulnerability to open redirects. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). \ No newline at end of file diff --git a/ERCS/erc-7575.md b/ERCS/erc-7575.md index efe9ec98ba..c3e279e88c 100644 --- a/ERCS/erc-7575.md +++ b/ERCS/erc-7575.md @@ -4,8 +4,7 @@ title: Multi-Asset ERC-4626 Vaults description: Extended ERC-4626 Interface enabling Multi-Asset Vaults author: Jeroen Offerijns (@hieronx), Alina Sinelnikova (@ilinzweilin), Vikram Arun (@vikramarun), Joey Santoro (@joeysantoro), Farhaan Ali (@0xfarhaan) discussions-to: https://ethereum-magicians.org/t/erc-7575-partial-and-extended-erc-4626-vaults/17274 -status: Last Call -last-call-deadline: 2024-06-11 +status: Final type: Standards Track category: ERC created: 2023-12-11 diff --git a/ERCS/erc-7578.md b/ERCS/erc-7578.md index 68c8f83b2f..fa520d71d6 100644 --- a/ERCS/erc-7578.md +++ b/ERCS/erc-7578.md @@ -2,9 +2,9 @@ eip: 7578 title: Physical Asset Redemption description: Provides the holder of physical asset backed NFTs readily available real-world information on the underlying physical asset. -author: Lee Vidor (@V1d0r) , David Tan , Lee Smith +author: Lee Vidor (@V1d0r), David Tan , Lee Smith , Gabriel Stoica (@gabrielstoica) discussions-to: https://ethereum-magicians.org/t/erc-7578-physical-asset-redemption/17556 -status: Draft +status: Review type: Standards Track category: ERC created: 2023-08-01 @@ -21,37 +21,33 @@ The first wave of NFT use cases encompass predominately the representation of ow Addressing the lack of readily available information and paving the way for mass adoption for a tokenized economy, this proposal requires that each minted token includes a defined number of predefined variables enabling verification of authenticity and facilitating redemption of the underlying physical asset. -- `[TOKEN ISSUER]` **Identification of individual or entity minting the NFT (Issuer)**: [NAME], [UNIQUE IDENTIFICATION NUMBER] OR [NETWORK IDENTIFICATION] +## 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. + +When a token is minted, its properties SHOULD be initialized beforehand, with each field being defined as follows: + +- **Token issuer**: identification of an individual or entity minting the NFT
_The token issuer is the key person connecting the physical asset and the digital representation. By identifying and disclosing the token issuer, a reference point is made instantly available to the NFT holder which allows them to conduct a due diligence on the NFT issuer and assessment of the NFT issuer’s trustworthiness. At the same time, it creates accountability for the token issuer which leads to overall improvement and standardisation of the NFT minting process. Token issuers will compete for best practices and recognition to gain advantages over competitors. A reputable NFT issuer will e.g. keep information on the legal owner of the physical asset prior to the minting of the underlying physical asset to satisfy any AML and KYC concerns. Ideally the NFT issuer is identified by a name but may also be identifiable via unique identification number or network ID that is issued by a service provider who stores relevant information on the NFT issuer._ -- `[LEGAL OWNER OF UNDERLYING PHYSICAL ASSET]` **Identification of legal owner of underlying physical asset:** [NAME], [UNIQUE IDENTIFICATION NUMBER] OR [NETWORK ID] +- **Asset holder**: identification of legal owner of underlying physical asset
_In view of a redemption of the underlying physical asset and enforcing of legal rights, it is (from a legal perspective) essential for the NFT holder to identify the legal owner of the underlying physical asset. It allows the NFT holder to consider the legal counterparty risk. It cannot be assumed that the NFT issuer is the legal owner of the underlying physical asset, therefore it is vital for the NFT holder to have instant access to this additional information. Same as with the NFT issuer’s identity, the legal owner is ideally identified by a name but may also be identifiable via unique identification number or network ID that is issued by a service provider who stores relevant information on the legal owner._ -- `[STORAGE LOCATION]` **Identification of storage location of underlying physical asset:** [PHYSICAL ADDRESS OR JURISDICTION] +- **Storage location**: identification of storage location of underlying physical asset
_Certain physical assets require specific storage conditions. A digital representation of an inappropriately stored physical asset may impact the value of the NFT significantly. Disclosing the storage location and making it directly accessible to the NFT holder, allows them to evaluate the storage risk of the underlying physical asset. In addition, it provides the NFT holder with a second point of contact for the enforcement of the redemption of the underlying physical asset._ -- `[LEGAL CONTRACT]` **Type of legal relationship:** [WITH LINK TO DOCUMENT IPSF] +- **Terms**: identification of legal relationship
_The disclosure and accessibility of the legal basis of the relationship between NFT holder and legal owner of the underlying physical asset promotes token issuers to stipulate and define the legal rights of the involved key parties. It furthermore allows the NFT Holder to conduct a legal risk and enforcement assessment. Ideally, the information is provided by embedding a link to the actual legal documentation such as an agreement or terms. The more information is accessible via the NFT, the better the NFT holder can assess the legal risks associated with enforcement of the redemption of the underlying physical asset._ -- `[APPLICABLE LAW]` **Governing Law and Jurisdiction:** [JURISDICTION] +- **Jurisdiction**: governing law and jurisdiction
_The applicable law is an extension of the legal contract disclosure and makes instantly available to the NFT holder or smart contract under what jurisdiction an enforcement would be governed without the need to review the details legal contract. This allows for an instant assessment of jurisdiction risk._ -- `[DECLARED VALUE]` **Value of the underlying asset:** [VALUE] +- **Declared value**: value of the underlying asset
_Certain auxiliary services such as insurance are tied to a value of the NFT and underlying physical asset. By defining a declared value, NFTs are able to be categorised in certain ways while the declared value provides an indication regarding the underlying asset’s value. The declared value of the underlying physical asset does not necessarily represent the market value._ -## 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. - -`Properties` MUST be set before a token can be minted. - -The `tokenId` MUST be greater than 0. - -The `terms` parameter MUST be a link to a document that is stored on IPFS. - -When a token has no `properties` set mint reverts. +The `terms` parameter SHOULD be an HTTP link to a document that is stored on IPFS. This is to ensure that the document is immutable and can be verified by the NFT holder. -When a token has a valid properties, if the token burns, the properties MUST be deleted. +When a token with valid properties is to be burned, the properties MUST be removed. ### Contract Interface @@ -62,7 +58,7 @@ pragma solidity ^0.8.21; * @notice Struct encapsulating fields required to by the ERC-7578 standard to represent the physical asset * @param tokenIssuer The network or entity minting the token * @param assetHolder The legal owner of the physical asset - * @param storedLocation The physical storage location + * @param storageLocation The physical location where the asset is stored * @param terms Link to IPFS contract, agreement or terms * @param jurisdiction The legal justification set out in the terms * @param declaredValue The declared value at time of token minting @@ -70,7 +66,7 @@ pragma solidity ^0.8.21; struct Properties { string tokenIssuer; string assetHolder; - string storedLocation; + string storageLocation; string terms; string jurisdiction; Amount declaredValue; @@ -92,7 +88,6 @@ struct Amount { interface IERC7578 { /** * @notice Emitted when the properties of the `tokenId` token are set - * * @param tokenId The ID of the token * @param properties The properties of the token */ @@ -100,47 +95,30 @@ interface IERC7578 { /** * @notice Emitted when the properties of the `tokenId` token are removed - * * @param tokenId The ID of the token */ event PropertiesRemoved(uint256 indexed tokenId); /** - * @notice Retrieves all the properties of the `tokenId` token + * @notice Retrieves all properties of the `tokenId` token * @dev Does NOT revert if token doesn't exist * @param tokenId The token ID of the minted token */ function getProperties(uint256 tokenId) external view returns (Properties memory properties); - - /** - * @notice Sets the properties of the `tokenId` token - * - * IMPORTANT: Properties required to be set when minting a token - * - * @param tokenId The ID of the token - * @param properties The properties of the token - */ - function setProperties(uint256 tokenId, Properties calldata properties) external; } ``` -The `setProperties(uint256 tokenId, Properties calldata properties)` function is called before minting a token. - -The `getProperties(uint256 tokenId)` function MUST return the unique `properties` for a token. - When `properties` are set, the `PropertiesSet(uint256 indexed tokenId, Properties properties)` event is emitted. When `properties` are removed, the `PropertiesRemoved(uint256 indexed tokenId)` event is emitted. -## Rationale - -The `tokenId` must be greater than 0 so the `properties` can be checked before minting. +The `getProperties(uint256 tokenId)` function MUST return the unique `properties` of a token. If the ERC-721 token is burned or has no properties set, it SHOULD return an empty `Properties` struct. -The `_update` function is overridden to check if the `properties` are set before minting and removed after burning. +## Rationale -The `terms` parameter is a HTTP link to a document that is stored on IPFS. This is to ensure that the document is immutable and can be verified by the NFT holder. +By not initializing a token's properties before minting, one risks that the asset's provenance represented by the token cannot be established. -Contract level validation is not used on the properties as we believe the accuracy of the data declared is the responsibility of the token issuer. This builds trust in the token issuer and the token itself. +Contract level validation is not used on the properties as we believe the accuracy of the data declared is the responsibility of the token issuer. This builds trust on the token issuer and the token itself. ## Backwards Compatibility @@ -153,7 +131,7 @@ An example of an [ERC-721](./eip-721.md) that includes this proposal using the O ```solidity pragma solidity ^0.8.21; -import { ERC721 } from "@openzeppelin/contracts-v5/token/ERC721/ERC721.sol"; +import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import { IERC7578, Properties, Amount } from "./interfaces/IERC7578.sol"; /** @@ -161,7 +139,7 @@ import { IERC7578, Properties, Amount } from "./interfaces/IERC7578.sol"; * @author DESAT * @notice Implementation of the ERC-7578: Physical Asset Redemption standard **/ -contract ERC7578 is IERC7578, ERC721 { +contract ERC7578 is ERC721, IERC7578 { /** * @notice Thrown when the properties of a token are not initialized */ @@ -178,13 +156,13 @@ contract ERC7578 is IERC7578, ERC721 { constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) {} /** - * @inheritdoc IERC7578 + * @notice Initializes the ERC-7578 properties of the `tokenId` token */ function setProperties(uint256 tokenId, Properties calldata properties) public { _properties[tokenId] = Properties({ tokenIssuer: properties.tokenIssuer, assetHolder: properties.assetHolder, - storedLocation: properties.storedLocation, + storageLocation: properties.storageLocation, terms: properties.terms, jurisdiction: properties.jurisdiction, declaredValue: Amount({ @@ -232,7 +210,7 @@ contract ERC7578 is IERC7578, ERC721 { ## Security Considerations -For further discussion. +To ensure the authenticity of a token's properties, the `setProperties()` method should only be called by a trusted externally owned account (EOA) or contract. This trusted entity must verify that the properties accurately reflect the real physical attributes of the token. ## Copyright diff --git a/ERCS/erc-7579.md b/ERCS/erc-7579.md index 5d41db4915..2f3542e23c 100644 --- a/ERCS/erc-7579.md +++ b/ERCS/erc-7579.md @@ -54,8 +54,8 @@ To comply with this standard, smart accounts MUST implement the execution interf interface IExecution { /** * @dev Executes a transaction on behalf of the account. - * @param mode The encoded execution mode of the transaction. See ModeLib.sol for details - * @param executionCalldata The encoded execution call data + * @param mode The encoded execution mode of the transaction. + * @param executionCalldata The encoded execution call data. * * MUST ensure adequate authorization control: e.g. onlyEntryPointOrSelf if used with ERC-4337 * If a mode is requested that is not supported by the Account, it MUST revert @@ -65,8 +65,8 @@ interface IExecution { /** * @dev Executes a transaction on behalf of the account. * This function is intended to be called by Executor Modules - * @param mode The encoded execution mode of the transaction. See ModeLib.sol for details - * @param executionCalldata The encoded execution call data + * @param mode The encoded execution mode of the transaction. + * @param executionCalldata The encoded execution call data. * * MUST ensure adequate authorization control: i.e. onlyExecutorModule * If a mode is requested that is not supported by the Account, it MUST revert @@ -93,7 +93,7 @@ function executeUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) The execution mode is a `bytes32` value that is structured as follows: -- callType (1 byte): `0x00` for a single `call`, `0x01` for a batch `call` and `0xff` for `delegatecall` +- callType (1 byte): `0x00` for a single `call`, `0x01` for a batch `call`, `0xfe` for `staticcall` and `0xff` for `delegatecall` - execType (1 byte): `0x00` for executions that revert on failure, `0x01` for executions that do not revert on failure but implement some form of error handling - unused (4 bytes): this range is reserved for future standardization - modeSelector (4 bytes): an additional mode selector that can be used to create further execution modes @@ -105,7 +105,13 @@ Here is a visual representation of the execution mode: | -------- | -------- | ------- | ------------ | ----------- | | 1 byte | 1 byte | 4 bytes | 4 bytes | 22 bytes | -Accounts are NOT REQUIRED to implement all execution modes. The account MUST declare what modes are supported in `supportsAccountMode` (see below) and if a mode is requested that is not supported by the account, the account MUST revert. +Accounts are NOT REQUIRED to implement all execution modes. The account MUST declare what modes are supported in `supportsExecutionMode` (see below) and if a mode is requested that is not supported by the account, the account MUST revert. + +The account MUST encode the execution data the following ways: + +- For single calls, the `target`, `value` and `callData` are packed in this order (ie `abi.encodePacked` in Solidity). +- For delegatecalls, the `target` and `callData` are packed in this order (ie `abi.encodePacked` in Solidity). +- For batch calls, the `targets`, `values` and `callDatas` are put into an array of `Execution` structs that includes these fields in this order (ie `Execution(address target, uint256 value, bytes memory callData)`). Then, this array is encoded with padding (ie `abi.encode` in Solidity). #### Account configurations @@ -212,10 +218,10 @@ Smart accounts MAY implement a fallback function that forwards the call to a fal If the smart account has a fallback handler installed, it: -- MUST implement authorization control -- MUST use `call` to invoke the fallback handler +- MUST use `call` or `staticcall` to invoke the fallback handler - MUST utilize [ERC-2771](./eip-2771.md) to add the original `msg.sender` to the `calldata` sent to the fallback handler - MUST route to fallback handlers based on the function selector of the calldata +- MAY implement authorization control, which SHOULD be done via hooks #### ERC-165 @@ -299,7 +305,7 @@ Executors MUST implement the `IModule` interface and have module type id: `2`. Fallback handlers MUST implement the `IModule` interface and have module type id: `3`. -Fallback handlers that implement authorization control, MUST NOT rely on `msg.sender` for authorization control but MUST use ERC-2771 `_msgSender()` instead. +Fallback handlers MAY implement authorization control. Fallback handlers that do implement authorization control, MUST NOT rely on `msg.sender` for authorization control but MUST use ERC-2771 `_msgSender()` instead. #### Hooks @@ -320,12 +326,10 @@ interface IHook is IModule { /** * @dev Called by the smart account after execution * @param hookData the data that was returned by the `preCheck` function - * @param executionSuccess whether the execution(s) was (were) successful - * @param executionReturn the return/revert data of the execution(s) * * MAY validate the `hookData` to validate transaction context of the `preCheck` function */ - function postCheck(bytes calldata hookData, bool executionSuccess, bytes calldata executionReturn) external; + function postCheck(bytes calldata hookData) external; } ``` diff --git a/ERCS/erc-7582.md b/ERCS/erc-7582.md new file mode 100644 index 0000000000..5b2746508a --- /dev/null +++ b/ERCS/erc-7582.md @@ -0,0 +1,163 @@ +--- +eip: 7582 +title: Modular Accounts with Delegated Validation +description: Extends ERC-4337 interface with nonce-based plugins +author: Shivanshi Tyagi (@nerderlyne), Ross Campbell (@z0r0z) +discussions-to: https://ethereum-magicians.org/t/erc-7582-modular-accounts-with-delegated-validation/17640 +status: Draft +type: Standards Track +category: ERC +created: 2023-12-25 +requires: 4337 +--- + +## Abstract + +This proposal standardizes a method for adding plugins and composable logic to smart contract accounts built on existing interfaces like [ERC-4337](eip-4337.md) (e.g., ERC-4337's `IAccount`). Specifically, by formalizing how applications can use the ERC-4337 Entry Point `NonceManager` and the emission of the `IEntryPoint` `UserOperationEvent` to account for plugin interactions, as well, as how to extract designated validators (in this case, by means of `IAccount`'s `validateUserOp`), accounts can specify how they call plugin contracts and grant special executory access for more advanced operations. Furthermore, this minimalist plugin approach is developer-friendly and complimentary to existing account abstraction standards by not requiring any additional functions for contracts that follow the `IAccount` interface (itself minimalist in only specifying one function, `validateUserOp`). + +## Motivation + +Smart contract accounts (contract accounts) are a powerful tool for managing digital assets and executing transactions by allowing users to program their interactions with blockchains. However, they are often limited in their functionality and flexibility without sufficient consensus around secure abstraction designs (albeit, the adoption of ERC-4337 is the preferred path of this proposal). For example, contract accounts are often unable to support social recovery, payment schedules, and other features that are common in traditional financial systems without efficient and predictable schemes to delegate execution and other access rights to approximate the UX of custodial and more specialized applications. + +Account abstraction standards like ERC-4337 have achieved simplification of many core contract account concerns such as transaction fee payments, but to fully leverage the expressive capability of these systems to accomplish user intents, minimalist methods to delegate contract account access and validation to other contracts would aid their UX and extend the benefits of centering operations around the Entry Point. + +While the `IAccount` interface from ERC-4337 does not specify a way to add custom validation logic to contract accounts to support plugins and similar extensions without upgrades or migrations, it nevertheless contains sufficient information to do so efficiently. This proposal therefore offers a method for adding plugins and other composable validation logic to smart contract accounts built on existing interfaces with singleton nonce-tracking like ERC-4337's `IAccount` and `NonceManager`. + +## Specification + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. + +![diagram showing proposed flow](../assets/eip-7582/base-flow.svg) + +We leverage the key in ERC-4337 semi-abstracted nonce as the pointer to `validator` identifier. If a non-sequential key (`>type(uint64).max`) is used as an ERC-4337 Entry Point `UserOperation` (userOp) `nonce`, the `validateUserOp` function in the `sender` contract account MUST extract the validator identifier, this MAY be the address itself or a pointer to the validator address in storage. Once the validator contract address is extracted, the proposed contract account (henceforth, shall be referred to as MADV account) MUST forward the userOp calldata to the validator. This calldata SHOULD be the entire userOp. In response to this delegated validation, the validator contract MUST return the ERC-4337 `validationData`, and the MADV `sender` account MUST return this as the `validationData` to the Entry Point. + +In all of the above validation steps, the validator contract MUST respect the ERC-4337 Entry Point conventions. Note, that while validator key data might be included elsewhere in a `UserOperation` to achieve similar contract account modularity, for example, by packing this data into the `signature` field, this proposal opts to repurpose `nonce` for this pointer to minimize calldata costs and to benefit from the EntryPoint's `getNonce` accounting, as well as the discoverability of user plugin interactions in the `UserOperationEvent` which exposes `nonce` but not other userOp data. + +### ERC-4337 references: + +`PackedUserOperation` interface + +```solidity +/** + * User Operation struct + * @param sender - The sender account of this request. + * @param nonce - Unique value the sender uses to verify it is not a replay. In MADV, the validator identifier is encoded in the high 192 bit (`key`) of the nonce value + * @param initCode - If set, the account contract will be created by this constructor/ + * @param callData - The method call to execute on this account. + * @param accountGasLimits - Packed gas limits for validateUserOp and gas limit passed to the callData method call. + * @param preVerificationGas - Gas not calculated by the handleOps method, but added to the gas paid. + * Covers batch overhead. + * @param gasFees - packed gas fields maxPriorityFeePerGas and maxFeePerGas - Same as EIP-1559 gas parameters. + * @param paymasterAndData - If set, this field holds the paymaster address, verification gas limit, postOp gas limit and paymaster-specific extra data + * The paymaster will pay for the transaction instead of the sender. + * @param signature - Sender-verified signature over the entire request, the EntryPoint address and the chain ID. + */ +struct PackedUserOperation { + address sender; + uint256 nonce; + bytes initCode; + bytes callData; + bytes32 accountGasLimits; + uint256 preVerificationGas; + bytes32 gasFees; + bytes paymasterAndData; + bytes signature; +} +``` + +`IAccount` interface + +```solidity +interface IAccount { + /** + * Validate user's signature and nonce + * the entryPoint will make the call to the recipient only if this validation call returns successfully. + * signature failure should be reported by returning SIG_VALIDATION_FAILED (1). + * This allows making a "simulation call" without a valid signature + * Other failures (e.g. nonce mismatch, or invalid signature format) should still revert to signal failure. + * + * @dev Must validate caller is the entryPoint. + * Must validate the signature and nonce + * @param userOp - The operation that is about to be executed. + * @param userOpHash - Hash of the user's request data. can be used as the basis for signature. + * @param missingAccountFunds - Missing funds on the account's deposit in the entrypoint. + * This is the minimum amount to transfer to the sender(entryPoint) to be + * able to make the call. The excess is left as a deposit in the entrypoint + * for future calls. Can be withdrawn anytime using "entryPoint.withdrawTo()". + * In case there is a paymaster in the request (or the current deposit is high + * enough), this value will be zero. + * @return validationData - Packaged ValidationData structure. use `_packValidationData` and + * `_unpackValidationData` to encode and decode. + * <20-byte> sigAuthorizer - 0 for valid signature, 1 to mark signature failure, + * otherwise, an address of an "authorizer" contract. + * <6-byte> validUntil - Last timestamp this operation is valid. 0 for "indefinite" + * <6-byte> validAfter - First timestamp this operation is valid + * If an account doesn't use time-range, it is enough to + * return SIG_VALIDATION_FAILED value (1) for signature failure. + * Note that the validation code cannot use block.timestamp (or block.number) directly. + */ + function validateUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash, + uint256 missingAccountFunds + ) external returns (uint256 validationData); +} +``` + +`NonceManager` interface + +```solidity + /** + * Return the next nonce for this sender. + * Within a given key, the nonce values are sequenced (starting with zero, and incremented by one on each userop) + * But UserOp with different keys can come with arbitrary order. + * + * @param sender the account address + * @param key the high 192 bit of the nonce, in MADV the validator identifier is encoded here + * @return nonce a full nonce to pass for next UserOp with this sender. + */ + function getNonce(address sender, uint192 key) + external view returns (uint256 nonce); +``` + +`UserOperationEvent` + +```solidity +/*** + * An event emitted after each successful request + * @param userOpHash - unique identifier for the request (hash its entire content, except signature). + * @param sender - the account that generates this request. + * @param paymaster - if non-null, the paymaster that pays for this request. + * @param nonce - the nonce value from the request. + * @param success - true if the sender transaction succeeded, false if reverted. + * @param actualGasCost - actual amount paid (by account or paymaster) for this UserOperation. + * @param actualGasUsed - total gas used by this UserOperation (including preVerification, creation, validation and execution). + */ + event UserOperationEvent(bytes32 indexed userOpHash, address indexed sender, address indexed paymaster, uint256 nonce, bool success, uint256 actualGasCost, uint256 actualGasUsed); +``` + +## Rationale + +This proposal is designed to be a minimalist extension to ERC-4337 that allows for additional functionality without requiring changes to the existing interface. Keeping the proposal's footprint small. + +Further, by repurposing the nonce field for the validator identifier we minimize calldata costs and leverage existing `getNonce` accounting. The `UserOperationEvent` emits nonce which can be used for tracking validator invocations without additional events. Other options like packing the validator identifier into the `signature` field were considered but were rejected due to potential for conflict with other signatures schemes and increased opaqueness into validator invocation. + +This proposal allows for MADV accounts to specify their own method for extracting the validator address from the `nonce`. This provides flexibility to account developers and supports both "just in time" validators as well as a more predictable storage pattern for plugin reuse. + +The requirement is simply to use `nonce` for encoding an identifier and to return the `validationData` from the extracted validator contract to the `EntryPoint` in line with the requirements of the ERC-4337 `validateUserOp` function. + +## Backwards Compatibility + +No backward compatibility issues found. + +## Reference Implementation + +See the [MADV reference implementation](../assets/eip-7582/MADVAccount.sol) for a simple example of how to implement this proposal. + +## Security Considerations + +As this proposal introduces no new functions and leaves implementation of the validator extraction method and approval logic open to developers, the surface for security issues is intentionally kept small. Nevertheless, specific validator use cases require further discussion and consideration of the overall ERC-4337 verification flow and its underlying security. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). \ No newline at end of file diff --git a/ERCS/erc-7585.md b/ERCS/erc-7585.md index 0e7eedf6b3..ddbd016e93 100644 --- a/ERCS/erc-7585.md +++ b/ERCS/erc-7585.md @@ -13,7 +13,7 @@ requires: 165, 721, 1155 ## Abstract -This proposal introduces a design for `minimum value selection` storage proofs on Merkle trees. The design consists of two main components: +This proposal introduces a design for "minimum value selection" storage proofs on Merkle trees. The design consists of two main components: 1. A hashing algorithm termed MixHash, aimed to replace the commonly used Keccak256 and SHA256 algorithms. 2. Public data storage proofs. This enables anyone to present a proof to a public network, verifying their possession of a copy of specific public data marked by MixHash. @@ -192,7 +192,7 @@ def generateProofWithPow(mixHash, blockHeight,file) noise = noise + 1 m_path = getMerkleTreePath(chunk_hash_array, min_index) - return strorage_proof(mixHash, blockHeight, min_index, m_path, min_chunk,noise) + return storage_proof(mixHash, blockHeight, min_index, m_path, min_chunk,noise) ``` Applying this mechanism increases the cost of generating storage proofs, which deviates from our initial intent to reduce the widespread effective storage of public data. Moreover, heavily relying on a PoW-based economic model may allow Suppliers with significant advantages in PoW through specialized hardware to disrupt the basic participatory nature of the game, reducing the widespread distribution of public data. Therefore, it is advised not to enable the PoW mechanism unless absolutely necessary. @@ -262,12 +262,15 @@ PublicDataProofDemo includes test cases written using Hardhat. ## Reference Implementation PublicDataProof Demo + - A standard reference implementation DMC public data inscription + - Based on public data storage certification, a complete economic model and gameplay has been designed on ETH network and BTC inscription network Learn more background and existing attempts + - DMC Main Chain - CYFS @@ -280,4 +283,3 @@ The design of MixHash can support storage proofs for private files, but this req ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md). - diff --git a/ERCS/erc-7586.md b/ERCS/erc-7586.md index 06549c6f3f..5f1fdfb2d6 100644 --- a/ERCS/erc-7586.md +++ b/ERCS/erc-7586.md @@ -4,7 +4,7 @@ title: Interest Rate Swaps description: Interest rate swaps derivative contracts author: Samuel Gwlanold Edoumou (@Edoumou) discussions-to: https://ethereum-magicians.org/t/interest-rate-swaps/17777 -status: Draft +status: Review type: Standards Track category: ERC created: 2023-12-31 @@ -29,7 +29,7 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S ![alt text](../assets/eip-7586/irs.jpeg "IRS diagram") -Every contract compliant with this ERC MUST implement the following interface. The contract MUST inherit from [`ERC20`](./eip-20) to tokenize the swap cash flows. +Every contract compliant with this ERC MUST implement the following interface. The contract MUST inherit from [ERC-20](./eip-20.md) to tokenize the swap cash flows. ```solidity pragma solidity ^0.8.0; @@ -57,12 +57,12 @@ interface IERC7586 /** is ERC20, ERC165 */ { /** * @notice Returns the IRS `payer` account address. The party who agreed to pay fixed interest */ - function fixedInterestPayer() external view returns(address); + function fixedRatePayer() external view returns(address); /** * @notice Returns the IRS `receiver` account address. The party who agreed to pay floating interest */ - function floatingInterestPayer() external view returns(address); + function floatingRatePayer() external view returns(address); /** * @notice Returns the number of decimals the swap rate and spread use - e.g. `4` means to divide the rates by `10000` @@ -73,23 +73,34 @@ interface IERC7586 /** is ERC20, ERC165 */ { function ratesDecimals() external view returns(uint8); /** - * @notice Returns the fixed interest rate + * @notice Returns the fixed interest rate. All rates MUST be multiplied by 10^(ratesDecimals) */ function swapRate() external view returns(uint256); /** - * @notice Returns the floating rate spread, i.e. the fixed part of the floating interest rate - * + * @notice Returns the floating rate spread, i.e. the fixed part of the floating interest rate. All rates MUST be multiplied by 10^(ratesDecimals) * floatingRate = benchmark + spread */ function spread() external view returns(uint256); /** - * @notice Returns the contract address of the asset to be transferred when swapping IRS. Depending on what the two parties agreed upon, this could be a currency, etc. - * Example: If the two parties agreed to swap interest rates in USDC, then this function should return the USDC contract address. - * This address SHOULD be used in the `swap` function to transfer the interest difference to either the `payer` or the `receiver`. Example: IERC(assetContract).transfer + * @notice Returns the day count basis + * For example, 0 can denote actual/actual, 1 can denote actual/360, and so on + */ + function dayCountBasis() external view returns(uint8); + + /** + * @notice Returns the contract address of the currency for which the notional amount is denominated (Example: USDC contract address). + * Returns the zero address if the notional is expressed in FIAT currency like USD + */ + function notionalCurrency() external view returns(address); + + /** + * @notice Returns an array of acceptable contract address of the assets to be transferred when swapping IRS + * The two counterparties may wish to get the payment in different currencies. + * Ex: if the payer wants to receive the payment in USDC and the receiver in DAI, then the function should return [USDC, DAI] or [DAI, USDC] */ - function assetContract() external view returns(address); + function paymentAssets() external view returns(address[] memory); /** * @notice Returns the notional amount in unit of asset to be transferred when swapping IRS. This amount serves as the basis for calculating the interest payments, and may not be exchanged @@ -98,17 +109,25 @@ interface IERC7586 /** is ERC20, ERC165 */ { function notionalAmount() external view returns(uint256); /** - * @notice Returns the interest payment frequency + * @notice Returns the number of times payments must be realized in 1 year */ function paymentFrequency() external view returns(uint256); /** - * @notice Returns an array of specific dates on which the interest payments are exchanged. Each date MUST be a Unix timestamp like the one returned by block.timestamp + * @notice Returns an array of specific dates on which the fix interest payments are exchanged. Each date MUST be a Unix timestamp like the one returned by block.timestamp + * The length of the array returned by this function MUST equal the total number of swaps that should be realized + * + * OPTIONAL + */ + function fixPaymentDates() external view returns(uint256[] memory); + + /** + * @notice Returns an array of specific dates on which the floating interest payments are exchanged. Each date MUST be a Unix timestamp like the one returned by block.timestamp * The length of the array returned by this function MUST equal the total number of swaps that should be realized * * OPTIONAL */ - function paymentDates() external view returns(uint256[] memory); + function floatingPaymentDates() external view returns(uint256[] memory); /** * @notice Returns the starting date of the swap contract. This is a Unix Timestamp like the one returned by block.timestamp @@ -121,22 +140,23 @@ interface IERC7586 /** is ERC20, ERC165 */ { function maturityDate() external view returns(uint256); /** - * @notice Returns the benchmark in basis point unit + * @notice Returns the benchmark (the reference rate). All rates MUST be multiplied by 10^(ratesDecimals) * Example: value of one the following rates: CF BIRC, EURIBOR, HIBOR, SHIBOR, SOFR, SONIA, TONAR, etc. + * Or set manually */ function benchmark() external view returns(uint256); /** - * @notice Returns the oracle contract address for the benchmark rate, or the zero address when the two parties agreed to set the benchmark manually. + * @notice Returns the oracle contract address for acceptable reference rates (benchmark), or the zero address when the two parties agreed to set the benchmark manually. * This contract SHOULD be used to fetch real time benchmark rate * Example: Contract address for `CF BIRC` * * OPTIONAL. The two parties MAY agree to set the benchmark manually */ - function oracleContractForBenchmark() external view returns(address); + function oracleContractsForBenchmark() external view returns(address); /** - * @notice Makes swap calculation and transfers the interest difference to either the `payer` or the `receiver` + * @notice Makes swap calculation and transfers the payment to counterparties */ function swap() external returns(bool); diff --git a/ERCS/erc-7588.md b/ERCS/erc-7588.md index 81ca154ec2..212f051034 100644 --- a/ERCS/erc-7588.md +++ b/ERCS/erc-7588.md @@ -4,7 +4,7 @@ title: Blob Transactions Metadata JSON Schema description: Attaching metadata to blobs carried by blob transactions author: Gavin Fu (@gavfu), Leo Wang (@wanglie1986), Bova Chen (@appoipp), Aiden X (@4ever9) discussions-to: https://ethereum-magicians.org/t/erc7588-attaching-metadata-to-blobs-carried-by-blob-transactions/17873 -status: Draft +status: Final type: Standards Track category: ERC created: 2024-01-01 @@ -149,4 +149,4 @@ This EIP does not introduce any new security risks or vulnerabilities, as the me ## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). \ No newline at end of file +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/ERCS/erc-7595.md b/ERCS/erc-7595.md new file mode 100644 index 0000000000..c56748caa4 --- /dev/null +++ b/ERCS/erc-7595.md @@ -0,0 +1,491 @@ +--- +eip: 7595 +title: Collateralized NFT +description: ERC-721 Extension to enable collateralization with ERC-20 based tokens. +author: 571nKY (@571nKY), Cosmos (@Cosmos4k), f4t50 (@f4t50), Harpocrates (@harpocrates555) +discussions-to: https://ethereum-magicians.org/t/collateralized-nft-standard/18097 +status: Draft +type: Standards Track +category: ERC +created: 2023-03-13 +requires: 20, 721 +--- + +## Abstract + +This proposal recommends an extension of [ERC-721](./eip-721.md) to allow for collateralization using a list of [ERC-20](./eip-20.md) based tokens. The proprietor of this ERC collection could hold both the native coin and [ERC-20](./eip-20.md) based tokens, with the `ownerOf` tokenId being able to unlock the associated portion of the underlying [ERC-20](./eip-20.md) balance. + +## Motivation + +The emerging trend of NFT finance focuses on the NFT floor price to enable the market value of the NFT serve as a collateral in lending protocols. The NFT floor price is susceptible to the supply-demand dynamics of the NFT market, characterized by higher volatility compared to the broader crypto market. Furthermore, potential price manipulation in specific NFT collections can artificially inflate NFT market prices, impacting the floor price considered by lending protocols. Relying solely on the NFT floor price based on market value is both unpredictable and unreliable. + +This ERC addresses various challenges encountered by the crypto community with [ERC-721](./eip-721.md) based collections and assets. This ERC brings forth advantages such as sustainable NFT royalties supported by tangible assets, an on-chain verifiable floor price, and the introduction of additional monetization avenues for NFT collection creators. + +### Presets + +* The Basic Preset allows for the evaluation of an on-chain verifiable price floor for a specified NFT asset. + +* The Dynamic Preset facilitates on-chain modification of tokenURI based on predefined collateral rules for a specified NFT asset. + +* With the Royalty Preset, NFT collection creators can receive royalty payments for each transaction involving asset owners and Externally Owned Accounts (EOA), as well as transactions with smart contracts. + +* The VRF Preset enables the distribution of collateral among multiple NFT asset holders using the Verifiable Random Function (VRF) by Chainlink. + +### Extension to Existing ERC-721 Based Collections + +For numerous [ERC-721](./eip-721.md) based collections that cannot be redeployed, we propose the implementation of an abstraction layer embodied by a smart contract. This smart contract would replicate all the functionalities of this ERC standard and grant access to collateral through mapping. + +## Specification +### ERC standard for new NFT collections + +```solidity + +interface IERC721Envious is IERC721 { + event Collateralized(uint256 indexed tokenId, uint256 amount, address tokenAddress); + event Uncollateralized(uint256 indexed tokenId, uint256 amount, address tokenAddress); + event Dispersed(address indexed tokenAddress, uint256 amount); + event Harvested(address indexed tokenAddress, uint256 amount, uint256 scaledAmount); + + /** + * @dev An array with two elements. Each of them represents percentage from collateral + * to be taken as a commission. First element represents collateralization commission. + * Second element represents uncollateralization commission. There should be 3 + * decimal buffer for each of them, e.g. 1000 = 1%. + * + * @param uint 256 index of value in array. + */ + function commissions(uint256 index) external view returns (uint256); + + /** + * @dev 'Black hole' is any address that guarantees that tokens sent to it will not be + * retrieved from it. Note: some tokens revert on transfer to zero address. + * + * @return address address of black hole. + */ + function blackHole() external view returns (address); + + /** + * @dev Token that will be used to harvest collected commissions. + * + * @return address address of token. + */ + function communityToken() external view returns (address); + + /** + * @dev Pool of available tokens for harvesting. + * + * @param uint256 index in array. + * @return address address of token. + */ + function communityPool(uint256 index) external view returns (address); + + /** + * @dev Token balance available for harvesting. + * + * @param address address of token. + * @return uint256 token balance. + */ + function communityBalance(address tokenAddress) external view returns (uint256); + + /** + * @dev Array of tokens that have been dispersed. + * + * @param uint256 index in array. + * @return address address of dispersed token. + */ + function disperseTokens(uint256 index) external view returns (address); + + /** + * @dev Amount of tokens that has been dispersed. + * + * @param address address of token. + * @return uint256 token balance. + */ + function disperseBalance(address tokenAddress) external view returns (uint256); + + /** + * @dev Amount of tokens that was already taken from the disperse. + * + * @param address address of token. + * @return uint256 total amount of tokens already taken. + */ + function disperseTotalTaken(address tokenAddress) external view returns (uint256); + + /** + * @dev Amount of disperse already taken by each tokenId. + * + * @param tokenId unique identifier of unit. + * @param address address of token. + * @return uint256 amount of tokens already taken. + */ + function disperseTaken(uint256 tokenId, address tokenAddress) external view returns (uint256); + + /** + * @dev Mapping of `tokenId`s to token addresses that have collateralized before. + * + * @param tokenId unique identifier of unit. + * @param index in array. + * @return address address of token. + */ + function collateralTokens(uint256 tokenId, uint256 index) external view returns (address); + + /** + * @dev Token balances that are stored under `tokenId`. + * + * @param tokenId unique identifier of unit. + * @param address address of token. + * @return uint256 token balance. + */ + function collateralBalances(uint256 tokenId, address tokenAddress) external view returns (uint256); + + /** + * @dev Calculator function for harvesting. + * + * @param amount of `communityToken`s to spend + * @param address address of token to be harvested + * @return amount to harvest based on inputs + */ + function getAmount(uint256 amount, address tokenAddress) external view returns (uint256); + + /** + * @dev Collect commission fees gathered in exchange for `communityToken`. + * + * @param amounts[] array of amounts to collateralize + * @param address[] array of token addresses + */ + function harvest(uint256[] memory amounts, address[] memory tokenAddresses) external; + + /** + * @dev Collateralize NFT with different tokens and amounts. + * + * @param tokenId unique identifier for specific NFT + * @param amounts[] array of amounts to collateralize + * @param address[] array of token addresses + */ + function collateralize( + uint256 tokenId, + uint256[] memory amounts, + address[] memory tokenAddresses + ) external payable; + + /** + * @dev Withdraw underlying collateral. + * + * Requirements: + * - only owner of NFT + * + * @param tokenId unique identifier for specific NFT + * @param amounts[] array of amounts to collateralize + * @param address[] array of token addresses + */ + function uncollateralize( + uint256 tokenId, + uint256[] memory amounts, + address[] memory tokenAddresses + ) external; + + /** + * @dev Split collateral among all existent tokens. + * + * @param amounts[] to be dispersed among all NFT owners + * @param address[] address of token to be dispersed + */ + function disperse(uint256[] memory amounts, address[] memory tokenAddresses) external payable; +} +``` + +### Abstraction layer for already deployed NFT collections + +```solidity + +interface IEnviousHouse { + event Collateralized( + address indexed collection, + uint256 indexed tokenId, + uint256 amount, + address tokenAddress + ); + + event Uncollateralized( + address indexed collection, + uint256 indexed tokenId, + uint256 amount, + address tokenAddress + ); + + event Dispersed( + address indexed collection, + address indexed tokenAddress, + uint256 amount + ); + + event Harvested( + address indexed collection, + address indexed tokenAddress, + uint256 amount, + uint256 scaledAmount + ); + + /** + * @dev totalCollections function returns the total count of registered collections. + * + * @return uint256 number of registered collections. + */ + function totalCollections() external view returns (uint256); + + /** + * @dev 'Black hole' is any address that guarantees that tokens sent to it will not be + * retrieved from it. Note: some tokens revert on transfer to zero address. + * + * @param address collection address. + * @return address address of black hole. + */ + function blackHole(address collection) external view returns (address); + + /** + * @dev collections function returns the collection address based on the collection index input. + * + * @param uint256 index of a registered collection. + * @return address address collection. + */ + function collections(uint256 index) external view returns (address); + + /** + * @dev collectionIds function returns the collection index based on the collection address input. + * + * @param address collection address. + * @return uint256 collection index. + */ + function collectionIds(address collection) external view returns (uint256); + + /** + * @dev specificCollections function returns whether a particular collection follows the ERC721 standard or not. + * + * @param address collection address. + * @return bool specific collection or not. + */ + function specificCollections(address collection) external view returns (bool); + + /** + * @dev An array with two elements. Each of them represents percentage from collateral + * to be taken as a commission. First element represents collateralization commission. + * Second element represents uncollateralization commission. There should be 3 + * decimal buffer for each of them, e.g. 1000 = 1%. + * + * @param address collection address. + * @param uint256 index of value in array. + * @return uint256 collected commission. + */ + function commissions(address collection, uint256 index) external view returns (uint256); + + /** + * @dev Token that will be used to harvest collected commissions. + * + * @param address collection address. + * @return address address of token. + */ + function communityToken(address collection) external view returns (address); + + /** + * @dev Pool of available tokens for harvesting. + * + * @param address collection address. + * @param uint256 index in array. + * @return address address of token. + */ + function communityPool(address collection, uint256 index) external view returns (address); + + /** + * @dev Token balance available for harvesting. + * + * @param address collection address. + * @param address address of token. + * @return uint256 token balance. + */ + function communityBalance(address collection, address tokenAddress) external view returns (uint256); + + /** + * @dev Array of tokens that have been dispersed. + * + * @param address collection address. + * @param uint256 index in array. + * @return address address of dispersed token. + */ + function disperseTokens(address collection, uint256 index) external view returns (address); + + /** + * @dev Amount of tokens that has been dispersed. + * + * @param address collection address. + * @param address address of token. + * @return uint256 token balance. + */ + function disperseBalance(address collection, address tokenAddress) external view returns (uint256); + + /** + * @dev Amount of tokens that was already taken from the disperse. + * + * @param address collection address. + * @param address address of token. + * @return uint256 total amount of tokens already taken. + */ + function disperseTotalTaken(address collection, address tokenAddress) external view returns (uint256); + + /** + * @dev Amount of disperse already taken by each tokenId. + * + * @param address collection address. + * @param tokenId unique identifier of unit. + * @param address address of token. + * @return uint256 amount of tokens already taken. + */ + function disperseTaken(address collection, uint256 tokenId, address tokenAddress) external view returns (uint256); + + /** + * @dev Mapping of `tokenId`s to token addresses that have collateralized before. + * + * @param address collection address. + * @param tokenId unique identifier of unit. + * @param index in array. + * @return address address of token. + */ + function collateralTokens(address collection, uint256 tokenId, uint256 index) external view returns (address); + + /** + * @dev Token balances that are stored under `tokenId`. + * + * @param address collection address. + * @param tokenId unique identifier of unit. + * @param address address of token. + * @return uint256 token balance. + */ + function collateralBalances(address collection, uint256 tokenId, address tokenAddress) external view returns (uint256); + + /** + * @dev Calculator function for harvesting. + * + * @param address collection address. + * @param amount of `communityToken`s to spend. + * @param address address of token to be harvested. + * @return amount to harvest based on inputs. + */ + function getAmount(address collection, uint256 amount, address tokenAddress) external view returns (uint256); + + /** + * @dev setSpecificCollection function enables the addition of any collection that is not compatible with the ERC721 standard to the list of exceptions. + * + * @param address collection address. + */ + function setSpecificCollection(address collection) external; + + /** + * @dev registerCollection function grants Envious functionality to any ERC721-compatible collection and streamlines + * the distribution of an initial minimum disbursement to all NFT holders. + * + * @param address collection address. + * @param address address of `communityToken`. + * @param uint256 collateralization fee, incoming / 1e5 * 100%. + * @param uint256 uncollateralization fee, incoming / 1e5 * 100%. + */ + function registerCollection( + address collection, + address token, + uint256 incoming, + uint256 outcoming + ) external payable; + + /** + * @dev Collect commission fees gathered in exchange for `communityToken`. + * + * @param address collection address. + * @param amounts[] array of amounts to collateralize. + * @param address[] array of token addresses. + */ + function harvest( + address collection, + uint256[] memory amounts, + address[] memory tokenAddresses + ) external; + + /** + * @dev Collateralize NFT with different tokens and amounts. + * + * @param address collection address. + * @param tokenId unique identifier for specific NFT. + * @param amounts[] array of amounts to collateralize. + * @param address[] array of token addresses. + */ + function collateralize( + address collection, + uint256 tokenId, + uint256[] memory amounts, + address[] memory tokenAddresses + ) external payable; + + /** + * @dev Withdraw underlying collateral. + * + * Requirements: + * - only owner of NFT + * + * @param address collection address. + * @param tokenId unique identifier for specific NFT. + * @param amounts[] array of amounts to collateralize. + * @param address[] array of token addresses. + */ + function uncollateralize( + address collection, + uint256 tokenId, + uint256[] memory amounts, + address[] memory tokenAddresses + ) external; + + /** + * @dev Split collateral among all existent tokens. + * + * @param address collection address. + * @param amounts[] to be dispersed among all NFT owners. + * @param address[] address of token to be dispersed. + */ + function disperse( + address collection, + uint256[] memory amounts, + address[] memory tokenAddresses + ) external payable; +} +``` + +## Rationale +### “Envious” Term Choice +We propose adopting the term "Envious" to describe any NFT collection minted using this ERC standard or any [ERC-721](./eip-721.md) based NFT collection that utilized the EnviousHouse abstraction layer. + +### NFT Collateralization with Multiple Tokens +Some Web3 projects primarily collateralize a specific NFT asset with one [ERC-20](./eip-20.md) based token, resulting in increased gas fees and complications in User Experience (UX). + +This ERC has been crafted to enable the collateralization of a designated NFT asset with multiple [ERC-20](./eip-20.md) based tokens within a single transaction. + +### NFT Collateralization with the Native Coin +Each [ERC-20](./eip-20.md) based token possesses a distinct address. However, a native coin does not carry an address. To address this, we propose utilizing a null address (`0x0000000000000000000000000000000000000000`) as an identifier for the native coin during collateralization, as it eliminates the possibility of collisions with smart contract addresses. + +### Disperse Functionality +We have implemented the capability to collateralize all assets within a particular NFT collection in a single transaction. The complete collateral amount is deposited into a smart contract, enabling each user to claim their respective share of the collateral when they add or redeem collateral for that specific asset. + +### Harvest Functionality +Each Envious NFT collection provides an option to incorporate a community [ERC-20](./eip-20.md) based token, which can be exchanged for commissions accrued from collateralization and uncollateralization activities. + +### BlackHole Instance +Some [ERC-20](./eip-20.md) based token implementations forbid transfers to the null address, it is necessary to have a reliable burning mechanism in the harvest transactions. `blackHole` smart contract removes [ERC-20](./eip-20.md) communityTokens from the circulating supply in exchange for commission fees withdrawn. + +`blackHole` has been designed to prevent the transfer of any tokens from itself and can only perform read operations. It is intended to be used with the Envious extension in implementations related to commission harvesting. + +## Backwards Compatibility + +EnviousHouse abstraction layer is suggested for already deployed [ERC-721](./eip-721.md) based NFT collections. + +## Security Considerations + +Envious may share security concerns similar to those found in [ERC-721](./eip-721.md), such as hidden logic within functions like burn, add resource, accept resource, etc. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/ERCS/erc-7597.md b/ERCS/erc-7597.md new file mode 100644 index 0000000000..bbb29dab88 --- /dev/null +++ b/ERCS/erc-7597.md @@ -0,0 +1,134 @@ +--- +eip: 7597 +title: Signature Validation Extension for Permit +description: An ERC to extend ERC-2612 Permit to support ERC-1271-based signature validation. +author: Yvonne Zhang (@yvonnezhangc), Aloysius Chan (@circle-aloychan) +discussions-to: https://ethereum-magicians.org/t/add-erc-contract-signature-validation-extension-for-eip-2612-permit/18157 +status: Draft +type: Standards Track +category: ERC +created: 2024-01-15 +requires: 1271, 2612 +--- + +# EIP: Contract signature validation extension for [ERC-2612](./eip-2612.md) Permit + +## Abstract + +This proposal aims to extend the functionality of the existing [ERC-2612](./eip-2612.md) Permit to support gasless [ERC-20](./eip-20.md) approval operations initiated by smart contract wallets. + +## Motivation + +The current signature validation scheme in [ERC-2612](./eip-2612.md), based on V, R, S parameters, restricts signature validation to EOA wallets. + +With the growing popularity of smart contract wallets and increased adoption of [ERC-1271](./eip-1271.md), it is necessary to allow for flexible signature validation methods and the use of custom logic in each contract's signature verification. By accepting unstructured signature bytes as input, custom algorithms and signature schemes can be utilized, enabling a wider range of wallet types. + +## Specification + +Compliant contracts must implement the `permit` using the following spec + +``` +function permit(address owner, address spender, uint value, uint deadline, bytes memory signature) external +``` +as well as two other interfaces previously mandated by [ERC-2612](./eip-2612.md): +``` +function nonces(address owner) external view returns (uint) +function DOMAIN_SEPARATOR() external view returns (bytes32) +``` + +A call to `permit(owner, spender, value, deadline, signature)` will set `allowance[owner][spender]` to value, increment `nonces[owner]` by 1, and emit a corresponding `Approval` event, if and only if the following conditions are met: + +- The current blocktime is less than or equal to `deadline`. +- `owner` is not the zero address. +- `nonces[owner]` (before the state update) is equal to nonce. +- `signature` validation: + - If `owner` is an EOA, `signature` is a valid secp256k1 signature in the form of `abi.encodePacked(r, s, v)`. + - If `owner` is a contract, `signature` is validated by calling `isValidSignature()` on the `owner` contract. + +If any of these conditions are not met, the permit call must revert. + +## Rationale + +By replacing the existing V, R, S signature validation scheme and introducing support for unstructured bytes input, contract developers can use a unified interface to validate signature from both EOAs and SC wallets. This allows for the utilization of different signature schemes and algorithms fitting the wallet type, paving the way for smart contract wallets and advanced wallet types to enhance their signature validation processes, promoting flexibility and innovation. + +## Backwards Compatibility + +This proposal is fully backward-compatible with the existing ERC-2612 standard. Contracts that currently rely on the V, R, S signature validation scheme will continue to function without any issues. + +If both V, R, S signature validation and the new unstructured bytes signature validation need to be supported for backward compatibility reasons, developers can reduce duplicates by adapting the following code block as an example: + +``` +function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s +) external { + _permit(owner, spender, value, deadline, abi.encodePacked(r, s, v)); +} +``` + +## Reference Implementation + +Sample `permit` implemented with OZ's SignatureChecker + +```solidity +/** + * @notice Update allowance with a signed permit + * @dev Signature bytes can be used for both EOA wallets and contract wallets. + * @param owner Token owner's address (Authorizer) + * @param spender Spender's address + * @param value Amount of allowance + * @param deadline The time at which the signature expires (unix time) + * @param signature Unstructured bytes signature signed by an EOA wallet or a contract wallet + */ +function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + bytes memory signature +) external { + require(deadline >= now, "Permit is expired"); + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + bytes32 digest = keccak256(abi.encodePacked( + hex"1901", + DOMAIN_SEPARATOR, + keccak256(abi.encode( + keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"), + owner, + spender, + value, + nonce, + deadline + )) + )); + + require( + // Check for both ECDSA signature and and ERC-1271 signature. A sample SignatureChecker is available at + // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/7bd2b2a/contracts/utils/cryptography/SignatureChecker.sol + SignatureChecker.isValidSignatureNow( + owner, + typedDataHash, + signature + ), + "Invalid signature" + ); + + allowed[owner][spender] = value; + emit Approval(owner, spender, value); +} +``` + +## Security Considerations + +- For contract wallets, the security of `permit` relies on `isValidSignature()` to ensure the signature bytes represent the desired execution from contract wallet owner(s). Contract wallet developers must exercise caution when implementing custom signature validation logic to ensure the security of their contracts. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/ERCS/erc-7603.md b/ERCS/erc-7603.md new file mode 100644 index 0000000000..1c705bc133 --- /dev/null +++ b/ERCS/erc-7603.md @@ -0,0 +1,159 @@ +--- +eip: 7603 +title: ERC-1155 Multi-Asset extension +description: An interface compatible with ERC-1155 for Multi-Asset tokens with context-dependent asset type output control. +author: Haru (@haruu8) +discussions-to: https://ethereum-magicians.org/t/erc-multi-context-dependent-multi-asset-tokens-eip1155-extension/18303 +status: Draft +type: Standards Track +category: ERC +created: 2024-01-25 +requires: 165, 1155 +--- + +## Abstract + +The Multi-Asset Token standard, compatible with [ERC-1155](./eip-1155.md), facilitates the development of a new fundamental component: the context-dependent data output for each collection. + +The context-dependent data output means that the asset is displayed in an appropriate format based on how the token is accessed. I.e., if the token is being opened in an e-book reader, the PDF asset is displayed; if the token is opened in the marketplace, the PNG or the SVG asset is displayed; if the token is accessed from within a game, the 3D model asset is accessed, and if the token is accessed by an Internet of Things (IoT) hub, the asset providing the necessary addressing and specification information is accessed. + +A Token Collection can have multiple assets (outputs), which can be any file to order them by priority. They do not have to match in mime-type or tokenURI, nor do they depend on one another. Assets are not standalone entities but should be considered “namespaced tokenURIs”. + +## Motivation + +With ERC-1155 compatible tokens being a widespread form of tokens in the Ethereum ecosystem and being used for various use cases, it is time to standardize additional utility for them. Having multiple assets associated with a single Token Collection allows for greater utility, usability, and forward compatibility. This EIP improves upon ERC-1155 in the following areas: + +- [Cross-metaverse compatibility](#cross-metaverse-compatibility) +- [Multi-media output](#multi-media-output) +- [Media redundancy](#media-redundancy) + +### Cross-metaverse compatibility + +The proposal can support any number of different implementations. + +Cross-metaverse compatibility could also be referred to as cross-engine compatibility. An example is where a cosmetic item for game A is unavailable in game B because the frameworks are incompatible. + +Such Tokens can be given further utility through new assets: more games, cosmetic items, and more. + +The following is a more concrete example. One asset is a cosmetic item for game A, a file containing the cosmetic assets. Another is a cosmetic asset file for game B. A third is a generic asset intended to be shown in catalogs, marketplaces, portfolio trackers, or other generalized Token Collection viewers, containing a representation, stylized thumbnail, and animated demo/trailer of the cosmetic item. + +This EIP adds a layer of abstraction, allowing game developers to pull asset data from a user's Tokens directly instead of hard-coding it. + +### Multi-media output + +Tokens of an eBook can be represented as a PDF, MP3, or some other format, depending on what software loads it. If loaded into an eBook reader, a PDF should be displayed, and if loaded into an audiobook application, the MP3 representation should be used. Other metadata could be present in the Tokens (perhaps the book's cover image) for identification on various marketplaces, Search Engine Result Pages (SERPs), or portfolio trackers. + +### Media redundancy + +Many Tokens are minted hastily without best practices in mind. Specifically, many Tokens are minted with metadata centralized on a server somewhere or, in some cases, a hardcoded IPFS gateway which can also go down, instead of just an IPFS hash. + +By adding the same metadata file as different assets, e.g., one asset of metadata and its linked image on Arweave, one asset of this same combination on Sia, another of the same combination on IPFS, etc., the resilience of the metadata and its referenced information increases exponentially as the chances of all the protocols going down at once become less likely. + +## Specification + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. + +```solidity +/// @title ERC-7603 Context-Dependent Multi-Asset Tokens, ERC-1155 Execution +/// @dev See https://eips.ethereum.org/EIPS/erc-7603 + +pragma solidity ^0.8.23; + +interface IERC7603 /* is ERC165 */ { + /** + * @notice Used to notify listeners that an asset object is initialised at `assetId`. + * @param assetId ID of the asset that was initialised + */ + event AssetSet(uint64 assetId); + + /** + * @notice Used to notify listeners that an asset object at `assetId` is added to token's asset + * array. + * @param tokenId An ID of the token that received a new asset + * @param assetId ID of the asset that has been added to the token's assets array + * @param replacesId ID of the asset that would be replaced + */ + event AssetAddedToToken( + uint256[] tokenId, + uint64 indexed assetId, + uint64 indexed replacesId + ); + + /** + * @notice Used to notify listeners that token's priority array is reordered. + * @param tokenId ID of the token that had the asset priority array updated + */ + event AssetPrioritySet(uint256 indexed tokenId); + + /** + * @notice Sets a new priority array for a given token. + * @dev The priority array is a non-sequential list of `uint16`s, where the lowest value is considered highest + * priority. + * @dev Value `0` of a priority is a special case equivalent to uninitialised. + * @dev Requirements: + * + * - `tokenId` must exist. + * - The length of `priorities` must be equal the length of the assets array. + * @dev Emits a {AssetPrioritySet} event. + * @param tokenId ID of the token to set the priorities for + * @param priorities An array of priorities of assets. The succession of items in the priorities array + * matches that of the succession of items in the array + */ + function setPriority(uint256 tokenId, uint64[] calldata priorities) + external; + + /** + * @notice Used to retrieve IDs of assets of given token. + * @dev Asset data is stored by reference, in order to access the data corresponding to the ID, call + * `getAssetMetadata(tokenId, assetId)`. + * @dev You can safely get 10k + * @param tokenId ID of the token to retrieve the IDs of the assets + * @return uint64[] An array of the asset IDs of the given token + */ + function getAssets(uint256 tokenId) + external + view + returns (uint64[] memory); + + /** + * @notice Used to retrieve the priorities of the assets of a given token. + * @dev Asset priorities are a non-sequential array of uint16 values with an array size equal to asset + * priorites. + * @param tokenId ID of the token for which to retrieve the priorities of the assets + * @return uint16[] An array of priorities of the assets of the given token + */ + function getAssetPriorities(uint256 tokenId) + external + view + returns (uint64[] memory); + + /** + * @notice Used to fetch the asset metadata of the specified token's asset with the given index. + * @dev Can be overridden to implement enumerate, fallback or other custom logic. + * @param tokenId ID of the token from which to retrieve the asset metadata + * @param assetId Asset Id, must be in the assets array + * @return string The metadata of the asset belonging to the specified index in the token's assets array + */ + function getAssetMetadata(uint256 tokenId, uint64 assetId) + external + view + returns (string memory); +} + +``` + +## Rationale + +TBD + +## Backwards Compatibility + +The MultiAsset token standard has been made compatible with ERC-1155 in order to take advantage of the robust tooling available for implementations of ERC-1155 and to ensure compatibility with existing ERC-1155 infrastructure. + +## Security Considerations + +Needs discussion. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/ERCS/erc-7613.md b/ERCS/erc-7613.md new file mode 100644 index 0000000000..a1f71a8789 --- /dev/null +++ b/ERCS/erc-7613.md @@ -0,0 +1,345 @@ +--- +eip: 7613 +title: Puppet Proxy Contract +description: A proxy that, if called by its deployer, delegates to an implementation specified in calldata. +author: Igor Żuk (@CodeSandwich) +discussions-to: https://ethereum-magicians.org/t/eip-7613-puppet-proxy-contract/18482 +status: Draft +type: Standards Track +category: ERC +created: 2024-02-04 +--- + +## Abstract + +A puppet is a contract that, when called, acts like an empty account. It doesn't do anything and it has no API, except when it is called by the address that deployed it. In that case, it delegates the call to the address passed to it in calldata. This gives the deployer the ability to execute any logic they want in the context of the puppet. + +## Motivation + +A puppet can be used as an alternative account of its deployer. It has a different address, so it has a separate set of asset balances. This enables sophisticated accounting, e.g. each user of a protocol can get their own address where assets can be sent and stored. The user may call the protocol contract, which in turn will deploy a new puppet and consider it assigned to the user. If the puppet is deployed under a predictable address, e.g. by using the user's address as the CREATE2 salt, the puppet may not even need to be deployed before funds are sent to its address. From now on the protocol will consider all the assets sent to the puppet as owned by the user. If the protocol needs to move the funds out from the puppet address, it can call the puppet ordering it to delegate to a function transferring the assets to arbitrary addresses, or making arbitrary calls triggering approved transfers to other contracts. + +Puppets can be used as an alternative to approved transfers when loading funds into the protocol. Any contract and any wallet can transfer the funds to the puppet address assigned to the user without making any approvals or calling the protocol contracts. Funds can be loaded across multiple transactions and potentially from multiple sources. To funnel funds from another protocol, there's no need for integration in the 3rd party contracts as long as they are capable of transferring funds to an arbitrary address. Wallets limited to plain [ERC-20](./eip-20.md) transfers and stripped of any web3 functionality can be used to load funds into the protocol. The users of the fully featured wallets don't need to sign opaque calldata blobs that may be harmful or approve the protocol to take their tokens, they only need to make a transfer, which is a simple process with a familiar UX. When the funds are already stored in the puppet assigned to the user, somebody needs to call the protocol so it's notified that the funds were loaded. Depending on the protocol and its API this call may or may not be permissionless potentially making the UX even more convenient with gasless transactions or 3rd parties covering the gas cost. Some protocols don't need the users to specify what needs to be done with the loaded funds or they allow the users to configure that in advance. Most of the protocols using approved transfers to load funds may benefit from using the puppets. + +The puppet's logic doesn't need to be ever upgraded. To change its behavior the deployer needs to change the address it passes to the puppet to delegate to or the calldata it passes for delegation. The entire fleet of puppets deployed by a single contract can be upgraded by upgrading the contract that deployed them, without using beacons. A nice trick is that the deployer can make the puppet delegate to the address holding the deployer's own logic, so the puppet's logic is encapsulated in the deployer's. + +A puppet is unable to expose any API to any caller except the deployer. If a 3rd party needs to be able to somehow make the puppet execute some logic, it can't be requested by directly calling the puppet. Instead, the deployer needs to expose a function that if called by the 3rd parties, will call the puppet, and make it execute the desired logic. Mechanisms expecting contracts to expose some APIs don't work with puppet, e.g. [ERC-721](./eip-721.md)'s `safeTransfer`s. + +This standard defines the puppet as a blob of bytes used as creation code, which enables integration with many frameworks and codebases written in variety of languages. The specific tooling is outside of the scope of this standard, but it should be easy to create the libraries and helpers necessary for usage in practice. All the implementations will be interoperable because they will be creating identical puppets and if CREATE2 is used, they will have deterministic addresses predictable by all implementations. + +Because the puppet can be deployed under a predictable address despite having no fixed logic, in some cases it can be used as a CREATE3 alternative. It can be also used as a full replacement of the CREATE3 factory by using a puppet deployed using CREATE2 to deploy arbitrary code using plain CREATE. + +Deploying a new puppet is almost as cheap as deploying a new clone proxy. Its whole deployed bytecode is 66 bytes, and its creation code is 62 bytes. Just like clone proxy, it can be deployed using just the Solidity scratch space in memory. The cost to deploy a puppet is 45K gas, only 4K more than a clone. Because the bytecode is not compiled, it can be reliably deployed under a predictable CREATE2 address regardless of the compiler version. + +## Specification + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. + +To delegate, the deployer must prepend the calldata with an ABI-encoded address to delegate to. +All the data after the address will be passed verbatim as the delegation calldata. +If the caller isn't the deployer, the calldata is shorter than 32 bytes, or it doesn't start with +an address left-padded with zeros, the puppet doesn't do anything. +This lets the deployer make a plain native tokens transfer to the puppet, +it will have an empty calldata, and the puppet will accept the transfer without delegating. + +The puppet is deployed with this creation code: +``` +0x604260126D60203D3D3683113D3560A01C17733D3360147F331817604057823603803D943D373D3D355AF43D82803E903D91604057FD5BF36034525252F3 +``` + +The bytecode breakdown: +``` +// The creation code. +// [code 1] and [code 2] are parts of the deployed code, +// placed respectively before and after the deployer's address. +// | Opcode used | Hex value | Stack content after executing +// Code size and offset in memory +// | PUSH1 | 60 42 | 66 +// | PUSH1 | 60 12 | 18 66 +// The code before the deployer's address and where it's stored in memory +// | PUSH14 | 6D [code 1] | [code 1] 18 66 +// | RETURNDATASIZE | 3D | 0 [code 1] 18 66 +// The deployer's address and where it's stored in memory +// | CALLER | 33 | [deployer] 0 [code 1] 18 66 +// | PUSH1 | 60 14 | 20 [deployer] 0 [code 1] 18 66 +// The code after the deployer's address and where it's stored in memory +// | PUSH32 | 7F [code 2] | [code 2] 20 [deployer] 0 [code 1] 18 66 +// | PUSH1 | 60 34 | 52 [code 2] 20 [deployer] 0 [code 1] 18 66 +// Return the entire code +// | MSTORE | 52 | 20 [deployer] 0 [code 1] 18 66 +// | MSTORE | 52 | 0 [code 1] 18 66 +// | MSTORE | 52 | 18 66 +// | RETURN | F3 | + +// The deployed code. +// `deployer` is the deployer's address. +// | Opcode used | Hex value | Stack content after executing +// Push some constants +// | PUSH1 | 60 20 | 32 +// | RETURNDATASIZE | 3D | 0 32 +// | RETURNDATASIZE | 3D | 0 0 32 +// Do not delegate if calldata shorter than 32 bytes +// | CALLDATASIZE | 36 | [calldata size] 0 0 32 +// | DUP4 | 83 | 32 [calldata size] 0 0 32 +// | GT | 11 | [do not delegate] 0 0 32 +// Do not delegate if the first word of calldata is not a zero-padded address +// | RETURNDATASIZE | 3D | 0 [do not delegate] 0 0 32 +// | CALLDATALOAD | 35 | [first word] [do not delegate] 0 0 32 +// | PUSH1 | 60 A0 | 160 [first word] [do not delegate] 0 0 32 +// | SHR | 1C | [first word upper bits] [do not delegate] 0 0 32 +// | OR | 17 | [do not delegate] 0 0 32 +// Do not delegate if not called by the deployer +// | PUSH20 | 73 [deployer] | [deployer] [do not delegate] 0 0 32 +// | CALLER | 33 | [sender] [deployer] [do not delegate] 0 0 32 +// | XOR | 18 | [sender not deployer] [do not delegate] 0 0 32 +// | OR | 17 | [do not delegate] 0 0 32 +// Skip to the return if should not delegate +// | PUSH1 | 60 40 | [success branch] [do not delegate] 0 0 32 +// | JUMPI | 57 | 0 0 32 +// Calculate the payload size +// | DUP3 | 82 | 32 0 0 32 +// | CALLDATASIZE | 36 | [calldata size] 32 0 0 32 +// | SUB | 03 | [payload size] 0 0 32 +// Copy the payload from calldata +// | DUP1 | 80 | [payload size] [payload size] 0 0 32 +// | RETURNDATASIZE | 3D | 0 [payload size] [payload size] 0 0 32 +// | SWAP5 | 94 | 32 [payload size] [payload size] 0 0 0 +// | RETURNDATASIZE | 3D | 0 32 [payload size] [payload size] 0 0 0 +// | CALLDATACOPY | 37 | [payload size] 0 0 0 +// Delegate call +// | RETURNDATASIZE | 3D | 0 [payload size] 0 0 0 +// | RETURNDATASIZE | 3D | 0 0 [payload size] 0 0 0 +// | CALLDATALOAD | 35 | [delegate to] 0 [payload size] 0 0 0 +// | GAS | 5A | [gas] [delegate to] 0 [payload size] 0 0 0 +// | DELEGATECALL | F4 | [success] 0 +// Copy return data +// | RETURNDATASIZE | 3D | [return size] [success] 0 +// | DUP3 | 82 | 0 [return size] [success] 0 +// | DUP1 | 80 | 0 0 [return size] [success] 0 +// | RETURNDATACOPY | 3E | [success] 0 +// Return +// | SWAP1 | 90 | 0 [success] +// | RETURNDATASIZE | 3D | [return size] 0 [success] +// | SWAP2 | 91 | [success] 0 [return size] +// | PUSH1 | 60 40 | [success branch] [success] 0 [return size] +// | JUMPI | 57 | 0 [return size] +// | REVERT | FD | +// | JUMPDEST | 5B | 0 [return size] +// | RETURN | F3 | +``` + +## Rationale + +The main goals of the puppet design are low cost and modularity. It should be cheap to deploy and cheap to interact with. The contract should be self-contained, simple to reason about, and easy to use as an architectural building block. + +The puppet behavior could be implemented fairly easily in Solidity with some inline Yul for delegation. This would make the bytecode much larger and more expensive to deploy. It would also be different depending on the compiler version and configuration, so deployments under predictable addresses using CREATE2 would be trickier. + +A workaround for the problems with the above solution could be to use the clone proxy pattern to deploy copies of the puppet implementation. It would make the cost to deploy each puppet a little lower than deploying the bytecode proposed in this document, and the addresses of the clones would be predictable when deploying using CREATE2. The downside is that now there would be 1 extra delegation for each call, from the clone proxy to the puppet implementation address, which costs gas. The architecture of such solution is also more complicated with more contracts involved, and it requires the initialization step of deploying the puppet implementation before any clone can be deployed. The initialization step limits the CREATE2 address predictability because the creation code of the clone proxy includes the implementation address, which affects the deployment address. + +Another alternative is to use the beacon proxy pattern. Making a Solidity API call safely is a relatively complex procedure that takes up a non-trivial space in the bytecode. To lower the cost of the puppets, the beacon proxy probably should be used with the clone proxy, which would be even more complicated and more expensive to use than the above solutions. Querying a beacon for the delegation address is less flexible than passing it in calldata, it requires updating the state of the beacon to change the address. + +## Backwards Compatibility + +No backward compatibility issues found. + +The puppet bytecode doesn't use PUSH0, because many chains don't support it yet. + +## Test Cases + +Here are the tests verifying that the bytecode and the reference implementation library are working as expected, using the Foundry test tools: + +```solidity +pragma solidity ^0.8.0; + +import {Test} from "forge-std/Test.sol"; +import {Puppet} from "src/Puppet.sol"; + +contract Logic { + string public constant ERROR = "Failure called"; + + fallback(bytes calldata data) external returns (bytes memory) { + return abi.encode(data); + } + + function success(uint256 arg) external payable returns (address, uint256, uint256) { + return (address(this), arg, msg.value); + } + + function failure() external pure { + revert(ERROR); + } +} + +contract PuppetTest is Test { + address puppet = Puppet.deploy(); + address logic = address(new Logic()); + + function logicFailurePayload() internal view returns (bytes memory) { + return Puppet.delegationCalldata(logic, abi.encodeWithSelector(Logic.failure.selector)); + } + + function call(address target, bytes memory data) internal returns (bytes memory) { + return call(target, data, 0); + } + + function call(address target, bytes memory data, uint256 value) + internal + returns (bytes memory) + { + (bool success, bytes memory returned) = target.call{value: value}(data); + require(success, "Unexpected revert"); + return returned; + } + + function testDeployDeterministic() public { + bytes32 salt = keccak256("Puppet"); + address newPuppet = Puppet.deployDeterministic(salt); + assertEq( + newPuppet, Puppet.predictDeterministicAddress(salt, address(this)), "Invalid address" + ); + assertEq( + newPuppet, Puppet.predictDeterministicAddress(salt), "Invalid address when no deployer" + ); + assertEq(newPuppet.code, puppet.code, "Invalid code"); + } + + function testPuppetDelegates() public { + uint256 arg = 1234; + bytes memory data = abi.encodeWithSelector(Logic.success.selector, arg); + bytes memory payload = Puppet.delegationCalldata(logic, data); + uint256 value = 5678; + + bytes memory returned = call(puppet, payload, value); + + (address thisAddr, uint256 receivedArg, uint256 receivedValue) = + abi.decode(returned, (address, uint256, uint256)); + assertEq(thisAddr, puppet, "Invalid delegation context"); + assertEq(receivedArg, arg, "Invalid argument"); + assertEq(receivedValue, value, "Invalid value"); + } + + function testPuppetDelegatesWithEmptyCalldata() public { + bytes memory payload = Puppet.delegationCalldata(logic, ""); + bytes memory returned = call(puppet, payload); + bytes memory data = abi.decode(returned, (bytes)); + assertEq(data.length, 0, "Delegated with non-empty calldata"); + } + + function testPuppetBubblesRevertPayload() public { + vm.expectRevert(bytes(Logic(logic).ERROR())); + call(puppet, logicFailurePayload()); + } + + function testPuppetDoesNothingForNonDeployer() public { + vm.prank(address(1234)); + call(puppet, logicFailurePayload()); + } + + function testCallingWithCalldataShorterThan32BytesDoesNothing() public { + address delegateTo = address(uint160(1234) << 8); + bytes memory payload = abi.encodePacked(bytes31(bytes32(uint256(uint160(delegateTo))))); + vm.mockCallRevert(delegateTo, "", "Logic called"); + call(puppet, payload); + } + + function testCallingWithDelegationAddressOver20BytesDoesNothing() public { + bytes memory payload = logicFailurePayload(); + payload[11] = 0x01; + call(puppet, payload); + } + + function testCallingPuppetDoesNothing() public { + // Forge the calldata, so if puppet uses it to delegate, it will run `Logic.failure` + uint256 forged = uint256(uint160(address(this))) << 32; + forged |= uint32(Logic.failure.selector); + bytes memory payload = abi.encodeWithSignature("abc(uint)", forged); + call(puppet, payload); + } + + function testTransferFromDeployerToPuppet() public { + uint256 amt = 123; + payable(puppet).transfer(amt); + assertEq(puppet.balance, amt, "Invalid balance"); + } + + function testTransferToPuppet() public { + uint256 amt = 123; + address sender = address(456); + payable(sender).transfer(amt); + vm.prank(sender); + payable(puppet).transfer(amt); + assertEq(puppet.balance, amt, "Invalid balance"); + } +} +``` + +## Reference Implementation + +The puppet bytecode is explained in the specification section. Here's the example helper library: + +```solidity +library Puppet { + bytes internal constant CREATION_CODE = + hex"604260126D60203D3D3683113D3560A01C17733D3360147F33181760405782" + hex"3603803D943D373D3D355AF43D82803E903D91604057FD5BF36034525252F3"; + bytes32 internal constant CREATION_CODE_HASH = keccak256(CREATION_CODE); + + /// @notice Deploy a new puppet. + /// @return instance The address of the puppet. + function deploy() internal returns (address instance) { + bytes memory creationCode = CREATION_CODE; + assembly { + instance := create(0, add(creationCode, 32), mload(creationCode)) + } + require(instance != address(0), "Failed to deploy the puppet"); + } + + /// @notice Deploy a new puppet under a deterministic address. + /// @param salt The salt to use for the deterministic deployment. + /// @return instance The address of the puppet. + function deployDeterministic(bytes32 salt) internal returns (address instance) { + bytes memory creationCode = CREATION_CODE; + assembly { + instance := create2(0, add(creationCode, 32), mload(creationCode), salt) + } + require(instance != address(0), "Failed to deploy the puppet"); + } + + /// @notice Calculate the deterministic address for a puppet deployment made by this contract. + /// @param salt The salt to use for the deterministic deployment. + /// @return predicted The address of the puppet. + function predictDeterministicAddress(bytes32 salt) internal view returns (address predicted) { + return predictDeterministicAddress(salt, address(this)); + } + + /// @notice Calculate the deterministic address for a puppet deployment. + /// @param salt The salt to use for the deterministic deployment. + /// @param deployer The address of the deployer of the puppet. + /// @return predicted The address of the puppet. + function predictDeterministicAddress(bytes32 salt, address deployer) + internal + pure + returns (address predicted) + { + bytes32 hash = keccak256(abi.encodePacked(hex"ff", deployer, salt, CREATION_CODE_HASH)); + return address(uint160(uint256(hash))); + } + + function delegationCalldata(address delegateTo, bytes memory data) + internal + pure + returns (bytes memory payload) + { + return abi.encodePacked(bytes32(uint256(uint160(delegateTo))), data); + } +} +``` + +## Security Considerations + +The bytecode is made to resemble clone proxy's wherever it makes sense to simplify auditing. + +ABI-encoding the delegation address protects the deployer from being tricked by a 3rd party into calling the puppet and making it delegate to an arbitrary address. Such scenario would only be possible if the deployer called on the puppet a function with the selector `0x00000000`, which as of now doesn't come from any reasonably named function. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/ERCS/erc-7615.md b/ERCS/erc-7615.md new file mode 100644 index 0000000000..71057699f0 --- /dev/null +++ b/ERCS/erc-7615.md @@ -0,0 +1,299 @@ +--- +eip: 7615 +title: Atomic Push-based Data Feed Among Contracts +description: An Atomic Mechanism to Allow Publisher Contract Push Data to Subcriber Contracts +author: Elaine Zhang (@lanyinzly) , Jerry , Amandafanny , Shouhao Wong (@wangshouh) , Doris Che (@Cheyukj) , Henry Yuan (@onehumanbeing) +discussions-to: https://ethereum-magicians.org/t/erc-7615-smart-contract-data-push-mechanism/18466 +status: Draft +type: Standards Track +category: ERC +created: 2024-02-03 +--- +## Abstract +This ERC proposes a push-based mechanism for sending data, allowing publisher contract to automatically push certain data to subscriber contracts during a call. The specific implementation relies on two interfaces: one for publisher contract to push data, and another for the subscriber contract to receive data. When the publisher contract is called, it checks if the called function corresponds to subscriber addresses. If it does, the publisher contract push data to the subscriber contracts. + +## Motivation +Currently, there are many keepers rely on off-chain data or seperate data collection process to monitor the events on chain. This proposal aims to establish a system where the publisher contract can atomicly push data to inform subscriber contracts about the updates. The direct on-chain interaction bewteen the publisher and the subscriber allows the system to be more trustless and efficient. + +This proposal will offer significant advantages across a range of applications, such as enabling the boundless and permissionless expansion of DeFi, as well as enhancing DAO governance, among others. + +### Lending Protocol + +An example of publisher contract could be an oracle, which can automatically push the price update through initiating a call to the subscriber protocol. The lending protocol, as the subscriber, can automatically liquidate the lending positions based on the received price. + +### Automatic Payment + +A service provider can use a smart contract as a publisher contract, so that when a user call this contract, it can push the information to the subsriber contracts, such as, the users' wallets like NFT bound accounts that follows [ERC-6551](./eip-6551.md) or other smart contract wallets. The user's smart contract wallet can thus perform corresponding payment operations automatically. Compared to traditional `approve` needed approach, this solution allows more complex logic in implementation, such as limited payment, etc. + +### PoS Without Transferring Assets + +For some staking scenarios, especially NFT staking, the PoS contract can be set as the subscriber and the NFT contracts can be set as the publisher. Staking can thus achieved through contracts interation, allowing users to earn staking rewards without transferring assets. + +When operations like `transfer` of NFT occur, the NFT contract can push this information to the PoS contract, which can then perform unstaking or other functions. + +### DAO Voting + +The DAO governance contract as a publisher could automatically triggers the push mechanism after the vote is completed, calling relevant subscriber contracts to directly implement the voting results, such as injecting funds into a certain account or pool. + +## Specification + +The key words “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. + +### Overview + +The push mechanism can be divided into the following four steps: + +1. The publisher contract is called. +2. The publisher contract query the subscriber list from the `selector` of the function called. The subscriber contract can put the selected data into `inbox`. +3. The publisher contract push `selector` and data through calling `exec` function of the subscriber contract. +4. The subscriber contract executes based on pushed `selector` and data, or it may request information from the publisher contract's inbox function as needed. + +In the second step, the relationship between a called function and the corresponding subscriber can be configured in the publisher contract. Two configuration schemes are proposed: + +1. Unconditional Push: Any call to the configured `selector` triggers a push +2. Conditional Push: Only the conditioned calls to the configured `selector` trigger a push based on the configuration. + +It's allowed to configure multiple, different types of subscriber contracts for a single `selector`. The publisher contract will call the `exec` function of each subscriber contract to push the request. + +When unsubscribing a contract from a `selector`, publisher contract MUST check whether `isLocked` function of the subscriber contract returns `true`. + +It is OPTIONAL for a publisher contract to use the `inbox` mechanism to store data. + +In the fourth step, the subscriber contract SHOULD handle all possible `selector` requests and data in the implementation of `exec` function. In some cases, `exec` MAY call `inbox` function of publisher contract to obtain the pushed data in full. + + +![Workflow](../assets/eip-7615/ERC7615.svg) + +### Contract interface + +As mentioned above, there are Unconditional Push and Conditional Push two types of implementation. To implement Unconditional Push, the publisher contract SHOULD implement the following interface: + +``` +interface IPushForce { + event ForceApprove(bytes4 indexed selector, address indexed target); + event ForceCancel(bytes4 indexed selector, address indexed target); + event RenounceForceApprove(); + event RenounceForceCancel(); + + error MustRenounce(); + error ForceApproveRenounced(); + error ForceCancelRenounced(); + + function isForceApproved(bytes4 selector, address target) external returns (bool); + function forceApprove(bytes4 selector, address target) external; + function forceCancel(bytes4 selector, address target) external; + function isRenounceForceApprove() external returns (bool); + function isRenounceForceCancel() external returns (bool); + function renounceForceApprove(bytes memory) external; + function renounceForceCancel(bytes memory) external; +} +``` + +`isForceApproved` is to query whether `selector` has already unconditionally bound to the subscriber contract with the address `target`. +`forceApprove` is to bind `selector` to the subscriber contract `target`. `forceCancel` is to cancel the binding relationship between `selector` and `target`, where `isLocked` function of `target` returns `true` is REQUIRED. + +`renounceForceApprove` is used to relinquish the `forceApprove` permission. After calling the `renounceForceApprove` function, `forceApprove` can no longer be called. Similarly, `renounceForceCancel` is used to relinquish the `forceCancel` permission. After calling the `renounceForceCancel` function, `forceCancel` can no longer be called. + +To implement Conditional Push, the publisher contract SHOULD implement the following interface: + +``` +interface IPushFree { + event Approve(bytes4 indexed selector, address indexed target, bytes data); + event Cancel(bytes4 indexed selector, address indexed target, bytes data); + + function inbox(bytes4 selector) external returns (bytes memory); + function isApproved(bytes4 selector, address target, bytes calldata data) external returns (bool); + function approve(bytes4 selector, address target, bytes calldata data) external; + function cancel(bytes4 selector, address target, bytes calldata data) external; +} +``` + +`isApproved`, `approve`, and `cancel` have functionalities similar to the corresponding functions in `IPushForce`. However, an additional `data` parameter is introduced here for checking whether a push is needed. +The `inbox` here is used to store data in case of being called from the subscriber contract. + +The publisher contract SHOULD implement `_push(bytes4 selector, bytes calldata data)` function, which acts as a hook. Any function within the publisher contract that needs to implement push mechanism must call this internal function. The function MUST include querying both unconditional and conditional subscription contracts based on `selector` and `data`, and then calling corresponding `exec` function of the subscribers. + +A subscriber need to implement the following interface: + +```solidity +interface IExec { + function isLocked(bytes4 selector, bytes calldata data) external returns (bool); + function exec(bytes4 selector, bytes calldata data) external; +} +``` + +`exec` is to receive requests from the publisher contracts and further proceed to execute. +`isLocked` is to check the status of whether the subscriber contract can unsubscribe the publisher contract based on `selector` and `data`. It is triggered when a request to unsubscribe is received. + +## Rationale + +### Unconditional and Conditional Configuration + +When the sending contract is called, it is possible to trigger a push, requiring the caller to pay the resulting gas fees. +In some cases, an Unconditional Push is necessary, such as pushing price changes to a lending protocol. While, Conditional Push will reduce the unwanted gas consumption. + +### Check `isLocked` Before Unsubscribing + +Before `forceCancel` or `cancel`, the publisher contract MUST call the `isLocked` function of the subscriber contract to avoid unilateral unsubscribing. The subscriber contract may have a significant logical dependence on the publisher contract, and thus unsubscription could lead to severe issues within the subscriber contract. Therefore, the subscriber contract should implement `isLocked` function with thorough consideration. + +### `inbox` Mechanism + +In certain scenarios, the publisher contract may only push essential data with `selector` to the subscriber contracts, while the full data might be stored within `inbox`. Upon receiving the push from the publisher contract, the subscriber contract is optional to call `inbox`. +`inbox` mechanism simplifies the push information while still ensuring the availability of complete data, thereby reducing gas consumption. + +### Using Function Selectors as Parameters + +Using function selectors to retrieve the addresses of subscriber contracts allows +more detailed configuration. +For the subscriber contract, having the specific function of the request source based on the push information enables more accurate handling of the push information. + +### Renounce Safety Enhancement + +Both `forceApprove` and `forceCancel` permissions can be relinquished using their respective renounce functions. When both `renounceForceApprove` and `renounceForceCancel` are called, the registered push targets can longer be changed, greatly enhancing security. + +## Reference Implementation + +``` +pragma solidity ^0.8.24; + +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {IPushFree, IPushForce} from "./interfaces/IPush.sol"; +import {IExec} from "./interfaces/IExec.sol"; + +contract Foo is IPushFree, IPushForce { + using EnumerableSet for EnumerableSet.AddressSet; + + bool public override isRenounceForceApprove; + bool public override isRenounceForceCancel; + + mapping(bytes4 selector => mapping(uint256 tokenId => EnumerableSet.AddressSet targets)) private _registry; + mapping(bytes4 selector => EnumerableSet.AddressSet targets) private _registryOfAll; + // mapping(bytes4 => bytes) public inbox; + + modifier notLock(bytes4 selector, address target, bytes memory data) { + require(!IExec(target).isLocked(selector, data), "Foo: lock"); + _; + } + + function inbox(bytes4 selector) public view returns (bytes memory data) { + uint256 loadData; + assembly { + loadData := tload(selector) + } + + data = abi.encode(loadData); + } + + function isApproved(bytes4 selector, address target, bytes calldata data) external view override returns (bool) { + uint256 tokenId = abi.decode(data, (uint256)); + return _registry[selector][tokenId].contains(target); + } + + function isForceApproved(bytes4 selector, address target) external view override returns (bool) { + return _registryOfAll[selector].contains(target); + } + + function approve(bytes4 selector, address target, bytes calldata data) external override { + uint256 tokenId = abi.decode(data, (uint256)); + _registry[selector][tokenId].add(target); + } + + function cancel(bytes4 selector, address target, bytes calldata data) + external + override + notLock(selector, target, data) + { + uint256 tokenId = abi.decode(data, (uint256)); + _registry[selector][tokenId].remove(target); + } + + function forceApprove(bytes4 selector, address target) external override { + if (isRenounceForceApprove) revert ForceApproveRenounced(); + _registryOfAll[selector].add(target); + } + + function forceCancel(bytes4 selector, address target) external override notLock(selector, target, "") { + if (isRenounceForceCancel) revert ForceCancelRenounced(); + _registryOfAll[selector].remove(target); + } + + function renounceForceApprove(bytes memory data) external override { + (bool burn) = abi.decode(data, (bool)); + if (burn != true) { + revert MustRenounce(); + } + + isRenounceForceApprove = true; + emit RenounceForceApprove(); + } + + function renounceForceCancel(bytes memory data) external override { + (bool burn) = abi.decode(data, (bool)); + if (burn != true) { + revert MustRenounce(); + } + + isRenounceForceCancel = true; + emit RenounceForceCancel(); + } + + function send(uint256 message) external { + _push(this.send.selector, message); + } + + function _push(bytes4 selector, uint256 message) internal { + assembly { + tstore(selector, message) + } + + address[] memory targets = _registry[selector][message].values(); + for (uint256 i = 0; i < targets.length; i++) { + IExec(targets[i]).exec(selector, abi.encode(message)); + } + + targets = _registryOfAll[selector].values(); + for (uint256 i = 0; i < targets.length; i++) { + IExec(targets[i]).exec(selector, abi.encode(message)); + } + } +} + +contract Bar is IExec { + event Log(bytes4 indexed selector, bytes data, bytes inboxData); + + function isLocked(bytes4, bytes calldata) external pure override returns (bool) { + return true; + } + + function exec(bytes4 selector, bytes calldata data) external { + bytes memory inboxData = IPushFree(msg.sender).inbox(selector); + + emit Log(selector, data, inboxData); + } +} +``` + +## Security Considerations + +### `exec` Attacks + +The `exec` function is `public`, therefore, it is vulnerable to malicious calls where arbitrary push information can be inserted. Implementations of `exec` should carefully consider the arbitrariness of calls and should not directly use data passed by the exec function without verification. + +### Reentrancy Attack + +The publisher contract's call to the subscriber contract's `exec` function could lead to reentrancy attacks. Malicious subscription contracts might construct reentrancy attacks to the publisher contract within `exec`. + +### Arbitrary Target Approve + +Implementation of `forceApprove` and `approve` should have reasonable access controls; otherwise, unnecessary gas losses could be imposed on callers. + +Check the gas usage of the `exec` function. + +### isLocked implementation + +Subscriber contracts should implement the `isLocked` function to avoid potential loss brought by unsubscription. This is particularly crucial for lending protocols implementing this proposal. Improper unsubscription can lead to abnormal clearing, causing considerable losses. + +Similarly, when subscribing, the publisher contract should consider whether `isLocked` is properly implemented to prevent irrevocable subscriptions. + +## Copyright +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/ERCS/erc-7627.md b/ERCS/erc-7627.md index 5188d1e9d9..f9c6428703 100644 --- a/ERCS/erc-7627.md +++ b/ERCS/erc-7627.md @@ -1,10 +1,10 @@ --- eip: 7627 title: Secure Messaging Protocol -description: A solution implementing end-to-end encryption for sending messages between users. +description: End-to-end encryption for sending messages between users. author: Chen Liaoyuan (@chenly) discussions-to: https://ethereum-magicians.org/t/erc-7627-secure-messaging-protocol/18761 -status: Draft +status: Review type: Standards Track category: ERC created: 2024-02-19 @@ -16,7 +16,7 @@ This proposal implements the capability to securely exchange encrypted messages ## Motivation -With the emergence of Layer 2 chains featuring sub-second block times and the introduction of account abstraction, the use of end-to-end encrypted communication has facilitated the proliferation of real-time communication and online chat dApps. Leveraging asymmetric encryption now enables the establishment of decentralized, end-to-end interoperable messaging protocols as a standard. +With the emergence of Layer 2 chains featuring sub-second block times and the introduction of account abstraction, the use of end-to-end encrypted communication has facilitated the proliferation of real-time communication and online chat dApps. Providing a unified interface enables easy integration of encrypted communication into smart contracts, thereby fostering innovation. Standardization promotes interoperability, facilitating seamless communication across platforms. ## Specification @@ -39,57 +39,72 @@ pragma solidity ^0.8.0; interface IERC7627 { - enum PublicKeyAlgorithm { RSA, ECDSA, ED25519, DSA, DH, ECDH, X25519 } + enum PublicKeyAlgorithm { ECDSA, ED25519, X25519 } + + struct PublicKey { + bytes public_key; + uint64 valid_before; + PublicKeyAlgorithm algorithm; + } // Events - - /** - * @dev Event emitted when a message is sent. - * @param from The address of the sender. - * @param to The address of the recipient. - * @param sessionId The session ID of the message. - * @param encryptedMessage The encrypted message. - */ - event MessageSent(address indexed from, address indexed to, bytes32 sessionId, bytes encryptedMessage); - - /** - * @dev Event emitted when a user updates their public key. - * @param user The address of the user. - * @param newPublicKey The new public key of the user. - * @param algorithm The algorithm used for the public key. - */ - event PublicKeyUpdated(address indexed user, bytes newPublicKey, PublicKeyAlgorithm algorithm); + /** + * @notice Event emitted when a message is sent between users. + * @param from The address of the sender + * @param to The address of the recipient + * @param keyIndex The index of the public key used to encrypt the message + * @param sessionId The session ID for the communication + * @param encryptedMessage The encrypted message in bytes + */ + event MessageSent(address indexed from, address indexed to, bytes32 indexed keyIndex, bytes32 sessionId, bytes encryptedMessage); + + /** + * @notice Event emitted when a user's public key is updated. + * @param user The address of the user whose public key is updated + * @param keyIndex The index of the public key being updated + * @param newPublicKey The new public key data + */ + event PublicKeyUpdated(address indexed user, bytes32 indexed keyIndex, PublicKey newPublicKey); // Functions - /** - * @dev Function to update a user's public key. - * @param _publicKey The public key of the user. - * @param _algorithm The algorithm used for the public key. - */ - function updatePublicKey(bytes calldata _publicKey, PublicKeyAlgorithm _algorithm) external; - - /** - * @dev Function to send an encrypted message from one user to another. - * @param _to The address of the recipient. - * @param _sessionId The session ID of the message. - * @param _encryptedMessage The encrypted message. - */ - function sendMessage(address _to, bytes32 _sessionId, bytes calldata _encryptedMessage) external; - - /** - * @dev Function to retrieve a user's public key and algorithm. - * @param _user The address of the user. - * @return publicKey The public key of the user. - * @return algorithm The algorithm used for the public key. - */ - function getUserPublicKey(address _user) external view returns (bytes memory publicKey, PublicKeyAlgorithm algorithm); + /** + * @notice Updates the public key for the sender. + * @param _keyIndex The index of the key to be updated + * @param _publicKey The new public key data + */ + function updatePublicKey(bytes32 _keyIndex, PublicKey memory _publicKey) external; + + /** + * @notice Sends an encrypted message to a specified address. + * @param _to The recipient's address + * @param _keyIndex The index of the public key used to encrypt the message + * @param _sessionId The session ID for the communication + * @param _encryptedMessage The encrypted message in bytes + */ + function sendMessage(address _to, bytes32 _keyIndex, bytes32 _sessionId, bytes calldata _encryptedMessage) external; + + /** + * @notice Retrieves a public key for a specific user and key index. + * @param _user The address of the user + * @param _keyIndex The index of the key to retrieve + * @return The public key data associated with the user and key index + */ + function getUserPublicKey(address _user, bytes32 _keyIndex) external view returns (PublicKey memory); } ``` ## Rationale -Traditional messaging lacks security and transparency for blockchain communication. The choice of asymmetric encryption ensures the confidentiality and integrity of messages, which is why we opted for this encryption method. Providing a unified interface enables easy integration of encrypted communication into smart contracts, thereby fostering innovation. Encrypted messaging guarantees adherence to best practices in data security. Due to security reasons, public keys need to be regularly updated, hence we have added a feature that allows users to autonomously update their public keys. The interface supports various encryption methods, enhancing adaptability. Event tracking enhances the observability and auditability of the contract, aiding compliance efforts. Standardization promotes interoperability, facilitating seamless communication across platforms. +### Event Emission for Off-Chain Integration +By emitting events when messages are sent or public keys are updated, the implementation facilitates seamless integration with off-chain dApps. This enables these dApps to easily track and display the latest messages and updates, ensuring real-time responsiveness and enhancing user interaction. + +### End-to-End Encryption Security +The design ensures that only the owner of an address can update their public key. This restriction preserves the integrity of the end-to-end encryption, making sure that only the intended recipient can decrypt and read the messages, thereby securing communication. + +### Session ID for Conversation Organization +The use of session IDs in message transactions allows multiple messages to be grouped under specific conversations. This feature is crucial for organizing and managing discussions within a dApp, providing users with a coherent and structured messaging experience. + ## Reference Implementation @@ -98,34 +113,34 @@ pragma solidity ^0.8.0; contract ERC7627 { - enum PublicKeyAlgorithm { RSA, ECDSA, ED25519, DSA, DH, ECDH, X25519 } + /// @dev Enum to specify the algorithm used for the public key. + enum PublicKeyAlgorithm { ECDSA, ED25519, X25519 } - struct UserInfo { - bytes publicKey; - PublicKeyAlgorithm algorithm; + /// @dev Structure to represent a user's public key. + struct PublicKey { + bytes public_key; + uint64 valid_before; + PublicKeyAlgorithm algorithm; } - mapping(address => UserInfo) public pk; + /// @dev Mapping to store public keys for each address. The mapping is by user address and a unique key index. + mapping(address => mapping(bytes32 => PublicKey)) public pk; - event MessageSent(address indexed from, address indexed to, bytes32 sessionId, bytes encryptedMessage); - event PublicKeyUpdated(address indexed user, bytes newPublicKey, PublicKeyAlgorithm algorithm); + event MessageSent(address indexed from, address indexed to, bytes32 indexed keyIndex, bytes32 sessionId, bytes encryptedMessage); - // Function to register a user with their public key - function updatePublicKey(bytes calldata _publicKey, PublicKeyAlgorithm _algorithm) public { - pk[msg.sender].publicKey = _publicKey; - pk[msg.sender].algorithm = _algorithm; - emit PublicKeyUpdated(msg.sender, _publicKey, _algorithm); + event PublicKeyUpdated(address indexed user, bytes32 indexed keyIndex, PublicKey newPublicKey); + + function updatePublicKey(bytes32 _keyIndex, PublicKey memory _publicKey) external { + pk[msg.sender][_keyIndex] = _publicKey; + emit PublicKeyUpdated(msg.sender, _keyIndex, _publicKey); } - // Function to send an encrypted message from one user to another - function sendMessage(address _to, bytes32 _sessionId, bytes calldata _encryptedMessage) public { - emit MessageSent(msg.sender, _to, _sessionId, _encryptedMessage); + function sendMessage(address _to, bytes32 _keyIndex, bytes32 _sessionId, bytes calldata _encryptedMessage) external { + emit MessageSent(msg.sender, _to, _keyIndex, _sessionId, _encryptedMessage); } - // Function to retrieve a user's public key - function getUserPublicKey(address _user) public view returns (bytes memory, PublicKeyAlgorithm) { - UserInfo memory userInfo = pk[_user]; - return (userInfo.publicKey, userInfo.algorithm); + function getUserPublicKey(address _user, bytes32 _keyIndex) external view returns (PublicKey memory) { + return pk[_user][_keyIndex]; } } ``` @@ -141,9 +156,6 @@ To maintain message confidentiality, the content of sent messages must be strict #### Key Management and Protection Robust key management and protection measures are necessary for both user public and private keys. Ensure secure storage and transmission of keys to prevent leakage and tampering. Employ multi-factor authentication and key rotation strategies to enhance key security and regularly assess key management processes to mitigate potential security risks. -#### Auditing and Monitoring -Implement auditing and monitoring mechanisms to track message sending and receiving, as well as key usage. Promptly identify anomalous activities and potential security threats and take appropriate response measures. Record critical operations and events for security incident investigation and traceability purposes. - ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md) diff --git a/ERCS/erc-7628.md b/ERCS/erc-7628.md index c348fa4850..ff0a1398ea 100644 --- a/ERCS/erc-7628.md +++ b/ERCS/erc-7628.md @@ -4,7 +4,7 @@ title: ERC-721 Ownership Shares Extension description: Introduces ownership shares to ERC-721 tokens, allowing for queryable, transferable, and approvable fractional ownership. author: Chen Liaoyuan (@chenly) discussions-to: https://ethereum-magicians.org/t/erc-7628-erc-721-ownership-shares-extension/18744 -status: Draft +status: Review type: Standards Track category: ERC created: 2024-02-20 @@ -13,7 +13,7 @@ requires: 721 ## Abstract -The proposal introduces an attribute of ownership and profit share quantities for each token under an NFT. This attribute signifies a stake in the ownership and profit rights associated with the NFT's specific privileges, enabling the querying, transferring, and approval of these shares, thereby making the shares represented by each token applicable in a broader range of use cases. +This proposal introduces an attribute of ownership and profit share quantities for each token under an NFT. This attribute signifies a stake in the ownership and profit rights associated with the NFT's specific privileges, enabling the querying, transferring, and approval of these shares, thereby making the shares represented by each token applicable in a broader range of use cases. ## Motivation diff --git a/ERCS/erc-7629.md b/ERCS/erc-7629.md new file mode 100644 index 0000000000..275da09470 --- /dev/null +++ b/ERCS/erc-7629.md @@ -0,0 +1,330 @@ +--- +eip: 7629 +title: ERC-20/ERC-721 Unified Token Interface +description: introduces a single interface for ERC-20/ERC-721 tokens, enabling seamless interaction by defining common functions for both token types. +author: 0xZeus1111 (@0xZeus1111), Nvuwa (@Nvuwa) +discussions-to: https://ethereum-magicians.org/t/erc-7629-unified-token/18793 +status: Draft +type: Standards Track +category: ERC +created: 2024-02-18 +requires: 20, 165, 721 +--- + + +## Abstract + +This proposal introduces a protocol that establishes a unified interface for managing both [ERC-20](./eip-20.md) fungible tokens and [ERC-721](./eip-721.md) non-fungible tokens (NFTs) on the Ethereum blockchain. By defining a common set of functions applicable to both token types, developers can seamlessly interact with [ERC-20](./eip-20.md) and [ERC-721](./eip-721.md) tokens using a single interface. This simplifies integration efforts and enhances interoperability within decentralized applications (DApps). + + +## Motivation + +The proposal aims to address the demand for assets combining the liquidity of [ERC-20](./eip-20.md) tokens and the uniqueness of [ERC-721](./eip-721.md) tokens. Current standards present a fragmentation, requiring users to choose between these features. This proposal fills that gap by providing a unified token interface, enabling smooth transitions between [ERC-20](./eip-20.md) and [ERC-721](./eip-721.md) characteristics to accommodate diverse blockchain applications. + +## Specification + +- Introduces a token contract that combines features from both [ERC-20](./eip-20.md) and [ERC-721](./eip-721.md) standards. +- Supports state transitions between [ERC-20](./eip-20.md) and [ERC-721](./eip-721.md) modes, facilitating seamless conversion and utilization of both liquidity and non-fungibility. +- Defines essential functions and events to support token interactions, conversions, and queries. +- Implements low gas consumption [ERC-20](./eip-20.md) mode to maintain efficiency comparable to typical [ERC-20](./eip-20.md) token transfers. + + +Compliant contracts MUST implement the following Solidity interface: + +```solidity + +pragma solidity ^0.8.0; +/** + * @title ERC-7629 Unify Token Interface + * @dev This interface defines the ERC-7629 Unify Token, which unifies ERC-721 and ERC-20 assets. + */ +interface IERC7629 is IERC165 { + // ERC-20 Transfer event + event ERC20Transfer( + address indexed from, + address indexed to, + uint256 amount + ); + + // ERC-721 Transfer event + event ERC721Transfer( + address indexed from, + address indexed to, + uint256 indexed tokenId + ); + + // ERC-721 Transfer event + event Transfer( + address indexed from, + address indexed to, + uint256 indexed tokenId + ); + + // Approval event for ERC-20 and ERC-721 + event Approval( + address indexed owner, + address indexed approved, + uint256 indexed tokenId + ); + + // Approval event for ERC-20 and ERC-721 + event Approval( + address indexed owner, + address indexed approved, + uint256 indexed tokenId + ); + + // Approval event for ERC-20 + event ERC20Approval( + address indexed owner, + address indexed approved, + uint256 indexed tokenId + ); + + // ApprovalForAll event for ERC-721 + event ApprovalForAll( + address indexed owner, + address indexed operator, + bool approved + ); + + // ERC-20 to ERC-721 Conversion event + event ERC20ToERC721(address indexed to, uint256 amount, uint256 tokenId); + + // ERC-721 to ERC-20 Conversion event + event ERC20ToERC721(address indexed to, uint256 amount, uint256[] tokenIds); + + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the number of decimals used in the token. + */ + function decimals() external view returns (uint8); + + /** + * @dev Returns the total supply of the ERC-20 tokens. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the balance of an address for ERC-20 tokens. + * @param owner The address to query the balance of. + */ + function balanceOf(address owner) external view returns (uint256); + + /** + * @dev Returns the total supply of ERC-20 tokens. + */ + function erc20TotalSupply() external view returns (uint256); + + /** + * @dev Returns the balance of an address for ERC-20 tokens. + * @param owner The address to query the balance of. + */ + function erc20BalanceOf(address owner) external view returns (uint256); + + /** + * @dev Returns the total supply of ERC-721 tokens. + */ + function erc721TotalSupply() external view returns (uint256); + + /** + * @dev Returns the balance of an address for ERC-721 tokens. + * @param owner The address to query the balance of. + */ + function erc721BalanceOf(address owner) external view returns (uint256); + + /** + * @notice Get the approved address for a single NFT + * @dev Throws if `tokenId` is not a valid NFT. + * @param tokenId The NFT to find the approved address for + * @return The approved address for this NFT, or the zero address if there is none + */ + function getApproved(uint256 tokenId) external view returns (address); + + /** + * @dev Checks if an operator is approved for all tokens of a given owner. + * @param owner The address of the token owner. + * @param operator The address of the operator to check. + */ + function isApprovedForAll( + address owner, + address operator + ) external view returns (bool); + + /** + * @dev Returns the remaining number of tokens that spender will be allowed to spend on behalf of owner. + * @param owner The address of the token owner. + * @param spender The address of the spender. + */ + function allowance( + address owner, + address spender + ) external view returns (uint256); + + /** + * @dev Returns the array of ERC-721 token IDs owned by a specific address. + * @param owner The address to query the tokens of. + */ + function owned(address owner) external view returns (uint256[] memory); + + /** + * @dev Returns the address that owns a specific ERC-721 token. + * @param tokenId The token ID. + */ + function ownerOf(uint256 tokenId) external view returns (address erc721Owner); + + /** + * @dev Returns the URI for a specific ERC-721 token. + * @param tokenId The token ID. + */ + function tokenURI(uint256 tokenId) external view returns (string memory); + + /** + * @dev Approve or disapprove the operator to spend or transfer all of the sender's tokens. + * @param spender The address of the spender. + * @param amountOrId The amount of ERC-20 tokens or ID of ERC-721 tokens. + */ + function approve( + address spender, + uint256 amountOrId + ) external returns (bool); + + /** + * @dev Set or unset the approval of an operator for all tokens. + * @param operator The address of the operator. + * @param approved The approval status. + */ + function setApprovalForAll(address operator, bool approved) external; + + /** + * @dev Transfer ERC-20 tokens or ERC-721 token from one address to another. + * @param from The address to transfer ERC-20 tokens or ERC-721 token from. + * @param to The address to transfer ERC-20 tokens or ERC-721 token to. + * @param amountOrId The amount of ERC-20 tokens or ID of ERC-721 tokens to transfer. + */ + function transferFrom( + address from, + address to, + uint256 amountOrId + ) external returns (bool); + + /** + * @notice Transfers the ownership of an NFT from one address to another address + * @dev Throws unless `msg.sender` is the current owner, an authorized + * operator, or the approved address for this NFT. Throws if `_rom` is + * not the current owner. Throws if `_to` is the zero address. Throws if + * `tokenId` is not a valid NFT. When transfer is complete, this function + * checks if `to` is a smart contract (code size > 0). If so, it calls + * `onERC721Received` on `to` and throws if the return value is not + * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`. + * @param from The current owner of the NFT + * @param to The new owner + * @param tokenId The NFT to transfer + * @param data Additional data with no specified format, sent in call to `to` + */ + function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external payable; + + /** + * @notice Transfers the ownership of an NFT from one address to another address + * @dev This works identically to the other function with an extra data parameter, + * except this function just sets data to "". + * @param from The current owner of the NFT + * @param to The new owner + * @param tokenId The NFT to transfer + */ + function safeTransferFrom(address from, address to, uint256 tokenId) external payable; + + /** + * @dev Transfer ERC-20 tokens to an address. + * @param to The address to transfer ERC-20 tokens to. + * @param amount The amount of ERC-20 tokens to transfer. + */ + function transfer(address to, uint256 amount) external returns (bool); + + /** + * @dev Retrieves the unit value associated with the token. + * @return The unit value. + */ + function getUnit() external view returns (uint256); + + /** + * @dev Converts ERC-721 token to ERC-20 tokens. + * @param tokenId The unique identifier of the ERC-721 token. + */ + function erc721ToERC20(uint256 tokenId) external; + + /** + * @dev Converts ERC-20 tokens to an ERC-721 token. + * @param amount The amount of ERC-20 tokens to convert. + */ + function erc20ToERC721(uint256 amount) external; +} + + +``` +## Rationale + +Common Interface for Different Token Types: + +- Introduces a unified interface to address the fragmentation caused by separate [ERC-20](./eip-20.md) and [ERC-721](./eip-721.md) standards. +- Standardizes functions like transferFrom, mint, and burn, enabling developers to interact with both token types without implementing distinct logic. + +Transfer Functionality: + +- Includes transferFrom function for seamless movement of tokens between addresses, as it's a core component of both [ERC-20](./eip-20.md) and [ERC-721](./eip-721.md) standards. + +Minting and Burning: + +- Incorporates mint and burn functions for creating and destroying tokens, essential for managing token supply and lifecycle. + +Balance and Ownership Queries: + +- Provides functions like balanceOf and ownerOf for retrieving token balances and ownership information, crucial for both [ERC-20](./eip-20.md) and [ERC-721](./eip-721.md) tokens. + +Compatibility and Extensibility: + +- Ensures compatibility with existing [ERC-20](./eip-20.md) and [ERC-721](./eip-721.md) implementations, minimizing disruption during transition. +- Allows extension with additional functions and events for future enhancements. + +Security Considerations: + +- Implements mechanisms to prevent common issues like reentrancy attacks and overflows, ensuring the security and robustness of the unified interface. + + + +## Backwards Compatibility + + +The proposed this proposal introduces a challenge in terms of backward compatibility due to the distinct balance query mechanisms utilized by [ERC-20](./eip-20.md) and [ERC-721](./eip-721.md) standards. [ERC-20](./eip-20.md) employs `balanceOf` to check an account's token balance, while [ERC-721](./eip-721.md) uses `balanceOf` to inquire about the quantity of tokens owned by an account. To reconcile these differences, the ERC must consider providing either two separate functions catering to each standard or adopting a more generalized approach. + +### Compatibility Points + +The primary compatibility point lies in the discrepancy between [ERC-20](./eip-20.md)'s balanceOf and [ERC-721](./eip-721.md)'s balanceOf functionalities. Developers accustomed to the specific balance query methods in each standard may face challenges when transitioning to this proposal. + +### Proposed Solutions + +Dual Balance Query Functions: + +Introduce two distinct functions, `erc20BalanceOf` and `erc721TotalSupply`, to align with the conventions of [ERC-20](./eip-20.md) and [ERC-721](./eip-721.md), respectively. Developers can choose the function based on the token type they are working with. + + + +## Security Considerations + +- Due to the dual nature of this proposal, potential differences in protocol interpretation may arise, necessitating careful consideration during development. +- Comprehensive security audits are recommended, especially during mode transitions by users, to ensure the safety of user assets. + + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). + diff --git a/ERCS/erc-7631.md b/ERCS/erc-7631.md index 2f905685bf..8909007dbe 100644 --- a/ERCS/erc-7631.md +++ b/ERCS/erc-7631.md @@ -4,7 +4,7 @@ title: Dual Nature Token Pair description: A specification for a co-joined fungible and non-fungible token pair author: vectorized (@vectorized), Thomas (@0xth0mas), Quit (@quitcrypto), Michael Amadi (@AmadiMichael), cygaar (@cygaar), Harrison (@pop-punk) discussions-to: https://ethereum-magicians.org/t/erc-7631-dual-nature-token-pair/18796 -status: Draft +status: Review type: Standards Track category: ERC created: 2024-02-21 @@ -62,17 +62,24 @@ The ERC-20 contract MAY implement the following interface. interface IERC7631BaseNFTSkippable { /// @dev Implementations SHOULD emit this event when the skip NFT status /// of `owner` is updated to `status`. + /// + /// The purpose of this event is to signal to indexers that the + /// skip NFT status has been changed. + /// + /// For simplicity of implementation, + /// this event MAY be emitted even if the status is unchanged. event SkipNFTSet(address indexed owner, bool status); /// @dev Returns true if ERC-721 mints and transfers to `owner` SHOULD be - /// skipped during ERC-20 to ERC-721 synchronization. Otherwise false. + /// skipped during ERC-20 to ERC-721 synchronization. + /// Otherwise, returns false. /// /// This method MAY revert /// (e.g. contract not initialized, method not supported). /// /// If this method reverts: /// - Interacting code SHOULD interpret `setSkipNFT` functionality as - /// unavailable (and hide any functionality to call `setSkipNFT`). + /// unavailable and hide any functionality to call `setSkipNFT`. /// - The skip NFT status for `owner` SHOULD be interpreted as undefined. /// /// Once a true or false value has been returned for a given `owner`, @@ -123,7 +130,7 @@ The `getSkipNFT` and `setSkipNFT` methods MAY revert. As contracts compiled with The skip NFT methods allow accounts to avoid having ERC-721 tokens automatically minted to it whenever there is an ERC-20 transfer. -This is useful in the following situations: +They are helpful in the following situations: - Loading vesting contracts with large amounts ERC-20 tokens to be vested to many users. - Loading candy machine contracts with large amounts of ERC-20 tokens to sell ERC-721 tokens to customers. @@ -132,7 +139,9 @@ This is useful in the following situations: Including the skip NFT methods in the standard will: - Enable applications to conveniently display the option for users to skip NFTs. -- Enable applications to transfer any amount of ERC-20 tokens without the O(n) gas costs associated with minting multiple ERC-721 tokens, which could surpass the block gas limit. +- Enable applications to transfer any amount of ERC-20 tokens without the O(n) gas costs associated with minting multiple ERC-721 tokens, which can surpass the block gas limit. + +These methods are recommended even on EVM chains with low gas costs, because bulk automatic ERC-721 transfers can still surpass the block gas limit. A useful pattern is to make `getSkipNFT` return true by default if `owner` is a smart contract. diff --git a/ERCS/erc-7634.md b/ERCS/erc-7634.md new file mode 100644 index 0000000000..b0ab5046f8 --- /dev/null +++ b/ERCS/erc-7634.md @@ -0,0 +1,223 @@ +--- +eip: 7634 +title: Limited Transfer Count NFT +description: An ERC-721 extension to limit transferability based on counts among NFTs +author: Qin Wang (@qinwang-git), Saber Yu (@OniReimu), Shiping Chen +discussions-to: https://ethereum-magicians.org/t/erc-7634-limited-transferable-nft/18861 +status: Draft +type: Standards Track +category: ERC +created: 2024-02-22 +requires: 165, 721 +--- + +## Abstract + +This standard extends [ERC-721](./eip-721.md) to introduce a mechanism that allows minters to customize the transferability of NFTs through a parameter called `TransferCount`. `TransferCount` sets a limit on how many times an NFT can be transferred. The standard specifies an interface that includes functions for setting and retrieving transfer limits, tracking transfer counts, and defining pre- and post-transfer states. The standard enables finer control over NFT ownership and transfer rights, ensuring that NFTs can be programmed to have specific, enforceable transfer restrictions. + + +## Motivation + +Once NFTs are sold, they detach from their minters (creators) and can be perpetually transferred thereafter. Yet, many circumstances demand precise control over NFT issuance. We outline their advantages across three dimensions. + +Firstly, by imposing limitations on the frequency of NFT sales or trades, the worth of rare NFTs can be safeguarded. For example, in auctions, limiting the round of bids for a coveted item can uphold its premium price (especially in the Dutch Auction). Similarly, in the intellectual property sector, patents could be bounded by a finite number of transfers prior to becoming freely accessible (entering CC0). In the gaming sphere, items like weapons, apparel, and vehicles might possess a finite lifespan, with each usage or exchange contributing to wear and tear, culminating in automatic decommissioning (burn) upon reaching a predetermined threshold. + +Secondly, enforcing restrictions on trading frequency can enhance network security and stability by mitigating the risks associated with malicious NFT arbitrage, including high-frequency trading (HFT). While this presents a common vulnerability, the lack of easily deployable and effective methods to address it has been notable, making our approach particularly valuable. + +Additionally, limiting the round of transfers can mitigate the economic risks associated with (re)staking NFTs, thereby curbing potential bubbles. With the rapid evolution of restaking mechanisms, it's foreseeable that users may soon engage in multiple rounds of NFT staking (e.g., NFT → stNFT → st^2NFT), akin to staking liquidity tokens with third-party platforms like EigenLayer (Ethereum), Babylon (Bitcoin), and Picasso (Solana). Notably, the current setup of EigenLayer employs an NFT as the restaking position (a type of proof-of-restake) for participants. Should this NFT be restaked repeatedly into the market, it could amplify leverage and exacerbate bubble dynamics. By imposing limits on the number of stake iterations, we can proactively prevent the emergence of Ponzi-like dynamics within staking ecosystems. + +### Key Takeaways + +This standard provides several advantages: + +*Controlled Value Preservation*: By allowing minters to set customized transfer limits for NFTs, this standard facilitates the preservation of value for digital assets Just as physical collectibles often gain or maintain value due to scarcity, limiting the number of transfers for an NFT can help ensure its continued value over time. + +*Ensuring Intended Usage*: Setting transfer limits can ensure that NFTs are used in ways that align with their intended purpose. For example, if an NFT represents a limited-edition digital artwork, limiting transfers can prevent it from being excessively traded and potentially devalued. + +*Expanding Use Cases*: These enhancements broaden the potential applications of NFTs by offering more control and flexibility to creators and owners. For instance, NFTs could be used to represent memberships or licenses with limited transferability, opening up new possibilities for digital ownership models. + +*Easy Integration*: To ensure broad adoption and ease of integration, this standard extends the existing [ERC-721](./eip-721.md) interface. By defining a separate interface (`IERC7634`) that includes the new functions, the standard allows existing [ERC-721](./eip-721.md) contracts to adopt the new features with minimal changes. This approach promotes backward compatibility and encourages the seamless incorporation of transfer limits into current NFT projects. + +## Specification + +The key words “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. + +- `setTransferLimit`: a function establishes the transfer limit for a tokenId. +- `transferLimitOf`: a function retrieves the transfer limit for a tokenId. +- `transferCountOf`: a function returns the current transfer count for a tokenId. + +Implementers of this standard **MUST** have all of the following functions: + +```solidity + +pragma solidity ^0.8.4; + +/// @title IERC7634 Interface for Limited Transferable NFT +/// @dev Interface for ERC7634 Limited Transferable NFT extension for ERC721 +/// @author Saber Yu + +interface IERC7634 { + + /** + * @dev Emitted when transfer count is set or updated + */ + event TransferCount(uint256 indexed tokenId, address owner, uint256 counts); + + /** + * @dev Returns the current transfer count for a tokenId + */ + function transferCountOf(uint256 tokenId) external view returns (uint256); + + /** + * @dev Sets the transfer limit for a tokenId. Can only be called by the token owner or an approved address. + * @param tokenId The ID of the token for which to set the limit + * @param limit The maximum number of transfers allowed for the token + */ + function setTransferLimit(uint256 tokenId, uint256 limit) external; + + /** + * @dev Returns the transfer limit for a tokenId + */ + function transferLimitOf(uint256 tokenId) external view returns (uint256); +} + +``` + +## Rationale + +### Does tracking the internal transfer count matter? + +Yes and no. It is optional and quite depends on the actual requirements. The reference implementation given below is a recommended one if you opt for tracking. The `_incrementTransferCount` function and related retrieval functions (`transferLimitOf` and `transferCountOf`) are designed to keep track of the number of transfers an NFT has undergone. This internal tracking mechanism is crucial for enforcing the minter's transfer limits, ensuring that no further transfers can occur once the limit is reached. + +### If opting for tracking, is that all we may want to track? + +It is recommended to also track the before and after. The optional `_beforeTokenTransfer` and `_afterTokenTransfer` functions are overridden to define the state of the NFT before and after a transfer. These functions ensure that any necessary checks or updates are performed in line with the transfer limits and counts. By integrating these checks into the transfer process, the standard ensures that transfer limits are consistently enforced. + + +## Backwards Compatibility + +This standard can be fully [ERC-721](./eip-721.md) compatible by adding an extension function set. + +### Extensions + +This standard can be enhanced with additional advanced functionalities alongside existing NFT protocols. For example: + +- Incorporating a burn function (e.g., [ERC-5679](./eip-5679.md)) would enable NFTs to automatically expire after reaching their transfer limits, akin to the ephemeral nature of Snapchat messages that disappear after multiple views. + +- Incorporating a non-transferring function, as defined in the SBT standards, would enable NFTs to settle and bond with a single owner after a predetermined number of transactions. This functionality mirrors the scenario where a bidder ultimately secures a treasury after participating in multiple bidding rounds. + + +## Reference Implementation + +A recommended implementation is demonstrated as follows: + +- `_incrementTransferCount`: an internal function facilitates incrementing the transfer count. +- `_beforeTokenTransfer`: an overrided function defines the state before transfer. +- `_afterTokenTransfe`: an overrided function outlines the state after transfer. + +```solidity + +pragma solidity ^0.8.4; + +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import "./IERC7634.sol"; + +/// @title Limited Transferable NFT Extension for ERC721 +/// @dev Implementation of the Limited Transferable NFT extension for ERC721 +/// @author Saber Yu + +contract ERC7634 is ERC721, IERC7634 { + + // Mapping from tokenId to the transfer count + mapping(uint256 => uint256) private _transferCounts; + + // Mapping from tokenId to its maximum transfer limit + mapping(uint256 => uint256) private _transferLimits; + + /** + * @dev See {IERC7634-transferCountOf}. + */ + function transferCountOf(uint256 tokenId) public view override returns (uint256) { + require(_exists(tokenId), "ERC7634: Nonexistent token"); + return _transferCounts[tokenId]; + } + + /** + * @dev See {IERC7634-setTransferLimit}. + */ + function setTransferLimit(uint256 tokenId, uint256 limit) public override { + require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC7634: caller is not owner nor approved"); + _transferLimits[tokenId] = limit; + } + + /** + * @dev See {IERC7634-transferLimitOf}. + */ + function transferLimitOf(uint256 tokenId) public view override returns (uint256) { + require(_exists(tokenId), "ERC7634: Nonexistent token"); + return _transferLimits[tokenId]; + } + + /** + * @dev Internal function to increment transfer count. + */ + function _incrementTransferCount(uint256 tokenId) internal { + _transferCounts[tokenId] += 1; + emit TransferCount(tokenId, ownerOf(tokenId), _transferCounts[tokenId]); + } + + /** + * @dev Override {_beforeTokenTransfer} to enforce transfer limit. + */ + function _beforeTokenTransfer( + address from, + address to, + uint256 tokenId + ) internal override { + require(_transferCounts[tokenId] < _transferLimits[tokenId], "ERC7634: Transfer limit reached"); + super._beforeTokenTransfer(from, to, tokenId); + } + + /** + * @dev Override {_afterTokenTransfer} to handle post-transfer logic. + */ + function _afterTokenTransfer( + address from, + address to, + uint256 tokenId, + uint256 quantity + ) internal virtual override { + _incrementTransferCount(tokenId); + + if (_transferCounts[tokenId] == _transferLimits[tokenId]) { + // Optional post-transfer operations once the limit is reached + // Uncomment the following based on the desired behavior such as the `burn` opearation + // --------------------------------------- + // _burn(tokenId); // Burn the token + // --------------------------------------- + } + + super._afterTokenTransfer(from, to, tokenId, quantity); + } + + + /** + * @dev Override {supportsInterface} to declare support for IERC7634. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) { + return interfaceId == type(IERC7634).interfaceId || super.supportsInterface(interfaceId); + } +} + +``` + +## Security Considerations + +- Ensure that each NFT minter can call this function to set transfer limits. +- Consider making transfer limits immutable once set to prevent tampering or unauthorized modifications. +- Avoid performing resource-intensive operations when integration with advanced functions that could exceed the gas limit during execution. + + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/ERCS/erc-7644.md b/ERCS/erc-7644.md index 474524d3c8..5eaaa8c987 100644 --- a/ERCS/erc-7644.md +++ b/ERCS/erc-7644.md @@ -15,7 +15,7 @@ requires: 721 This extension defines an interface that adds a naming mechanism to [ERC-721](./eip-721.md) tokens. It allows each token to have a unique name with a set expiration date, ensuring uniqueness within the current NFT contract. The interface includes functions for assigning, updating, and querying names and their associated tokens, ensuring that names remain unique until they expire. The entity responsible for setting names depends on the specific use case scenario when utilizing this extension. -### Motivation +## Motivation As decentralized domain registration methods evolve with the integration of NFTs, we see an opportunity to extend this paradigm to the realm of usernames. By associating token IDs with usernames, we enhance the intuitive identification of entities within decentralized ecosystems. @@ -44,13 +44,6 @@ pragma solidity ^0.8.0; * with associated expiry dates tied to specific tokens. */ interface IERC7644 /* is IERC721 */ { - /** - * @dev Emitted when a token is named. - * @param tokenId The token ID that is being named. - * @param newName The new name assigned to the token. - * @param expiryDate The expiry date of the name registration. - */ - event TokenNamed(uint256 indexed tokenId, bytes32 newName, uint256 expiryDate); /** * @dev Emitted when the name of a token is changed. @@ -121,7 +114,6 @@ pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; contract ERC7644 is ERC721 { - event TokenNamed(uint256 indexed tokenId, bytes32 newName, uint256 expiryDate); event NameChanged(uint256 indexed tokenId, bytes32 oldName, bytes32 newName, uint256 expiryDate); struct NameRegistration { @@ -136,41 +128,44 @@ contract ERC7644 is ERC721 { uint256 public constant MAX_DURATION = 10 * 365 days; uint256 public constant MIN_SET_NAME_INTERVAL = 1 days; - constructor() ERC721("My Token", "MTK") {} + constructor() ERC721("Asd Token", "ASDT") {} function nameOf(uint256 tokenId) public view returns (bytes32) { - require(_tokenNames[tokenId] != bytes32(0) && _nameRegistrations[_tokenNames[tokenId]].expiryDate > block.timestamp, "NameRegistry: Name expired or does not exist"); - return _tokenNames[tokenId]; + if(_tokenNames[tokenId] != bytes32(0) && _nameRegistrations[_tokenNames[tokenId]].expiryDate > block.timestamp) + { + return _tokenNames[tokenId]; + }else{ + return bytes32(0); + } } function tokenIdOf(bytes32 _name) public view returns (uint256) { require(_nameRegistrations[_name].expiryDate > block.timestamp, "NameRegistry: Name expired"); - return _nameRegistrations[_name].tokenId; + if(_nameRegistrations[_name].tokenId > 0) + { + return _nameRegistrations[_name].tokenId; + }else{ + return uint256(0); + } } function setName(uint256 tokenId, bytes32 _name, uint256 duration) public { require(ownerOf(tokenId) == msg.sender, "NameRegistry: Caller is not the token owner"); require(duration <= MAX_DURATION, "NameRegistry: Duration exceeds maximum limit"); require(block.timestamp - _lastSetNameTime[tokenId] >= MIN_SET_NAME_INTERVAL, "NameRegistry: Minimum interval not met"); - - // Check if name is either not registered or expired - require(_nameRegistrations[_name].expiryDate <= block.timestamp, "NameRegistry: Name already in use and not expired"); + require(tokenIdOf(_name) == uint256(0) || tokenIdOf(_name) == tokenId, "NameRegistry: Name already in use and not expired"); bytes32 oldName = _tokenNames[tokenId]; uint256 expiryDate = block.timestamp + duration; _setTokenName(tokenId, _name, expiryDate); - if (oldName != bytes32(0)) { - emit NameChanged(tokenId, oldName, _name, expiryDate); - } else { - emit TokenNamed(tokenId, _name, expiryDate); - } + emit NameChanged(tokenId, oldName, _name, expiryDate); _lastSetNameTime[tokenId] = block.timestamp; } function nameInfo(bytes32 _name) public view returns (uint256, uint256) { - require(_nameRegistrations[_name].expiryDate > block.timestamp, "NameRegistry: Name expired or does not exist"); + require(_nameRegistrations[_name].tokenId > 0 && _nameRegistrations[_name].expiryDate > block.timestamp, "NameRegistry: Name expired or does not exist"); NameRegistration memory registration = _nameRegistrations[_name]; return (registration.tokenId, registration.expiryDate); } diff --git a/ERCS/erc-7654.md b/ERCS/erc-7654.md new file mode 100644 index 0000000000..8e2eb4621f --- /dev/null +++ b/ERCS/erc-7654.md @@ -0,0 +1,148 @@ +--- +eip: 7654 +title: Request Method Types +description: Use a set of request methods to indicate the type of action to take on the contract. +author: Rickey (@HelloRickey) +discussions-to: https://ethereum-magicians.org/t/erc-7654-request-method-types/19183 +status: Draft +type: Standards Track +category: ERC +created: 2024-03-13 +--- + +## Abstract + +This proposal standardizes a set of request and response communication standards between clients and smart contracts, using POST, GET, and PUT requests to create, read, and update the states of smart contracts. You can customize different request method names, request parameters and response values, and each request method will be mapped to a specific operation. + +## Motivation + +Since each contract has different functions, the client cannot use a standard to call different functions of different contracts. Contract Request Methods redefines the request method of the contract, so that different functions of multiple different contracts can be called using a consistent set of rules and protocols. + +By dividing the function types into POST, GET, and PUT, different operations can be performed on the contract. This clear operation type can not only help all parties limit the access and operation of contract data, but also effectively simplify the interaction between the client and the contract, making it easier for all parties to understand the functions and hierarchical structure of the contract. The request and response parameter data types of each function of this standard can express the expected operation of the contract and have the ability to describe its own structure, which is conducive to the parties and contracts to create a unified and predictable way of exchanging data. + +## Specification + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. + +It consists of four request method types: + +**GET**: Request the contract to retrieve records. + +**POST**: Request the contract to create a new record. + +**PUT**: Request the contract to update a record. + +**OPTIONS**: Supported request method types. + +Workflow: + +1. Call ```options``` to obtain supported request method types. +2. Call ```getMethods``` to obtain the request method name. +3. Call ```getMethodReqAndRes``` to obtain the request parameter data type and response value data type. +4. Encode request parameters and call ```get```, ```post```, and ```put```. +5. Decode response value. + +### Interfaces + +#### `IRequestMethodTypes.sol` + +```solidity +// SPDX-License-Identifier: CC0-1.0 +pragma solidity >=0.8.0; +import "./Types.sol"; +interface IRequestMethodTypes{ + + /** + * Requested method type. + * GET, POST, PUT, OPTIONS + */ + enum MethodTypes{ + GET, + POST, + PUT, + OPTIONS + } + + /** + * Response data event. + * @param _response is the response value of the post request or put request. + */ + event Response(bytes _response); + + /** + * Get method names based on request method type. + * @param _methodTypes is the request method type. + * @return Method names. + */ + function getMethods(MethodTypes _methodTypes)external view returns (string[] memory); + + /** + * Get the data types of request parameters and responses based on the requested method name. + * @param _methodName is the method name. + * @return Data types of request parameters and responses. + */ + function getMethodReqAndRes(string memory _methodName) external view returns(Types.Type[] memory ,Types.Type[] memory ); + + /** + * Request the contract to retrieve records. + * @param _methodName is the method name. + * @param _methodReq is the method type. + * @return The response to the get request. + */ + function get(string memory _methodName,bytes memory _methodReq)external view returns(bytes memory); + + /** + * Request the contract to create a new record. + * @param _methodName is the method name. + * @param _methodReq is the method type. + * @return The response to the post request. + */ + function post(string memory _methodName,bytes memory _methodReq)external payable returns(bytes memory); + + /** + * Request the contract to update a record. + * @param _methodName is the method name. + * @param _methodReq is the method type. + * @return The response to the put request. + */ + function put(string memory _methodName,bytes memory _methodReq)external payable returns(bytes memory); + + /** + * Supported request method types. + * @return Method types. + */ + function options()external returns(MethodTypes[] memory); +} + +``` + +### Library + +The library [`Types.sol`](../assets/eip-7654/Types.sol) contains an enumeration of Solidity types used in the above interfaces. + +## Rationale + +### Type of request method + +In order to enable the client to operate the contract in a standardized and predictable way, three request method types ```GET```, ```POST```, and ```PUT``` are set. The functions of each need to be defined in these three types to facilitate the contract caller to understand and process the information required for the request. However, there is no ```DELETE``` operation type because deleting data in the contract is an inefficient operation. Developers can add a ```PUT``` request method by themselves to set the data to be valid and invalid, and only return valid data in the ```GET``` method. + +### Request method parameter type + +Some functions are defined in each request method type. They all include request parameter data type and response parameter data type, which need to be set in the ```constructor``` and then obtained according to the method name through ```getMethodReqAndRes```. The data type of the parameter is defined by the enumeration of the data type. When processing the request parameter, ```abi.decode``` is used to decode according to the request parameter type and the request value. When returning the response, ```abi.encode``` is used to encode according to the response value and the response parameter type. + + +## Reference Implementation + +See [Request Method Types Example](../assets/eip-7654/RequestMethodTypes.sol) + +## Security Considerations + +Contract request methods are divided into safe methods and unsafe methods. If the method request is a read-only operation and will not change the state of the contract, then the method is safe. + +**Safe Methods:** GET, OPTIONS +**Unsafe Methods:** POST, PUT + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). + diff --git a/ERCS/erc-7656.md b/ERCS/erc-7656.md index f7f0eac2a6..8083137097 100644 --- a/ERCS/erc-7656.md +++ b/ERCS/erc-7656.md @@ -1,10 +1,10 @@ --- eip: 7656 -title: Generalized Token-Linked Contracts -description: Define a registry for generic contracts linked to a specific NFT +title: Generalized Token-Linked Services +description: Define a registry for generic services linked to a specific NFT author: Francesco Sullo (@sullof) discussions-to: https://ethereum-magicians.org/t/variation-to-erc6551-to-deploy-any-kind-of-contract-linked-to-an-nft/19223 -status: Draft +status: Review type: Standards Track category: ERC created: 2024-03-15 @@ -13,15 +13,15 @@ requires: 165, 1167, 5313, 6551 ## Abstract -This proposal introduces a variation of [ERC-6551](./eip-6551.md) that extends to all types of contracts linked to non-fungible tokens (NFTs), i.e., contracts owned by a single NFT and thus by the owner of the NFT. It achieves this goal using generic language for functions, errors, and events, and avoids conflicting with the strict restrictions imposed by the original proposal. +This proposal introduces a variation of [ERC-6551](./eip-6551.md) that extends to all types of services linked to non-fungible tokens (NFTs), i.e., contracts extending an NFT, owned by a single NFT and thus by the owner of the NFT. It achieves this goal using generic language for functions, errors, and events, and avoids conflicting with the strict restrictions imposed by the original proposal. ## Motivation [ERC-6551](./eip-6551.md) aims to bind smart accounts to tokens, allowing its registry to deploy accounts owned by a specific tokenID. The issue we attempt to address with this new proposal is that [ERC-6551](./eip-6551.md) explicitly requires any contract deployed via the `ERC6551Registry` to implement `IERC6551Account` and `IERC6551Execute`, i.e., it must be an account. This requirement is underscored by the choices for the names of functions and events in the interface. Additionally, [ERC-6551](./eip-6551.md) specifies that the `ERC6551Registry` smart contract is deployed as a singleton at a specific address on any chain. Due to this centralization of services, projects building on it are prone to consider any contract deployed via that registry that is not an account as spam or invalid. -With this new ERC, we propose a more generic registry that uses generic function/event names to allow the deployment of any kind of contract that makes sense when associated with an NFT, so that the contract is under the full control of the NFT's owner. Since one of this proposal's goals is flexibility, there is no expectation for an `ERC7656Registry` contract to be deployed as a singleton, allowing any project to adjust it to their needs. Consequently, we require that any registry explicitly supports the `IERC7656Registry` interface. +With this new ERC, we propose a more generic registry that uses generic function/event names to allow the deployment of any kind of contract that makes sense when associated with an NFT, so that the contract is under the full control of the NFT's owner. In comparison with [ERC-6551](./eip-6551.md), since one of this proposal's goals is flexibility, there is no expectation for an `ERC7656Registry` contract to be deployed as a singleton, allowing any project to adjust it to their needs; consequently, we require that any registry explicitly supports the `IERC7656Registry` interface. -The expansion of the registry's capabilities to manage contracts beyond accounts provides several advantages: +The expansion of the registry's capabilities to manage contracts implementing any kind of service beyond accounts provides several advantages: - **Flexibility**: Developers can allow NFTs to interact with a broader range of linked contracts, unlocking new use cases and functionalities (lending systems, vested asset distribution, fractional ownership, identity, etc.) - **Compatibility**: By ensuring that account-like contracts can still be identified as such, the proposal maintains backward compatibility with [ERC-6551](./eip-6551.md). @@ -61,15 +61,15 @@ interface IERC7656Registry { error CreationFailed(); /** - * @notice Creates a token linked account for a non-fungible token. - * If account has already been created, returns the account address without calling create2. + * @notice Creates a token linked service for a non-fungible token. + * If the service has already been created, returns the service address without calling create2. * @param implementation The address of the implementation contract * @param salt The salt to use for the create2 operation - * @param chainId The chain id of the chain where the account is being created + * @param chainId The chain id of the chain where the service is being created * @param tokenContract The address of the token contract * @param tokenId The id of the token * Emits Created event. - * @return account The address of the token linked account + * @return service The address of the token linked service */ function create( address implementation, @@ -77,16 +77,16 @@ interface IERC7656Registry { uint256 chainId, address tokenContract, uint256 tokenId - ) external returns (address account); + ) external returns (address service); /** - * @notice Returns the computed token linked account address for a non-fungible token. + * @notice Returns the computed token linked service address for a non-fungible token. * @param implementation The address of the implementation contract * @param salt The salt to use for the create2 operation - * @param chainId The chain id of the chain where the account is being created + * @param chainId The chain id of the chain where the service is being created * @param tokenContract The address of the token contract * @param tokenId The id of the token - * @return account The address of the token linked account + * @return service The address of the token linked service */ function compute( address implementation, @@ -94,15 +94,15 @@ interface IERC7656Registry { uint256 chainId, address tokenContract, uint256 tokenId - ) external view returns (address account); + ) external view returns (address service); } ``` Any `ERC7656Registry` implementation MUST support the `IERC7656Registry`'s interface ID, i.e., `0xc6bdc908`. -Similarly to [ERC-6551](./eip-6551.md), The registry MUST deploy each token linked account as an [ERC-1167](./eip-1167.md) minimal proxy with immutable constant data appended to the bytecode. +Similarly to [ERC-6551](./eip-6551.md), The registry MUST deploy each token linked service as an [ERC-1167](./eip-1167.md) minimal proxy with immutable constant data appended to the bytecode. -The deployed bytecode of each token bound account MUST have the following structure: +The deployed bytecode of each token bound service MUST have the following structure: ``` ERC-1167 Header (10 bytes) (20 bytes) @@ -113,10 +113,11 @@ ERC-1167 Footer (15 bytes) (32 bytes) ``` -Any contract created using a `ERC7656Registry` SHOULD implement the `IERC7656Contract` interface: +Any contract created using a `ERC7656Registry` SHOULD implement the `IERC7656Service` interface: ```solidity -interface IERC7656Linked { +// InterfaceId 0xfc0c546a +interface IERC7656Service { /** * @notice Returns the token linked to the contract * @return chainId The chainId of the token @@ -189,12 +190,12 @@ contract ERC7656Registry is IERC7656Registry { mstore(0x01, shl(96, address())) // registry address mstore(0x15, salt) // salt - // Compute account address + // Compute service address let computed := keccak256(0x00, 0x55) - // If the account has not yet been deployed + // If the service has not yet been deployed if iszero(extcodesize(computed)) { - // Deploy account contract + // Deploy service contract let deployed := create2(0, 0x55, 0xb7, salt) // Revert if the deployment fails @@ -203,7 +204,7 @@ contract ERC7656Registry is IERC7656Registry { revert(0x1c, 0x04) } - // Store account address in memory before salt and chainId + // Store service address in memory before salt and chainId mstore(0x6c, deployed) // Emit the Created event @@ -216,11 +217,11 @@ contract ERC7656Registry is IERC7656Registry { tokenId ) - // Return the account address + // Return the service address return(0x6c, 0x20) } - // Otherwise, return the computed account address + // Otherwise, return the computed service address mstore(0x00, shr(96, shl(96, computed))) return(0x00, 0x20) } @@ -247,10 +248,10 @@ contract ERC7656Registry is IERC7656Registry { mstore(0x01, shl(96, address())) // registry address mstore(0x15, salt) // salt - // Store computed account address in memory + // Store computed service address in memory mstore(0x00, shr(96, shl(96, keccak256(0x00, 0x55)))) - // Return computed account address + // Return computed service address return(0x00, 0x20) } } @@ -263,10 +264,10 @@ contract ERC7656Registry is IERC7656Registry { } ``` -A simple implementation of `IERC7656Linked`: +An example of implementation of `IERC7656Service`: ```solidity -contract LinkedContract is IERC7656Linked, EIP5313 { +contract LinkedService is IERC7656Service, EIP5313 { function token() public view virtual returns (uint256, address, uint256) { bytes memory footer = new bytes(0x60); diff --git a/ERCS/erc-7673.md b/ERCS/erc-7673.md new file mode 100644 index 0000000000..ef1ab23168 --- /dev/null +++ b/ERCS/erc-7673.md @@ -0,0 +1,373 @@ +--- +eip: 7673 +title: Distinguishable base256emoji Addresses +description: Depict Account Addresses As A String of Emoji +author: William Morriss (@wjmelements) +discussions-to: https://ethereum-magicians.org/t/erc-7673-distinguishable-account-addresses/19461 +status: Draft +type: Standards Track +category: ERC +created: 2024-04-01 +--- + +## Abstract + +Introduce base256emoji for use as the primary input and display for account addresses in all user interfaces. + +## Motivation + +Human users often fail to distinguish between long strings of hexadecimal characters, especially when they match at the beginning and at the end. +This makes hexadecimal strings a poor format for human-readable account addresses. +The problem is being exploited by several spoofing strategies that mine similar addresses and spoof [ERC-20](./eip-20.md) Transfer events with the goal of tricking the end user into copying the wrong recipient address. +These address spoofing attacks have mislead tens of thousands of ether, and countless other tokens. +Spoofers flooding the network with fake Transfer events waste network resources and complicate blockchain accounting. +Improving the distinguishability of addresses will reduce the incentives for this behavior. + +## Specification + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. + +User interfaces: +- SHALL depict account addresses as a base256emoji string instead of hexadecimal. +- SHALL accept base256emoji strings as input for user-supplied account address parameters. +- SHOULD recognize and interpret strings of exactly 20 consecutive emoji as addresses when all of them are valid base256emoji. + +### base256emoji encoding table + +| Emoji | Unicode codepoint | Byte Value | +|:-:|:-:|:-:| +| 🚀 | U+1F680 | 0 | +| 🪐 | U+1FA90 | 1 | +| ☄ | U+2604 | 2 | +| 🛰 | U+1F6F0 | 3 | +| 🌌 | U+1F30C | 4 | +| 🌑 | U+1F311 | 5 | +| 🌒 | U+1F312 | 6 | +| 🌓 | U+1F313 | 7 | +| 🌔 | U+1F314 | 8 | +| 🌕 | U+1F315 | 9 | +| 🌖 | U+1F316 | 10 | +| 🌗 | U+1F317 | 11 | +| 🌘 | U+1F318 | 12 | +| 🌍 | U+1F30D | 13 | +| 🌏 | U+1F30F | 14 | +| 🌎 | U+1F30E | 15 | +| 🐉 | U+1F409 | 16 | +| ☀ | U+2600 | 17 | +| 💻 | U+1F4BB | 18 | +| 🖥 | U+1F5A5 | 19 | +| 💾 | U+1F4BE | 20 | +| 💿 | U+1F4BF | 21 | +| 😂 | U+1F602 | 22 | +| ❤ | U+2764 | 23 | +| 😍 | U+1F60D | 24 | +| 🤣 | U+1F923 | 25 | +| 😊 | U+1F60A | 26 | +| 🙏 | U+1F64F | 27 | +| 💕 | U+1F495 | 28 | +| 😭 | U+1F62D | 29 | +| 😘 | U+1F618 | 30 | +| 👍 | U+1F44D | 31 | +| 😅 | U+1F605 | 32 | +| 👏 | U+1F44F | 33 | +| 😁 | U+1F601 | 34 | +| 🔥 | U+1F525 | 35 | +| 🥰 | U+1F970 | 36 | +| 💔 | U+1F494 | 37 | +| 💖 | U+1F496 | 38 | +| 💙 | U+1F499 | 39 | +| 😢 | U+1F622 | 40 | +| 🤔 | U+1F914 | 41 | +| 😆 | U+1F606 | 42 | +| 🙄 | U+1F644 | 43 | +| 💪 | U+1F4AA | 44 | +| 😉 | U+1F609 | 45 | +| ☺ | U+263A | 46 | +| 👌 | U+1F44C | 47 | +| 🤗 | U+1F917 | 48 | +| 💜 | U+1F49C | 49 | +| 😔 | U+1F614 | 50 | +| 😎 | U+1F60E | 51 | +| 😇 | U+1F607 | 52 | +| 🌹 | U+1F339 | 53 | +| 🤦 | U+1F926 | 54 | +| 🎉 | U+1F389 | 55 | +| 💞 | U+1F49E | 56 | +| ✌ | U+270C | 57 | +| ✨ | U+2728 | 58 | +| 🤷 | U+1F937 | 59 | +| 😱 | U+1F631 | 60 | +| 😌 | U+1F60C | 61 | +| 🌸 | U+1F338 | 62 | +| 🙌 | U+1F64C | 63 | +| 😋 | U+1F60B | 64 | +| 💗 | U+1F497 | 65 | +| 💚 | U+1F49A | 66 | +| 😏 | U+1F60F | 67 | +| 💛 | U+1F49B | 68 | +| 🙂 | U+1F642 | 69 | +| 💓 | U+1F493 | 70 | +| 🤩 | U+1F929 | 71 | +| 😄 | U+1F604 | 72 | +| 😀 | U+1F600 | 73 | +| 🖤 | U+1F5A4 | 74 | +| 😃 | U+1F603 | 75 | +| 💯 | U+1F4AF | 76 | +| 🙈 | U+1F648 | 77 | +| 👇 | U+1F447 | 78 | +| 🎶 | U+1F3B6 | 79 | +| 😒 | U+1F612 | 80 | +| 🤭 | U+1F92D | 81 | +| ❣ | U+2763 | 82 | +| 😜 | U+1F61C | 83 | +| 💋 | U+1F48B | 84 | +| 👀 | U+1F440 | 85 | +| 😪 | U+1F62A | 86 | +| 😑 | U+1F611 | 87 | +| 💥 | U+1F4A5 | 88 | +| 🙋 | U+1F64B | 89 | +| 😞 | U+1F61E | 90 | +| 😩 | U+1F629 | 91 | +| 😡 | U+1F621 | 92 | +| 🤪 | U+1F92A | 93 | +| 👊 | U+1F44A | 94 | +| 🥳 | U+1F973 | 95 | +| 😥 | U+1F625 | 96 | +| 🤤 | U+1F924 | 97 | +| 👉 | U+1F449 | 98 | +| 💃 | U+1F483 | 99 | +| 😳 | U+1F633 | 100 | +| ✋ | U+270B | 101 | +| 😚 | U+1F61A | 102 | +| 😝 | U+1F61D | 103 | +| 😴 | U+1F634 | 104 | +| 🌟 | U+1F31F | 105 | +| 😬 | U+1F62C | 106 | +| 🙃 | U+1F643 | 107 | +| 🍀 | U+1F340 | 108 | +| 🌷 | U+1F337 | 109 | +| 😻 | U+1F63B | 110 | +| 😓 | U+1F613 | 111 | +| ⭐ | U+2B50 | 112 | +| ✅ | U+2705 | 113 | +| 🥺 | U+1F97A | 114 | +| 🌈 | U+1F308 | 115 | +| 😈 | U+1F608 | 116 | +| 🤘 | U+1F918 | 117 | +| 💦 | U+1F4A6 | 118 | +| ✔ | U+2714 | 119 | +| 😣 | U+1F623 | 120 | +| 🏃 | U+1F3C3 | 121 | +| 💐 | U+1F490 | 122 | +| ☹ | U+2639 | 123 | +| 🎊 | U+1F38A | 124 | +| 💘 | U+1F498 | 125 | +| 😠 | U+1F620 | 126 | +| ☝ | U+261D | 127 | +| 😕 | U+1F615 | 128 | +| 🌺 | U+1F33A | 129 | +| 🎂 | U+1F382 | 130 | +| 🌻 | U+1F33B | 131 | +| 😐 | U+1F610 | 132 | +| 🖕 | U+1F595 | 133 | +| 💝 | U+1F49D | 134 | +| 🙊 | U+1F64A | 135 | +| 😹 | U+1F639 | 136 | +| 🗣 | U+1F5E3 | 137 | +| 💫 | U+1F4AB | 138 | +| 💀 | U+1F480 | 139 | +| 👑 | U+1F451 | 140 | +| 🎵 | U+1F3B5 | 141 | +| 🤞 | U+1F91E | 142 | +| 😛 | U+1F61B | 143 | +| 🔴 | U+1F534 | 144 | +| 😤 | U+1F624 | 145 | +| 🌼 | U+1F33C | 146 | +| 😫 | U+1F62B | 147 | +| ⚽ | U+26BD | 148 | +| 🤙 | U+1F919 | 149 | +| ☕ | U+2615 | 150 | +| 🏆 | U+1F3C6 | 151 | +| 🤫 | U+1F92B | 152 | +| 👈 | U+1F448 | 153 | +| 😮 | U+1F62E | 154 | +| 🙆 | U+1F646 | 155 | +| 🍻 | U+1F37B | 156 | +| 🍃 | U+1F343 | 157 | +| 🐶 | U+1F436 | 158 | +| 💁 | U+1F481 | 159 | +| 😲 | U+1F632 | 160 | +| 🌿 | U+1F33F | 161 | +| 🧡 | U+1F9E1 | 162 | +| 🎁 | U+1F381 | 163 | +| ⚡ | U+26A1 | 164 | +| 🌞 | U+1F31E | 165 | +| 🎈 | U+1F388 | 166 | +| ❌ | U+274C | 167 | +| ✊ | U+270A | 168 | +| 👋 | U+1F44B | 169 | +| 😰 | U+1F630 | 170 | +| 🤨 | U+1F928 | 171 | +| 😶 | U+1F636 | 172 | +| 🤝 | U+1F91D | 173 | +| 🚶 | U+1F6B6 | 174 | +| 💰 | U+1F4B0 | 175 | +| 🍓 | U+1F353 | 176 | +| 💢 | U+1F4A2 | 177 | +| 🤟 | U+1F91F | 178 | +| 🙁 | U+1F641 | 179 | +| 🚨 | U+1F6A8 | 180 | +| 💨 | U+1F4A8 | 181 | +| 🤬 | U+1F92C | 182 | +| ✈ | U+2708 | 183 | +| 🎀 | U+1F380 | 184 | +| 🍺 | U+1F37A | 185 | +| 🤓 | U+1F913 | 186 | +| 😙 | U+1F619 | 187 | +| 💟 | U+1F49F | 188 | +| 🌱 | U+1F331 | 189 | +| 😖 | U+1F616 | 190 | +| 👶 | U+1F476 | 191 | +| 🥴 | U+1F974 | 192 | +| ▶ | U+25B6 | 193 | +| ➡ | U+27A1 | 194 | +| ❓ | U+2753 | 195 | +| 💎 | U+1F48E | 196 | +| 💸 | U+1F4B8 | 197 | +| ⬇ | U+2B07 | 198 | +| 😨 | U+1F628 | 199 | +| 🌚 | U+1F31A | 200 | +| 🦋 | U+1F98B | 201 | +| 😷 | U+1F637 | 202 | +| 🕺 | U+1F57A | 203 | +| ⚠ | U+26A0 | 204 | +| 🙅 | U+1F645 | 205 | +| 😟 | U+1F61F | 206 | +| 😵 | U+1F635 | 207 | +| 👎 | U+1F44E | 208 | +| 🤲 | U+1F932 | 209 | +| 🤠 | U+1F920 | 210 | +| 🤧 | U+1F927 | 211 | +| 📌 | U+1F4CC | 212 | +| 🔵 | U+1F535 | 213 | +| 💅 | U+1F485 | 214 | +| 🧐 | U+1F9D0 | 215 | +| 🐾 | U+1F43E | 216 | +| 🍒 | U+1F352 | 217 | +| 😗 | U+1F617 | 218 | +| 🤑 | U+1F911 | 219 | +| 🌊 | U+1F30A | 220 | +| 🤯 | U+1F92F | 221 | +| 🐷 | U+1F437 | 222 | +| ☎ | U+260E | 223 | +| 💧 | U+1F4A7 | 224 | +| 😯 | U+1F62F | 225 | +| 💆 | U+1F486 | 226 | +| 👆 | U+1F446 | 227 | +| 🎤 | U+1F3A4 | 228 | +| 🙇 | U+1F647 | 229 | +| 🍑 | U+1F351 | 230 | +| ❄ | U+2744 | 231 | +| 🌴 | U+1F334 | 232 | +| 💣 | U+1F4A3 | 233 | +| 🐸 | U+1F438 | 234 | +| 💌 | U+1F48C | 235 | +| 📍 | U+1F4CD | 236 | +| 🥀 | U+1F940 | 237 | +| 🤢 | U+1F922 | 238 | +| 👅 | U+1F445 | 239 | +| 💡 | U+1F4A1 | 240 | +| 💩 | U+1F4A9 | 241 | +| 👐 | U+1F450 | 242 | +| 📸 | U+1F4F8 | 243 | +| 👻 | U+1F47B | 244 | +| 🤐 | U+1F910 | 245 | +| 🤮 | U+1F92E | 246 | +| 🎼 | U+1F3BC | 247 | +| 🥵 | U+1F975 | 248 | +| 🚩 | U+1F6A9 | 249 | +| 🍎 | U+1F34E | 250 | +| 🍊 | U+1F34A | 251 | +| 👼 | U+1F47C | 252 | +| 💍 | U+1F48D | 253 | +| 📣 | U+1F4E3 | 254 | +| 🥂 | U+1F942 | 255 | + +## Rationale + +Previous attempts to reduce spoofing and other copy errors such as [ERC-55](./eip-55.md) have not reduced the number of characters in an address. +Any base-256 standard would achieve this goal but emoji were chosen to maximize human-distinguishability. +Multiple base-256 emoji encodings have been proposed. +The base256emoji encoding was chosen due to its acceptance into the multibase repository. + +This standard does not also recommend base256emoji for use in depicting other bytestrings such as transaction hashes and calldata. +Transaction hashes are not yet being spoofed. +Calldata is best decoded via the appropriate ABI. +By only using base256emoji for addresses, addresses can be easily noticed among other information. + +## Backwards Compatibility + +Using the encoding table, the base256emoji encoding can be transcoded into hexadecimal and vice-versa. + +## Test Cases +| base256emoji | ERC-55 | +|:-:|:-:| +|🚀🚀🚀🚀🚀🚀😀💓🥴💣👻🙌🙈🤢😥☹🌏💩🍎💕|`0x0000000000004946c0e9F43F4Dee607b0eF1fA1c`| +|🚀🚀🚀🚀🚀🚀💸🎊💡🌿🚩🔥📌🙂💙❄🛰💩🤝⭐|`0x000000000000c57CF0A1f923d44527e703F1ad70`| +|☀☀☀☀☀❤🌊🌖❌💀✔🌎🎈❌💞🛰💗😅❓☄|`0x111111111117dC0aa78b770fA6A738034120C302`| +|👍🤫😋✊🤪😞🤐👶😭❤👉🚩💔🌱🤝🌊💚🪐🚩😐|`0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984`| +|😆🌎✅✨👋😜💛☺😶👋🐸🤩🌔🙌✋🤤⭐🍑☹⚡|`0x2a0f713aA953442EacA9EA47083f656170e67BA4`| +|🔥🤬🌔😝😞🙄👌💢🗣🌍✨😙🐾😡😑🤘💸😂😤🔵|`0x23B608675a2B2fB1890d3ABBd85c5775c51691d5`| +|🗣😅😞✨🤷😆🌟🐷🌷👶☝🪐🥀🖥🤟🐉💀💪😏❄|`0x89205A3A3b2A69De6Dbf7f01ED13B2108B2c43e7`| +|🥴😆😰✌🤟🔥📣🎵🌖🌏😡🎶💙🐸🍒🌔😱🤘🍀➡|`0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2`| +|▶🌻😥👏💘😛💐💨❄💸😂😪😝🤤🐸💻😟☝🍃🥺|`0xC18360217D8F7Ab5e7c516566761Ea12Ce7F9D72`| + + +## Reference Implementation + +```python3 +to_emoji = [ + '🚀', '🪐', '☄', '🛰', '🌌', '🌑', '🌒', '🌓', '🌔', '🌕', '🌖', '🌗', '🌘', '🌍', '🌏', '🌎', + '🐉', '☀', '💻', '🖥', '💾', '💿', '😂', '❤', '😍', '🤣', '😊', '🙏', '💕', '😭', '😘', '👍', + '😅', '👏', '😁', '🔥', '🥰', '💔', '💖', '💙', '😢', '🤔', '😆', '🙄', '💪', '😉', '☺', '👌', + '🤗', '💜', '😔', '😎', '😇', '🌹', '🤦', '🎉', '💞', '✌', '✨', '🤷', '😱', '😌', '🌸', '🙌', + '😋', '💗', '💚', '😏', '💛', '🙂', '💓', '🤩', '😄', '😀', '🖤', '😃', '💯', '🙈', '👇', '🎶', + '😒', '🤭', '❣', '😜', '💋', '👀', '😪', '😑', '💥', '🙋', '😞', '😩', '😡', '🤪', '👊', '🥳', + '😥', '🤤', '👉', '💃', '😳', '✋', '😚', '😝', '😴', '🌟', '😬', '🙃', '🍀', '🌷', '😻', '😓', + '⭐', '✅', '🥺', '🌈', '😈', '🤘', '💦', '✔', '😣', '🏃', '💐', '☹', '🎊', '💘', '😠', '☝', + '😕', '🌺', '🎂', '🌻', '😐', '🖕', '💝', '🙊', '😹', '🗣', '💫', '💀', '👑', '🎵', '🤞', '😛', + '🔴', '😤', '🌼', '😫', '⚽', '🤙', '☕', '🏆', '🤫', '👈', '😮', '🙆', '🍻', '🍃', '🐶', '💁', + '😲', '🌿', '🧡', '🎁', '⚡', '🌞', '🎈', '❌', '✊', '👋', '😰', '🤨', '😶', '🤝', '🚶', '💰', + '🍓', '💢', '🤟', '🙁', '🚨', '💨', '🤬', '✈', '🎀', '🍺', '🤓', '😙', '💟', '🌱', '😖', '👶', + '🥴', '▶', '➡', '❓', '💎', '💸', '⬇', '😨', '🌚', '🦋', '😷', '🕺', '⚠', '🙅', '😟', '😵', + '👎', '🤲', '🤠', '🤧', '📌', '🔵', '💅', '🧐', '🐾', '🍒', '😗', '🤑', '🌊', '🤯', '🐷', '☎', + '💧', '😯', '💆', '👆', '🎤', '🙇', '🍑', '❄', '🌴', '💣', '🐸', '💌', '📍', '🥀', '🤢', '👅', + '💡', '💩', '👐', '📸', '👻', '🤐', '🤮', '🎼', '🥵', '🚩', '🍎', '🍊', '👼', '💍', '📣', '🥂' +] +from_emoji = {emoji: "{0:02x}".format(i) for i, emoji in enumerate(to_emoji)} + +def encode_address(hexadecimal_address): + if len(hexadecimal_address) != 42 or not hexadecimal_address.startswith('0x'): + return None + return ''.join([to_emoji[int(hexadecimal_address[i:i+2], 16)] for i in range(2, 42, 2)]) + + +def decode_address(emoji_address): + # In python, these unicode characters all have a len() of 1 + if len(emoji_address) != 20: + return None + try: + return '0x' + ''.join(from_emoji[emoji] for emoji in emoji_address) + except IndexError: + return None +``` + +## Security Considerations + +With the base256emoji encoding, addresses use half as many characters. +The characters used are more distinguishable. +This squares the difficulty of generating similar addresses, making address spoofing impractical. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/ERCS/erc-7677.md b/ERCS/erc-7677.md new file mode 100644 index 0000000000..e6dce38f95 --- /dev/null +++ b/ERCS/erc-7677.md @@ -0,0 +1,388 @@ +--- +eip: 7677 +title: Paymaster Web Service Capability +description: A way for apps to communicate with smart wallets about paymaster web services +author: Lukas Rosario (@lukasrosario), Dror Tirosh (@drortirosh), Wilson Cusack (@wilsoncusack), Kristof Gazso (@kristofgazso), Hazim Jumali (@hazim-j) +discussions-to: https://ethereum-magicians.org/t/erc-7677-paymaster-web-service-capability/19530 +status: Review +type: Standards Track +category: ERC +created: 2024-04-03 +requires: 4337, 5792 +--- + +## Abstract + +With [EIP-5792](./eip-5792.md), apps can communicate with wallets about advanced features via capabilities. This proposal defines a capability that allows apps to request that [ERC-4337](./eip-4337.md) wallets communicate with a specified paymaster web service. To support this, we also define a standardized API for paymaster web services. + +## Motivation + +App developers want to start sponsoring their users' transactions using paymasters. Paymasters are commonly used via web services. However, there is currently no way for apps to tell wallets to communicate with a specific paymaster web service. Similarly, there is no standard for how wallets should communicate with these services. We need both a way for apps to tell wallets to communicate with a specific paymaster web service and a communication standard for wallets to do so. + +## Specification + +One new [EIP-5792](./eip-5792.md) wallet capability is defined. We also define a standard interface for paymaster web services as a prerequisite. + +### Paymaster Web Service Interface + +We define two JSON-RPC methods to be implemented by paymaster web services. + +#### `pm_getPaymasterStubData` + +Returns stub values to be used in paymaster-related fields of an unsigned user operation for gas estimation. Accepts an unsigned user operation, entrypoint address, chain id, and a context object. Paymaster service providers can define fields that app developers should use in the context object. + +This method MAY return paymaster-specific gas values if applicable to the provided EntryPoint version. For example, if provided with EntryPoint v0.7, this method MAY return `paymasterVerificationGasLimit`. Furthermore, for EntryPoint v0.7, this method MUST return a value for `paymasterPostOpGasLimit`. This is because it is the paymaster that pays the postOpGasLimit, so it cannot trust the wallet to estimate this amount. + +The wallet SHOULD use these provided gas values when submitting the `UserOperation` to a bundler for gas estimation. + +This method MAY also return a `sponsor` object with a `name` field and an optional `icon` field. The `name` field is the name of the party sponsoring the transaction, and the `icon` field is a URI pointing to an image. Wallet developers MAY choose to display sponsor information to users. The `icon` string MUST be a data URI as defined in [RFC-2397]. The image SHOULD be a square with 96x96px minimum resolution. The image format is RECOMMENDED to be either lossless or vector based such as PNG, WebP or SVG to make the image easy to render on the wallet. Since SVG images can execute Javascript, wallets MUST render SVG images using the `` tag to ensure no untrusted Javascript execution can occur. + +There are cases where a call to just `pm_getPaymasterStubData` is sufficient to provide valid paymaster-related user operation fields, e.g. when the `paymasterData` does not contain a signature. In such cases, the second RPC call to `pm_getPaymasterData` (defined below) MAY be skipped, by returning `isFinal: true` by this RPC call. + +Paymaster web services SHOULD do validations on incoming user operations during `pm_getPaymasterStubData` execution and reject the request at this stage if it would not sponsor the operation. + +##### `pm_getPaymasterStubData` RPC Specification + +Note that the user operation parameter does not include a signature, as the user signs after all other fields are populated. + +```typescript +// [userOp, entryPoint, chainId, context] +type GetPaymasterStubDataParams = [ + // Below is specific to Entrypoint v0.6 but this API can be used with other entrypoint versions too + { + sender: `0x${string}`; + nonce: `0x${string}`; + initCode: `0x${string}`; + callData: `0x${string}`; + callGasLimit: `0x${string}`; + verificationGasLimit: `0x${string}`; + preVerificationGas: `0x${string}`; + maxFeePerGas: `0x${string}`; + maxPriorityFeePerGas: `0x${string}`; + }, // userOp + `0x${string}`, // EntryPoint + `0x${string}`, // Chain ID + Record // Context +]; + +type GetPaymasterStubDataResult = { + sponsor?: { name: string; icon?: string }; // Sponsor info + paymaster?: string; // Paymaster address (entrypoint v0.7) + paymasterData?: string; // Paymaster data (entrypoint v0.7) + paymasterVerificationGasLimit?: `0x${string}`; // Paymaster validation gas (entrypoint v0.7) + paymasterPostOpGasLimit?: `0x${string}`; // Paymaster post-op gas (entrypoint v0.7) + paymasterAndData?: string; // Paymaster and data (entrypoint v0.6) + isFinal?: boolean; // Indicates that the caller does not need to call pm_getPaymasterData +}; +``` + +###### `pm_getPaymasterStubData` Example Parameters + +```json +[ + { + "sender": "0x...", + "nonce": "0x...", + "initCode": "0x", + "callData": "0x...", + "callGasLimit": "0x...", + "verificationGasLimit": "0x...", + "preVerificationGas": "0x...", + "maxFeePerGas": "0x...", + "maxPriorityFeePerGas": "0x..." + }, + "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789", + "0x2105", + { + // Illustrative context field. These should be defined by service providers. + "policyId": "962b252c-a726-4a37-8d86-333ce0a07299" + } +] +``` + +###### `pm_getPaymasterStubData` Example Return Value + +Paymaster services MUST detect which entrypoint version the account is using and return the correct fields. + +For example, if using entrypoint v0.6: + +```json +{ + "sponsor": { + "name": "My App", + "icon": "https://..." + }, + "paymasterAndData": "0x..." +} +``` + +If using entrypoint v0.7: + +```json +{ + "sponsor": { + "name": "My App", + "icon": "https://..." + }, + "paymaster": "0x...", + "paymasterData": "0x..." +} +``` + +If using entrypoint v0.7, with paymaster gas estimates: + +```json +{ + "sponsor": { + "name": "My App", + "icon": "https://..." + }, + "paymaster": "0x...", + "paymasterData": "0x...", + "paymasterVerificationGasLimit": "0x...", + "paymasterPostOpGasLimit": "0x..." +} +``` + +Indicating that the caller does not need to make a `pm_getPaymasterData` RPC call: + +```json +{ + "sponsor": { + "name": "My App", + "icon": "https://..." + }, + "paymaster": "0x...", + "paymasterData": "0x...", + "isFinal": true +} +``` + +#### `pm_getPaymasterData` + +Returns values to be used in paymaster-related fields of a signed user operation. These are not stub values and will be used during user operation submission to a bundler. Similar to `pm_getPaymasterStubData`, accepts an unsigned user operation, entrypoint address, chain id, and a context object. + +##### `pm_getPaymasterData` RPC Specification + +Note that the user operation parameter does not include a signature, as the user signs after all other fields are populated. + +```typescript +// [userOp, entryPoint, chainId, context] +type GetPaymasterDataParams = [ + // Below is specific to Entrypoint v0.6 but this API can be used with other entrypoint versions too + { + sender: `0x${string}`; + nonce: `0x${string}`; + initCode: `0x${string}`; + callData: `0x${string}`; + callGasLimit: `0x${string}`; + verificationGasLimit: `0x${string}`; + preVerificationGas: `0x${string}`; + maxFeePerGas: `0x${string}`; + maxPriorityFeePerGas: `0x${string}`; + }, // userOp + `0x${string}`, // Entrypoint + `0x${string}`, // Chain ID + Record // Context +]; + +type GetPaymasterDataResult = { + paymaster?: string; // Paymaster address (entrypoint v0.7) + paymasterData?: string; // Paymaster data (entrypoint v0.7) + paymasterAndData?: string; // Paymaster and data (entrypoint v0.6) +}; +``` + +###### `pm_getPaymasterData` Example Parameters + +```json +[ + { + "sender": "0x...", + "nonce": "0x...", + "initCode": "0x", + "callData": "0x...", + "callGasLimit": "0x...", + "verificationGasLimit": "0x...", + "preVerificationGas": "0x...", + "maxFeePerGas": "0x...", + "maxPriorityFeePerGas": "0x..." + }, + "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789", + "0x2105", + { + // Illustrative context field. These should be defined by service providers. + "policyId": "962b252c-a726-4a37-8d86-333ce0a07299" + } +] +``` + +###### `pm_getPaymasterData` Example Return Value + +Paymaster services MUST detect which entrypoint version the account is using and return the correct fields. + +For example, if using entrypoint v0.6: + +```json +{ + "paymasterAndData": "0x..." +} +``` + +If using entrypoint v0.7: + +```json +{ + "paymaster": "0x...", + "paymasterData": "0x..." +} +``` + +### `paymasterService` Capability + +The `paymasterService` capability is implemented by both apps and wallets. + +#### App Implementation + +Apps need to give wallets paymaster service URLs they can make the above RPC calls to. They can do this using the `paymasterService` capability as part of an [EIP-5792](./eip-5792.md) `wallet_sendCalls` call. + +##### `wallet_sendCalls` Paymaster Capability Specification + +```typescript +type PaymasterCapabilityParams = Record< + `0x${string}`, // Chain ID + { + url: string; // Paymaster service URL for provided chain ID + context: Record; // Additional data defined by paymaster service providers + } +>; +``` + +###### `wallet_sendCalls` Example Parameters + +```json +[ + { + "version": "1.0", + "from": "0xd46e8dd67c5d32be8058bb8eb970870f07244567", + "calls": [ + { + "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567", + "value": "0x9184e72a", + "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675", + "chainId": "0x01" + }, + { + "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567", + "value": "0x182183", + "data": "0xfbadbaf01", + "chainId": "0x2105" + } + ], + "capabilities": { + "paymasterService": { + "0x01": { + "url": "https://...", + "context": { + "policyId": "962b252c-a726-4a37-8d86-333ce0a07299" + } + }, + "0x2105": { + "url": "https://...", + "context": { + "policyId": "0a268db9-3243-4178-b1bd-d9b67a47d37b" + } + } + } + } + } +] +``` + +The wallet will then make the above paymaster RPC calls to the URLs specified in the `paymasterService` capability field. + +#### Wallet Implementation + +To conform to this specification, smart wallets that wish to leverage app-sponsored transactions: + +1. MUST indicate to apps that they can communicate with paymaster web services via their response to an [EIP-5792](./eip-5792.md) `wallet_getCapabilities` call. +2. SHOULD make calls to and use the values returned by the paymaster service specified in the capabilities field of an [EIP-5792](./eip-5792.md) `wallet_sendCalls` call. An example of an exception is a wallet that allows users to select a paymaster provided by the wallet. Since there might be cases in which the provided paymaster is ultimately not used—either due to service failure or due to a user selecting a different, wallet-provided paymaster—applications MUST NOT assume that the paymaster it provides to a wallet is the entity that pays for transaction fees. + +##### `wallet_getCapabilities` Response Specification + +```typescript +type PaymasterServiceCapability = { + supported: boolean; +}; +``` + +###### `wallet_getCapabilities` Example Response + +```json +{ + "0x2105": { + "paymasterService": { + "supported": true + } + }, + "0x14A34": { + "paymasterService": { + "supported": true + } + } +} +``` + +Below is a diagram illustrating the full `wallet_sendCalls` flow, including how a wallet might implement the interaction. + +![flow](../assets/eip-7677/0.svg) + +## Rationale + +### Gas Estimation + +The current loose standard for paymaster services is to implement `pm_sponsorUserOperation`. This method returns values for paymaster-related user operation fields and updated gas values. The problem with this method is that paymaster service providers have different ways of estimating gas, which results in different estimated gas values. Sometimes these estimates can be insufficient. As a result we believe it’s better to leave gas estimation up to the wallet, as the wallet has more context on how user operations will get submitted (e.g. which bundler they will get submitted to). Then wallets can ask paymaster services to sponsor given the estimates defined by the wallet. + +The above reason is also why we specify that the `pm_getPaymasterStubData` method MAY also return paymaster-specific gas estimates. I.e., bundlers are prone to insufficiently estimating the paymaster-specific gas values, and the paymaster servies themselves are ultimately better equipped to provide them. + +### Chain ID Parameter + +Currently, paymaster service providers typically provide developers with a URL per chain. That is, paymaster service URLs are not typically multichain. So why do we need a chain ID parameter? We recognize that we must specify some constraint so that wallets can communicate with paymaster services about which chain their requests are for. As we see it, there are two options: + +1. Formalize the current loose standard and require that paymaster service URLs are 1:1 with chains. +2. Require a chain ID parameter as part of paymaster service requests. + +We feel that option (2) is the better abstraction here. This allows service providers to offer multichain URLs if they wish at essentially no downside to providers who offer a URL per chain. Providers who offer a URL per chain would just need to accept an additional parameter that they can ignore. When an app developer who uses a URL-per-chain provider wants to submit a request to a different chain, they can just swap out the URL accordingly. + +### Challenges With Stub Data + +Enabling a workflow with greater flexibility in gas estimations will nonetheless come with some known challenges that paymaster services must be aware of in order to ensure reliable gas estimates are generated during the process. + +#### `preVerificationGas` + +The `preVerificationGas` value is largely influenced by the size of the user operation and it's ratio of zero to non-zero bytes. This can cause a scenario where `pm_getPaymasterStubData` returns values that results in upstream gas estimations to derive a lower `preVerificationGas` compared to what `pm_getPaymasterData` would require. If this occurs then bundlers will return an insufficient `preVerificationGas` error during `eth_sendUserOperation`. + +To avoid this scenario, a paymaster service MUST return stub data that: + +1. Is of the same length as the final data. +2. Has an amount of zero bytes (`0x00`) that is less than or equal to the final data. + +#### Consistent Code Paths + +In the naive case, a stub value of repeating non-zero bytes (e.g. `0x01`) that is of the same length as the final value will generate a usable `preVerificationGas`. Although this would immediately result in a gas estimation error given that the simulation will likely revert due to an invalid paymaster data. + +In a more realistic case, a valid stub can result in a successful simulation but still return insufficient gas limits. This can occur if the stub data causes `validatePaymasterUserOp` or `postOp` functions to simulate a different code path compared to the final value. For example, if the simulated code was to return early, the estimated gas limits would be less than expected which would cause upstream `out of gas` errors once a user operation is submitted to the bundler. + +Therefore, a paymaster service MUST also return a stub that can result in a simulation executing the same code path compared to what is expected of the final user operation. + +## Security Considerations + +The URLs paymaster service providers give to app developers commonly have API keys in them. App developers might not want to pass these API keys along to wallets. To remedy this, we recommend that app developers provide a URL to their app's backend, which can then proxy calls to paymaster services. Below is a modified diagram of what this flow might look like. + +![flowWithAPI](../assets/eip-7677/1.svg) + +This flow would allow developers to keep their paymaster service API keys secret. Developers might also want to do additional simulation / validation in their backends to ensure they are sponsoring a transaction they want to sponsor. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/ERCS/erc-7679.md b/ERCS/erc-7679.md new file mode 100644 index 0000000000..b3d12b8684 --- /dev/null +++ b/ERCS/erc-7679.md @@ -0,0 +1,223 @@ +--- +eip: 7679 +title: UserOperation Builder +description: Construct UserOperations without being coupled with account-specific logic. +author: Derek Chiang (@derekchiang), Garvit Khatri (@plusminushalf), Fil Makarov (@filmakarov), Kristof Gazso (@kristofgazso), Derek Rein (@arein), Tomas Rocchi (@tomiir), bumblefudge (@bumblefudge) +discussions-to: https://ethereum-magicians.org/t/erc-7679-smart-account-interfaces/19547 +status: Draft +type: Standards Track +category: ERC +created: 2024-04-05 +requires: 4337 +--- + +## Abstract + +Different [ERC-4337](./eip-4337.md) smart account implementations encode their signature, nonce, and calldata differently. This makes it difficult for DApps, wallets, and smart account toolings to integrate with smart accounts without integrating with account-specific SDKs, which introduces vendor lock-in and hurts smart account adoption. + +We propose a standard way for smart account implementations to put their account-specific encoding logic on-chain. It can be achieved by implementing methods that accept the raw signature, nonce, or calldata (along with the context) as an input, and output them properly formatted, so the smart account can consume them while validating and executing the User Operation. + + +## Motivation + +At the moment, to build a [ERC-4337](./eip-4337.md) UserOperation (UserOp for short) for a smart account requires detailed knowledge of how the smart account implementation works, since each implementation is free to encode its nonce, calldata, and signature differently. + +As a simple example, one account might use an execution function called `executeFoo`, whereas another account might use an execution function called `executeBar`. This will result in the `calldata` being different between the two accounts, even if they are executing the same call. + +Therefore, someone who wants to send a UserOp for a given smart account needs to: + +* Figure out which smart account implementation the account is using. +* Correctly encode signature/nonce/calldata given the smart account implementation, or use an account-specific SDK that knows how to do that. + +In practice, this means that most DApps, wallets, and AA toolings today are tied to a specific smart account implementation, resulting in fragmentation and vendor lock-in. + +## Specification + +The key words “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. + +### UserOp builder + +To conform to this standard, a smart account implementation MUST provide a “UserOp builder” contract that implements the `IUserOperationBuilder` interface, as defined below: + + +```solidity +struct Execution { + address target; + uint256 value; + bytes callData; +} + +interface IUserOperationBuilder { + /** + * @dev Returns the ERC-4337 EntryPoint that the account implementation + * supports. + */ + function entryPoint() external view returns (address); + + /** + * @dev Returns the nonce to use for the UserOp, given the context. + * @param smartAccount is the address of the UserOp sender. + * @param context is the data required for the UserOp builder to + * properly compute the requested field for the UserOp. + */ + function getNonce( + address smartAccount, + bytes calldata context + ) external view returns (uint256); + + /** + * @dev Returns the calldata for the UserOp, given the context and + * the executions. + * @param smartAccount is the address of the UserOp sender. + * @param executions are (destination, value, callData) tuples that + * the UserOp wants to execute. It's an array so the UserOp can + * batch executions. + * @param context is the data required for the UserOp builder to + * properly compute the requested field for the UserOp. + */ + function getCallData( + address smartAccount, + Execution[] calldata executions, + bytes calldata context + ) external view returns (bytes memory); + + /** + * @dev Returns a correctly encoded signature, given a UserOp that + * has been correctly filled out except for the signature field. + * @param smartAccount is the address of the UserOp sender. + * @param userOperation is the UserOp. Every field of the UserOp should + * be valid except for the signature field. The "PackedUserOperation" + * struct is as defined in ERC-4337. + * @param context is the data required for the UserOp builder to + * properly compute the requested field for the UserOp. + */ + function formatSignature( + address smartAccount, + PackedUserOperation calldata userOperation, + bytes calldata context + ) external view returns (bytes memory signature); +} +``` + +### Using the UserOp builder + +To build a UserOp using the UserOp builder, the building party SHOULD proceed as follows: + +1. Obtain the address of `UserOpBuilder` and a `context` from the account owner. The `context` is an opaque bytes array from the perspective of the building party. The `UserOpBuilder` implementation may need the `context` in order to properly figure out the UserOp fields. See [Rationale](#rationale) for more info. +2. Execute a multicall (batched `eth_call`s) of `getNonce` and `getCallData` with the `context` and executions. The building party will now have obtained the nonce and calldata. +3. Fill out a UserOp with the data obtained previously. Gas values can be set randomly or very low. This userOp will be used to obtain a dummy signature for gas estimations. Sign the hash of userOp. (See [Rationale](#rationale) for what a dummy signature is. See [Security Considerations](#security-considerations) for the details on dummy signature security). +4. Call (via `eth_call`) `formatSignature` with the UserOp and `context` to obtain a UserOp with a properly formatted dummy signature. This userOp can now be used for gas estimation. +5. In the UserOp, change the existing gas values to those obtained from a proper gas estimation. This UserOp must be valid except for the `signature` field. Sign the hash of the UserOp and place the signature in the UserOp.signature field. +6. Call (via `eth_call`) `formatSignature` with the UserOp and `context` to obtain a completely valid UserOp. + 1. Note that a UserOp has a lot more fields than `nonce`, `callData`, and `signature`, but how the building party obtains the other fields is outside of the scope of this document, since only these three fields are heavily dependent on the smart account implementation. + +At this point, the building party has a completely valid UserOp that they can then submit to a bundler or do whatever it likes with it. + +### Using the UserOp builder when the account hasn’t been deployed + +To provide the accurate data to the building party, the `UserOpBuilder` will in most cases have to call the account. +If the account has yet to be deployed, which means that the building party is looking to send the very first UserOp for this account, then the building party MAY modify the flow above as follows: + +- In addition to the `UserOpBuilder` address and the `context`, the building party also obtains the `factory` and `factoryData` as defined in ERC-4337. +- When calling one of the view functions on the UserOp builder, the building party may use `eth_call` to deploy the `CounterfactualCall` contract, which is going to deploy the account and call `UserOpBuilder` (see below). +- When filling out the UserOp, the building party includes `factory` and `factoryData`. + +The `CounterfactualCall` contract SHOULD: +- Deploy the account using `factory` and `factoryData` provided by the building party. +- Revert if the deployment has not succeeded. +- If the account has been deployed succesfully, call `UserOpBuilder` and return the data returned by `UserOpBuilder` to the building party. + +See Reference Implementation section for more details on the `CounterfactualCall` contract. + +## Rationale + +### Context + +The `context` is an array of bytes that encodes whatever data the UserOp builder needs in order to correctly determine the nonce, calldata, and signature. Presumably, the `context` is constructed by the account owner, with the help of a wallet software. + +Here we outline one possible use of `context`: delegation. Say the account owner wants to delegate a transaction to be executed by the building party. The account owner could encode a signature of the public key of the building party inside the `context`. Let’s call this signature from the account owner the `authorization`. + +Then, when the building party fills out the UserOp, it would fill the `signature` field with a signature generated by its own private key. When it calls `getSignature` on the UserOp builder, the UserOp builder would extract the `authorization` from the `context` and concatenates it with the building party’s signature. The smart account would presumably be implemented such that it would recover the building party’s public key from the signature, and check that the public key was in fact signed off by the `authorization`. If the check succeeds, the smart account would execute the UserOp, thus allowing the building party to execute a UserOp on the user’s behalf. + +### Dummy signature + +The “dummy signature” refers to the signature used in a UserOp sent to a bundler for estimating gas (via `eth_estimateUserOperationGas`). A dummy signature is needed because, at the time the bundler estimates gas, a valid signature does not exist yet, since the valid signature itself depends on the gas values of the UserOp, creating a circular dependency. To break the circular dependency, a dummy signature is used. + +However, the dummy signature is not just a fixed value that any smart account can use. The dummy signature must be constructed such that it would cause the UserOp to use about as much gas as a real signature would. Therefore, the dummy signature varies based on the specific validation logic that the smart account uses to validate the UserOp, making it dependent on the smart account implementation. + +## Backwards Compatibility + +This ERC is intended to be backwards compatible with all ERC-4337 smart accounts as of EntryPoint 0.7. + +For smart accounts deployed against EntryPoint 0.6, the `IUserOperationBuilder` interface needs to be modified such that the `PackedUserOperation` struct is replaced with the corresponding struct in EntryPoint 0.6. + +## Reference Implementation + +### Counterfactual call contract + +The counterfactual call contract is inspired by [ERC-6492](./eip-6492.md), which devised a mechanism to execute `isValidSignature` (see [ERC-1271](./eip-1271.md)) against a pre-deployed (counterfactual) contract. + +```solidity +contract CounterfactualCall { + + error CounterfactualDeployFailed(bytes error); + + constructor( + address smartAccount, + address create2Factory, + bytes memory factoryData, + address userOpBuilder, + bytes memory userOpBuilderCalldata + ) { + if (address(smartAccount).code.length == 0) { + (bool success, bytes memory ret) = create2Factory.call(factoryData); + if (!success || address(smartAccount).code.length == 0) revert CounterfactualDeployFailed(ret); + } + + assembly { + let success := call(gas(), userOpBuilder, 0, add(userOpBuilderCalldata, 0x20), mload(userOpBuilderCalldata), 0, 0) + let ptr := mload(0x40) + returndatacopy(ptr, 0, returndatasize()) + if iszero(success) { + revert(ptr, returndatasize()) + } + return(ptr, returndatasize()) + } + } + +} +``` + +Here’s an example of calling this contract using the ethers and viem libraries: + +```javascript +// ethers +const nonce = await provider.call({ + data: ethers.utils.concat([ + counterfactualCallBytecode, + ( + new ethers.utils.AbiCoder()).encode(['address','address', 'bytes', 'address','bytes'], + [smartAccount, userOpBuilder, getNonceCallData, factory, factoryData] + ) + ]) +}) + +// viem +const nonce = await client.call({ + data: encodeDeployData({ + abi: parseAbi(['constructor(address, address, bytes, address, bytes)']), + args: [smartAccount, userOpBuilder, getNonceCalldata, factory, factoryData], + bytecode: counterfactualCallBytecode, + }) +}) +``` + +## Security Considerations + +### Dummy Signature security + +Since the properly formatted dummy signature is going to be publicly disclosed, in theory it can be intercepted and used by the man in the middle. Risks and potential harm of this is very low though as the dummy signature will be effectively unusable after the final UserOp is submitted (as both UserOps use the same nonce). However, to mitigate even this small issue, it is recommended that the UserOp which hash is going to be signed to obtain an un-foirmatted dummy signature (step 3 above) is filled with very low gas values. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/ERCS/erc-7682.md b/ERCS/erc-7682.md new file mode 100644 index 0000000000..226aad6b19 --- /dev/null +++ b/ERCS/erc-7682.md @@ -0,0 +1,95 @@ +--- +eip: 7682 +title: Auxiliary Funds Capability +description: A capability allowing wallets to indicate that they have access to additional funds. +author: Lukas Rosario (@lukasrosario), Wilson Cusack (@wilsoncusack) +discussions-to: https://ethereum-magicians.org/t/erc-7682-auxiliary-funds-capability/19599 +status: Draft +type: Standards Track +category: ERC +created: 2024-04-09 +requires: 5792 +--- + +## Abstract + +An [EIP-5792](./eip-5792.md) compliant capability that allows wallets to indicate to apps that they have access to funds beyond those that can be accounted for by looking up balances onchain given the wallet's address. + +A wallet's ability to access auxiliary funds is communicated to apps as part of its response to an [EIP-5792](./eip-5792.md) `wallet_getCapabilities` request. The following standard does not specify the source of these auxiliary funds, but some examples are: + +- Funds from offchain sources that can be onramped and used just-in-time +- Wallets that manage many accounts, where assets across those accounts can be transfered to the required account before submitting a transaction requested by an app + +## Motivation + +Many applications check users' balances before letting them complete some action. For example, if a user wants to swap some amount of tokens on a dex, the dex will commonly block the user from doing so if it sees that the user does not have that amount of tokens at their address. However, more advanced wallets have features that let users access funds from other sources. Wallets need a way to tell apps that they have access to additional funds so that users using these more advanced wallets are not blocked by balance checks. + +## Specification + +One new [EIP-5792](./eip-5792.md) wallet capability is defined. + +### Wallet Implementation + +To conform to this specification, wallets that wish to indicate that they have access to auxiliary funds MUST, for each chain they have access to auxiliary funds on, respond to `wallet_getCapabilities` calls with an `auxiliaryFunds` object with a `supported` field set to `true`. + +Wallets may also optionally specify which assets they have additional access to with an `assets` field, which maps to an array of addresses representing the assets the wallet might have additional access to. If a wallet does not respond with this optional array of assets, the application SHOULD assume the wallet has additional access to any asset. + +This specification does not put any constraints on the source of the auxiliary funds. + +In this specification, a chain's native asset (e.g. Ether on Ethereum) MUST be represented by "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" as specified by [EIP-7528](./eip-7528). + +#### `wallet_getCapabilities` Response Specification + +```typescript +type AuxiliaryFundsCapability = { + supported: boolean; + assets?: `0x${string}`[]; +} +``` + +##### `wallet_getCapabilities` Example Response + +```json +{ + "0x2105": { + "auxiliaryFunds": { + "supported": true, + "assets": [ + "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" + ] + } + }, + "0x14A34": { + "auxiliaryFunds": { + "supported": true, + "assets": [ + "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + "0x036CbD53842c5426634e7929541eC2318f3dCF7e" + ] + } + } +} +``` + +### App Implementation + +When an app sees that a connected wallet has access to auxiliary funds via the `auxiliaryFunds` capability in a `wallet_getCapabilities` response, the app SHOULD NOT block users from taking actions on the basis of asset balance checks. + +## Rationale + +### Alternatives + +#### Advanced Balance Fetching + +An alternative we considered is defining a way for apps to fetch available auxiliary balances. This could be done, for example, by providing a URL as part of the `auxiliaryFunds` capability that apps could use to fetch auxiliary balance information. However, we ultimately decided that a boolean was enough to indicate to apps that they should not block user actions on the basis of balance checks, and it is minimally burdensome for apps to implement. + +The shape of this capability allows for a more advanced extension if apps feel more functionality is needed. + +## Security Considerations + +Apps MUST NOT make any assumptions about the source of auxiliary funds. Apps' smart contracts should still, as they would today, make appropriate balance checks onchain when processing a transaction. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). 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/ERCS/erc-7700.md b/ERCS/erc-7700.md new file mode 100644 index 0000000000..c46616ce53 --- /dev/null +++ b/ERCS/erc-7700.md @@ -0,0 +1,507 @@ +--- +eip: 7700 +title: Cross-chain Storage Router Protocol +description: Provides a mechanism to replace L1 storage with L2 and databases through cross-chain routers +author: Avneet Singh (@sshmatrix), 0xc0de4c0ffee (@0xc0de4c0ffee), Nick Johnson (@arachnid), Makoto Inoue (@makoto) +discussions-to: https://ethereum-magicians.org/t/erc-7700-cross-chain-storage-router-protocol/19853 +status: Draft +type: Standards Track +category: ERC +created: 2024-04-30 +requires: 155 +--- + +## Abstract +The following standard provides a mechanism by which smart contracts can route storage to external providers. In particular, protocols can reduce the gas fees associated with storing data on mainnet by routing the handling of storage operations to another system or network. These storage routers act as an extension to the core L1 contract. Methods in this document specifically target security and cost-effectiveness of storage routing to three router types: L1, L2 and databases. The cross-chain data written with these methods can be retrieved by generic [EIP-3668](./eip-3668)-compliant contracts, thus completing the cross-chain data life cycle. This document, nicknamed CCIP-Store, alongside [EIP-3668](./eip-3668), is a meaningful step toward a secure infrastructure for cross-chain storage routers and data retrievals. + +## Motivation +[EIP-3668](./eip-3668), aka 'CCIP-Read', has been key to retrieving cross-chain data for a variety of contracts on Ethereum blockchain, ranging from price feeds for DeFi contracts, to more recently records for ENS users. The latter case dedicatedly uses cross-chain storage to bypass the usually high gas fees associated with on-chain storage; this aspect has a plethora of use cases well beyond ENS records and a potential for significant impact on universal affordability and accessibility of Ethereum. + +Cross-chain data retrieval through [EIP-3668](./eip-3668) is a relatively simpler task since it assumes that all relevant data originating from cross-chain storages is translated by CCIP-Read-compliant HTTP gateways; this includes L2 chains and databases. On the flip side however, so far each service leveraging CCIP-Read must handle writing this data securely to these storage types on their own, while also incorporating reasonable security measures in their CCIP-Read-compatible contracts for verifying this data on L1. While these security measures are in-built into L2 architectures, database storage providers on the other hand must incorporate some form of explicit security measures during storage operations so that cross-chain data's integrity can be verified by CCIP-Read contracts during data retrieval stage. Examples of this include: + +- Services that allow the management of namespaces, e.g. ENS domains, stored externally on an L2 solution or off-chain database as if they were native L1 tokens, and, +- Services that allow the management of digital identities stored on external storages as if they were stored in the native L1 smart contract. + +In this context, a specification which allows storage routing to external routers will facilitate creation of services that are agnostic to the underlying storage solution. This in turn enables new applications to operate without knowledge of the underlying routers. This 'CCIP-Store' proposal outlines precisely this part of the process, i.e. how the bespoke storage routing can be made by smart contracts to L2s and databases. + +![Fig.1 CCIP-Store and CCIP-Read Workflows](../assets/eip-7700/images/Schema.svg) + +## Specification +### Overview +The following specification revolves around the structure and description of a cross-chain storage router tasked with the responsibility of writing to an L2 or database storage. This document introduces `StorageRoutedToL2()` and `StorageRoutedToDatabase()` storage routers, along with the trivial `StorageRoutedToL1()` router, and proposes that new `StorageRoutedTo__()` reverts be allowed through new EIPs that sufficiently detail their interfaces and designs. Some foreseen examples of new storage routers include `StorageRoutedToSolana()` for Solana, `StorageRoutedToFilecoin()` for Filecoin, `StorageRoutedToIPFS()` for IPFS, `StorageRoutedToIPNS()` for IPNS, `StorageRoutedToArweave()` for Arweave, `StorageRoutedToArNS()` for ArNS, `StorageRoutedToSwarm()` for Swarm etc. + +### L1 Router: `StorageRoutedToL1()` +A minimal L1 router is trivial and only requires the L1 `contract` address to which routing must be made, while the clients must ensure that the calldata is invariant under routing to another contract. One example implementation of an L1 router is given below. + +```solidity +// Define revert event +error StorageRoutedToL1( + address contractL1 +); + +// Generic function in a contract +function setValue( + bytes32 node, + bytes32 key, + bytes32 value +) external { + // Get metadata from on-chain sources + ( + address contractL1, // Routed contract address on L1; may be globally constant + ) = getMetadata(node); // Arbitrary code + // contractL1 = 0x32f94e75cde5fa48b6469323742e6004d701409b + // Route storage call to L1 router + revert StorageRoutedToL1( + contractL1 + ); +}; +``` + +In this example, the routing must prompt the client to build the transaction with the exact same original calldata, and submit it to the L1 `contract` by calling the exact same function. + +```solidity +// Function in routed L1 contract +function setValue( + bytes32 node, + bytes32 key, + bytes32 value +) external { + // Some code storing data mapped by node & msg.sender + ... +} +``` + +![Fig.2 L1 Call Lifecycle](../assets/eip-7700/images/L1.svg) + +### L2 Router: `StorageRoutedToL2()` +A minimal L2 router only requires the list of `chainId` values and the corresponding L2 `contract` addresses, while the clients must ensure that the calldata is invariant under routing to L2. One example implementation of an L2 router in an L1 contract is shown below. + +```solidity +// Define revert event +error StorageRoutedToL2( + address contractL2, + uint256 chainId +); + +// Generic function in a contract +function setValue( + bytes32 node, + bytes32 key, + bytes32 value +) external { + // Get metadata from on-chain sources + ( + address contractL2, // Contract address on L2; may be globally constant + uint256 chainId // L2 ChainID; may be globally constant + ) = getMetadata(node); // Arbitrary code + // contractL2 = 0x32f94e75cde5fa48b6469323742e6004d701409b + // chainId = 21 + // Route storage call to L2 router + revert StorageRoutedToL2( + contractL2, + chainId + ); +}; +``` + +In this example, the routing must prompt the client to build the transaction with the exact same original calldata, and submit it to the L2 by calling the exact same function on L2 as L1. + +```solidity +// Function in L2 contract +function setValue( + bytes32 node, + bytes32 key, + bytes32 value +) external { + // Some code storing data mapped by node & msg.sender + ... +} +``` + +![Fig.3 L2 Call Lifecycle](../assets/eip-7700/images/L2.svg) + +### Database Router: `StorageRoutedToDatabase()` +A minimal database router is similar to an L2 in the sense that: + + a) Similar to `chainId`, it requires the `gatewayUrl` that is tasked with handling off-chain storage operations, and + + b) Similar to `eth_call`, it requires `eth_sign` output to secure the data, and the client must prompt the users for these signatures. + +This specification does not require any other data to be stored on L1 other than the bespoke `gatewayUrl`; the storage router therefore should only return the `gatewayUrl` in revert. + +```solidity +error StorageRoutedToDatabase( + string gatewayUrl +); + +// Generic function in a contract +function setValue( + bytes32 node, + bytes32 key, + bytes32 value +) external { + ( + string gatewayUrl // Gateway URL; may be globally constant + ) = getMetadata(node); + // gatewayUrl = "https://api.namesys.xyz" + // Route storage call to database router + revert StorageRoutedToDatabase( + gatewayUrl + ); +}; +``` + +![Fig.4 Database Call Lifecycle](../assets/eip-7700/images/Database.svg) + +Following the revert, the client must take these steps: + +1. Request the user for a secret signature `sigKeygen` to generate a deterministic `dataSigner` keypair, + +2. Sign the calldata with generated data signer's private key and produce verifiable data signature `dataSig`, + +3. Request the user for an `approval` approving the generated data signer, and finally, + +4. Post the calldata to gateway along with signatures `dataSig` and `approval`, and the `dataSigner`. + +These steps are described in detail below. + +#### 1. Generate Data Signer +The data signer must be generated deterministically from ethereum wallet signatures; see figure below. + +![Fig.5 Data Signer Keygen Workflow](../assets/eip-7700/images/Keygen.svg) + +The deterministic key generation can be implemented concisely in a single unified `keygen()` function as follows. + +```js +/* Pseudo-code for key generation */ +function keygen( + username, // CAIP identifier for the blockchain account + sigKeygen, // Deterministic signature from wallet + spice // Stretched password +) { + // Calculate input key by hashing signature bytes using SHA256 algorithm + let inputKey = sha256(sigKeygen); + // Calculate salt for keygen by hashing concatenated username, stretched password (aka spice) and hex-encoded signature using SHA256 algorithm + let salt = sha256(`${username}:${spice}:${sigKeygen}`); + // Calculate hash key output by feeding input key, salt & username to the HMAC-based key derivation function (HKDF) with dLen = 42 + let hashKey = hkdf(sha256, inputKey, salt, username, 42); + // Calculate and return secp256k1 keypair + return secp256k1(hashKey); // Calculate secp256k1 keypair from hash key +} +``` + +This `keygen()` function requires three variables: `username`, `spice` and `sigKeygen`. Their definitions are given below. + +##### 1. `username` +[CAIP-10](https://github.com/ChainAgnostic/CAIPs/blob/ad0cfebc45a4b8368628340bf22aefb2a5edcab7/CAIPs/caip-10.md) identifier `username` is auto-derived from the connected wallet's checksummed address `wallet` and `chainId` using [EIP-155](./eip-155). + +```js +/* CAIP-10 identifier */ +const caip10 = `eip155:${chainId}:${wallet}`; +``` + +##### 2. `spice` +`spice` is calculated from the optional private field `password`, which must be prompted from the user by the client; this field allows users to change data signers for a given `username`. +```js +/* Secret derived key identifier */ +// Clients must prompt the user for this +const password = 'key1'; +``` + +Password must then be stretched before use with `PBKDF2` algorithm such that: + +```js +/* Calculate spice by stretching password */ +let spice = pbkdf2( + password, + pepper, + iterations + ); // Stretch password with PBKDF2 +``` + +where `pepper = keccak256(abi.encodePacked(username))` and the `iterations` count is fixed to `500,000` for brute-force vulnerability protection. + +```js +/* Definitions of pepper and iterations in PBKDF2 */ +let pepper = keccak256(abi.encodePacked(username)); +let iterations = 500000; // 500,000 iterations +``` + +##### 3. `sigKeygen` +The data signer must be derived from the owner or manager keys of a node. Message payload for the required `sigKeygen` must then be formatted as: + +```text +Requesting Signature To Generate Keypair(s)\n\nOrigin: ${username}\nProtocol: ${protocol}\nExtradata: ${extradata} +``` + +where the `extradata` is calculated as follows, + +```solidity +// Calculating extradata in keygen signatures +bytes32 extradata = keccak256( + abi.encodePacked( + spice + wallet + ) +) +``` + +The remaining `protocol` field is a protocol-specific identifier limiting the scope to a specific protocol represented by a unique contract address. This identifier cannot be global and must be uniquely defined for each implementating L1 `contract` such that: + +```js +/* Protocol identifier in CAIP-10 format */ +const protocol = `eth:${chainId}:${contract}`; +``` + +With this deterministic format for signature message payload, the client must prompt the user for the ethereum signature. Once the user signs the messages, the `keygen()` function can derive the data signer keypair. + +#### 2. Sign Data +Since the derived signer is wallet-specific, it can + +- sign batch data for multiple keys for a given node, and +- sign batches of data for multiple nodes owned by a wallet + +simultaneously in the background without ever prompting the user. Signature(s) `dataSig` accompanying the off-chain calldata must implement the following format in their message payloads: + +```text +Requesting Signature To Update Off-Chain Data\n\nOrigin: ${username}\nData Type: ${dataType}\nData Value: ${dataValue} +``` + +where `dataType` parameters are protocol-specific and formatted as object keys delimited by `/`. For instance, if the off-chain data is nested in keys as `a > b > c > field > key`, then the equivalent `dataType` is `a/b/c/field/key`. For example, in order to update off-chain ENS record `text > avatar` and `address > 60`, `dataType` must be formatted as `text/avatar` and `address/60` respectively. + +#### 3. Approve Data Signer +The `dataSigner` is not stored on L1, and the clients must instead + +- request an `approval` signature for `dataSigner` signed by the owner or manager of a node, and +- post this `approval` and the `dataSigner` along with the signed calldata in encoded form. + +CCIP-Read-enabled contracts can then verify during resolution time that the `approval` attached with the signed calldata comes from the node's manager or owner, and that it approves the expected `dataSigner`. The `approval` signature must have the following message payload format: + +```text +Requesting Signature To Approve Data Signer\n\nOrigin: ${username}\nApproved Signer: ${dataSigner}\nApproved By: ${caip10} +``` + +where `dataSigner` must be checksummed. + +#### 4. Post CCIP-Read Compatible Payload +The final [EIP-3668](./eip-3668)-compatible `data` payload in the off-chain data file is identified by a fixed `callback.signedData.selector` equal to `0x2b45eb2b` and must follow the format + +```solidity +/* Compile CCIP-Read-compatible payload*/ +bytes encodedData = abi.encode(['bytes'], [dataValue]); // Encode data +bytes funcSelector = callback.signedData.selector; // Identify off-chain data with a fixed 'signedData' selector = '0x2b45eb2b' +bytes data = abi.encode( + ['bytes4', 'address', 'bytes32', 'bytes32', 'bytes'], + [funcSelector, dataSigner, dataSig, approval, encodedData] +); // Compile complete CCIP-Readable off-chain data +``` + +The client must construct this `data` and pass it to the gateway in the `POST` request along with the raw values for indexing. The CCIP-Read-enabled contracts after decoding the four parameters from this `data` must + +- verify that the `dataSigner` is approved by the owner or manager of the node through `approval`, and +- verify that the `dataSig` is produced by `dataSigner` + +before resolving the `encodedData` value in decoded form. + +##### `POST` Request +The `POST` request made by the client to the `gatewayUrl` must follow the format as described below. + +```ts +/* POST request format*/ +type Post = { + node: string + preimage: string + chainId: number + approval: string + payload: { + field1: { + value: string + signature: string + timestamp: number + data: string + } + field2: [ + { + index: number + value: string + signature: string + timestamp: number + data: string + } + ] + field3: [ + { + key: number + value: string + signature: string + timestamp: number + data: string + } + ] + } +} +``` + +Example of a complete `Post` typed object for updating multiple ENS records for a node is shown below. + +```ts +/* Example of a POST request */ +let post: Post = { + node: "0xe8e5c24bb5f0db1f3cab7d3a7af2ecc14a7a4e3658dfb61c9b65a099b5f086fb", + preimage: "dev.namesys.eth", + chainId: 1, + approval: "0xa94da8233afb27d087f6fbc667cc247ef2ed31b5a1ff877ac823b5a2e69caa49069f0daa45a464d8db2f8e4e435250cb446d8f279d45a2b865ebf2fff291f69f1c", + payload: { + contenthash: { + value: "ipfs://QmYSFDzEcmk25JPFrHBHSMMLcTKLm6SvuZvKpijTHBnAYX", + signature: "0x24730d1d85d556245b7766aef413188e22f219c8de263ccbfafee4413f0937c32e4f44068d84c7424f923b878dcf22184f8df86506de1cea3dad932c5bd5e9de1c", + timestamp: 1708322868, + data: "0x2b45eb2b000000000000000000000000fe889053f7a0d2571f1898d2835c3cbdf50d766b000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000004124730d1d85d556245b7766aef413188e22f219c8de263ccbfafee4413f0937c32e4f44068d84c7424f923b878dcf22184f8df86506de1cea3dad932c5bd5e9de1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041a94da8233afb27d087f6fbc667cc247ef2ed31b5a1ff877ac823b5a2e69caa49069f0daa45a464d8db2f8e4e435250cb446d8f279d45a2b865ebf2fff291f69f1c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000026e301017012209603ccbcef5c2acd57bdec6a63e8a0292f3ce6bb583b6826060bcdc3ea84ad900000000000000000000000000000000000000000000000000000" + }, + address: [ + { + coinType: 0, + value: "1FfmbHfnpaZjKFvyi1okTjJJusN455paPH", + signature: "0x60ecd4979ae2c39399ffc7ad361066d46fc3d20f2b2902c52e01549a1f6912643c21d23d1ad817507413dc8b73b59548840cada57481eb55332c4327a5086a501b", + timestamp: 1708322877, + data: "0x2b45eb2b000000000000000000000000fe889053f7a0d2571f1898d2835c3cbdf50d766b000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000004160ecd4979ae2c39399ffc7ad361066d46fc3d20f2b2902c52e01549a1f6912643c21d23d1ad817507413dc8b73b59548840cada57481eb55332c4327a5086a501b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041a94da8233afb27d087f6fbc667cc247ef2ed31b5a1ff877ac823b5a2e69caa49069f0daa45a464d8db2f8e4e435250cb446d8f279d45a2b865ebf2fff291f69f1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a0e6ca5444e4d8b7c80f70237f332320387f18c7" + }, + { + coinType: 60, + value: "0x47C10B0491A138Ddae6cCfa26F17ADCfCA299753", + signature: "0xaad74ddef8c031131b6b83b3bf46749701ed11aeb585b63b72246c8dab4fff4f79ef23aea5f62b227092719f72f7cfe04f3c97bfad0229c19413f5cb491e966c1b", + timestamp: 1708322917, + data: "0x2b45eb2b000000000000000000000000fe889053f7a0d2571f1898d2835c3cbdf50d766b0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000041aad74ddef8c031131b6b83b3bf46749701ed11aeb585b63b72246c8dab4fff4f79ef23aea5f62b227092719f72f7cfe04f3c97bfad0229c19413f5cb491e966c1b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041a94da8233afb27d087f6fbc667cc247ef2ed31b5a1ff877ac823b5a2e69caa49069f0daa45a464d8db2f8e4e435250cb446d8f279d45a2b865ebf2fff291f69f1c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000047c10b0491a138ddae6ccfa26f17adcfca299753" + } + ], + text: [ + { + key: "avatar", + value: "https://namesys.xyz/logo.png", + signature: "0xbc3c7f1b511de151bffe8df033859295d83d400413996789e706e222055a2353404ce17027760c927af99e0bf621bfb24d3bfc52abb36bcfbe6e20cf43db7c561b", + timestamp: 1708329377, + data: "0x2b45eb2b000000000000000000000000fe889053f7a0d2571f1898d2835c3cbdf50d766b0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000041bc3c7f1b511de151bffe8df033859295d83d400413996789e706e222055a2353404ce17027760c927af99e0bf621bfb24d3bfc52abb36bcfbe6e20cf43db7c561b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041a94da8233afb27d087f6fbc667cc247ef2ed31b5a1ff877ac823b5a2e69caa49069f0daa45a464d8db2f8e4e435250cb446d8f279d45a2b865ebf2fff291f69f1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001c68747470733a2f2f6e616d657379732e78797a2f6c6f676f2e706e6700000000" + }, + { + key: "com.github", + value: "namesys-eth", + signature: "0xc9c33ff219e90510f79b6c9bb489917ee6e00ab123c55abe1117e71ea0d171356cf316420c71cfcf4bd63a791aaf37388ef1832e582f54a8c2df173917240fff1b", + timestamp: 1708322898, + data: "0x2b45eb2b000000000000000000000000fe889053f7a0d2571f1898d2835c3cbdf50d766b0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000041c9c33ff219e90510f79b6c9bb489917ee6e00ab123c55abe1117e71ea0d171356cf316420c71cfcf4bd63a791aaf37388ef1832e582f54a8c2df173917240fff1b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041a94da8233afb27d087f6fbc667cc247ef2ed31b5a1ff877ac823b5a2e69caa49069f0daa45a464d8db2f8e4e435250cb446d8f279d45a2b865ebf2fff291f69f1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b6e616d657379732d657468000000000000000000000000000000000000000000" + } + ] + } +} +``` + +### New Revert Events +1. Each new storage router must submit their `StorageRoutedTo__()` identifier through an ERC track proposal referencing the current document. + +2. Each `StorageRoutedTo__()` provider must be supported with detailed documentation of its structure and the necessary metadata that its implementers must return. + +3. Each `StorageRoutedTo__()` proposal must define the precise formatting of any message payloads that require signatures and complete descriptions of custom cryptographic techniques implemented for additional security, accessibility or privacy. + +### Implementation featuring ENS on L2 & Database +ENS off-chain resolvers capable of reading from and writing to databases are perhaps the most common use-case for CCIP-Read and CCIP-Write. One example of such a (minimal) resolver is given below along with the client-side code for handling the storage router revert. + +#### L1 Contract +```solidity +/* ENS resolver implementing StorageRoutedToDatabase() */ +interface iResolver { + // Defined in EIP-7700 + error StorageRoutedToL2( + uint chainId, + address contractL2 + ); + error StorageRoutedToDatabase( + string gatewayUrl + ); + // Defined in EIP-137 + function setAddr(bytes32 node, address addr) external; +} + +// Defined in EIP-7700 +string public gatewayUrl = "https://post.namesys.xyz"; // RESTful API endpoint +uint256 public chainId = uint(21); // ChainID of L2 +address public contractL2 = "0x839B3B540A9572448FD1B2335e0EB09Ac1A02885"; // Contract on L2 + +/** +* Sets the ethereum address associated with an ENS node +* [!] May only be called by the owner or manager of that node in ENS registry +* @param node Namehash of ENS domain to update +* @param addr Ethereum address to set +*/ +function setAddr( + bytes32 node, + address addr +) authorised(node) { + // Route to database storage + revert StorageRoutedToDatabase( + gatewayUrl + ); +} + +/** +* Sets the avatar text record associated with an ENS node +* [!] May only be called by the owner or manager of that node in ENS registry +* @param node Namehash of ENS domain to update +* @param key Key for ENS text record +* @param value URL to avatar +*/ +function setText( + bytes32 node, + string key, + string value +) external { + // Verify owner or manager permissions + require(authorised(node), "NOT_ALLOWED"); + // Route to L2 storage + revert StorageRoutedToL2( + chainId, + contractL2 + ); +} +``` + +#### L2 Contract +```solidity +// Function in L2 contract +function setText( + bytes32 node, + bytes32 key, + bytes32 value +) external { + // Store record mapped by node & sender + records[keccak256(abi.encodePacked(node, msg.sender))]["text"][key] = value; +} +``` + +#### Client-side Code +```ts +/* Client-side pseudo-code in ENS App */ +// Deterministically generate signer keypair +let signer = keygen(username, sigKeygen, spice); +// Construct POST body by signing calldata with derived private key +let post: Post = signData(node, addr, signer.priv); +// POST to gateway +await fetch(gatewayUrl, { + method: "POST", + body: JSON.stringify(post) +}); +``` + +## Rationale +Technically, the cases of L2s and databases are similar; routing to an L2 involves routing the `eth_call` to another EVM, while routing to a database can be made by extracting `eth_sign` from `eth_call` and posting the resulting signature explicitly along with the data for later verification. Methods in this document perform these precise tasks when routing storage operations to external routers. In addition, methods such as signing data with a derived signer (for databases) allow for significant UX improvement by fixing the number of signature prompts in wallets to 2, irrespective of the number of data instances to sign per node or the total number of nodes to update. This improvement comes at no additional cost to the user and allows services to perform batch updates. + +## Backwards Compatibility +None + +## Security Considerations +1. Clients must purge the derived signer private keys from local storage immediately after signing the off-chain data. + +2. Signature message payload and the resulting deterministic signature `sigKeygen` must be treated as a secret by the clients and immediately purged from local storage after usage in the `keygen()` function. + +3. Clients must immediately purge the `password` and `spice` from local storage after usage in the `keygen()` function. + +## Copyright +Copyright and related rights waived via [CC0](../LICENSE.md). \ No newline at end of file diff --git a/ERCS/erc-7720.md b/ERCS/erc-7720.md new file mode 100644 index 0000000000..83b830b697 --- /dev/null +++ b/ERCS/erc-7720.md @@ -0,0 +1,252 @@ +--- +eip: 7720 +title: Deferred Token Transfer +description: Allows users to schedule ERC-20 token transfers for withdrawal at a specified future time, enabling deferred payments. +author: Chen Liaoyuan (@chenly) +discussions-to: https://ethereum-magicians.org/t/erc-7720-deferred-token-transfer/20245 +status: Draft +type: Standards Track +category: ERC +created: 2024-06-09 +requires: 20 +--- + +## Abstract + +This standard specifies that allows users to deposit [ERC-20](./eip-20.md) tokens for a beneficiary. The beneficiary can withdraw the tokens only after a specified future timestamp. Each deposit transaction is assigned a unique ID and includes details such as the token address, sender, recipient, amount, unlock time, and withdrawal status. + +## Motivation + +In various scenarios, such as vesting schedules, escrow services, or timed rewards, there is a need for deferred payments. This contract provides a secure and reliable mechanism for time-locked token transfers, ensuring that tokens can only be transferred after a specified timestamp is reached. By facilitating structured and delayed payments, it adds an extra layer of security and predictability to token transfers. This is particularly useful for scenarios where payments are contingent upon the passage of time. + +## Specification + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. + +Implementers of this standard **MUST** have all of the following functions: + +```solidity +pragma solidity ^0.8.0; + +interface ITokenTransfer { + // Event emitted when a transfer is initiated. + event Transfer( + uint256 txnId, + address indexed token, + address indexed from, + address indexed to, + uint256 amount, + uint40 unlockTime, + bytes32 referenceNo + ); + + // Event emitted when tokens are withdrawn. + event Withdraw( + uint256 txnId, + address indexed token, + address indexed from, + address indexed to, + uint256 amount + ); + + // Function to initiate a token transfer. + // Parameters: + // - _token: Address of the ERC20 token contract. + // - _from: Address of the sender. + // - _to: Address of the recipient. + // - _amount: Amount of tokens to be transferred. + // - _unlockTime: Time after which the tokens can be withdrawn. + // - _reference: Reference ID for the transaction. + // Returns the transaction ID. + function transferFrom( + address _token, + address _from, + address _to, + uint256 _amount, + uint40 _unlockTime, + bytes32 _reference + ) external returns (uint256 txnId); + + // Function to withdraw tokens from a transaction. + // Parameters: + // - _txnId: ID of the transaction to withdraw from. + function withdraw(uint256 _txnId) external; + + // Function to get transaction details. + // Parameters: + // - _txnId: ID of the transaction. + // Returns the transaction details. + function getTransaction(uint256 _txnId) + external + view + returns ( + address token, + address from, + address to, + uint256 amount, + uint40 unlockTime, + bytes32 referenceNo, + bool withdrawn + ); +} + +``` + +## Rationale + +The design of the Deferred Token Transfer contract aims to provide a straightforward and secure method for handling time-locked token transfers. The following considerations were made during its development: + +**Unlock Time Precision with `uint40`**: We chose a full `uint40` for `_unlockTime` because it provides a sufficiently large range to cover all practical time-lock scenarios. This ensures that the contract can handle deferred payments that require precise timing over long periods, such as vesting schedules or long-term escrows. + +**Returning `txnId` from `transferFrom`**: The `transferFrom` function returns a unique `txnId` for each transaction. This design choice was made to facilitate easy and independent tracking of each transaction. By having a unique ID, users can manage and reference specific transactions, ensuring clarity and preventing confusion. This approach allows each transaction's state to be managed independently, simplifying the withdrawal process. + +**Compatibility with Existing ERC-20 Tokens**: The standard is designed as a separate interface rather than an extension of ERC-20 to ensure flexibility and broad compatibility. By not modifying the ERC-20 standard directly, this proposal can be used with any existing ERC-20 token without requiring changes to their contracts. This flexibility makes the standard applicable to a wide range of tokens already in circulation, enhancing its utility and adoption potential. + +## Reference Implementation + +```solidity +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +contract TokenTransfer { + using SafeERC20 for IERC20; + + struct Transaction { + address token; // Address of the ERC20 token contract. + address from; // Address of the sender. + address to; // Address of the recipient. + uint256 amount; // Amount of tokens to be transferred. + uint40 unlockTime; // Time after which the tokens can be withdrawn. + bytes32 referenceNo; // Reference ID for the transaction. + bool withdrawn; // Flag indicating if the tokens have been withdrawn. + } + + // Mapping from transaction ID to Transaction structure. + mapping(uint256 => Transaction) public transactions; + + // Variable to keep track of the next transaction ID. + uint256 public lastTxnId = 0; + + // Event emitted when a transfer is initiated. + event Transfer( + uint256 txnId, + address indexed token, + address indexed from, + address indexed to, + uint256 amount, + uint40 unlockTime, + bytes32 referenceNo + ); + + // Event emitted when tokens are withdrawn. + event Withdraw( + uint256 txnId, + address indexed token, + address indexed from, + address indexed to, + uint256 amount + ); + + constructor() {} + + // Function to initiate a token transfer. + // Parameters: + // - _token: Address of the ERC20 token contract. + // - _from: Address of the sender. + // - _to: Address of the recipient. + // - _amount: Amount of tokens to be transferred. + // - _unlockTime: Time after which the tokens can be withdrawn. + // - _reference: Reference ID for the transaction. + // Returns the transaction ID. + function transferFrom( + address _token, + address _from, + address _to, + uint256 _amount, + uint40 _unlockTime, + bytes32 _reference + ) external returns (uint256 txnId) { + require(_amount > 0, "Invalid transfer amount"); + + // Transfer tokens from sender to this contract. + IERC20(_token).safeTransferFrom(_from, address(this), _amount); + + lastTxnId++; + + // Store the transaction details. + transactions[lastTxnId] = Transaction({ + token: _token, + from: _from, + to: _to, + amount: _amount, + unlockTime: _unlockTime, + referenceNo: _reference, + withdrawn: false + }); + + // Emit an event for the transaction creation. + emit Transfer(lastTxnId, _token, _from, _to, _amount, _unlockTime, _reference); + return lastTxnId; + } + + // Function to withdraw tokens from a transaction. + // Parameters: + // - _txnId: ID of the transaction to withdraw from. + function withdraw(uint256 _txnId) external { + Transaction storage transaction = transactions[_txnId]; + require(transaction.amount > 0, "Invalid transaction ID"); + require(block.timestamp >= transaction.unlockTime, "Current time is before unlock time"); + // require(transaction.to == msg.sender, "Only the recipient can withdraw the tokens"); + require(!transaction.withdrawn, "Tokens already withdrawn"); + + IERC20(transaction.token).safeTransfer(transaction.to, transaction.amount); + + transaction.withdrawn = true; + + // Emit an event for the token withdrawal. + emit Withdraw(_txnId, transaction.token, transaction.from, transaction.to, transaction.amount); + } + + // Function to get transaction details. + // Parameters: + // - _txnId: ID of the transaction. + // Returns the transaction details. + function getTransaction(uint256 _txnId) + external + view + returns ( + address token, + address from, + address to, + uint256 amount, + uint40 unlockTime, + bytes32 referenceNo, + bool withdrawn + ) + { + Transaction storage transaction = transactions[_txnId]; + require(transaction.amount > 0, "Invalid transaction ID"); + + return ( + transaction.token, + transaction.from, + transaction.to, + transaction.amount, + transaction.unlockTime, + transaction.referenceNo, + transaction.withdrawn + ); + } +} +``` + +## Security Considerations + +**Ownerless Contract Design**: To prevent the risk of token loss after deposit, the contract should not have an owner. This ensures that the contract's token balance cannot be transferred to any address other than the designated beneficiary. + +**Strict Beneficiary Control**: During withdrawal, the contract must strictly ensure that tokens are transferred only to the beneficiary specified at the time of deposit. This prevents unauthorized access and ensures that only the intended recipient can withdraw the tokens. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). \ No newline at end of file diff --git a/ERCS/erc-7721.md b/ERCS/erc-7721.md new file mode 100644 index 0000000000..29c2b3b649 --- /dev/null +++ b/ERCS/erc-7721.md @@ -0,0 +1,158 @@ +--- +eip: 7721 +title: Lockable Extension for ERC-1155 +description: Interface for enabling locking of ERC-1155 using locker and token id based approvals +author: Piyush Chittara (@piyush-chittara) +discussions-to: https://ethereum-magicians.org/t/erc-7721-lockable-extension-for-erc1155/20250 +status: Draft +type: Standards Track +category: ERC +created: 2023-05-25 +requires: 165, 1155 +--- + +## Abstract + +The Lockable Extension for [ERC-1155](./eip-1155.md) introduces a robust locking mechanism for specific Non-Fungible Tokens (NFTs) within the ERC-1155 token standard, allowing for various uses while preventing sale or transfer. The token's `owner` can `lock` it, setting up locker address (either an EOA or a contract) that exclusively holds the power to unlock the token. Owner can also provide approval for `tokenId`, enabling ability to lock asset while address holds the token approval. Token can also be locked by `approved`, assigning locker to itself. Upon token transfer, these rights get purged. + +Inspired by the need for enhanced security and control over tokenized assets, this extension enables token owners to lock individual NFTs with `tokenId`, ensuring that only approved users can withdraw predetermined amounts of locked tokens. Thus, offering a safer approach by allowing token owners to specify approved token IDs and amounts for withdrawal. + +## Motivation + +[ERC-1155](./eip-1155.md) has sparked an unprecedented surge in demand for NFTs. However, despite this tremendous success, the NFT economy suffers from secondary liquidity where it remains illiquid in owner’s wallet. There are projects which aim to address the liquidity challenge, but they entail the below mentioned inconveniences and risks for owners as they necessitate transferring the participating NFTs to the projects' contracts. + +- Loss of utility: The utility value of NFTs diminishes when they are transferred to an escrow account, no longer remaining under the direct custody of the owners. +- Lack of composability: The market could benefit from increased liquidity if NFT owners had access to multiple financial tools, such as leveraging loans and renting out their assets for maximum returns. Composability serves as the missing piece in creating a more efficient market. +- Smart contract vulnerabilities: NFTs are susceptible to loss or theft due to potential bugs or vulnerabilities present in the smart contracts they rely on. + +The aforementioned issues contribute to a poor user experience (UX), and we propose enhancing the [ERC-1155](./eip-1155.md) standard by implementing a native locking mechanism: +Rather than being transferred to a smart contract, an NFT remains securely stored in self-custody but is locked. +During the lock period, the NFT's transfer is restricted while its other properties remain unchanged. +NFT Owner retains the ability to use or distribute it’s utility. + +NFTs have numerous use cases where the NFT must remain within the owner's wallet, even when it serves as collateral for a loan. Whether it's authorizing access to a Discord server, or utilizing NFT within a play-to-earn (P2E) game, owner should have the freedom to do so throughout the lending period. Just as real estate owner can continue living in their mortgaged house, take personal loan or keep tenants to generate passive income, these functionalities should be available to NFT owners to bring more investors in NFT economy. + + +Lockable NFTs enable the following use cases : + +- NFT-collateralized loans: Utilize NFT as collateral for a loan without locking it on the lending protocol contract. Instead, lock it within owner’s wallet while still enjoying all the utility of NFT. +- No collateral rentals of NFTs: Borrow an NFT for a fee without the need for significant collateral. Renter can use the NFT but not transfer it, ensuring the lender's safety. The borrowing service contract automatically returns the NFT to the lender once the borrowing period expires. +- Buy Now Pay Later (BNPL): The buyer receives the locked NFT and can immediately begin using it. However, they are unable to sell the NFT until all installments are paid. Failure to complete the full payment results in the NFT returning to the seller, along with a fee. +- Composability: Maximize liquidity by having access to multiple financial tools. Imagine taking a loan against NFT and putting it on rentals to generate passive income. +- Primary sales: Mint an NFT for a partial payment and settle the remaining amount once owner is satisfied with the collection's progress. +- Soulbound: Organization can mint and self-assign `locker`, send token to user and lock the asset. +- Safety: Safely and conveniently use exclusive blue chip NFTs. Lockable extension allows owner to lock NFT and designate secure cold wallet as the unlocker. This way, owner can keep NFT on MetaMask and easily use it, even if a hacker gains access to MetaMask account. Without access to the cold wallet, the hacker cannot transfer NFT, ensuring its safety. + +This proposal is different from other locking proposals in number of ways: + +- This implementation provides a minimal implementation of `lock` and `unlock` and believes other conditions like time-bound are great ideas but can be achieved without creating a specific implementation. Locking and Unlocking can be based on any conditions (e.g. repayment, expiry). Therefore time-bound unlocks a relatively specific use case that can be achieved via smart-contracts themselves without that being a part of the token contract. +- This implementation proposes a separation of rights between locker and approver. Token can be locked with approval and approved can unlock and withdraw tokens (opening up opportunities like renting, lending, BNPL etc), and token can be locked lacking the rights to revoke token, yet can unlock if required (opening up opportunities like account-bound NFTs). +- Our proposal implement ability to `transferAndLock` which can be used to transfer, lock and optionally approve token. Enabling the possibility of revocation after transfer. + +By extending the [ERC-1155](./eip-1155.md) standard, the proposed standard enables secure and convenient management of underlying NFT assets. It natively supports prevalent NFTFi use cases such as staking, lending, and renting. We anticipate that this proposed standard will foster increased engagement of NFT owners in NFTFi projects, thereby enhancing the overall vitality of the NFT ecosystem. + +## Specification + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. + +### Overview + +[ERC-1155](./eip-1155.md) compliant contracts MAY implement this EIP to provide standard methods of locking and unlocking the token at its current owner address. + +Token owner MAY `lock` the token and assign `locker` to some `address` using `lock(uint256 tokenId, address account, address _locker, uint256 amount)` function, this MUST set `locker` to `_locker`. Token owner or approved MAY `lock` the token using `lock(uint256 tokenId, address account, uint256 amount` function, this MUST set `locker` to `msg.sender`. Token MAY be `unlocked` by `locker` using `unlock(uint256 tokenId, address account, uint256 amount)` function. + +Token owner MAY `approve` specific for specific `tokenId` using `setApprovalForId(uint256 tokenId, address operator, uint256 amount)` ensuring only approved tokenId could be spent by operator. `getApprovalForId(uint256 tokenId, address account, address operator)` SHALL return `amount` approved on `account` by `operator`. + +If the token is `locked`, the `getLocked(uint256 tokenId, address account, address operator)` function MUST return an amount that is `locked` by `operator` on `account`. For tokens that are not `locked`, the `getLocked(uint256 tokenId, address account, address operator)` function MUST return `0`. + +`lock` function MUST revert if `account` has insufficient balance or not `owner` or `approved` of `tokenId`. `unlock` function MUST revert if provided `amount` of `tokenId` is not `locked`. ERC-1155 `safeTransferFrom` of a token MUST revert if `account` transfer `locked` amount, maximum transferable amount MUST be `balance - getLocked`. + +Token MAY be transferred and `locked`, also assign `approval` to `locker` using `transferAndLock` function. This is RECOMMENDED for use-cases where Token transfer and subsequent revocation is REQUIRED. + +### Interface + +``` +// SPDX-License-Identifier: CC0-1.0 + +pragma solidity >=0.7.0 <0.9.0; + +/// @title Lockable Extension for ERC1155 +/// @dev Interface for the Lockable extension +/// @author piyush-chittara + +interface IERCLockable1155 is IERC1155{ + + /** + * @dev Emitted when tokenId is locked + */ + event Lock(uint256 indexed tokenId, address account, address _locker, uint256 amount); + + /** + * @dev Emitted when tokenId is unlocked + */ + event Unlock (uint256 indexed tokenId, address account, address _locker, uint256 amount); + + /** + * @dev Lock the tokenId if msg.sender is owner or approved and set locker to msg.sender + */ + function lock(uint256 tokenId, address account, uint256 amount) external; + + /** + * @dev Lock the tokenId if msg.sender is owner and set locker to _locker + */ + function lock(uint256 tokenId, address account, address _locker, uint256 amount) external; + + /** + * @dev Unlocks the tokenId if msg.sender is locker + */ + function unlock(uint256 tokenId, address account, uint256 amount) external; + + /** + * @dev Tranfer and lock the token if the msg.sender is owner or approved. + * Lock the token and set locker to caller + * Optionally approve caller if bool setApprove flag is true + */ + function transferAndLock(address from, address to, uint256 tokenId, uint256 amount, bool setApprove) external; + + /** + * @dev Returns the wallet, that is stated as unlocking wallet for the tokenId. + * If (0) returned, that means token is not locked. Any other result means token is locked. + */ + function getLocked(uint256 tokenId, address account, address operator) external view returns (uint256); + + function setApprovalForId(uint256 tokenId, address operator, uint256 amount) external; +} +``` + +## Rationale + +This proposal exposes `transferAndLock(address from, address to, uint256 tokenId, uint256 amount, bool setApprove)` which can be used to transfer token and lock at the receiver's address. This additionally accepts input `bool setApprove` which on `true` assign `approval` to `locker`, hence enabling `locker` to revoke the token (revocation conditions can be defined in contracts and `approval` provided to contract). This provides conditional ownership to receiver, without the privilege to `transfer` token. + +## Backwards Compatibility + +This standard is compatible with [ERC-1155](./eip-1155.md) standards. + +Existing Upgradeable [ERC-1155](./eip-1155.md) can upgrade to this standard, enabling locking capability inherently and unlock underlying liquidity features. + +## Test Cases + +## Reference Implementation + +Reference Interface can be found [here](../assets/eip-7721/IERC7721.sol). + +Reference Implementation can be found [here](../assets/eip-7721/ERC7721.sol). + +## Security Considerations + +There are no security considerations related directly to the implementation of this standard for the contract that manages [ERC-1155](./eip-1155.md). + +### Considerations for the contracts that work with lockable tokens + +- Once a certain `amount` is `locked`, specified `amount` can not be transferred from locked `account`. +- If token is `locked` and caller is `locker` and `approved` both, caller can transfer the token. +- `locked` token with `locker` as in-accesible account or un-verified contract address can lead to permanent lock of the token. +- There are no MEV considerations regarding lockable tokens as only authorized parties are allowed to lock and unlock. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/ERCS/erc-7726.md b/ERCS/erc-7726.md new file mode 100644 index 0000000000..85c4fec7c5 --- /dev/null +++ b/ERCS/erc-7726.md @@ -0,0 +1,134 @@ +--- +eip: 7726 +title: Common Quote Oracle +description: Interface for data feeds providing the relative value of assets. +author: alcueca (@alcueca), ruvaag (@ruvaag), totomanov (@totomanov), r0ohafza (@r0ohafza) +discussions-to: https://ethereum-magicians.org/t/erc-7726-common-quote-oracle/20351 +status: Draft +type: Standards Track +category: ERC +created: 2024-06-20 +requires: 7528 +--- + +## Abstract + +The following allows for the implementation of a standard API for data feeds providing the relative value of +assets, forcing compliant contracts to use explicit token amounts instead of price factors. This approach has been +shown to lead to better security and time-to-market outcomes. + +## Motivation + +The information required to value assets is scattered over a number of major and minor sources, each one with their own +integration API and security considerations. Many protocols over the years have implemented oracle adapter layers for +their own use to abstract this complexity away from their core implementations, leading to much duplicated effort. + +This specification provides a standard API aimed to serve the majority of use cases. Preference is given to ease of +integration and serving the needs of product teams with less knowledge, requirements and resources. + +## Specification +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. +### Definitions + +- base asset: The asset that the user needs to know the value for (e.g: USDC as in "I need to know the value of 1e6 USDC + in ETH terms"). +- quote asset: The asset in which the user needs to value the `base` (e.g: ETH as in "I need to know the value of 1e6 + USDC in ETH terms"). +- value: An amount of `base` in `quote` terms (e.g. The `value` of 1000e6 USDC in ETH terms is 283,969,794,427,307,000 + ETH, and the `value` of 1000e18 ETH in USDC terms is 3,521,501,299,000 USDC). Note that this is an asset amount, and + not a decimal factor. + +### Methods + +#### `getQuote` + +Returns the value of `baseAmount` of `base` in `quote` terms. + +MUST round down towards 0. + +MUST revert with `OracleUnsupportedPair` if not capable to provide data for the specified `base` and `quote` pair. + +MUST revert with `OracleUntrustedData` if not capable to provide data within a degree of confidence publicly specified. + +MUST revert if the value of `baseAmount` of `base` in `quote` terms would overflow in a uint256. + +```yaml +- name: getQuote + type: function + stateMutability: view + + inputs: + - name: baseAmount + type: uint256 + - name: base + type: address + - name: quote + type: address + + outputs: + - name: quoteAmount + type: uint256 +``` + +### Special Addresses + +Some assets under the scope of this specification don't have an address, such as ETH, BTC and national currencies. + +For ETH, the address will be `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` as per [ERC-7528](./eip-7528.md). + +For BTC, the address will be `0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB`. + +For assets without an address, but with an ISO 4217 code, the code will be used (e.g. `address(840)` for USD). + +### Errors + +#### OracleUnsupportedPair + +```yaml +- name: OracleUnsupportedPair + type: error + + inputs: + - name: base + type: address + - name: quote + type: address +``` + +#### OracleUntrustedData + +```yaml +- name: OracleUntrustedData + type: error + + inputs: + - name: base + type: address + - name: quote + type: address +``` + +## Rationale + +The use of `getQuote` doesn't require the consumer to be aware of any decimal partitions that might have been defined +for the `base` or `quote` and should be preferred in most data processing cases. + +The spec doesn't include a `getPrice` function because it is rarely needed on-chain, and it would be a decimal number of +difficult representation. The popular option for representing prices can be implemented for [ERC-20](./eip-20.md) with decimals as +`oracle.getQuote(base, quote, 10\*\*base.decimals()) and will give the value of a whole unit of base in quote terms. + +## Backwards Compatibility + +Most existing data feeds related to the relative value of pairs of assets should be representable using this standard. + +## Security Considerations + +This specification purposefully provides no methods for data consumers to assess the validity of the data they receive. +It is expected of individual implementations using this specification to decide and publish the quality of the data that +they provide, including the conditions in which they will stop providing it. + +Consumers should review these guarantees and use them to decide whether to integrate or not with a data provider. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/ERCS/erc-7738.md b/ERCS/erc-7738.md new file mode 100644 index 0000000000..235122fcf9 --- /dev/null +++ b/ERCS/erc-7738.md @@ -0,0 +1,154 @@ +--- +eip: 7738 +title: Permissionless Script Registry +description: Permissionless registry to fetch executable scripts for contracts +author: Victor Zhang (@zhangzhongnan928), James Brown (@JamesSmartCell) +discussions-to: https://ethereum-magicians.org/t/erc-7738-permissionless-script-registry/20503 +status: Draft +type: Standards Track +category: ERC +created: 2024-07-01 +requires: 173 +--- +## Abstract + +This EIP provides a means to create a standard registry for locating executable scripts associated with the token. + +## Motivation + +[ERC-5169](./eip-5169.md) provides a client script lookup method for contracts. This requires the contract to have implemented the `ERC-5169` interface at the time of construction (or allow an upgrade path). + +This proposal outlines a contract that can supply prototype and certified scripts. The contract would be a multichain singleton instance that would be deployed at identical addresses on supported chains. + +### Overview + +The registry contract will supply a set of URI links for a given contract address. These URI links point to script programs that can be fetched by a wallet, viewer or mini-dapp. + +The pointers can be set permissionlessly using a setter in the registry contract. + +## 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. + +The contract MUST implement the `IERC7738` interface. +The contract MUST emit the `ScriptUpdate` event when the script is updated. +The contract SHOULD order the `scriptURI` returned so that the `ERC-173` `owner()` of the contract's script entries are returned first (in the case of simple implementations the wallet will pick the first `scriptURI` returned). +The contract SHOULD provide a means to page through entries if there are a large number of scriptURI entries. + +```solidity +interface IERC7738 { + /// @dev This event emits when the scriptURI is updated, + /// so wallets implementing this interface can update a cached script + event ScriptUpdate(address indexed contractAddress, string[] newScriptURI); + + /// @notice Get the scriptURI for the contract + /// @return The scriptURI + function scriptURI(address contractAddress) external view returns (string[] memory); + + /// @notice Update the scriptURI + /// emits event ScriptUpdate(address indexed contractAddress, scriptURI memory newScriptURI); + function setScriptURI(address contractAddress, string[] memory scriptURIList) external; +} +``` + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. + +## Rationale + +This method allows contracts written without the [ERC-5169](./eip-5169.md) interface to associate scripts with themselves, and avoids the need for a centralised online server, with subsequent need for security and the requires an organisation to become a gatekeeper for the database. + +## Test Cases + +Instructions for test harness and deployment can be found in the [Asset folder](../assets/eip-7738/tests.md). + +## Reference Implementation + +```solidity +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract DecentralisedRegistry is IERC7738 { + struct ScriptEntry { + mapping(address => string[]) scriptURIs; + address[] addrList; + } + + mapping(address => ScriptEntry) private _scriptURIs; + + function setScriptURI( + address contractAddress, + string[] memory scriptURIList + ) public { + require (scriptURIList.length > 0, "> 0 entries required in scriptURIList"); + bool isOwnerOrExistingEntry = Ownable(contractAddress).owner() == msg.sender + || _scriptURIs[contractAddress].scriptURIs[msg.sender].length > 0; + _scriptURIs[contractAddress].scriptURIs[msg.sender] = scriptURIList; + if (!isOwnerOrExistingEntry) { + _scriptURIs[contractAddress].addrList.push(msg.sender); + } + + emit ScriptUpdate(contractAddress, msg.sender, scriptURIList); + } + + // Return the list of scriptURI for this contract. + // Order the return list so `Owner()` assigned scripts are first in the list + function scriptURI( + address contractAddress + ) public view returns (string[] memory) { + //build scriptURI return list, owner first + address contractOwner = Ownable(contractAddress).owner(); + address[] memory addrList = _scriptURIs[contractAddress].addrList; + uint256 i; + + //now calculate list length + uint256 listLen = _scriptURIs[contractAddress].scriptURIs[contractOwner].length; + for (i = 0; i < addrList.length; i++) { + listLen += _scriptURIs[contractAddress].scriptURIs[addrList[i]].length; + } + + string[] memory ownerScripts = new string[](listLen); + + // Add owner scripts + uint256 scriptIndex = _addScriptURIs(contractOwner, contractAddress, ownerScripts, 0); + + // Add remainder scripts + for (uint256 i = 0; i < addrList.length; i++) { + scriptIndex = _addScriptURIs(addrList[i], contractAddress, ownerScripts, scriptIndex); + } + + return ownerScripts; + } + + function _addScriptURIs( + address user, + address contractAddress, + string[] memory ownerScripts, + uint256 scriptIndex + ) internal view returns (uint256) { + for (uint256 j = 0; j < _scriptURIs[contractAddress].scriptURIs[user].length; j++) { + string memory thisScriptURI = _scriptURIs[contractAddress].scriptURIs[user][j]; + if (bytes(thisScriptURI).length > 0) { + ownerScripts[scriptIndex++] = thisScriptURI; + } + } + return scriptIndex; + } +} +``` + +## Security Considerations + +The scripts provided could be authenticated in various ways: + +1. The target contract which the setter specifies implements the [ERC-173](./eip-173.md) `Ownable` interface. Once the script is fetched, the signature can be verified to match the Owner(). In the case of TokenScript this can be checked by a dapp or wallet using the TokenScript SDK, the TokenScript online verification service, or by extracting the signature from the XML, taking a keccak256 of the script and ecrecover the signing key address. +2. If the contract does not implement Ownable, further steps can be taken: + a. The hosting app/wallet can acertain the deployment key using 3rd party API or block explorer. The implementing wallet, dapp or viewer would then check the signature matches this deployment key. + b. Signing keys could be pre-authenticated by a hosting app, using an embedded keychain. + c. A governance token could allow a script council to authenticate requests to set and validate keys. + +If these criteria are not met: +- For mainnet implementations the implementing wallet should be cautious about using the script - it would be at the app and/or user's discretion. +- For testnets, it is acceptable to allow the script to function, at the discretion of the wallet provider. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). \ No newline at end of file diff --git a/ERCS/erc-7741.md b/ERCS/erc-7741.md new file mode 100644 index 0000000000..7c7c6f57d7 --- /dev/null +++ b/ERCS/erc-7741.md @@ -0,0 +1,211 @@ +--- +eip: 7741 +title: Authorize Operator +description: Set Operator via EIP-712 secp256k1 signatures +author: Jeroen Offerijns (@hieronx), João Martins (@0xTimepunk) +discussions-to: https://ethereum-magicians.org/t/erc-7741-authorize-operator/20531 +status: Draft +type: Standards Track +category: ERC +created: 2024-06-03 +requires: 712, 1271 +--- + +## Abstract + +A set of functions to enable meta-transactions and atomic interactions with contracts implementing an operator model, via signatures conforming to the [EIP-712](./eip-712.md) typed message signing specification. + +## Motivation + +The primary motivation for this standard is to enhance the flexibility, security, and efficiency of operator management. By leveraging EIP-712 signatures, this standard allows users to authorize operators without the need for on-chain transactions, reducing gas costs and improving user experience. This is particularly beneficial whenever frequent operator changes and cross-chain interactions are required. + +Additionally, this standard aims to: + +1. **Enable Meta-Transactions**: Allow users to delegate the execution of transactions to operators, enabling meta-transactions where the user does not need to hold native tokens to pay for gas fees on each chain. +2. **Improve Security**: Utilize the EIP-712 standard for typed data signing, which provides a more secure and user-friendly way to sign messages compared to raw data signing. +3. **Facilitate Interoperability**: Provide a standardized interface for operator management that can be adopted across various vault protocols, promoting interoperability and reducing integration complexity for developers. +4. **Streamline Cross-Chain Operations**: Simplify the process of managing operators across different chains, making it easier for protocols to maintain consistent operator permissions and interactions in a multi-chain environment. + +By addressing these needs, the `Authorize Operator` standard aims to streamline the process of managing operators in decentralized vault protocols, making it easier for users and developers to interact with smart contracts in a secure, cost-effective, and interoperable manner across multiple blockchain networks. + +## Specification + +### Operator-compatible contracts + +This signed authorization scheme applies to any contracts implementing the following interface: + +```solidity + interface IOperator { + event OperatorSet(address indexed owner, address indexed operator, bool approved); + + function setOperator(address operator, bool approved) external returns (bool); + function isOperator(address owner, address operator) external returns (bool status); + } +``` + +[EIP-6909](./eip-6909.md) and [EIP-7540](./eip-7540.md) already implement this interface. + +The naming of the arguments is interchangeable, e.g. [EIP-6909](./eip-6909.md) uses `spender` instead of `operator`. + +### Methods + +#### `authorizeOperator` + +Grants or revokes permissions for `operator` to manage Requests on behalf of the `msg.sender`, using an [EIP-712](./eip-712.md) signature. + +MUST revert if the `deadline` has passed. + +MUST invalidate the nonce of the signature to prevent message replay. + +MUST revert if the `signature` is not a valid [EIP-712](./eip-712.md) signature, with the given input parameters. + +MUST set the operator status to the `approved` value. + +MUST log the `OperatorSet` event. + +MUST return `true`. + +```yaml +- name: authorizeOperator + type: function + stateMutability: nonpayable + + inputs: + - name: owner + type: address + - name: operator + type: address + - name: approved + type: bool + - name: deadline + type: uint256 + - name: nonce + type: bytes32 + - name: signature + type: bytes + + outputs: + - name: success + type: bool +``` + +#### `invalidateNonce` + +Revokes the given `nonce` for `msg.sender` as the `owner`. + +```yaml +- name: invalidateNonce + type: function + stateMutability: nonpayable + + inputs: + - name: nonce + type: bytes32 +``` + +#### `authorizations` + +Returns whether the given `nonce` has been used for the `controller`. + +```yaml +- name: authorizations + type: function + stateMutability: nonpayable + + inputs: + - name: controller + type: address + - name: nonce + type: bytes32 + outputs: + - name: used + type: bool +``` + +#### `DOMAIN_SEPARATOR` + +Returns the `DOMAIN_SEPARATOR` as defined according to EIP-712. The `DOMAIN_SEPARATOR` should be unique to the contract and chain to prevent replay attacks from other domains, and satisfy the requirements of EIP-712, but is otherwise unconstrained. + +```yaml +- name: DOMAIN_SEPARATOR + type: function + stateMutability: nonpayable + + outputs: + - type: bytes32 +``` + +### [ERC-165](./eip-165.md) support + +Smart contracts implementing this standard MUST implement the [ERC-165](./eip-165.md) `supportsInterface` function. + +Contracts MUST return the constant value `true` if `0x7a7911eb` is passed through the `interfaceID` argument. + +## Rationale + +### Similarity to [ERC-2612](./eip-2612.md) + +The specification is intentionally designed to closely match [ERC-2612](./eip-2612.md). This should simplify new integrations of the standard. + +The main difference is using `bytes32` vs `uint256`, which enables unordered nonces. + +## Reference Implementation + +```solidity + // This code snippet is incomplete pseudocode used for example only and is no way intended to be used in production or guaranteed to be secure + + bytes32 public constant AUTHORIZE_OPERATOR_TYPEHASH = + keccak256("AuthorizeOperator(address controller,address operator,bool approved,uint256 deadline,bytes32 nonce)"); + + mapping(address owner => mapping(bytes32 nonce => bool used)) authorizations; + + function DOMAIN_SEPARATOR() public view returns (bytes32) { + // EIP-712 implementation + } + + function isValidSignature(address signer, bytes32 digest, bytes memory signature) internal view returns (bool valid) { + // ERC-1271 implementation + } + + function authorizeOperator( + address controller, + address operator, + bool approved, + uint256 deadline, + bytes32 nonce, + bytes memory signature + ) external returns (bool success) { + require(block.timestamp <= deadline, "ERC7540Vault/expired"); + require(controller != address(0), "ERC7540Vault/invalid-controller"); + require(!authorizations[controller][nonce], "ERC7540Vault/authorization-used"); + + authorizations[controller][nonce] = true; + + bytes32 digest = keccak256( + abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR(), + keccak256(abi.encode(AUTHORIZE_OPERATOR_TYPEHASH, controller, operator, approved, deadline, nonce)) + ) + ); + + require(SignatureLib.isValidSignature(controller, digest, signature), "ERC7540Vault/invalid-authorization"); + + isOperator[controller][operator] = approved; + emit OperatorSet(controller, operator, approved); + + success = true; + } + + function invalidateNonce(bytes32 nonce) external { + authorizations[msg.sender][nonce] = true; + } +``` + +## Security Considerations + +Operators have significant control over users and the signed message can lead to undesired outcomes. The expiration date should be set as short as feasible to reduce the chance of an unused signature leaking at a later point. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/ERCS/erc-7744.md b/ERCS/erc-7744.md new file mode 100644 index 0000000000..2d48b0bac1 --- /dev/null +++ b/ERCS/erc-7744.md @@ -0,0 +1,118 @@ +--- +eip: 7744 +title: Code Index +description: Global repository of bytecode, enabling developers, auditors, and researchers to find, analyze, and reuse bytecode efficiently. +author: Tim Pechersky (@peersky) +discussions-to: https://ethereum-magicians.org/t/erc-7744-code-index/20569 +status: Draft +type: Standards Track +category: ERC +created: 2024-07-16 +--- + +## Abstract + +This EIP defines a standard interface for indexing smart contracts on Ethereum by their bytecode hash. This enables trustless discovery and verification of contract code, facilitating use cases like bytecode signing, whitelisting, and decentralized distribution mechanisms. + +## Motivation + +Existing contract discovery relies on addresses, which are non-deterministic and can be obfuscated through proxies. Indexing by bytecode hash provides a deterministic and tamper-proof way to identify and verify contract code, enhancing security and trust in the Ethereum ecosystem. + +Consider a security auditor who wants to attest to the integrity of a contract's code. By referencing bytecode hashes, auditors can focus their audit on the bytecode itself, without needing to assess deployment parameters or storage contents. This method verifies the integrity of a contract's codebase without auditing the entire contract state. + +Additionally, bytecode referencing allows whitelist contracts before deployment, allowing developers to get pre-approval for their codebase without disclosing the code itself, or even pre-setup infrastructure that will change it behavior upon adding some determined functionality on chain. + +For developers relying on extensive code reuse, bytecode referencing protects against malicious changes that can occur with address-based referencing through proxies. This builds long-term trust chains extending to end-user applications. + +For decentralized application (dApp) developers, a code index can save gas costs by allowing them to reference existing codebases instead of redeploying them, optimizing resource usage. This can be useful for dApps that rely on extensive re-use of same codebase as own dependencies. + +### Why this registry needs to be an ERC + +The Code Index is essential for trustless and secure smart contract development. By standardizing the interface for indexing contracts by their bytecode, developers can easily integrate this feature into their smart contracts, enhancing the security and trustworthiness of the Ethereum ecosystem. + +Its simplicity and generic nature make it suitable for a wide range of applications. The ability to globally reference the same codebase makes it an ideal candidate for standardization. + +Ultimately, this feature should be incorporated into EIP standards, as it is a fundamental building block for trustless and secure smart contract development. This standard is a step towards this goal. + + +## Specification + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. + +```solidity +// SPDX-License-Identifier: CC0-1.0 +pragma solidity =0.8.20; + +interface ICodeIndex { + event Indexed(address indexed container, bytes32 indexed codeHash); + error alreadyExists(bytes32 id, address source); + + function register(address container) external; + + function get(bytes32 id) external view returns (address); +} + +/** + * @title Byte Code Indexer Contract + * @notice You can use this contract to index contracts by their bytecode. + * @dev This allows to query contracts by their bytecode instead of addresses. + * @author Tim Pechersky (@Peersky) + */ +contract CodeIndex is ICodeIndex { + mapping(bytes32 => address) private index; + + /** + * @notice Registers a contract in the index by its bytecode hash + * @param container The contract to register + * @dev `msg.codeHash` will be used + * @dev It will revert if the contract is already indexed + */ + function register(address container) external { + address etalon = index[container.codehash]; + if (etalon != address(0)) { + revert alreadyExists(container.codehash, etalon); + } + index[container.codehash] = container; + emit Indexed(container, container.codehash); + } + + /** + * @notice Returns the contract address by its bytecode hash + * @dev returns zero if the contract is not indexed + * @param id The bytecode hash + * @return The contract address + */ + function get(bytes32 id) external view returns (address) { + return index[id]; + } +} + +``` + +### Deployment method + +The `CodeIndex` contract is deployed at: `0xc0D31d398c5ee86C5f8a23FA253ee8a586dA03Ce` using `CREATE2` via the deterministic deployer at `0x4e59b44847b379578588920ca78fbf26c0b4956c` with a salt of `0x220a70730c743a005cfd55180805d2c0d5b8c7695c5496100dcffa91c02befce` is obtained by seeking a vanity address with meaningful name "Code ID (`c0D31d`). + +## Rationale + +**Bytecode over Addresses**: Bytecode is deterministic and can be verified on-chain, while addresses are opaque and mutable. + +**Reverting on re-indexing**: There is small, yet non-zero probability of hash collision attack. Disallowing updates to indexed location of bytecode coupes with this. + +**Simple Interface**: The interface is minimal and focused to maximize composability and ease of implementation. + +**Library Implementation**: Implementing this as a library would limit its impact, making code reuse more difficult and lacking a single, official source of truth. By establishing this as an ERC, we ensure standardization and widespread adoption, driving the ecosystem forward. + +## Reference Implementation + +Reference implementation of the Code Index can be found in the assets folder. There you can find the [interface](../assets/eip-7744/ICodeIndex.sol) and the [implementation](../assets/eip-7744/CodeIndex.sol) of the Code Index. + +## Security Considerations + +**Malicious Code**: The index does NOT guarantee the safety or functionality of indexed contracts. Users MUST exercise caution and perform their own due diligence before interacting with indexed contracts. + +**Storage contents of registered contracts**: The index only refers to the bytecode of the contract, not the storage contents. This means that the contract state is not indexed and may change over time. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/ERCS/erc-7746.md b/ERCS/erc-7746.md new file mode 100644 index 0000000000..cf944f6d71 --- /dev/null +++ b/ERCS/erc-7746.md @@ -0,0 +1,101 @@ +--- +eip: 7746 +title: Composable Security Middleware Hooks +description: An interface for composable, runtime security checks in smart contracts. +author: Tim Pechersky (@peersky) +discussions-to: https://ethereum-magicians.org/t/erc-7746-composable-security-middleware-hooks/19471 +status: Draft +type: Standards Track +category: ERC +created: 2024-07-17 +--- + +## Abstract + +This EIP proposes a standard interface, `ILayer`, for implementing composable security layers in smart contracts. These layers act as middleware, enabling runtime validation of function calls before and after execution, independent of the protected contract's logic. This approach facilitates modular security, allowing independent providers to manage and upgrade security layers across multiple contracts. + +## Motivation + +Current smart contract security practices often rely on monolithic validation logic within the contract itself. This can lead to tightly coupled code, making it difficult to isolate and address security concerns. Existing ERCs already are using something that can be seen as specific implementation of such layers wrapping: [ERC-4337](./eip-4337.md) describes requirements to perform validate user operations in a separate contract, and later elaborates same need to paymaster validation, that can be seen as a separate layers of a generic system. +The Security Layers Standard introduces a modular approach, enabling: + +- **Independent Security Providers:** Specialized security providers can focus on developing and maintaining specific security checks. +- **Composable Security:** Layers can be combined to create comprehensive security profiles tailored to individual contract needs. +- **Upgradability:** Security layers can be updated without requiring changes to the protected contract. +- **Flexibility:** Layers can perform a wide range of validation checks, including access control, input sanitization, output verification, and more. + +Having a generalized standard for such layers can help to build more secure and modular systems as well as enable security providers to build generic, service-oriented security oracle solutions. + +## Specification + +A contract implementing the `ILayer` interface MUST provide two functions: + +```solidity +// SPDX-License-Identifier: CC0-1.0 +pragma solidity 0.8.20; + +interface ILayer { + /// @notice Validates a function call before execution. + /// @param configuration Layer-specific configuration data. + /// @param selector The function selector being called. + /// @param sender The address initiating the call. + /// @param value The amount of ETH sent with the call (if any). + /// @param data The calldata for the function call. + /// @return beforeCallResult Arbitrary data to be passed to `afterCallValidation`. + /// @dev MUST revert if validation fails. + function beforeCall( + bytes memory configuration, + bytes4 selector, + address sender, + uint256 value, + bytes memory data + ) external returns (bytes memory); + + /// @notice Validates a function call after execution. + /// @param configuration Layer-specific configuration data. + /// @param selector The function selector being called. + /// @param sender The address initiating the call. + /// @param value The amount of ETH sent with the call (if any). + /// @param data The calldata for the function call. + /// @param beforeCallResult The data returned by `beforeCallValidation`. + /// @dev MUST revert if validation fails. + function afterCall( + bytes memory configuration, + bytes4 selector, + address sender, + uint256 value, + bytes memory data, + bytes memory beforeCallResult + ) external; +} + +``` + +A protected contract MAY integrate security layers by calling the `beforeCallValidation` function before executing its logic and the `afterCallValidation` function afterwards. Multiple layers can be registered and executed in a defined order. The protected contract MUST revert if any layer reverts. + +## Rationale + +**Flexibility**: The `layerConfig` parameter allows for layer-specific customization, enabling a single layer implementation to serve multiple contracts with varying requirements. + +**non-static calls**: Layers can maintain their own state, allowing for more complex validation logic (e.g., rate limiting, usage tracking). + +**Strict Validation**: Reverts on validation failure ensure a fail-safe mechanism, preventing execution of potentially harmful transactions. + +**Gas Costs**: Layers naturally will have gas costs associated with their execution. However, the benefits of enhanced security and modularity outweigh these costs, especially as blockchain technology continues to evolve and we expect gas costs to decrease over time. + +## Reference Implementation + +A reference implementation of the `ILayer` interface and a sample protected contract can be found in the repository: +In the [`../assets/eip-7746/ILayer.sol`](../assets/eip-7746/ILayer.sol) a reference interface is provided. + +In this test, a [`Protected.sol`](../assets/eip-7746/test/Protected.sol) contract is protected by a [`RateLimitLayer.sol`](../assets/eip-7746/test/RateLimitLayer.sol) layer. The `RateLimitLayer` implements the `ILayer` interface and enforces a rate which client has configured. +The `Drainer` simulates a vulnerable contract that acts in a malicious way. In the `test.ts` The `Drainer` contract is trying to drain the funds from the `Protected` contract. It is assumed that `Protected` contract has bug that allows partial unauthorized access to the state. +The `RateLimitLayer` is configured to allow only 10 transactions per block from same sender. The test checks that the `Drainer` contract is not able to drain the funds from the `Protected` contract. + +## Security Considerations + +**Layer Trust**: Thoroughly audit and vet any security layer before integrating it into your contract. Malicious layers can compromise contract security. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/ERCS/erc-7758.md b/ERCS/erc-7758.md new file mode 100644 index 0000000000..1219feda4b --- /dev/null +++ b/ERCS/erc-7758.md @@ -0,0 +1,530 @@ +--- +eip: 7758 +title: Transfer With Authorization +description: Transfer fungible assets via a signed authorization. +author: Peter Jihoon Kim (@petejkim), Kevin Britz (@kbrizzle), David Knott (@DavidLKnott), Dongri Jin (@dongri) +discussions-to: https://ethereum-magicians.org/t/erc-7758-transfer-with-authorization/20859 +status: Draft +type: Standards Track +category: ERC +created: 2020-09-28 +requires: 20, 712 +--- + +## Abstract + +A set of functions to enable meta-transactions and atomic interactions with [ERC-20](./eip-20.md) token contracts via signatures conforming to the [EIP-712](./eip-712.md) typed message signing specification. + +This enables the user to: + +- delegate the gas payment to someone else, +- pay for gas in the token itself rather than in ETH, +- perform one or more token transfers and other operations in a single atomic transaction, +- transfer ERC-20 tokens to another address, and have the recipient submit the transaction, +- batch multiple transactions with minimal overhead, and +- create and perform multiple transactions without having to worry about them failing due to accidental nonce-reuse or improper ordering by the miner. + +## Motivation + +There is an existing spec, [EIP-2612](./eip-2612), that also allows meta-transactions, and it is encouraged that a contract implements both for maximum compatibility. The two primary differences between this spec and EIP-2612 are that: + +- EIP-2612 uses sequential nonces, but this uses random 32-byte nonces, and that +- EIP-2612 relies on the ERC-20 `approve`/`transferFrom` ("ERC-20 allowance") pattern. + +The biggest issue with the use of sequential nonces is that it does not allow users to perform more than one transaction at time without risking their transactions failing, because: + +- DApps may unintentionally reuse nonces that have not yet been processed in the blockchain. +- Miners may process the transactions in the incorrect order. + +This can be especially problematic if the gas prices are very high and transactions often get queued up and remain unconfirmed for a long time. Non-sequential nonces allow users to create as many transactions as they want at the same time. + +The ERC-20 allowance mechanism is susceptible to the multiple withdrawal attack, and encourages antipatterns such as the use of the "infinite" allowance. The wide-prevalence of upgradeable contracts have made the conditions favorable for these attacks to happen in the wild. + +The deficiencies of the ERC-20 allowance pattern brought about the development of alternative token standards such as the [ERC-777](./eip-777). However, they haven't been able to gain much adoption due to compatibility and potential security issues. + +## Specification + +### Event + +```solidity +event AuthorizationUsed( + address indexed authorizer, + bytes32 indexed nonce +); + +// keccak256("TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)") +bytes32 public constant TRANSFER_WITH_AUTHORIZATION_TYPEHASH = 0x7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a2267; + +// keccak256("ReceiveWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)") +bytes32 public constant RECEIVE_WITH_AUTHORIZATION_TYPEHASH = 0xd099cc98ef71107a616c4f0f941f04c322d8e254fe26b3c6668db87aae413de8; + +/** + * @notice Returns the state of an authorization + * @dev Nonces are randomly generated 32-byte data unique to the authorizer's + * address + * @param authorizer Authorizer's address + * @param nonce Nonce of the authorization + * @return True if the nonce is used + */ +function authorizationState( + address authorizer, + bytes32 nonce +) external view returns (bool); + +/** + * @notice Execute a transfer with a signed authorization + * @param from Payer's address (Authorizer) + * @param to Payee's address + * @param value Amount to be transferred + * @param validAfter The time after which this is valid (unix time) + * @param validBefore The time before which this is valid (unix time) + * @param nonce Unique nonce + * @param v v of the signature + * @param r r of the signature + * @param s s of the signature + */ +function transferWithAuthorization( + address from, + address to, + uint256 value, + uint256 validAfter, + uint256 validBefore, + bytes32 nonce, + uint8 v, + bytes32 r, + bytes32 s +) external; + +/** + * @notice Receive a transfer with a signed authorization from the payer + * @dev This has an additional check to ensure that the payee's address matches + * the caller of this function to prevent front-running attacks. (See security + * considerations) + * @param from Payer's address (Authorizer) + * @param to Payee's address + * @param value Amount to be transferred + * @param validAfter The time after which this is valid (unix time) + * @param validBefore The time before which this is valid (unix time) + * @param nonce Unique nonce + * @param v v of the signature + * @param r r of the signature + * @param s s of the signature + */ +function receiveWithAuthorization( + address from, + address to, + uint256 value, + uint256 validAfter, + uint256 validBefore, + bytes32 nonce, + uint8 v, + bytes32 r, + bytes32 s +) external; +``` + +**Optional:** + +``` +event AuthorizationCanceled( + address indexed authorizer, + bytes32 indexed nonce +); + +// keccak256("CancelAuthorization(address authorizer,bytes32 nonce)") +bytes32 public constant CANCEL_AUTHORIZATION_TYPEHASH = 0x158b0a9edf7a828aad02f63cd515c68ef2f50ba807396f6d12842833a1597429; + +/** + * @notice Attempt to cancel an authorization + * @param authorizer Authorizer's address + * @param nonce Nonce of the authorization + * @param v v of the signature + * @param r r of the signature + * @param s s of the signature + */ +function cancelAuthorization( + address authorizer, + bytes32 nonce, + uint8 v, + bytes32 r, + bytes32 s +) external; +``` + + +The arguments `v`, `r`, and `s` must be obtained using the [EIP-712](./eip-712.md) typed message signing spec. + +**Example:** + +``` +DomainSeparator := Keccak256(ABIEncode( + Keccak256( + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" + ), + Keccak256("USD Coin"), // name + Keccak256("2"), // version + 1, // chainId + 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 // verifyingContract +)) +``` + +With the domain separator, the typehash, which is used to identify the type of the EIP-712 message being used, and the values of the parameters, you are able to derive a Keccak-256 hash digest which can then be signed using the token holder's private key. + +**Example:** + +``` +// Transfer With Authorization +TypeHash := Keccak256( + "TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)" +) +Params := { From, To, Value, ValidAfter, ValidBefore, Nonce } + +// ReceiveWithAuthorization +TypeHash := Keccak256( + "ReceiveWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)" +) +Params := { From, To, Value, ValidAfter, ValidBefore, Nonce } + +// CancelAuthorization +TypeHash := Keccak256( + "CancelAuthorization(address authorizer,bytes32 nonce)" +) +Params := { Authorizer, Nonce } +``` + +``` +// "‖" denotes concatenation. +Digest := Keecak256( + 0x1901 ‖ DomainSeparator ‖ Keccak256(ABIEncode(TypeHash, Params...)) +) + +{ v, r, s } := Sign(Digest, PrivateKey) +``` + +Smart contract functions that wrap `receiveWithAuthorization` call may choose to reduce the number of arguments by accepting the full ABI-encoded set of arguments for the `receiveWithAuthorization` call as a single argument of the type `bytes`. + +**Example:** + +```solidity +// keccak256("receiveWithAuthorization(address,address,uint256,uint256,uint256,bytes32,uint8,bytes32,bytes32)")[0:4] +bytes4 private constant _RECEIVE_WITH_AUTHORIZATION_SELECTOR = 0xef55bec6; + +function deposit(address token, bytes calldata receiveAuthorization) + external + nonReentrant +{ + (address from, address to, uint256 amount) = abi.decode( + receiveAuthorization[0:96], + (address, address, uint256) + ); + require(to == address(this), "Recipient is not this contract"); + + (bool success, ) = token.call( + abi.encodePacked( + _RECEIVE_WITH_AUTHORIZATION_SELECTOR, + receiveAuthorization + ) + ); + require(success, "Failed to transfer tokens"); + + ... +} +``` + +### Use with web3 providers + +The signature for an authorization can be obtained using a web3 provider with the `eth_signTypedData{_v4}` method. + +**Example:** + +```javascript +const data = { + types: { + EIP712Domain: [ + { name: "name", type: "string" }, + { name: "version", type: "string" }, + { name: "chainId", type: "uint256" }, + { name: "verifyingContract", type: "address" }, + ], + TransferWithAuthorization: [ + { name: "from", type: "address" }, + { name: "to", type: "address" }, + { name: "value", type: "uint256" }, + { name: "validAfter", type: "uint256" }, + { name: "validBefore", type: "uint256" }, + { name: "nonce", type: "bytes32" }, + ], + }, + domain: { + name: tokenName, + version: tokenVersion, + chainId: selectedChainId, + verifyingContract: tokenAddress, + }, + primaryType: "TransferWithAuthorization", + message: { + from: userAddress, + to: recipientAddress, + value: amountBN.toString(10), + validAfter: 0, + validBefore: Math.floor(Date.now() / 1000) + 3600, // Valid for an hour + nonce: Web3.utils.randomHex(32), + }, +}; + +const signature = await ethereum.request({ + method: "eth_signTypedData_v4", + params: [userAddress, JSON.stringify(data)], +}); + +const v = "0x" + signature.slice(130, 132); +const r = signature.slice(0, 66); +const s = "0x" + signature.slice(66, 130); +``` + +## Rationale + +### Unique Random Nonce, Instead of Sequential Nonce + +One might say transaction ordering is one reason why sequential nonces are preferred. However, sequential nonces do not actually help achieve transaction ordering for meta transactions in practice: + +- For native Ethereum transactions, when a transaction with a nonce value that is too-high is submitted to the network, it will stay pending until the transactions consuming the lower unused nonces are confirmed. +- However, for meta-transactions, when a transaction containing a sequential nonce value that is too high is submitted, instead of staying pending, it will revert and fail immediately, resulting in wasted gas. +- The fact that miners can also reorder transactions and include them in the block in the order they want (assuming each transaction was submitted to the network by different meta-transaction relayers) also makes it possible for the meta-transactions to fail even if the nonces used were correct. (e.g. User submits nonces 3, 4 and 5, but miner ends up including them in the block as 4,5,3, resulting in only 3 succeeding) +- Lastly, when using different applications simultaneously, in absence of some sort of an off-chain nonce-tracker, it is not possible to determine what the correct next nonce value is if there exists nonces that are used but haven't been submitted and confirmed by the network. +- Under high gas price conditions, transactions can often "get stuck" in the pool for a long time. Under such a situation, it is much more likely for the same nonce to be unintentionally reused twice. For example, if you make a meta-transaction that uses a sequential nonce from one app, and switch to another app to make another meta-transaction before the previous one confirms, the same nonce will be used if the app relies purely on the data available on-chain, resulting in one of the transactions failing. +- In conclusion, the only way to guarantee transaction ordering is for relayers to submit transactions one at a time, waiting for confirmation between each submission (and the order in which they should be submitted can be part of some off-chain metadata), rendering sequential nonce irrelevant. + +### Valid After and Valid Before + +- Relying on relayers to submit transactions for you means you may not have exact control over the timing of transaction submission. +- These parameters allow the user to schedule a transaction to be only valid in the future or before a specific deadline, protecting the user from potential undesirable effects that may be caused by the submission being made either too late or too early. + +### EIP-712 + +- EIP-712 ensures that the signatures generated are valid only for this specific instance of the token contract and cannot be replayed on a different network with a different chain ID. +- This is achieved by incorporating the contract address and the chain ID in a Keccak-256 hash digest called the domain separator. The actual set of parameters used to derive the domain separator is up to the implementing contract, but it is highly recommended that the fields `verifyingContract` and `chainId` are included. + +## Backwards Compatibility + +New contracts benefit from being able to directly utilize this proposal in order to create atomic transactions, but existing contracts may still rely on the conventional [ERC-20](./eip-20.md) allowance pattern (`approve`/`transferFrom`). + +In order to add support for this proposal to existing contracts ("parent contract") that use the ERC-20 allowance pattern, a forwarding contract ("forwarder") can be constructed that takes an authorization and does the following: + +1. Extract the user and deposit amount from the authorization +2. Call `receiveWithAuthorization` to transfer specified funds from the user to the forwarder +3. Approve the parent contract to spend funds from the forwarder +4. Call the method on the parent contract that spends the allowance set from the forwarder +5. Transfer the ownership of any resulting tokens back to the user + +**Example:** + +```solidity +interface IDeFiToken { + function deposit(uint256 amount) external returns (uint256); + + function transfer(address account, uint256 amount) + external + returns (bool); +} + +contract DepositForwarder { + bytes4 private constant _RECEIVE_WITH_AUTHORIZATION_SELECTOR = 0xef55bec6; + + IDeFiToken private _parent; + IERC20 private _token; + + constructor(IDeFiToken parent, IERC20 token) public { + _parent = parent; + _token = token; + } + + function deposit(bytes calldata receiveAuthorization) + external + nonReentrant + returns (uint256) + { + (address from, address to, uint256 amount) = abi.decode( + receiveAuthorization[0:96], + (address, address, uint256) + ); + require(to == address(this), "Recipient is not this contract"); + + (bool success, ) = address(_token).call( + abi.encodePacked( + _RECEIVE_WITH_AUTHORIZATION_SELECTOR, + receiveAuthorization + ) + ); + require(success, "Failed to transfer to the forwarder"); + + require( + _token.approve(address(_parent), amount), + "Failed to set the allowance" + ); + + uint256 tokensMinted = _parent.deposit(amount); + require( + _parent.transfer(from, tokensMinted), + "Failed to transfer the minted tokens" + ); + + uint256 remainder = _token.balanceOf(address(this); + if (remainder > 0) { + require( + _token.transfer(from, remainder), + "Failed to refund the remainder" + ); + } + + return tokensMinted; + } +} +``` + +## Reference Implementation + +### `EIP7758.sol` + +```solidity +abstract contract EIP7758 is IERC20Transfer, EIP712Domain { + // keccak256("TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)") + bytes32 public constant TRANSFER_WITH_AUTHORIZATION_TYPEHASH = 0x7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a2267; + + // keccak256("ReceiveWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)") + bytes32 public constant RECEIVE_WITH_AUTHORIZATION_TYPEHASH = 0xd099cc98ef71107a616c4f0f941f04c322d8e254fe26b3c6668db87aae413de8; + + mapping(address => mapping(bytes32 => bool)) internal _authorizationStates; + + event AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce); + + string internal constant _INVALID_SIGNATURE_ERROR = "EIP7758: invalid signature"; + + function authorizationState(address authorizer, bytes32 nonce) + external + view + returns (bool) + { + return _authorizationStates[authorizer][nonce]; + } + + function transferWithAuthorization( + address from, + address to, + uint256 value, + uint256 validAfter, + uint256 validBefore, + bytes32 nonce, + uint8 v, + bytes32 r, + bytes32 s + ) external { + require(now > validAfter, "EIP7758: authorization is not yet valid"); + require(now < validBefore, "EIP7758: authorization is expired"); + require( + !_authorizationStates[from][nonce], + "EIP7758: authorization is used" + ); + + bytes memory data = abi.encode( + TRANSFER_WITH_AUTHORIZATION_TYPEHASH, + from, + to, + value, + validAfter, + validBefore, + nonce + ); + require( + EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == from, + "EIP7758: invalid signature" + ); + + _authorizationStates[from][nonce] = true; + emit AuthorizationUsed(from, nonce); + + _transfer(from, to, value); + } +} +``` + +### `IERC20Transfer.sol` + +```solidity +abstract contract IERC20Transfer { + function _transfer( + address sender, + address recipient, + uint256 amount + ) internal virtual; +} +``` + +### `EIP712Domain.sol` +```solidity +abstract contract EIP712Domain { + bytes32 public DOMAIN_SEPARATOR; +} +``` + +### `EIP712.sol` + +```solidity +library EIP712 { + // keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") + bytes32 public constant EIP712_DOMAIN_TYPEHASH = 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; + + function makeDomainSeparator(string memory name, string memory version) + internal + view + returns (bytes32) + { + uint256 chainId; + assembly { + chainId := chainid() + } + + return + keccak256( + abi.encode( + EIP712_DOMAIN_TYPEHASH, + keccak256(bytes(name)), + keccak256(bytes(version)), + bytes32(chainId), + address(this) + ) + ); + } + + function recover( + bytes32 domainSeparator, + uint8 v, + bytes32 r, + bytes32 s, + bytes memory typeHashAndData + ) internal pure returns (address) { + bytes32 digest = keccak256( + abi.encodePacked( + "\x19\x01", + domainSeparator, + keccak256(typeHashAndData) + ) + ); + address recovered = ecrecover(digest, v, r, s); + require(recovered != address(0), "EIP712: invalid signature"); + return recovered; + } +} +``` + +## Security Considerations + +Use `receiveWithAuthorization` instead of `transferWithAuthorization` when calling from other smart contracts. It is possible for an attacker watching the transaction pool to extract the transfer authorization and front-run the `transferWithAuthorization` call to execute the transfer without invoking the wrapper function. This could potentially result in unprocessed, locked up deposits. `receiveWithAuthorization` prevents this by performing an additional check that ensures that the caller is the payee. Additionally, if there are multiple contract functions accepting receive authorizations, the app developer could dedicate some leading bytes of the nonce could as the identifier to prevent cross-use. + +When submitting multiple transfers simultaneously, be mindful of the fact that relayers and miners will decide the order in which they are processed. This is generally not a problem if the transactions are not dependent on each other, but for transactions that are highly dependent on each other, it is recommended that the signed authorizations are submitted one at a time. + +The zero address must be rejected when using `ecrecover` to prevent unauthorized transfers and approvals of funds from the zero address. The built-in `ecrecover` returns the zero address when a malformed signature is provided. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/ERCS/erc-7760.md b/ERCS/erc-7760.md new file mode 100644 index 0000000000..50fb32b43a --- /dev/null +++ b/ERCS/erc-7760.md @@ -0,0 +1,514 @@ +--- +eip: 7760 +title: Minimal Upgradeable Proxies +description: Minimal upgradeable proxies with immutable arguments and support for onchain implementation queries +author: Atarpara (@Atarpara), JT Riley (@jtriley-eth), Thomas (@0xth0mas), xiaobaiskill (@xiaobaiskill), Vectorized (@Vectorized) +discussions-to: https://ethereum-magicians.org/t/erc-7760-minimal-upgradeable-proxies/20868 +status: Draft +type: Standards Track +category: ERC +created: 2024-08-19 +requires: 1967 +--- + +## Abstract + +This standard defines minimal [ERC-1967](./eip-1967.md) proxies for three patterns: (1) transparent, (2) UUPS, (3) beacon. The proxies support optional immutable arguments which are appended to the end of their runtime bytecode. Additional variants which support onchain implementation querying are provided. + +## Motivation + +Having standardized minimal bytecode for upgradeable proxies enables the following: + +1. Automatic verification on block explorers. +2. Ability for immutable arguments to be queried onchain, as these arguments are stored at the same bytecode offset, +3. Ability for the implementation to be queried and verified onchain. + +The minimal nature of the proxies enables cheaper deployment and runtime costs. + +## Specification + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. + +### General specifications + +All of the following proxies MAY have optional data bytecode appended to the end of their runtime bytecode. + +Emitting the ERC-1967 events during initialization is OPTIONAL. Indexers MUST NOT expect the initialization code to emit the ERC-1967 events. + +### Onchain querying of implementation for I-variants + +The I-variants have logic that returns the implementation baked into their bytecode. + +When called with any 1-byte calldata, these I-variants will return the address (left-zero-padded to 32 bytes) and will not forward the calldata to the target. + +The bytecode of the proxies before any optional immutable arguments MUST be verified with the following steps: + +1. Fetch the bytecode before any immutable arguments with `EXTCODECOPY`. +2. Zeroize any baked-in factory address in the fetched bytecode. +3. Ensure that the hash of the final fetched bytecode matches the expected hash of the bytecode. + +If the hash does not match, the implementation address returned MUST NOT be trusted. + +### Minimal ERC-1967 transparent upgradeable proxy + +The transparent upgradeable proxy is RECOMMENDED to be deployed by a factory that doubles as the account that is authenticated to perform upgrades. An externally owned account may perform the deployment on behalf of the factory. For convention, we will refer to the factory as the immutable account authorized to invoke the upgrade logic on the proxy. + +As the proxy's runtime bytecode contains logic to allow the factory to set any storage slot with any value, the initialization code MAY skip storing the implementation slot. + +The upgrading logic does not emit the ERC-1967 event. Indexers MUST NOT expect the upgrading logic to emit the ERC-1967 events. + +During upgrades, the factory MUST call the upgradeable proxy with following calldata: + +```solidity +abi.encodePacked( + // The new implementation address, converted to a 32-byte word. + uint256(uint160(implementation)), + // ERC-1967 implementation slot. + bytes32(0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc), + // Optional calldata to be forwarded to the implementation + // via delegatecall after setting the implementation slot. + "" +) +``` + +#### Minimal ERC-1967 transparent upgradeable proxy for (basic variant) + +Runtime bytecode (20-byte factory address subvariant): + +``` +3d3d3373________________________________________14605757363d3d37363d7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af43d6000803e6052573d6000fd5b3d6000f35b3d356020355560408036111560525736038060403d373d3d355af43d6000803e6052573d6000fd +``` + +where `________________________________________` is the 20-byte factory address. + +Runtime bytecode (14-byte factory address subvariant): + +``` +3d3d336d____________________________14605157363d3d37363d7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af43d6000803e604c573d6000fd5b3d6000f35b3d3560203555604080361115604c5736038060403d373d3d355af43d6000803e604c573d6000fd +``` + +where `____________________________` is the 14-byte factory address. + +#### Minimal ERC-1967 transparent upgradeable proxy (I-variant) + +Runtime bytecode (20-byte factory address subvariant): + +``` +3658146083573d3d3373________________________________________14605D57363d3d37363D7f360894a13ba1A3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af43d6000803e6058573d6000fd5b3d6000f35b3d35602035556040360380156058578060403d373d3d355af43d6000803e6058573d6000fd5b602060293d393d51543d52593df3 +``` + +where `________________________________________` is the 20-byte factory address. + +Runtime bytecode (14-byte factory address subvariant): + +``` +365814607d573d3d336d____________________________14605757363d3D37363d7F360894A13Ba1A3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af43d6000803e6052573d6000fd5b3d6000f35b3d35602035556040360380156052578060403d373d3d355af43d6000803e6052573d6000fd5b602060233d393d51543d52593df3 +``` + +where `____________________________` is the 14-byte factory address. + +### Minimal ERC-1967 UUPS proxy + +As this proxy does not contain upgrading logic, the initialization code MUST store the implementation at the ERC-1967 implementation storage slot `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`. + +#### Minimal ERC-1967 UUPS proxy (basic variant) + +Runtime bytecode: + +``` +363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3 +``` + +#### Minimal ERC-1967 UUPS proxy (I-variant) + +Runtime bytecode: + +``` +365814604357363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af43d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3 +``` + +### Minimal ERC-1967 beacon proxy + +As this proxy does not contain upgrading logic, the initialization code MUST store the implementation at the ERC-1967 implementation storage slot `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`. + +#### Minimal ERC-1967 beacon proxy (basic variant) + +Runtime bytecode: + +``` +363d3d373d3d363d602036600436635c60da1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3 +``` + +#### Minimal ERC-1967 beacon proxy (I-variant) + +Runtime bytecode: + +``` +363d3d373d3d363d602036600436635c60da1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3 +``` + +## Rationale + +### No usage of `PUSH0` opcode + +For more widespread EVM compatibility, the proxies deliberately do not use the `PUSH0` opcode proposed in [EIP-3855](./eip-3855.md). + +Converting the proxies to `PUSH0` variants may be done in a separate future ERC. + +### Optimization priorities + +The proxies are first optimized for minimal runtime gas before minimal bytecode size. + +### Minimal nature + +These proxies made from handcrafted EVM bytecode. While utmost efforts have been made to ensure that they are as minimal as possible at the time of development, it is possible that they can be further optimized. If a variant has already been used in the wild, it is preferable to keep their existing layout in this standard, as the benefits of automatic block explorer verification will outweigh the few gas saved during runtime or deployment. + +For historical reference, the [ERC-1167](./eip-1167.md) minimal proxy was not the theoretical minimal at the time of writing. The 0age minimal proxy has lower runtime gas costs and smaller bytecode size. + +### Transparent upgradeable proxy + +The factory address in the transparent upgradeable proxy is baked into the immutable bytecode of the minimal transparent upgradeable proxy. + +This is to save a `SLOAD` for every proxy call. + +As the factory can contain custom authorization logic that allows for admin rotation, we do not lose any flexibility. + +The upgrade logic takes in any 32 byte value and 32 byte storage slot. This is for flexibility and bytecode conciseness. + +We do not lose any security as the implementation can still modify any storage slot. + +### 14-byte factory address subvariants + +It is beneficial to install the transparent upgradeable proxy factory at a vanity address with leading zero bytes so that the proxy's bytecode can be optimized to be shorter. + +A 14-byte factory address (i.e. 6 leading zero bytes) is chosen because it strikes a balance between mining costs and bytecode size. + +### I-variants + +The so-called "I-variants" contain logic that returns the implementation address baked into the proxy bytecode. + +This allows contracts to retrieve the implementation of the proxy onchain in a verifiable way. + +As long as the proxy's runtime bytecode starts with the bytecode in this standard, we can be sure that the implementation address is not spoofed. + +The choice of reserving 1-byte calldata to denote an implementation query request is for efficiency and to prevent calldata collision. Regular ETH transfers use 0-byte calldata, and regular Solidity function calls use calldata that is 4 bytes or longer. + +### Omission of events in bytecode + +This is for minimal bytecode size and deployment costs. + +Most block explorers and indexers are able to deduce the latest implementation without the use of events simply by reading the slots. + +### Immutable arguments are not appended to forwarded calldata + +This is to avoid compatibility and safety issues with other ERC standards that append extra data to the calldata. + +The `EXTCODECOPY` opcode can be used to retrieve the immutable arguments. + +### No fixed initialization code + +As long as the initialization code is able to initialize the relevant ERC-1967 implementation slot where needed (i.e. for the UUPS proxy and Beacon proxy), there is no need for additional requirements on the initialization code. + +### Out of scope topics + +The following topics are intentionally out of scope of this standard, as they can contain custom logic: + +- Factories for proxy deployment. +- Logic for reading and verifying the implementation from the I-variants onchain. +- Beacon for the beacon proxies. + +Nevertheless, they require careful implementation to ensure security and correctness. + +## Backwards Compatibility + +No backward compatibility issues found. + +## Reference Implementation + +### Minimal ERC-1967 transparent upgradeable proxy implementation + +#### Minimal ERC-1967 transparent upgradeable proxy implementation (basic variant) + +```solidity +pragma solidity ^0.8.0; + +library ERC1967MinimalTransparentUpgradeableProxyLib { + function initCodeFor20ByteFactoryAddress() internal view returns (bytes memory) { + return abi.encodePacked( + bytes13(0x607f3d8160093d39f33d3d3373), + address(this), + bytes32(0x14605757363d3d37363d7f360894a13ba1a3210667c828492db98dca3e2076cc), + bytes32(0x3735a920a3ca505d382bbc545af43d6000803e6052573d6000fd5b3d6000f35b), + bytes32(0x3d356020355560408036111560525736038060403d373d3d355af43d6000803e), + bytes7(0x6052573d6000fd) + ); + } + + function initCodeFor14ByteFactoryAddress() internal view returns (bytes memory) { + return abi.encodePacked( + bytes13(0x60793d8160093d39f33d3d336d), + uint112(uint160(address(this))), + bytes32(0x14605157363d3d37363d7f360894a13ba1a3210667c828492db98dca3e2076cc), + bytes32(0x3735a920a3ca505d382bbc545af43d6000803e604c573d6000fd5b3d6000f35b), + bytes32(0x3d3560203555604080361115604c5736038060403d373d3d355af43d6000803e), + bytes7(0x604c573d6000fd) + ); + } + + function initCode() internal view returns (bytes memory) { + if (uint160(address(this)) >> 112 != 0) { + return initCodeFor20ByteFactoryAddress(); + } else { + return initCodeFor14ByteFactoryAddress(); + } + } + + function deploy(address implementation, bytes memory initializationData) + internal + returns (address instance) + { + bytes memory m = initCode(); + assembly { + instance := create(0, add(m, 0x20), mload(m)) + } + require(instance != address(0), "Deployment failed."); + upgrade(instance, implementation, initializationData); + } + + function upgrade(address instance, address implementation, bytes memory upgradeData) internal { + (bool success,) = instance.call( + abi.encodePacked( + // The new implementation address, converted to a 32-byte word. + uint256(uint160(implementation)), + // ERC-1967 implementation slot. + bytes32(0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc), + // Optional calldata to be forwarded to the implementation + // via delegatecall after setting the implementation slot. + upgradeData + ) + ); + require(success, "Upgrade failed."); + } +} +``` + +#### Minimal ERC-1967 transparent upgradeable proxy implementation (I-variant) + +```solidity +pragma solidity ^0.8.0; + +library ERC1967IMinimalTransparentUpgradeableProxyLib { + function initCodeFor20ByteFactoryAddress() internal view returns (bytes memory) { + return abi.encodePacked( + bytes19(0x60923d8160093d39f33658146083573d3d3373), + address(this), + bytes20(0x14605D57363d3d37363D7f360894a13ba1A32106), + bytes32(0x67c828492db98dca3e2076cc3735a920a3ca505d382bbc545af43d6000803e60), + bytes32(0x58573d6000fd5b3d6000f35b3d35602035556040360380156058578060403d37), + bytes32(0x3d3d355af43d6000803e6058573d6000fd5b602060293d393d51543d52593df3) + ); + } + + function initCodeFor14ByteFactoryAddress() internal view returns (bytes memory) { + return abi.encodePacked( + bytes19(0x608c3d8160093d39f3365814607d573d3d336d), + uint112(uint160(address(this))), + bytes20(0x14605757363d3D37363d7F360894A13Ba1A32106), + bytes32(0x67c828492db98dca3e2076cc3735a920a3ca505d382bbc545af43d6000803e60), + bytes32(0x52573d6000fd5b3d6000f35b3d35602035556040360380156052578060403d37), + bytes32(0x3d3d355af43d6000803e6052573d6000fd5b602060233d393d51543d52593df3) + ); + } + + function initCode() internal view returns (bytes memory) { + if (uint160(address(this)) >> 112 != 0) { + return initCodeFor20ByteFactoryAddress(); + } else { + return initCodeFor14ByteFactoryAddress(); + } + } + + function deploy(address implementation, bytes memory initializationData) + internal + returns (address instance) + { + bytes memory m = initCode(); + assembly { + instance := create(0, add(m, 0x20), mload(m)) + } + require(instance != address(0), "Deployment failed."); + upgrade(instance, implementation, initializationData); + } + + function upgrade(address instance, address implementation, bytes memory upgradeData) internal { + (bool success,) = instance.call( + abi.encodePacked( + // The new implementation address, converted to a 32-byte word. + uint256(uint160(implementation)), + // ERC-1967 implementation slot. + bytes32(0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc), + // Optional calldata to be forwarded to the implementation + // via delegatecall after setting the implementation slot. + upgradeData + ) + ); + require(success, "Upgrade failed."); + } +} +``` + +### Minimal ERC-1967 UUPS proxy implementation + +#### Minimal ERC-1967 UUPS proxy implementation (basic variant) + +```solidity +pragma solidity ^0.8.0; + +library ERC1967MinimalUUPSProxyLib { + function initCode(address implementation, bytes memory args) + internal + pure + returns (bytes memory) + { + uint256 n = 0x003d + args.length; + require(n <= 0xffff, "Immutable args too long."); + return abi.encodePacked( + bytes1(0x61), + uint16(n), + bytes7(0x3d8160233d3973), + implementation, + bytes2(0x6009), + bytes32(0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076), + bytes32(0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3), + args + ); + } + + function deploy(address implementation, bytes memory args) + internal + returns (address instance) + { + bytes memory m = initCode(implementation, args); + assembly { + instance := create(0, add(m, 0x20), mload(m)) + } + require(instance != address(0), "Deployment failed."); + } +} +``` + +#### Minimal ERC-1967 UUPS proxy implementation (I-variant) + +```solidity +pragma solidity ^0.8.0; + +library ERC1967IMinimalUUPSProxyLib { + function initCode(address implementation, bytes memory args) + internal + pure + returns (bytes memory) + { + uint256 n = 0x0052 + args.length; + require(n <= 0xffff, "Immutable args too long."); + return abi.encodePacked( + bytes1(0x61), + uint16(n), + bytes7(0x3d8160233d3973), + implementation, + bytes23(0x600f5155f3365814604357363d3d373d3d363d7f360894), + bytes32(0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4), + bytes32(0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3), + args + ); + } + + function deploy(address implementation, bytes memory args) + internal + returns (address instance) + { + bytes memory m = initCode(implementation, args); + assembly { + instance := create(0, add(m, 0x20), mload(m)) + } + require(instance != address(0), "Deployment failed."); + } +} +``` + +### Minimal ERC-1967 beacon proxy implementation + +#### Minimal ERC-1967 beacon proxy implementation (basic variant) + +```solidity +pragma solidity ^0.8.0; + +library ERC1967MinimalBeaconProxyLib { + function initCode(address beacon, bytes memory args) internal pure returns (bytes memory) { + uint256 n = 0x0052 + args.length; + require(n <= 0xffff, "Immutable args too long."); + return abi.encodePacked( + bytes1(0x61), + uint16(n), + bytes7(0x3d8160233d3973), + beacon, + bytes23(0x60195155f3363d3d373d3d363d602036600436635c60da), + bytes32(0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c), + bytes32(0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3), + args + ); + } + + function deploy(address beacon, bytes memory args) internal returns (address instance) { + bytes memory m = initCode(beacon, args); + assembly { + instance := create(0, add(m, 0x20), mload(m)) + } + require(instance != address(0), "Deployment failed."); + } +} +``` + +#### Minimal ERC-1967 beacon proxy implementation (I-variant) + +```solidity +pragma solidity ^0.8.0; + +library ERC1967IMinimalBeaconProxyLib { + function initCode(address beacon, bytes memory args) internal pure returns (bytes memory) { + uint256 n = 0x0057 + args.length; + require(n <= 0xffff, "Immutable args too long."); + return abi.encodePacked( + bytes1(0x61), + uint16(n), + bytes7(0x3d8160233d3973), + beacon, + bytes28(0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36), + bytes32(0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513), + bytes32(0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3), + args + ); + } + + function deploy(address beacon, bytes memory args) internal returns (address instance) { + bytes memory m = initCode(beacon, args); + assembly { + instance := create(0, add(m, 0x20), mload(m)) + } + require(instance != address(0), "Deployment failed."); + } +} +``` + +## Security Considerations + +### Transparent upgradeable proxy factory security considerations + +To ensure security, the transparent upgradeable proxy factory must implement proper access control to allow proxies to be upgraded by only authorized accounts. + +### Calldata length collision for I-variants + +The I-variants reserve all calldata of length 1 to denote a request to return the implementation. This may pose compatibility issues if the underlying implementation actually uses 1-byte calldata for special purposes. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/ERCS/erc-838.md b/ERCS/erc-838.md new file mode 100644 index 0000000000..2e6369dcef --- /dev/null +++ b/ERCS/erc-838.md @@ -0,0 +1,83 @@ +--- +eip: 838 +title: ABI specification for REVERT reason string +description: A proposal to extend the ABI specification to include typed errors in the REVERT reason string. +author: Federico Bond (@federicobond), Renan Rodrigues de Souza (@RenanSouza2) +discussions-to: https://ethereum-magicians.org/t/eip-838-what-is-the-current-status/14671 +status: Draft +type: Standards Track +category: ERC +created: 2020-08-20 +--- + +## Abstract + +This proposal specifies how to encode potential error conditions in the JSON ABI of a smart contract. A high-level language could then provide a syntax for declaring and throwing these errors. The compiler will encode these errors in the reason parameter of the REVERT opcode in a way that can be easily reconstructed by libraries such as web3. + + +## Motivation + +It's important to provide clear feedback to users (and developers) about what went wrong with their Ethereum transactions. The REVERT opcode is a step in the right direction, as it allows smart contract developers to encode a message describing the failure in the reason parameter. There is an implementation under review in Solidity that accepts a string, thus providing a low-level interface to this parameter. However, standardizing a method for passing errors from this parameter back to clients will bring many benefits to both users and developers. + +The key words "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. + +## Specification + +To conform to this specification, compilers producing JSON ABIs SHOULD include error declarations alongside functions and events. Each error object MUST contain the keys name (string) and arguments (same types as the function’s inputs list). The value of type MUST be "error". + +Example: + +``` +{ "type": "error", "name": "InsufficientBalance", "arguments": [ { "name": "amount", "type": "uint256" } ] } +``` + +A selector for this error can be computed from its signature (InsufficientBalance() for the example above) in the same way that it's currently done for public functions and events. This selector MUST be included in the reason string so that clients can perform a lookup. Any arguments for the error are RLP encoded in the same way as return values from functions. The exact format in which both the selector and the arguments are encoded is to be defined. The Solidity implementation mentioned above leaves room for expansion by prefixing the free-form string with uint256(0). + +A high-level language like Solidity can then implement a syntax like this: + +``` +contract MyToken { + error InsufficientFunds(uint256 amount); + + function transfer(address _to, uint256 _amount) { + if (balances[msg.sender] <= _amount) + throw InsufficientFunds(_amount); + ... + } + ... +} +``` + +### Possible extensions + + +1. A NatSpec comment above the error declaration can be used to provide a default error message. Arguments to the error can be interpolated in the message string with familiar NatSpec syntax. + +``` +/// @notice You don't have enough funds to transfer `amount`. +error InsufficientFunds(uint256 amount); +``` + +2. A function may declare to its callers which errors it can throw. A list of these errors must be included in the JSON ABI item for that function, under the `errors` key. Example: + +``` +function transfer(address _to, uint256 _amount) throws(InsufficientFunds); +``` + +Special consideration should be given to error overloading if we want to support a similar syntax in the future, as errors with same name but different arguments will produce a different selector. + +## Rationale + +Needs discussion. + +## Backwards Compatibility + +Apps and tools that have not implemented this spec can ignore the encoded reason string when it's not prefixed by zero. + +## Security Considerations + +Needs discussion. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/_includes/head.html b/_includes/head.html index bc8a94533a..d388a866fa 100644 --- a/_includes/head.html +++ b/_includes/head.html @@ -43,7 +43,6 @@ {%- feed_meta -%} - + + + + + + Info + + + +

+ +
+
+ + + + Mint + + + + + + + + + +

+ +
+

Use this to mint your own tokens.

+
+
+ + \ No newline at end of file diff --git a/assets/erc-7738/tokenscript/info.html b/assets/erc-7738/tokenscript/info.html new file mode 100644 index 0000000000..9755f14073 --- /dev/null +++ b/assets/erc-7738/tokenscript/info.html @@ -0,0 +1,181 @@ + +
+

ERC-7738 registry

+ +

Contract

+
+ +

Order #

+
+ +

ScriptURI

+
+ +

ENS

+
+ +

Name

+
+ +

Authenticated by Owner

+
+ +
+ + \ No newline at end of file diff --git a/assets/erc-7738/tokenscript/onboard.html b/assets/erc-7738/tokenscript/onboard.html new file mode 100644 index 0000000000..f5e19e4f6b --- /dev/null +++ b/assets/erc-7738/tokenscript/onboard.html @@ -0,0 +1,221 @@ + +

Create a new script mapping for a contract. Input the contract address and the scriptURI

+

+
+

Contract Address

+ + Not a valid contract + +
+
+

ScriptURI

+ + Not a valid URL + +
+ + +

+ \ No newline at end of file diff --git a/assets/erc-7738/tokenscript/out/tokenscript.tsml b/assets/erc-7738/tokenscript/out/tokenscript.tsml new file mode 100644 index 0000000000..6b7762175c --- /dev/null +++ b/assets/erc-7738/tokenscript/out/tokenscript.tsml @@ -0,0 +1,1163 @@ + + + + + ERC-7738 Registry Token + + + ERC-7738 Registry Tokens + + + + + 0x0077380bCDb2717C9640e892B9d5Ee02Bb5e0682 + + + [ + { + "name": "setScriptURI", + "inputs": [ + { + "internalType": "address", + "name": "contractAddressLocal", + "type": "address" + }, + { + "internalType": "string[]", + "name": "scriptURILocal", + "type": "string[]" + } + ], + "outputs": [ + + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "name": "updateScriptURI", + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "string", + "name": "scriptURILocal", + "type": "string" + } + ], + "outputs": [ + + ], + "stateMutability": "nonpayable", + "type": "function" + } + ] + + + + + + + + + + + Info + + + + + +
+

ERC-7738 registry

+ +

Contract

+
+ +

Order #

+
+ +

ScriptURI

+
+ +

ENS

+
+ +

Name

+
+ +

Authenticated by Owner

+
+ +
+ +
+
+ + + + Update ScriptURI + + + + + + + + + + + + + +

Create a new script mapping for a contract. Input the contract address and the scriptURI

+

+
+

Current ScriptURI

+
+
+
+

+
+

ScriptURI

+ + Not a valid URL + +
+ + +

+
+
+ + + + Set Name + + + + + + + + + + + + + +

Add a name for your script entry

+

+
+

Current Name

+
+
+
+

+
+

Name

+ +
+ +

+
+
+ + + + Set Icon + + + + + + + + + + + + + +
+

Set an icon for your script entry

+

(note, you must upload the icon yourself and provide the URI here)

+
+

+
+

Current Icon

+
+
+
+

+
+

Icon URL

+ + Not a valid URL + +
+ + +

+
+
+ + + + Set ScriptURI + + + + + + + + + + + + + + +

Create a new script mapping for a contract. Input the contract address and the scriptURI

+

+
+

Contract Address

+ + Not a valid contract + +
+
+

ScriptURI

+ + Not a valid URL + +
+ + +

+
+
+
+ + + 1.3.6.1.4.1.1466.115.121.1.36 + + + + totalSupply + + + + + + + + +
\ No newline at end of file diff --git a/assets/erc-7738/tokenscript/setIcon.html b/assets/erc-7738/tokenscript/setIcon.html new file mode 100644 index 0000000000..5497415e1c --- /dev/null +++ b/assets/erc-7738/tokenscript/setIcon.html @@ -0,0 +1,226 @@ + +
+

Set an icon for your script entry

+

(note, you must upload the icon yourself and provide the URI here)

+
+

+
+

Current Icon

+
+
+
+

+
+

Icon URL

+ + Not a valid URL + +
+ + +

+ \ No newline at end of file diff --git a/assets/erc-7738/tokenscript/setName.html b/assets/erc-7738/tokenscript/setName.html new file mode 100644 index 0000000000..6f5858a163 --- /dev/null +++ b/assets/erc-7738/tokenscript/setName.html @@ -0,0 +1,165 @@ + +

Add a name for your script entry

+

+
+

Current Name

+
+
+
+

+
+

Name

+ +
+ +

+ \ No newline at end of file diff --git a/assets/erc-7738/tokenscript/tokenscript.xml b/assets/erc-7738/tokenscript/tokenscript.xml new file mode 100644 index 0000000000..d7a46fcc42 --- /dev/null +++ b/assets/erc-7738/tokenscript/tokenscript.xml @@ -0,0 +1,170 @@ + + + + + + ERC-7738 Registry Token + + + ERC-7738 Registry Tokens + + + + + 0x0077380bCDb2717C9640e892B9d5Ee02Bb5e0682 + + + + + + + + + + + + Info + + + + + + + + + + Update ScriptURI + + + + + + + + + + + + + + + + + + Set Name + + + + + + + + + + + + + + + + + + Set Icon + + + + + + + + + + + + + + + + + + Set ScriptURI + + + + + + + + + + + + + + + + + + + 1.3.6.1.4.1.1466.115.121.1.36 + + + + totalSupply + + + + + + + + + \ No newline at end of file diff --git a/assets/erc-7738/tokenscript/updateScript.html b/assets/erc-7738/tokenscript/updateScript.html new file mode 100644 index 0000000000..7a5e8341dc --- /dev/null +++ b/assets/erc-7738/tokenscript/updateScript.html @@ -0,0 +1,191 @@ + +

Create a new script mapping for a contract. Input the contract address and the scriptURI

+

+
+

Current ScriptURI

+
+
+
+

+
+

ScriptURI

+ + Not a valid URL + +
+ + +

+ \ No newline at end of file diff --git a/assets/erc-7738/tsconfig.json b/assets/erc-7738/tsconfig.json new file mode 100644 index 0000000000..574e785c71 --- /dev/null +++ b/assets/erc-7738/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "commonjs", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true + } +} diff --git a/assets/erc-7744/CodeIndex.sol b/assets/erc-7744/CodeIndex.sol new file mode 100644 index 0000000000..bd7a076378 --- /dev/null +++ b/assets/erc-7744/CodeIndex.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity 0.8.20; +import "./ICodeIndex.sol"; + +/** + * @title Byte Code Indexer Contract + * @notice You can use this contract to index contracts by their bytecode. + * @dev This allows to query contracts by their bytecode instead of addresses. + * @author Tim Pechersky (@Peersky) + */ +contract CodeIndex is ICodeIndex { + mapping(bytes32 => address) private index; + + /** + * @notice Registers a contract in the index by its bytecode hash + * @param container The contract to register + * @dev `msg.codeHash` will be used + * @dev It will revert if the contract is already indexed + */ + function register(address container) external { + address etalon = index[container.codehash]; + if (etalon != address(0)) { + revert alreadyExists(container.codehash, etalon); + } + index[container.codehash] = container; + emit Indexed(container, container.codehash); + } + + /** + * @notice Returns the contract address by its bytecode hash + * @dev returns zero if the contract is not indexed + * @param id The bytecode hash + * @return The contract address + */ + function get(bytes32 id) external view returns (address) { + return index[id]; + } +} \ No newline at end of file diff --git a/assets/erc-7744/ICodeIndex.sol b/assets/erc-7744/ICodeIndex.sol new file mode 100644 index 0000000000..aadd4f4f3d --- /dev/null +++ b/assets/erc-7744/ICodeIndex.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity 0.8.20; + +interface ICodeIndex { + event Indexed(address indexed container, bytes32 indexed codeHash); + error alreadyExists(bytes32 id, address source); + + function register(address container) external; + + function get(bytes32 id) external view returns (address); +} \ No newline at end of file diff --git a/assets/erc-7746/ILayer.sol b/assets/erc-7746/ILayer.sol new file mode 100644 index 0000000000..283773b31c --- /dev/null +++ b/assets/erc-7746/ILayer.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity =0.8.20; + +interface ILayer { + function beforeCall( + bytes memory configuration, + bytes4 selector, + address sender, + uint256 value, + bytes memory data + ) external returns (bytes memory); + + function afterCall( + bytes memory configuration, + bytes4 selector, + address sender, + uint256 value, + bytes memory data, + bytes memory beforeCallResult + ) external; +} diff --git a/assets/erc-7746/README.md b/assets/erc-7746/README.md new file mode 100644 index 0000000000..2d1594c300 --- /dev/null +++ b/assets/erc-7746/README.md @@ -0,0 +1,7 @@ +# References for ERC-7746 + +In this directory you can find a reference implementation of the ILayer interface and a sample MockERC20 contract. + +In this test, a [Protected.sol](./test/Protected.sol) contract is protected by a [RateLimitLayer.sol](./test/RateLimitLayer.sol) layer. The RateLimitLayer implements the ILayer interface and enforces a rate which client has configured. +The Drainer simulates a vulnerable contract that acts in a malicious way. In the `test.ts` The Drainer contract is trying to drain the funds from the Protected contract. It is assumed that Protected contract has bug that allows partial unauthorized access to the state. +The RateLimitLayer is configured to allow only 10 transactions per block from same sender. The test checks that the Drainer contract is not able to drain the funds from the Protected contract. \ No newline at end of file diff --git a/assets/erc-7746/test/AccessLayers.sol b/assets/erc-7746/test/AccessLayers.sol new file mode 100644 index 0000000000..c7a08503ed --- /dev/null +++ b/assets/erc-7746/test/AccessLayers.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 + +/** + * Author: @Peersky https://github.com/peersky + */ + +pragma solidity =0.8.20; +import "./LibAccessLayers.sol"; + +abstract contract AccessLayers { + modifier layers( + bytes4 _selector, + address sender, + bytes calldata data, + uint256 value + ) { + bytes[] memory layerReturns = LibAccessLayers.beforeCall(_selector, sender, data, value); + _; + LibAccessLayers.afterCall(_selector, sender, data, value, layerReturns); + } +} diff --git a/assets/erc-7746/test/Drainer.sol b/assets/erc-7746/test/Drainer.sol new file mode 100644 index 0000000000..7eb8d05422 --- /dev/null +++ b/assets/erc-7746/test/Drainer.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: CC0-1.0 +import "./MockERC20.sol"; +pragma solidity =0.8.20; + +contract Drainer { + constructor() {} + + function drain(address payable victim, uint256 cycles) public { + for (uint256 i = 0; i < cycles; i++) { + MockERC20(victim).mint(address(this), 1); + } + } +} diff --git a/assets/erc-7746/test/IVictim.sol b/assets/erc-7746/test/IVictim.sol new file mode 100644 index 0000000000..3320104d6d --- /dev/null +++ b/assets/erc-7746/test/IVictim.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity =0.8.20; + +interface IVictim { + function drainedMethod() external; +} diff --git a/assets/erc-7746/test/LibAccessLayers.sol b/assets/erc-7746/test/LibAccessLayers.sol new file mode 100644 index 0000000000..29d9c3d40d --- /dev/null +++ b/assets/erc-7746/test/LibAccessLayers.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity =0.8.20; +import "../ILayer.sol"; + +library LibAccessLayers { + bytes32 constant ACCESS_LAYERS_STORAGE_POSITION = keccak256("lib.access.layer.storage"); + + struct LayerStruct { + address layerAddess; + bytes4 beforeSig; + bytes4 afterSig; + bytes layerConfigData; + } + + function accessLayersStorage() internal pure returns (LayerStruct[] storage ls) { + bytes32 position = ACCESS_LAYERS_STORAGE_POSITION; + assembly { + ls.slot := position + } + } + + function setLayer( + address layerAddress, + uint256 layerIndex, + bytes memory layerConfigData, + bytes4 beforeCallMethodSignature, + bytes4 afterCallMethodSignature + ) internal { + LayerStruct[] storage ls = accessLayersStorage(); + ls[layerIndex].layerAddess = layerAddress; + ls[layerIndex].layerConfigData = layerConfigData; + ls[layerIndex].beforeSig = beforeCallMethodSignature; + ls[layerIndex].afterSig = afterCallMethodSignature; + } + + function addLayer(LayerStruct memory newLayer) internal { + LayerStruct[] storage ls = accessLayersStorage(); + ls.push(newLayer); + } + + function setLayers(LayerStruct[] memory newLayers) internal { + for (uint256 i = 0; i < newLayers.length; i++) { + addLayer(newLayers[i]); + } + } + + function addLayer( + address layerAddress, + bytes memory layerConfigData, + bytes4 beforeCallMethodSignature, + bytes4 afterCallMethodSignature + ) internal { + LayerStruct[] storage ls = accessLayersStorage(); + LayerStruct memory newLayer = LayerStruct({ + layerAddess: layerAddress, + layerConfigData: layerConfigData, + beforeSig: beforeCallMethodSignature, + afterSig: afterCallMethodSignature + }); + ls.push(newLayer); + } + + function popLayer() internal { + LayerStruct[] storage ls = accessLayersStorage(); + ls.pop(); + } + + function getLayer(uint256 layerIdx) internal view returns (LayerStruct storage) { + LayerStruct[] storage ls = accessLayersStorage(); + return ls[layerIdx]; + } + + function beforeCall( + bytes4 _selector, + address sender, + bytes calldata data, + uint256 value + ) internal returns (bytes[] memory) { + LayerStruct[] storage ls = accessLayersStorage(); + bytes[] memory layerReturns = new bytes[](ls.length); + for (uint256 i = 0; i < ls.length; i++) { + layerReturns[i] = validateLayerBeforeCall(ls[i], _selector, sender, data, value); + } + return layerReturns; + } + + function validateLayerBeforeCall( + LayerStruct storage layer, + bytes4 _selector, + address sender, + bytes memory data, + uint256 value + ) internal returns (bytes memory) { + bytes memory retval = ILayer(layer.layerAddess).beforeCallValidation( + layer.layerConfigData, + _selector, + sender, + value, + data + ); + + return retval; + } + + function afterCall( + bytes4 _selector, + address sender, + bytes calldata data, + uint256 value, + bytes[] memory beforeCallReturns + ) internal { + LayerStruct[] storage ls = accessLayersStorage(); + for (uint256 i = 0; i < ls.length; i++) { + validateLayerAfterCall(ls[ls.length - 1 - i], _selector, sender, data, value, beforeCallReturns[i]); + } + } + + function extractRevertReason(bytes memory revertData) internal pure returns (string memory reason) { + uint l = revertData.length; + if (l < 68) return ""; + uint t; + assembly { + revertData := add(revertData, 4) + t := mload(revertData) // Save the content of the length slot + mstore(revertData, sub(l, 4)) // Set proper length + } + reason = abi.decode(revertData, (string)); + assembly { + mstore(revertData, t) // Restore the content of the length slot + } + } + + function validateLayerAfterCall( + LayerStruct storage layer, + bytes4 _selector, + address sender, + bytes calldata data, + uint256 value, + bytes memory beforeCallReturnValue + ) internal { + ILayer(layer.layerAddess).afterCallValidation( + layer.layerConfigData, + _selector, + sender, + value, + data, + beforeCallReturnValue + ); + } +} diff --git a/assets/erc-7746/test/MockERC20.sol b/assets/erc-7746/test/MockERC20.sol new file mode 100644 index 0000000000..2a78e84c0b --- /dev/null +++ b/assets/erc-7746/test/MockERC20.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: CC0-1.0 +import "@openzeppelin/contracts-upgradable/token/ERC20/extensions/ERC20Burnable.sol"; +import "@openzeppelin/contracts-upgradalbe/access/OwnableUpgradeable.sol"; + +pragma solidity ^0.8.0; + +contract MockERC20 is ERC20Burnable, Ownable { + uint256 numTokens; + + constructor() ERC20("Mock", "MCK") Ownable(msg.sender) {} + + function mint(address to, uint256 amount) public { + require(to != address(0), "MockERC20->mint: Address not specified"); + require(amount != 0, "MockERC20->mint: amount not specified"); + _mint(to, amount); + } +} diff --git a/assets/erc-7746/test/Protected.sol b/assets/erc-7746/test/Protected.sol new file mode 100644 index 0000000000..aebd1bfc32 --- /dev/null +++ b/assets/erc-7746/test/Protected.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity =0.8.20; + +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "./AccessLayers.sol"; +import "./LibAccessLayers.sol"; + +contract Protected is TransparentUpgradeableProxy, AccessLayers { + uint256 balance = 10000000 ether; + + constructor( + address initialOwner, + LibAccessLayers.LayerStruct[] memory layers, + address initialImplementation + ) TransparentUpgradeableProxy(initialImplementation, initialOwner, "") { + LibAccessLayers.setLayers(layers); + } + + event Transfer(address from, address to, uint256 amount); + + fallback() external payable override layers(msg.sig, msg.sender, msg.data, msg.value) { + // _delegate(_implementation()); <- this method will not return to solidity :( + (bool success, bytes memory result) = _implementation().delegatecall(msg.data); + require(success, string(result)); + } + + receive() external payable { + // custom function code + } +} diff --git a/assets/erc-7746/test/RateLimitLayer.sol b/assets/erc-7746/test/RateLimitLayer.sol new file mode 100644 index 0000000000..c576ff9f86 --- /dev/null +++ b/assets/erc-7746/test/RateLimitLayer.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity =0.8.20; +import "../ILayer.sol"; + +contract RateLimitLayer is ILayer { + mapping(address => mapping(bytes4 => uint256)) usage; + mapping(address => mapping(bytes4 => uint256)) usageUpdatedAtBlock; + + function beforeCallValidation( + bytes memory, + bytes4 messageSig, + address, + uint256, + bytes memory + ) public returns (bytes memory) { + if (usageUpdatedAtBlock[msg.sender][messageSig] != block.number) { + usage[msg.sender][messageSig] = 0; + usageUpdatedAtBlock[msg.sender][messageSig] = block.number; + } else { + usage[msg.sender][messageSig] += 1; + } + return ""; + } + + function afterCallValidation( + bytes memory layerConfig, + bytes4 messageSig, + address, + uint256, + bytes memory, + bytes memory + ) public view { + uint256 blockQuota = uint256(bytes32(layerConfig)); + require(usage[msg.sender][messageSig] < blockQuota, "Rate limited"); + } +} diff --git a/assets/erc-7746/test/deploy/drainer.ts b/assets/erc-7746/test/deploy/drainer.ts new file mode 100644 index 0000000000..7f67b20d28 --- /dev/null +++ b/assets/erc-7746/test/deploy/drainer.ts @@ -0,0 +1,19 @@ +import { HardhatRuntimeEnvironment } from "hardhat/types"; +import { DeployFunction } from "hardhat-deploy/types"; + +const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { + const { deployments, getNamedAccounts } = hre; + const { deploy } = deployments; + + const { deployer, owner } = await getNamedAccounts(); + + await deploy("Drainer", { + from: deployer, + args: [], + skipIfAlreadyDeployed: true, + }); +}; + +export default func; +func.dependencies = ["layer_proxy"]; +func.tags = ["poc", "drainer"]; diff --git a/assets/erc-7746/test/deploy/layered_proxy.ts b/assets/erc-7746/test/deploy/layered_proxy.ts new file mode 100644 index 0000000000..ed7c223088 --- /dev/null +++ b/assets/erc-7746/test/deploy/layered_proxy.ts @@ -0,0 +1,40 @@ +import { HardhatRuntimeEnvironment } from "hardhat/types"; +import { DeployFunction } from "hardhat-deploy/types"; +import { MockERC20, RateLimitLayer } from "../types"; +import { ethers } from "hardhat"; +import { LibAccessLayers } from "../types/src/MockERC20"; + +const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { + const { deployments, getNamedAccounts } = hre; + const { deploy } = deployments; + + const { deployer, owner } = await getNamedAccounts(); + + const simpleLayer = await deployments.get("RateLimitLayer"); + const simpleLayerContract = (await ethers.getContractAt(simpleLayer.abi, simpleLayer.address)) as RateLimitLayer; + + let layer: LibAccessLayers.LayerStructStruct = { + layerAddess: simpleLayer.address, + beforeSig: simpleLayerContract.interface.getSighash(simpleLayerContract.interface.functions["beforeCallValidation(bytes,bytes4,address,uint256,bytes)"]), + afterSig: simpleLayerContract.interface.getSighash( + simpleLayerContract.interface.functions["afterCallValidation(bytes,bytes4,address,uint256,bytes,bytes)"] + ), + layerConfigData: ethers.utils.defaultAbiCoder.encode(["uint256"], [10]), + }; + + const result = await deploy("MockERC20", { + from: deployer, + args: [], + skipIfAlreadyDeployed: true, + }); + + const lp = await deploy("MockERC20", { + from: deployer, + args: [owner, [layer], result.address], + skipIfAlreadyDeployed: true, + }); +}; + +export default func; +func.dependencies = ["simple_layer"]; +func.tags = ["poc", "layer_proxy"]; diff --git a/assets/erc-7746/test/deploy/simple_layer.ts b/assets/erc-7746/test/deploy/simple_layer.ts new file mode 100644 index 0000000000..a4baa8fc61 --- /dev/null +++ b/assets/erc-7746/test/deploy/simple_layer.ts @@ -0,0 +1,18 @@ +import { HardhatRuntimeEnvironment } from "hardhat/types"; +import { DeployFunction } from "hardhat-deploy/types"; + +const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { + const { deployments, getNamedAccounts } = hre; + const { deploy } = deployments; + + const { deployer } = await getNamedAccounts(); + + await deploy("RateLimitLayer", { + from: deployer, + args: [], + skipIfAlreadyDeployed: true, + }); +}; + +export default func; +func.tags = ["poc", "simple_layer"]; diff --git a/assets/erc-7746/test/test.ts b/assets/erc-7746/test/test.ts new file mode 100644 index 0000000000..94b469043b --- /dev/null +++ b/assets/erc-7746/test/test.ts @@ -0,0 +1,40 @@ +/* global ethers */ + +import { deployments, ethers } from "hardhat"; +import { HardhatRuntimeEnvironment } from "hardhat/types"; +import { DeployFunction } from "hardhat-deploy/types"; + +import { expect } from "chai"; +import { Drainer, Protected, MockERC20 } from "../types"; + +const setupTest = deployments.createFixture(async ({ deployments, getNamedAccounts, ethers: _eth }, options) => { + const { deployer, owner } = await getNamedAccounts(); + await deployments.fixture(["poc"]); + console.warn(deployer, owner); + const c = await deployments.get("Protected"); + const d = await deployments.get("Drainer"); + return { + owner, + deployer, + victim: (await ethers.getContractAt(c.abi, c.address)) as Protected, + attacker: (await ethers.getContractAt(d.abi, d.address)) as Drainer, + }; +}); + +describe("test drainage", async function () { + let env: { + owner: string; + deployer: string; + victim: Protected; + attacker: Drainer; + }; + beforeEach(async function () { + env = await setupTest(); + }); + it("succeeds below 10 transactions", async () => { + await expect(env.attacker.drain(env.victim.address, 1)).to.emit(env.victim, "Transfer"); + }); + it("fails beyond 10 transactions", async () => { + await expect(env.attacker.drain(env.victim.address, 11)).to.be.revertedWith("Rate limited"); + }); +});