diff --git a/ERCS/erc-1066.md b/ERCS/erc-1066.md index 7f60fe566e..8b8c2a813a 100644 --- a/ERCS/erc-1066.md +++ b/ERCS/erc-1066.md @@ -236,7 +236,7 @@ Currently unspecified. (Full range reserved) #### `0x7*` TBD -Currently unspecifie. (Full range reserved) +Currently unspecified. (Full range reserved) #### `0x8*` TBD diff --git a/ERCS/erc-1077.md b/ERCS/erc-1077.md index 4ae82ddc2c..ca2a8a91f2 100644 --- a/ERCS/erc-1077.md +++ b/ERCS/erc-1077.md @@ -85,7 +85,7 @@ 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 the reward. -The [EIP-191] message must be constructed as following: +The [EIP-191] message must be constructed as follows: ```solidity keccak256( abi.encodePacked( diff --git a/ERCS/erc-1081.md b/ERCS/erc-1081.md index f73b2c7011..bbb330361f 100644 --- a/ERCS/erc-1081.md +++ b/ERCS/erc-1081.md @@ -28,7 +28,7 @@ After studying bounties as they've existed for thousands of years (and after imp To implement these steps, a number of functions are needed: - `initializeBounty(address _issuer, address _arbiter, string _data, uint _deadline)`: This is used when deploying a new StandardBounty contract, and is particularly useful when applying the proxy design pattern, whereby bounties cannot be initialized in their constructors. Here, the data string should represent an IPFS hash, corresponding to a JSON object which conforms to the schema (described below). - `fulfillBounty(address[] _fulfillers, uint[] _numerators, uint _denomenator, string _data)`: This is called to submit a fulfillment, submitting a string representing an IPFS hash which contains the deliverable for the bounty. Initially fulfillments could only be submitted by one individual at a time, however users consistently told us they desired to be able to collaborate on fulfillments, thereby allowing the credit for submissions to be shared by several parties. The lines along which eventual payouts are split are determined by the fractions of the submission credited to each fulfiller (using the array of numerators and single denominator). Here, a bounty platform may also include themselves as a collaborator to collect a small fee for matching the bounty with fulfillers. -- `acceptFulfillment(uint _fulfillmentId, StandardToken[] _payoutTokens, uint[] _tokenAmounts)`: This is called by the `issuer` or the `arbiter` to pay out a given fulfillment, using an array of tokens, and an array of amounts of each token to be split among the contributors. This allows for the bounty payout amount to move as it needs to based on incoming contributions (which may be transferred directly to the contract address). It also allows for the easy splitting of a given bounty's balance among several fulfillments, if the need should arise. +- `acceptFulfillment(uint _fulfillmentId, StandardToken[] _payoutTokens, uint[] _tokenAmounts)`: This is called by the `issuer` or the `arbiter` to pay out a given fulfillment, using an array of tokens, and an array of amounts of each token to be split among the contributors. This allows for the bounty payout amount to move as it needs to be based on incoming contributions (which may be transferred directly to the contract address). It also allows for the easy splitting of a given bounty's balance among several fulfillments, if the need should arise. - `drainBounty(StandardToken[] _payoutTokens)`: This may be called by the `issuer` to drain a bounty of it's funds, if the need should arise. - `changeBounty(address _issuer, address _arbiter, string _data, uint _deadline)`: This may be called by the `issuer` to change the `issuer`, `arbiter`, `data`, and `deadline` fields of their bounty. - `changeIssuer(address _issuer)`: This may be called by the `issuer` to change to a new `issuer` if need be diff --git a/ERCS/erc-4626.md b/ERCS/erc-4626.md index c28df5cba1..41de5ab9c0 100644 --- a/ERCS/erc-4626.md +++ b/ERCS/erc-4626.md @@ -576,7 +576,7 @@ For production implementations of Vaults which do not use EIP-4626, wrapper adap ## Reference Implementation -See [Solmate EIP-4626](https://github.com/Rari-Capital/solmate/blob/main/src/mixins/ERC4626.sol): +See [Solmate EIP-4626](https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC4626.sol): a minimal and opinionated implementation of the standard with hooks for developers to easily insert custom logic into deposits and withdrawals. See [Vyper EIP-4626](https://github.com/fubuloubu/ERC4626): diff --git a/ERCS/erc-5173.md b/ERCS/erc-5173.md index 61eb9e4547..0eb2fcfe42 100644 --- a/ERCS/erc-5173.md +++ b/ERCS/erc-5173.md @@ -317,14 +317,14 @@ Any realized profits (P) when an NFT is sold are distributed among the buyers/ow We define a sliding window mechanism to decide which previous owners will be involved in the profit distribution. Let's imagine the owners as a queue starting from the first hand owner to the current owner. The profit distribution window starts from the previous owner immediately to the current owner and extends towards the first owner, and the size of the windows is fixed. Only previous owners located inside the window will join the profit distribution. -![Future Rewards calculation formula](../assets/eip-5173/nFR_distribution_formula.png?raw=true) +![Future Rewards calculation formula](../assets/eip-5173/nFR_distribution_formula.jpg?raw=true) In this equation: - P is the total profit, the difference between the selling price minus the buying price; -- r is buyer reward ratio of the total P; -- g is the common ratio of successive in the geometric sequence; -- n is the actual number of owners eligible and participating in the future rewards sharing. To calculate n, we have n = min(m, w), where m is the current number of owners for a token, and w is the window size of the profit distribution sliding window algorithm +- _R_ is buyer reward ratio of the total P; +- _g_ is the common ratio of successive in the geometric sequence; +- _n_ is the actual number of owners eligible and participating in the future rewards sharing. To calculate _n_, we have _n_ = min(_m_, _w_), where _m_ is the current number of owners for a token, and _w_ is the window size of the profit distribution sliding window algorithm #### Converting into Code @@ -334,7 +334,7 @@ pragma solidity ^0.8.0; //... /* Assumes usage of a Fixed Point Arithmetic library (prb-math) for both int256 and uint256, and OpenZeppelin Math utils for Math.min. */ -function _calculateFR(uint256 P, uint256 r, uint256 g, uint256 m, uint256 w) pure internal virtual returns(uint256[] memory) { +function _calculateFR(uint256 p, uint256 r, uint256 g, uint256 m, uint256 w) pure internal virtual returns(uint256[] memory) { uint256 n = Math.min(m, w); uint256[] memory FR = new uint256[](n); @@ -344,11 +344,11 @@ function _calculateFR(uint256 P, uint256 r, uint256 g, uint256 m, uint256 w) pur if (successiveRatio != 1e18) { int256 v1 = 1e18 - int256(g).powu(n); int256 v2 = int256(g).powu(i - 1); - int256 v3 = int256(P).mul(int256(r)); + int256 v3 = int256(p).mul(int256(r)); int256 v4 = v3.mul(1e18 - int256(g)); pi = uint256(v4 * v2 / v1); } else { - pi = P.mul(r).div(n); + pi = p.mul(r).div(n); } FR[i - 1] = pi; diff --git a/ERCS/erc-5791.md b/ERCS/erc-5791.md index 34e06f58fb..14d38362c2 100644 --- a/ERCS/erc-5791.md +++ b/ERCS/erc-5791.md @@ -2,7 +2,7 @@ eip: 5791 title: Physical Backed Tokens description: Minimal interface for linking ownership of ERC-721 NFTs to a physical chip -author: 2pmflow (@2pmflow), locationtba (@locationtba), Cameron Robertson (@ccamrobertson), cygaar (@cygaar), Brian Weick (@bweick) +author: 2pmflow (@2pmflow), locationtba (@locationtba), Cameron Robertson (@ccamrobertson), cygaar (@cygaar), Brian Weick (@bweick), vectorized (@vectorized), djdabs (@djdabs) discussions-to: https://ethereum-magicians.org/t/physical-backed-tokens/11350 status: Draft type: Standards Track @@ -29,12 +29,12 @@ The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL ### Requirements -This approach requires that the physical item must have a chip attached to it that fulfills the following requirements: +This approach requires that the physical item must have a chip attached to it that should be secure and signal authenticity: -- The chip can securely generate and store an ECDSA secp256k1 asymmetric key pair; +- The chip can securely generate and store an asymmetric key pair; - The chip can sign messages using the private key of the previously-generated asymmetric key pair; - The chip exposes the public key; and -- The private key cannot be extracted +- The private key cannot be extracted or duplicated by design The approach also requires that the contract uses an account-bound implementation of [ERC-721](./eip-721.md) (where all [ERC-721](./eip-721.md) functions that transfer must throw, e.g. the "read only NFT registry" implementation referenced in [ERC-721](./eip-721.md)). This ensures that ownership of the physical item is required to initiate transfers and manage ownership of the NFT, through a new function introduced in this interface described below. @@ -42,9 +42,9 @@ The approach also requires that the contract uses an account-bound implementatio Each NFT is conceptually linked to a physical chip. -When the NFT is minted, it must also emit an event that includes the corresponding chip address (20-byte address derived from the chip's public key). This lets downstream indexers know which chip addresses are mapped to which tokens for the NFT collection. The NFT cannot be minted without its token id being linked to a specific chip. +When the chipId is paired to a tokenId, an event will be emitted. This lets downstream indexers know which chip addresses are mapped to which tokens for the NFT collection. The NFT cannot be minted without its token id being linked to a specific chip. -The interface includes a function called `transferTokenWithChip` that transfers the NFT to the function caller if a valid signature signed by the chip is passed in. A valid signature must follow the schemes set forth in [ERC-191](./eip-191.md) and [EIP-2](./eip-2.md) (s-value restrictions), where the data to sign consists of the target recipient address (the function caller) and a recent blockhash (the level of recency is up to the implementation). +The interface includes a function called `transferToken` that transfers the NFT to the function caller if a valid signature signed by the chip is passed in. A valid signature must follow the schemes set forth in [ERC-191](./eip-191.md) and [EIP-2](./eip-2.md) (s-value restrictions), where the data to sign consists of the target recipient address (the function caller), the chip address, a block timestamp, and any extra params used for additional custom logic in the implementation. The interface also includes other functions that let anyone validate whether the chip in the physical item is backing an existing NFT in the collection. @@ -53,73 +53,80 @@ The interface also includes other functions that let anyone validate whether the ```solidity interface IERC5791 { - /// @notice Returns the token id for a given chip address. - /// @dev Throws if there is no existing token for the chip in the collection. - /// @param chipAddress The address for the chip embedded in the physical item (computed from the chip's public key). - /// @return The token id for the passed in chip address. - function tokenIdFor(address chipAddress) external view returns (uint256); - - /// @notice Returns true if the chip for the specified token id is the signer of the signature of the payload. - /// @dev Throws if tokenId does not exist in the collection. - /// @param tokenId The token id. - /// @param payload Arbitrary data that is signed by the chip to produce the signature param. - /// @param signature Chip's signature of the passed-in payload. - /// @return Whether the signature of the payload was signed by the chip linked to the token id. - function isChipSignatureForToken(uint256 tokenId, bytes calldata payload, bytes calldata signature) + /// @dev Returns the ERC-721 `tokenId` for a given chip address. + /// Reverts if `chipId` has not been paired to a `tokenId`. + /// For minimalism, this will NOT revert if the `tokenId` does not exist. + /// If there is a need to check for token existence, external contracts can + /// call `ERC721.ownerOf(uint256 tokenId)` and check if it passes or reverts. + /// @param chipId The address for the chip embedded in the physical item + /// (computed from the chip's public key). + function tokenIdFor(address chipId) external view returns (uint256 tokenId); + + /// @dev Returns true if `signature` is signed by the chip assigned to `tokenId`, else false. + /// Reverts if `tokenId` has not been paired to a chip. + /// For minimalism, this will NOT revert if the `tokenId` does not exist. + /// If there is a need to check for token existence, external contracts can + /// call `ERC721.ownerOf(uint256 tokenId)` and check if it passes or reverts. + /// @param tokenId ERC-721 `tokenId`. + /// @param data Arbitrary bytes string that is signed by the chip to produce `signature`. + /// @param signature EIP-191 signature by the chip to check. + function isChipSignatureForToken(uint256 tokenId, bytes calldata data, bytes calldata signature) external view returns (bool); - /// @notice Transfers the token into the message sender's wallet. - /// @param signatureFromChip An EIP-191 signature of (msgSender, blockhash), where blockhash is the block hash for blockNumberUsedInSig. - /// @param blockNumberUsedInSig The block number linked to the blockhash signed in signatureFromChip. Should be a recent block number. - /// @param useSafeTransferFrom Whether EIP-721's safeTransferFrom should be used in the implementation, instead of transferFrom. - /// - /// @dev The implementation should check that block number be reasonably recent to avoid replay attacks of stale signatures. - /// The implementation should also verify that the address signed in the signature matches msgSender. - /// If the address recovered from the signature matches a chip address that's bound to an existing token, the token should be transferred to msgSender. - /// If there is no existing token linked to the chip, the function should error. - function transferTokenWithChip( - bytes calldata signatureFromChip, - uint256 blockNumberUsedInSig, - bool useSafeTransferFrom - ) external; - - /// @notice Calls transferTokenWithChip as defined above, with useSafeTransferFrom set to false. - function transferTokenWithChip(bytes calldata signatureFromChip, uint256 blockNumberUsedInSig) external; - - /// @notice Emitted when a token is minted - event PBTMint(uint256 indexed tokenId, address indexed chipAddress); - - /// @notice Emitted when a token is mapped to a different chip. - /// Chip replacements may be useful in certain scenarios (e.g. chip defect). - event PBTChipRemapping(uint256 indexed tokenId, address indexed oldChipAddress, address indexed newChipAddress); + /// @dev Transfers the token into the address. + /// Returns the `tokenId` transferred. + /// @param to The recipient. Dynamic to allow easier transfers to vaults. + /// @param chipId Chip ID (address) of chip being transferred. + /// @param chipSignature EIP-191 signature by the chip to authorize the transfer. + /// @param signatureTimestamp Timestamp used in `chipSignature`. + /// @param useSafeTransferFrom Whether ERC-721's `safeTransferFrom` should be used, + /// instead of `transferFrom`. + /// @param extras Additional data that can be used for additional logic/context + /// when the PBT is transferred. + function transferToken( + address to, + address chipId, + bytes calldata chipSignature, + uint256 signatureTimestamp, + bool useSafeTransferFrom, + bytes calldata extras + ) external returns (uint256 tokenId); + + /// @dev Emitted when `chipId` is paired to `tokenId`. + /// `tokenId` may not necessarily exist during assignment. + /// Indexers can combine this event with the {ERC721.Transfer} event to + /// infer which tokens exists and are paired with a chip ID. + event ChipSet(uint256 indexed tokenId, address indexed chipId); } ``` To aid recognition that an [ERC-721](./eip-721.md) token implements physical binding via this EIP: upon calling [ERC-165](./eip-165.md)’s `function supportsInterface(bytes4 interfaceID) external view returns (bool)` with `interfaceID=0x4901df9f`, a contract implementing this EIP must return true. -The mint interface is up to the implementation. The minted NFT's owner should be the owner of the physical chip (this authentication could be implemented using the signature scheme defined for `transferTokenWithChip`). +The mint interface is up to the implementation. The minted NFT's owner should be the owner of the physical chip (this authentication could be implemented using the signature scheme defined for `transferToken`). ## Rationale This solution's intent is to be the simplest possible path towards linking physical items to digital NFTs without a centralized authority. -The interface includes a `transferTokenWithChip` function that's opinionated with respect to the signature scheme, in order to enable a downstream aggregator-like product that supports transfers of any NFTs that implement this EIP in the future. +The interface includes a `transferToken` function that's opinionated with respect to the signature scheme, in order to enable a downstream aggregator-like product that supports transfers of any NFTs that implement this EIP in the future. + +The chip address is included in `transferToken` to allow signature verification by a smart contract. This ensures that chips in physically backed tokens are not strictly tied to implementing secp256k1 signatures, but instead may use a variety of signature schemes such as P256 or BabyJubJub. ### Out of Scope The following are some peripheral problems that are intentionally not within the scope of this EIP: -- trusting that a specific NFT collection's chip addresses actually map to physical chips embedded in items, instead of arbitrary EOAs -- ensuring that the chip does not deterioriate or get damaged +- trusting that a specific NFT collection's chip addresses actually map to physical chips embedded in items, instead of arbitrary EOAs that purport to be chips +- ensuring that the chip does not deteriorate or get damaged - ensuring that the chip stays attached to the physical item - etc. Work is being done on these challenges in parallel. -Mapping token ids to chip addresses is also out of scope. This can be done in multiple ways, e.g. by having the contract owner preset this mapping pre-mint, or by having a `(tokenId, chipAddress)` tuple passed into a mint function that's pre-signed by an address trusted by the contract, or by doing a lookup in a trusted registry, or by assigning token ids at mint time first come first served, etc. +Mapping token ids to chip addresses is also out of scope. This can be done in multiple ways, e.g. by having the contract owner preset this mapping pre-mint, or by having a `(tokenId, chipId)` tuple passed into a mint function that's pre-signed by an address trusted by the contract, or by doing a lookup in a trusted registry, or by assigning token ids at mint time first come first served, etc. Additionally, it's possible for the owner of the physical item to transfer the NFT to a wallet owned by somebody else (by sending a chip signature to that other person for use). We still consider the NFT physical backed, as ownership management is tied to the physical item. This can be interpreted as the item's owner temporarily lending the item to somebody else, since (1) the item's owner must be involved for this to happen as the one signing with the chip, and (2) the item's owner can reclaim ownership of the NFT at any time. @@ -129,34 +136,63 @@ This proposal is backward compatible with [ERC-721](./eip-721.md) on an API leve ## Reference Implementation -The following is a snippet on how to recover a chip address from a signature. +The following is a snippet on how to validate a chip signature in a transfer event. ```solidity import '@openzeppelin/contracts/utils/cryptography/ECDSA.sol'; -function getChipAddressFromChipSignature( - bytes calldata signatureFromChip, - uint256 blockNumberUsedInSig -) internal returns (TokenData memory) { - if (block.number <= blockNumberUsedInSig) { - revert InvalidBlockNumber(); - } - unchecked { - if (block.number - blockNumberUsedInSig > getMaxBlockhashValidWindow()) { - revert BlockNumberTooOld(); +/// @dev Transfers the `tokenId` assigned to `chipId` to `to`. +function transferToken( + address to, + address chipId, + bytes memory chipSignature, + uint256 signatureTimestamp, + bool useSafeTransfer, + bytes memory extras +) public virtual returns (uint256 tokenId) { + tokenId = tokenIdFor(chipId); + _validateSigAndUpdateNonce(to, chipId, chipSignature, signatureTimestamp, extras); + if (useSafeTransfer) { + _safeTransfer(ownerOf(tokenId), to, tokenId, ""); + } else { + _transfer(ownerOf(tokenId), to, tokenId); + } +} + +/// @dev Validates the `chipSignature` and update the nonce for the future signature of `chipId`. +function _validateSigAndUpdateNonce( + address to, + address chipId, + bytes memory chipSignature, + uint256 signatureTimestamp, + bytes memory extras +) internal virtual { + bytes32 hash = _getSignatureHash(signatureTimestamp, chipId, to, extras); + if (!SignatureCheckerLib.isValidSignatureNow(chipId, hash, chipSignature)) { + revert InvalidSignature(); } - } - bytes32 blockHash = blockhash(blockNumberUsedInSig); - bytes32 signedHash = keccak256(abi.encodePacked(_msgSender(), blockHash)) - .toEthSignedMessageHash(); - address chipAddr = signedHash.recover(signatureFromChip); + chipNonce[chipId] = bytes32(uint256(hash) ^ uint256(blockhash(block.number - 1))); +} + +/// @dev Returns the digest to be signed by the `chipId`. +function _getSignatureHash(uint256 signatureTimestamp, address chipId, address to, bytes memory extras) + internal + virtual + returns (bytes32) +{ + if (signatureTimestamp > block.timestamp) revert SignatureTimestampInFuture(); + if (signatureTimestamp + maxDurationWindow < block.timestamp) revert SignatureTimestampTooOld(); + bytes32 hash = keccak256( + abi.encode(address(this), block.chainid, chipNonce[chipId], to, signatureTimestamp, keccak256(extras)) + ); + return ECDSA.toEthSignedMessageHash(hash); } ``` ## Security Considerations -The [ERC-191](./eip-191.md) signature passed to `transferTokenWithChip` requires the function caller's address in its signed data so that the signature cannot be used in a replay attack. It also requires a recent blockhash so that a malicious chip owner cannot pre-generate signatures to use after a short time window (e.g. after the owner of the physical item changes). +The [ERC-191](./eip-191.md) signature passed to `transferToken` requires the function caller's address in its signed data so that the signature cannot be used in a replay attack. It also requires a recent block timestamp so that a malicious chip owner cannot pre-generate signatures to use after a short time window (e.g. after the owner of the physical item changes). It's recommended to use a non-deterministic `chipNonce` when generating signatures. Additionally, the level of trust that one has for whether the token is physically-backed is dependent on the security of the physical chip, which is out of scope for this EIP as mentioned above. diff --git a/ERCS/erc-6734.md b/ERCS/erc-6734.md index bacfe8f19e..f22df1b8fe 100644 --- a/ERCS/erc-6734.md +++ b/ERCS/erc-6734.md @@ -4,11 +4,11 @@ title: L2 Token List description: Token List that ensures the correct identification of tokens from different Layer 1, Layer 2, or Sidechains. author: Kelvin Fichter (@smartcontracts), Andreas Freund (@Therecanbeonlyone1969), Pavel Sinelnikov (@psinelnikov) discussions-to: https://ethereum-magicians.org/t/canonical-token-list-standard-from-the-eea-oasis-community-projects-l2-standards-working-group/13091 -status: Draft +status: Review type: Standards Track category: ERC created: 2023-03-20 -requires: 155, 3220 +requires: 155 --- ## Abstract @@ -133,12 +133,7 @@ The chainId property utilized MUST allow for the requirements of the [EIP-155](. Namely, transaction replay protection on the network that is identified by the chainId property value. Note, that for replay protection to be guaranteed, the chainId should be unique. Ensuring a unique chainId is beyond the scope of this document. -[[R4]](#r4) testability: EIP-155 requires that a transaction hash is derived from the keccak256 hash of the following nine RLP encoded elements `(nonce, gasprice, startgas, to, value, data, chainid, 0, 0)` which can be tested easily with existing cryptographic libraries. EIP-155 further requires that the `v` value of the secp256k1 signature must be set to `{0,1} + CHAIN_ID * 2 + 35` where `{0,1}` is the parity of the `y` value of the curve point for which the signature `r`-value is the `x`-value in the secp256k1 signing process. This requirement is testable with available open-source secp256k1 digital signature suites. Therefore, [[R4]](#r4) is testable. - - **[D2]** -The `chainId` property SHOULD follow [EIP-3220](./eip-3220.md) draft standard. - -[[D2]](#d2) testability: The [EIP-3220](./eip-3220.md) draft standard can be tested because the crosschain id is specified as a concatenation of well-defined strings, and using open source tooling can be used to parse and split a crosschain id, the obtained string segments can be compared against expected string lengths, and context dependent, the values for the strings specified in the standard. Consequently, [[D2]](#d2) is testable. +[[R4]](#r4) testability: EIP-155 requires that a transaction hash is derived from the keccak256 hash of the following nine RLP encoded elements `(nonce, gasprice, startgas, to, value, data, chainid, 0, 0)` which can be tested easily with existing cryptographic libraries. EIP-155 further requires that the `v` value of the secp256k1 signature must be set to `{0,1} + CHAIN_ID * 2 + 35` where `{0,1}` is the parity of the `y` value of the curve point for which the signature `r`-value is the `x`-value in the secp256k1 signing process. This requirement is testable with available open-source secp256k1 digital signature suites. Therefore, [[R4]](#r4) is testable. **[O1]** The `humanReadableTokenSymbol` property MAY be used. @@ -491,10 +486,10 @@ This document defines the conformance levels of a canonical token list as follow * **Level 2:** All MUST and SHOULD requirements are fulfilled by a specific implementation as proven by a test report that proves in an easily understandable manner the implementation's conformance with each requirement based on implementation-specific test-fixtures with implementation-specific test-fixture inputs. * **Level 3:** All MUST, SHOULD, and MAY requirements with conditional MUST or SHOULD requirements are fulfilled by a specific implementation as proven by a test report that proves in an easily understandable manner the implementation's conformance with each requirement based on implementation-specific test-fixtures with implementation-specific test-fixture inputs. - **[D3]** + **[D2]** A claim that a canonical token list implementation conforms to this specification SHOULD describe a testing procedure carried out for each requirement to which conformance is claimed, that justifies the claim with respect to that requirement. -[[D3]](#d3) testability: Since each of the non-conformance-target requirements in this documents is testable, so must be the totality of the requirements in this document. Therefore, conformance tests for all requirements can exist, and can be described as required in [[D3]](#d3). +[[D2]](#d2) testability: Since each of the non-conformance-target requirements in this documents is testable, so must be the totality of the requirements in this document. Therefore, conformance tests for all requirements can exist, and can be described as required in [[D2]](#d2). **[R5]** A claim that a canonical token list implementation conforms to this specification at **Level 2** or higher MUST describe the testing procedure carried out for each requirement at **Level 2** or higher, that justifies the claim to that requirement. diff --git a/ERCS/erc-6735.md b/ERCS/erc-6735.md index aa28a59a38..aed7c4f0b6 100644 --- a/ERCS/erc-6735.md +++ b/ERCS/erc-6735.md @@ -4,7 +4,7 @@ title: L2 Aliasing of EVM-based Addresses description: Identify and translate EVM-based addresses from different Layer 1, Layer 2, or Sidechains author: Kelvin Fichter (@smartcontracts), Andreas Freund (@Therecanbeonlyone1969) discussions-to: https://ethereum-magicians.org/t/l2-aliasing-of-evm-based-addresses-from-the-eea-oasis-community-projects-l2-standards-working-group/13093 -status: Draft +status: Review type: Standards Track category: ERC created: 2022-03-20 diff --git a/ERCS/erc-6909.md b/ERCS/erc-6909.md index 29a5011354..63d566397f 100644 --- a/ERCS/erc-6909.md +++ b/ERCS/erc-6909.md @@ -4,7 +4,7 @@ title: Minimal Multi-Token Interface description: A minimal specification for managing multiple tokens by their id in a single contract. author: JT Riley (@jtriley-eth), Dillon (@d1ll0n), Sara (@snreynolds), Vectorized (@Vectorized), Neodaoist (@neodaoist) discussions-to: https://ethereum-magicians.org/t/eip-6909-multi-token-standard/13891 -status: Draft +status: Review type: Standards Track category: ERC created: 2023-04-19 @@ -536,7 +536,7 @@ The `totalSupply` for a token `id`. ### Granular Approvals -While the "operator model" from the ERC-1155 standard allows an account to set another account as an operator, giving full permissions to transfer any amount of any token id on behalf of the owner, this may not always be the desired permission scheme. The "allowance model" from [ERC-20](./eip-20.md) allows an account to set an explicit amount of the token that another account can spend on the owner's behalf. This standard requires both be implemented, with the only modification being to the "allowance model" where the token id must be specified as well. This allows an account to grant specific approvals to specific token ids, infinite approvals to specific token ids, or infinite approvals to all token ids. If an account is set as an operator, the allowance SHOULD NOT be decreased when tokens are transferred on behalf of the owner. +While the "operator model" from the ERC-1155 standard allows an account to set another account as an operator, giving full permissions to transfer any amount of any token id on behalf of the owner, this may not always be the desired permission scheme. The "allowance model" from [ERC-20](./eip-20.md) allows an account to set an explicit amount of the token that another account can spend on the owner's behalf. This standard requires both be implemented, with the only modification being to the "allowance model" where the token id must be specified as well. This allows an account to grant specific approvals to specific token ids, infinite approvals to specific token ids, or infinite approvals to all token ids. ### Removal of Batching @@ -544,7 +544,7 @@ While batching operations is useful, its place should not be in the standard its ### Removal of Required Callbacks -Callbacks MAY be used within a multi-token compliant contract, but it is not required. This allows for more gas efficient methods by reducing external calls and additional checks. +Requiring callbacks unnecessarily encumbers implementors that either have no particular use case for callbacks or prefer a bespoke callback mechanism. Minimization of such requirements saves contract size, gas efficiency and complexity. ### Removal of "Safe" Naming @@ -684,7 +684,7 @@ models. There are two security considerations in regards to delegating permissio The first consideration is consistent with all delegated permission models. Any account with an allowance may transfer the full allowance for any reason at any time until the allowance is revoked. Any account with operator permissions may transfer any amount of any token id on behalf of the owner until the operator permission is revoked. -The second consideration is unique to systems with both delegated permission models. 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. A spender with both operator permission and a non-infinite approval may introduce functional ambiguity. If the operator permission takes precedence, that is, the allowance is never deducted when a spender has operator permissions, there is no ambiguity. However, in the event the allowance takes precedence over the operator permissions, an additional branch may be necessary to ensure an allowance underflow does not occur. The following is an example of such an issue. +The second consideration is unique to systems with both delegated permission models. If an account has both operator permissions and an insufficient allowance for a given transfer, performing the allowance check before the operator check would result in a revert while performing the operator check before the allowance check would not. The specification intentionally leaves this unconstrained for cases where implementors may track allowances despite the operator status. Nonetheless, this is a notable consideration. ```solidity contract ERC6909OperatorPrecedence { diff --git a/ERCS/erc-7007.md b/ERCS/erc-7007.md index caac5be17b..ddf6a83c22 100644 --- a/ERCS/erc-7007.md +++ b/ERCS/erc-7007.md @@ -4,8 +4,7 @@ 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: Last Call -last-call-deadline: 2024-09-30 +status: Final type: Standards Track category: ERC created: 2023-05-10 @@ -233,4 +232,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). \ No newline at end of file +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/ERCS/erc-7208.md b/ERCS/erc-7208.md index 8b4d1d3f23..b175e8dec8 100644 --- a/ERCS/erc-7208.md +++ b/ERCS/erc-7208.md @@ -1,10 +1,10 @@ --- eip: 7208 title: On-Chain Data Container -description: ERC interoperability by abstracting logic away from storage +description: 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 +status: Review type: Standards Track category: ERC created: 2023-06-09 @@ -271,6 +271,8 @@ We present an **educational example** implementation showcasing two types of tok **This example has not been audited and should not be used in production environments.** +See [contracts](../assets/eip-7208/contracts/README.md) + ## Security Considerations diff --git a/ERCS/erc-7417.md b/ERCS/erc-7417.md index 33b8b711af..5f2af30f98 100644 --- a/ERCS/erc-7417.md +++ b/ERCS/erc-7417.md @@ -15,11 +15,15 @@ requires: 20, 165, 223 There are multiple token standards on Ethereum chain currently. This EIP introduces a concept of cross-standard interoperability by creating a service that allows [ERC-20](./eip-20.md) tokens to be upgraded to [ERC-223](./eip-223.md) tokens anytime. [ERC-223](./eip-223.md) tokens can be converted back to [ERC-20](./eip-20.md) version without any restrictions to avoid any problems with backwards compatibility and allow different standards to co-exist and become interoperable and interchangeable. -To perform the conversion, the user must send tokens of one standard to the Converter contract and he will automatically receive tokens of another standard. +In order to perform the conversion, a user must deposit tokens of one standard to the Converter contract and it will automatically send tokens of another standard back. ## Motivation -When an ERC-20 contract is upgraded, finding the new address introduces risk. This proposal creates a service that performs token conversion and prevents potentially unsafe implementations from spreading. +This proposal introduces a concept of token standard upgrading procedure driven by a specialized smart-contract which can convert tokens of one standard to another at any time as well as create an alternative version of any existing token of the older standard. + +Currently some tokens are available on different chains in different standards, for example most exchanges support [ERC-20](./eip-20.md) USDT, TRX USDT, BEP-20 USDT and all this tokens are in fact the same USDT token. This proposal is intended to introduce a concept where there can be a [ERC-20](./eip-20.md) USDT and [ERC-223](./eip-223.md) USDT available on Ethereum mainnet at the same time and both can co-exist. + +The address of the deployed Token Converter must be described here as to solve the trust issues for the token developers and help them figure out a proper way of interacting with the Converter. ## Specification @@ -41,21 +45,54 @@ Converter contract MUST accept deposits of [ERC-223](./eip-223.md) tokens and se #### Conver contract methods -##### `getWrapperFor` +##### `getERC20WrapperFor` + +```solidity +function getERC20WrapperFor(address _token) public view returns (address) +``` + +Returns the address of the [ERC-20](./eip-20.md) wrapper for a given token address. Returns `0x0` if there is no [ERC-20](./eip-20.md) version for the provided token address. There can be exactly one wrapper for any given [ERC-223](./eip-223.md) token address created by the Token Converter contract. + +##### `getERC223WrapperFor` ```solidity -function getWrapperFor(address _erc20Token) public view returns (address) +function getERC223WrapperFor(address _token) public view returns (address) ``` -Returns the address of the [ERC-223](./eip-223.md) wrapper for a given [ERC-20](./eip-20.md) original token. Returns `0x0` if there is no [ERC-223](./eip-223.md) version for the provided [ERC-20](./eip-20.md) token address. There MUST be exactly one wrapper for any given [ERC-20](./eip-20.md) token address created by the Token Converter contract. +Returns the address of the [ERC-223](./eip-223.md) wrapper for a given token address. Returns `0x0` if there is no [ERC-223](./eip-223.md) version for the provided token address. There can be exactly one [ERC-223](./eip-223.md) wrapper for any given [ERC-20](./eip-20.md) token address created by the Token Converter contract. -##### `getOriginFor` +##### `getERC20OriginFor` ```solidity -function getOriginFor(address _erc223Token) public view returns (address) +function getERC20OriginFor(address _erc223Token) public view returns (address) ``` -Returns the address of the original [ERC-20](./eip-20.md) token for a given [ERC-223](./eip-223.md) wrapper. Returns `0x0` if the provided `_erc223Token` is not an address of any [ERC-223](./eip-223.md) wrapper created by the Token Converter contract. +Returns the address of the original [ERC-20](./eip-20.md) token for the provided [ERC-223](./eip-223.md) wrapper. Returns `0x0` if the provided `_erc223Token` is not an address of any [ERC-223](./eip-223.md) wrapper created by the Token Converter contract. + +##### `getERC223OriginFor` + +```solidity +function getERC223OriginFor(address _erc20Token) public view returns (address) +``` + +Returns the address of the original [ERC-223](./eip-223.md) token for the provided [ERC-20](./eip-20.md) wrapper. Returns `0x0` if the provided `_erc20Token` is not an address of any wrapper created by the Token Converter contract. + +##### `predictWrapperAddress` + +```solidity +function predictWrapperAddress(address _token, + bool _isERC20 // Is the provided _token a ERC-20 or not? + // If it is set as ERC-20 then we will predict the address of a + // ERC-223 wrapper for that token. + // Otherwise we will predict ERC-20 wrapper address. + ) view external returns (address) +``` + +Wrapper contracts are deployed via `CREATE2` opcode and it is possible to predict the address of a wrapper which is not yet deployed. The address of a wrapper contract depends on the bytecode therefore it is necessary to specify if the address of wrapper [ERC-20](./eip-20.md) or wrapper [ERC-223](./eip-223.md) must be predicted. + +Providing `_token` address and `_isERC20 = false` will result in [ERC-20](./eip-20.md) wrapper address being predicted. + +Providing `_token` address and `_isERC20 = true` will result in [ERC-223](./eip-223.md) wrapper address being predicted. ##### `createERC223Wrapper` @@ -63,19 +100,66 @@ Returns the address of the original [ERC-20](./eip-20.md) token for a given [ERC function createERC223Wrapper(address _erc20Token) public returns (address) ``` -Creates a new [ERC-223](./eip-223.md) wrapper for a given `_erc20Token` if it does not exist yet. Reverts the transaction if the wrapper already exist. Returns the address of the new wrapper token contract on success. +Creates a new [ERC-223](./eip-223.md) wrapper for a given `_erc20Token` if it does not exist yet. Reverts the transaction if the wrapper already exist. Returns the address of the new wrapper token contract on success. Reverts if `_erc223Token` is a wrapper created by the Converter. + +The deployed contract will be a standard [ERC-223](./eip-223.md) token with `approve` and `transferFrom` functions implemented for backwards compatibility. + +All [ERC-223](./eip-223.md) wrappers deployed by the Converter will have `standard() pure returns (bytes32)` function implemented which returns `223`. This serves further token standard introspection as [ERC-165](./eip-165.md) may not be reliable when dealing with identifying the internal logic implemented within `transfer` function of a token. -##### `convertERC20toERC223` +NOTE: This function does not verify the standard of `_erc20Token` because there is no reliable method of introspection available which could guarantee that the provided token implements a particular standard. As the result it is possible to create a [ERC-223](./eip-223.md) wrapper for an original [ERC-223](./eip-223.md) token. + +##### `createERC20Wrapper` ```solidity -function convertERC20toERC223(address _erc20token, uint256 _amount) public returns (bool) +function createERC20Wrapper(address _erc223Token) public returns (address) ``` -Withdraws `_amount` of [ERC-20](./eip-20.md) token from the transaction senders balance with `transferFrom` function. Sends the `_amount` of [ERC-223](./eip-223.md) wrapper tokens to the sender of the transaction. Stores the original tokens at the balance of the Token Converter contract for future claims. Returns `true` on success. The Token Converter must keep record of the amount of [ERC-20](./eip-20.md) tokens that were deposited with `convertERC20toERC223` function because it is possible to deposit [ERC-20](./eip-20.md) tokens to any contract by directly sending them with `transfer` function. +Creates a new [ERC-20](./eip-20.md) wrapper for a given `_erc223Token` if it does not exist yet. Reverts the transaction if the wrapper already exist. Returns the address of the new wrapper token contract on success. Reverts if `_erc223Token` is a wrapper created by the Converter. + +NOTE: This function does not verify the standard of `_erc223Token` because there is no reliable method of introspection available which could guarantee that the provided token implements a particular standard. As the result it is possible to create a [ERC-20](./eip-20.md) wrapper for an original [ERC-20](./eip-20.md) token. + +##### `wrapERC20toERC223` + +```solidity +function wrapERC20toERC223(address _ERC20token, uint256 _amount) public returns (bool) +``` + +Withdraws `_amount` of [ERC-20](./eip-20.md) tokens from the transaction sender with `transferFrom` function. Delivers the `_amount` of [ERC-223](./eip-223.md) wrapper tokens to the sender of the transaction. Stores the original tokens at the balance of the Token Converter contract for future claims. Returns `true` on success. The Token Converter must keep record of the amount of [ERC-20](./eip-20.md) tokens that were deposited with `wrapERC20toERC223` function because it is possible to deposit [ERC-20](./eip-20.md) tokens to any contract by directly sending them with `transfer` function. If there is no [ERC-223](./eip-223.md) wrapper for the `_ERC20token` then creates it by calling a `createERC223Wrapper(_erc20toke)` function. -If the provided `_erc20token` address is an address of a [ERC-223](./eip-223.md) wrapper reverts the transaction. +There is no special function to unwrap [ERC-223](./eip-223.md) wrappers to [ERC-20](./eip-20.md) origin as this logic is implemented in the `tokenReceived` function of the Converter. + +##### `unwrapERC20toERC223` + +```solidity +function unwrapERC20toERC223(address _ERC20token, uint256 _amount) public returns (bool) +``` + +Withdraws `_amount` of [ERC-20](./eip-20.md) tokens from the transaction sender with `transferFrom` function. Delivers the `_amount` of [ERC-223](./eip-223.md) wrapper tokens to the sender of the transaction. Stores the original tokens at the balance of the Token Converter contract for future claims. Returns `true` on success. The Token Converter must keep record of the amount of [ERC-20](./eip-20.md) tokens that were deposited with `wrapERC20toERC223` function because it is possible to deposit [ERC-20](./eip-20.md) tokens to any contract by directly sending them with `transfer` function. + +If there is no [ERC-223](./eip-223.md) wrapper for the `_ERC20token` then creates it by calling a `createERC223Wrapper(_erc20toke)` function. + + +##### `convertERC20` + +```solidity +function convertERC20(address _token, uint256 _amount) public returns (bool) +``` + +Automatically determines if the provided [ERC-20](./eip-20.md) token is a wrapper or not. If it is a wrapper then executes `unwrapERC20toERC223` function. If the provided token is an origin then executes `wrapERC20toERC223` function. + +This function is implemented to significantly simplify the workflow of services that integrate both versions of one token in the same contract and need to automatically convert tokens through the Converter. + +##### `isWrapper` + +```solidity +function isWrapper(address _token) public view returns (bool) +``` + +Returns `true` if the provided `_token` address is an address of a wrapper created by the Converter. + +NOTE: This function does not identify the standard of a `_token`. There can be exactly one origin for any wrapper created by the Converter. However an original token can have two wrappers, one of each standard. ##### `tokenReceived` @@ -83,23 +167,23 @@ If the provided `_erc20token` address is an address of a [ERC-223](./eip-223.md) function tokenReceived(address _from, uint _value, bytes memory _data) public override returns (bytes4) ``` -This is a standard [ERC-223](./eip-223.md) transaction handler function and it is called by the [ERC-223](./eip-223.md) token contract when `_from` is sending `_value` of [ERC-223](./eip-223.md) tokens to `address(this)` address. In the scope of this function `msg.sender` is the address of the [ERC-223](./eip-223.md) token contract and `_from` is the initiator of the transaction. +This is a standard [ERC-223](./eip-223.md) transaction handler function and it is called by the [ERC-223](./eip-223.md) token contract when `_from` is sending `_value` of [ERC-223](./eip-223.md) tokens to `address(this)` address. In the scope of this function `msg.sender` is the address of the [ERC-223](./eip-223.md) token contract and `_from` is the sender of the token transfer. -If `msg.sender` is an address of [ERC-223](./eip-223.md) wrapper created by the Token Converter then `_value` of [ERC-20](./eip-20.md) original token must be sent to the `_from` address. +Automatically determines -If `msg.sender` is not an address of any [ERC-223](./eip-223.md) wrapper known to the Token Converter then revert the transaction thus returning any `ERC-223` tokens back to the sender. +If `msg.sender` is an address of [ERC-223](./eip-223.md) wrapper created by the Token Converter then `_value` of [ERC-20](./eip-20.md) original token must be sent to the `_from` address. -This is the function that MUST be used to convert [ERC-223](./eip-223.md) wrapper tokens back to original [ERC-20](./eip-20.md) tokens. This function is automatically executed when [ERC-223](./eip-223.md) tokens are sent to the address of the Token Converter. If any arbitrary [ERC-223](./eip-223.md) token is sent to the Token Converter it will be rejected. +If `msg.sender` is not an address of any [ERC-223](./eip-223.md) wrapper known to the Token Converter then it is considered a [ERC-223](./eip-223.md) origin and `_value` amount of [ERC-20](./eip-20.md) wrapper tokens must be sent to the `_from` address. If the [ERC-20](./eip-20.md) wrapper for the `msg.sender` token does not exist then create it first. Returns `0x8943ec02`. -##### `rescueERC20` +##### `extractStuckERC20` ```solidity -function rescueERC20(address _token) external +function extractStuckERC20(address _token) ``` -This function allows to extract the [ERC-20](./eip-20.md) tokens that were directly deposited to the contract with `transfer` function to prevent users who may send tokens by mistake from permanently freezing their assets. Since the Token Converter calculates the amount of tokens that were deposited legitimately with `convertERC20toERC223` function it is always possible to calculate the amount of "accidentally deposited tokens" by subtracting the recorded amount from the returned value of the `balanceOf( address(this) )` function called on the [ERC-20](./eip-20.md) token contract. +This function allows to extract the [ERC-20](./eip-20.md) tokens that were directly deposited to the contract with `transfer` function to prevent users who may send tokens by mistake from permanently losing their tokens. Since the Token Converter calculates the amount of tokens that were deposited legitimately with `convertERC20toERC223` function it is always possible to calculate the amount of "accidentally deposited tokens" by subtracting the recorded amount from the returned value of the `balanceOf( address(this) )` function called on the [ERC-20](./eip-20.md) token contract. ### Converting [ERC-20](./eip-20.md) tokens to [ERC-223](./eip-223.md) @@ -117,15 +201,27 @@ In order to convert [ERC-20](./eip-20.md) tokens to [ERC-223](./eip-223.md) the ## Rationale - +For example it was a common case with [ERC-20](./eip-20.md) where developers could implement custom logic of the `transfer` function and mess the return values. The [ERC-20](./eip-20.md) specification declares that a `transfer` function MUST return a `bool` value, however in practice we have three different types of [ERC-20](./eip-20.md) tokens which are not compatible with each other: -TBD +1. [ERC-20](./eip-20.md) tokens that return `true` on success and revert on an error. +2. [ERC-20](./eip-20.md) tokens that return `true` on success and `false` on an error without reverting the transaction. +3. [ERC-20](./eip-20.md) tokens that don't have return values and revert on an error. + +Technically the third category of tokens is not compatible with [ERC-20](./eip-20.md) standard. However, USDT token deployed on Ethereum mainnet at `0xdac17f958d2ee523a2206206994597c13d831ec7` address does not implement return values and it is one of the most used tokens and it is not an option to deny supporting USDT due to it's improper implementation of the standard. + +The Token Converter eliminates the issue where different development teams may implement the standard with slight modifications and result in a situation where we would have different versions of the same standard on the mainnet. + +At the same time the Converter enables the concurrent token support in other smart-contracts, such as decentralized exchanges. The Converter can guarantee that a pair of two tokens one of which is a wrapper for another is in fact the same token that can be converted from one standard to another at any time. This enables the creation of liquidity pools where two different tokens are dealt with as if they were one token while in fact these are exactly one token available in different standards. ## Backwards Compatibility @@ -136,7 +232,389 @@ This service is the first of its kind and therefore does not have any backwards ## Reference Implementation ```solidity - address public ownerMultisig; + +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity =0.8.19; + +library Address { + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + + uint256 size; + // solhint-disable-next-line no-inline-assembly + assembly { size := extcodesize(account) } + return size > 0; + } +} + +interface IERC20 { + function totalSupply() external view returns (uint256); + function balanceOf(address account) external view returns (uint256); + function transfer(address recipient, uint256 amount) external returns (bool); + function allowance(address owner, address spender) external view returns (uint256); + function approve(address spender, uint256 amount) external returns (bool); + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +interface IERC20Metadata is IERC20 { + /// @return The name of the token + function name() external view returns (string memory); + + /// @return The symbol of the token + function symbol() external view returns (string memory); + + /// @return The number of decimal places the token has + function decimals() external view returns (uint8); +} + +abstract contract IERC223Recipient { + function tokenReceived(address _from, uint _value, bytes memory _data) public virtual returns (bytes4) + { + return 0x8943ec02; + } +} + +abstract contract ERC165 { + /* + * bytes4(keccak256('supportsInterface(bytes4)')) == 0x01ffc9a7 + */ + bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7; + mapping(bytes4 => bool) private _supportedInterfaces; + + constructor () { + // Derived contracts need only register support for their own interfaces, + // we register support for ERC165 itself here + _registerInterface(_INTERFACE_ID_ERC165); + } + function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { + return _supportedInterfaces[interfaceId]; + } + function _registerInterface(bytes4 interfaceId) internal virtual { + require(interfaceId != 0xffffffff, "ERC165: invalid interface id"); + _supportedInterfaces[interfaceId] = true; + } +} + +abstract contract IERC223 { + function name() public view virtual returns (string memory); + function symbol() public view virtual returns (string memory); + function decimals() public view virtual returns (uint8); + function totalSupply() public view virtual returns (uint256); + function balanceOf(address who) public virtual view returns (uint); + function transfer(address to, uint value) public virtual returns (bool success); + function transfer(address to, uint value, bytes calldata data) public payable virtual returns (bool success); + event Transfer(address indexed from, address indexed to, uint value, bytes data); +} + +interface standardERC20 +{ + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); + function totalSupply() external view returns (uint256); + function balanceOf(address account) external view returns (uint256); + function transfer(address to, uint256 value) external returns (bool); + function allowance(address owner, address spender) external view returns (uint256); + function approve(address spender, uint256 value) external returns (bool); + function transferFrom(address from, address to, uint256 value) external returns (bool); +} + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC223WrapperToken { + function name() external view returns (string memory); + function symbol() external view returns (string memory); + function decimals() external view returns (uint8); + function standard() external view returns (string memory); + function origin() external view returns (address); + + function totalSupply() external view returns (uint256); + function balanceOf(address account) external view returns (uint256); + function transfer(address to, uint256 value) external payable returns (bool); + function transfer(address to, uint256 value, bytes calldata data) external payable returns (bool); + function allowance(address owner, address spender) external view returns (uint256); + function approve(address spender, uint256 value) external returns (bool); + function transferFrom(address from, address to, uint256 value) external returns (bool); + + function mint(address _recipient, uint256 _quantity) external; + function burn(address _recipient, uint256 _quantity) external; +} + +interface IERC20WrapperToken { + function name() external view returns (string memory); + function symbol() external view returns (string memory); + function decimals() external view returns (uint8); + function standard() external view returns (string memory); + function origin() external view returns (address); + + function totalSupply() external view returns (uint256); + function balanceOf(address account) external view returns (uint256); + function transfer(address to, uint256 value) external returns (bool); + function allowance(address owner, address spender) external view returns (uint256); + function approve(address spender, uint256 value) external returns (bool); + function transferFrom(address from, address to, uint256 value) external returns (bool); + + function mint(address _recipient, uint256 _quantity) external; + function burn(address _recipient, uint256 _quantity) external; +} + +contract ERC20Rescue +{ + // ERC-20 tokens can get stuck on a contracts balance due to lack of error handling. + // + // The author of the ERC-7417 can extract ERC-20 tokens if they are mistakenly sent + // to the wrapper-contracts balance. + // Contact dexaran@ethereumclassic.org + address public extractor = 0x01000B5fE61411C466b70631d7fF070187179Bbf; + + function safeTransfer(address token, address to, uint value) internal { + // bytes4(keccak256(bytes('transfer(address,uint256)'))); + (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value)); + require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FAILED'); + } + + function rescueERC20(address _token, uint256 _amount) external + { + safeTransfer(_token, extractor, _amount); + } +} + +contract ERC223WrapperToken is IERC223, ERC165, ERC20Rescue +{ + address public creator = msg.sender; + address private wrapper_for; + + mapping(address account => mapping(address spender => uint256)) private allowances; + + event Transfer(address indexed from, address indexed to, uint256 amount); + event TransferData(bytes data); + event Approval(address indexed owner, address indexed spender, uint256 amount); + + function set(address _wrapper_for) external + { + require(msg.sender == creator); + wrapper_for = _wrapper_for; + } + + uint256 private _totalSupply; + + mapping(address => uint256) private balances; // List of user balances. + + function totalSupply() public view override returns (uint256) { return _totalSupply; } + function balanceOf(address _owner) public view override returns (uint256) { return balances[_owner]; } + + + /** + * @dev The ERC165 introspection function. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return + interfaceId == type(IERC20).interfaceId || + interfaceId == type(standardERC20).interfaceId || + interfaceId == type(IERC223WrapperToken).interfaceId || + interfaceId == type(IERC223).interfaceId || + super.supportsInterface(interfaceId); + } + + /** + * @dev Standard ERC-223 transfer function. + * Calls _to if it is a contract. Does not transfer tokens to contracts + * which do not explicitly declare the tokenReceived function. + * @param _to - transfer recipient. Can be contract or EOA. + * @param _value - the quantity of tokens to transfer. + * @param _data - metadata to send alongside the transaction. Can be used to encode subsequent calls in the recipient. + */ + function transfer(address _to, uint _value, bytes calldata _data) public payable override returns (bool success) + { + balances[msg.sender] = balances[msg.sender] - _value; + balances[_to] = balances[_to] + _value; + if(Address.isContract(_to)) { + IERC223Recipient(_to).tokenReceived(msg.sender, _value, _data); + } + if (msg.value > 0) payable(_to).transfer(msg.value); + emit Transfer(msg.sender, _to, _value, _data); + emit Transfer(msg.sender, _to, _value); // Old ERC-20 compatible event. Added for backwards compatibility reasons. + + return true; + } + + /** + * @dev Standard ERC-223 transfer function without _data parameter. It is supported for + * backwards compatibility with ERC-20 services. + * Calls _to if it is a contract. Does not transfer tokens to contracts + * which do not explicitly declare the tokenReceived function. + * @param _to - transfer recipient. Can be contract or EOA. + * @param _value - the quantity of tokens to transfer. + */ + function transfer(address _to, uint _value) public override returns (bool success) + { + bytes memory _empty = hex"00000000"; + balances[msg.sender] = balances[msg.sender] - _value; + balances[_to] = balances[_to] + _value; + if(Address.isContract(_to)) { + IERC223Recipient(_to).tokenReceived(msg.sender, _value, _empty); + } + emit Transfer(msg.sender, _to, _value, _empty); + emit Transfer(msg.sender, _to, _value); // Old ERC-20 compatible event. Added for backwards compatibility reasons. + + return true; + } + + function name() public view override returns (string memory) { return IERC20Metadata(wrapper_for).name(); } + function symbol() public view override returns (string memory) { return string.concat(IERC20Metadata(wrapper_for).name(), "223"); } + function decimals() public view override returns (uint8) { return IERC20Metadata(wrapper_for).decimals(); } + function standard() public pure returns (uint32) { return 223; } + function origin() public view returns (address) { return wrapper_for; } + + + /** + * @dev Minting function which will only be called by the converter contract. + * @param _recipient - the address which will receive tokens. + * @param _quantity - the number of tokens to create. + */ + function mint(address _recipient, uint256 _quantity) external + { + require(msg.sender == creator, "Wrapper Token: Only the creator contract can mint wrapper tokens."); + balances[_recipient] += _quantity; + _totalSupply += _quantity; + } + + /** + * @dev Burning function which will only be called by the converter contract. + * @param _quantity - the number of tokens to destroy. TokenConverter can only destroy tokens on it's own address. + * Only the token converter is allowed to burn wrapper-tokens. + */ + function burn(uint256 _quantity) external + { + require(msg.sender == creator, "Wrapper Token: Only the creator contract can destroy wrapper tokens."); + balances[msg.sender] -= _quantity; + _totalSupply -= _quantity; + } + + // ERC-20 functions for backwards compatibility. + + function allowance(address owner, address spender) public view virtual returns (uint256) { + return allowances[owner][spender]; + } + + function approve(address _spender, uint _value) public returns (bool) { + + // Safety checks. + require(_spender != address(0), "ERC-223: Spender error."); + + allowances[msg.sender][_spender] = _value; + emit Approval(msg.sender, _spender, _value); + + return true; + } + + function transferFrom(address _from, address _to, uint _value) public returns (bool) { + + require(allowances[_from][msg.sender] >= _value, "ERC-223: Insufficient allowance."); + + balances[_from] -= _value; + allowances[_from][msg.sender] -= _value; + balances[_to] += _value; + + emit Transfer(_from, _to, _value); + + return true; + } +} + +contract ERC20WrapperToken is IERC20, ERC165, ERC20Rescue +{ + address public creator = msg.sender; + address public wrapper_for; + + mapping(address account => mapping(address spender => uint256)) private allowances; + + function set(address _wrapper_for) external + { + require(msg.sender == creator); + wrapper_for = _wrapper_for; + } + + uint256 private _totalSupply; + mapping(address => uint256) private balances; // List of user balances. + + + function balanceOf(address _owner) public view override returns (uint256) { return balances[_owner]; } + + function name() public view returns (string memory) { return IERC20Metadata(wrapper_for).name(); } + function symbol() public view returns (string memory) { return string.concat(IERC223(wrapper_for).name(), "20"); } + function decimals() public view returns (uint8) { return IERC20Metadata(wrapper_for).decimals(); } + function totalSupply() public view override returns (uint256) { return _totalSupply; } + function origin() public view returns (address) { return wrapper_for; } + + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return + interfaceId == type(IERC20).interfaceId || + interfaceId == type(IERC20WrapperToken).interfaceId || + super.supportsInterface(interfaceId); + } + + function transfer(address _to, uint _value) public override returns (bool success) + { + balances[msg.sender] = balances[msg.sender] - _value; + balances[_to] = balances[_to] + _value; + emit Transfer(msg.sender, _to, _value); + return true; + } + + function mint(address _recipient, uint256 _quantity) external + { + require(msg.sender == creator, "Wrapper Token: Only the creator contract can mint wrapper tokens."); + balances[_recipient] += _quantity; + _totalSupply += _quantity; + } + + function burn(address _from, uint256 _quantity) external + { + require(msg.sender == creator, "Wrapper Token: Only the creator contract can destroy wrapper tokens."); + balances[_from] -= _quantity; + _totalSupply -= _quantity; + } + + function allowance(address owner, address spender) public view virtual returns (uint256) { + return allowances[owner][spender]; + } + + function approve(address _spender, uint _value) public returns (bool) { + + // Safety checks. + + require(_spender != address(0), "ERC-20: Spender error."); + + allowances[msg.sender][_spender] = _value; + emit Approval(msg.sender, _spender, _value); + + return true; + } + + function transferFrom(address _from, address _to, uint _value) public returns (bool) { + + require(allowances[_from][msg.sender] >= _value, "ERC-20: Insufficient allowance."); + + balances[_from] -= _value; + allowances[_from][msg.sender] -= _value; + balances[_to] += _value; + + emit Transfer(_from, _to, _value); + + return true; + } +} + +contract TokenStandardConverter is IERC223Recipient +{ + event ERC223WrapperCreated(address indexed _token, address indexed _ERC223Wrapper); + event ERC20WrapperCreated(address indexed _token, address indexed _ERC20Wrapper); mapping (address => ERC223WrapperToken) public erc223Wrappers; // A list of token wrappers. First one is ERC-20 origin, second one is ERC-223 version. mapping (address => ERC20WrapperToken) public erc20Wrappers; @@ -145,24 +623,14 @@ This service is the first of its kind and therefore does not have any backwards mapping (address => address) public erc20Origins; mapping (address => uint256) public erc20Supply; // Token => how much was deposited. - function getERC20WrapperFor(address _token) public view returns (address, string memory) + function getERC20WrapperFor(address _token) public view returns (address) { - if ( address(erc20Wrappers[_token]) != address(0) ) - { - return (address(erc20Wrappers[_token]), "ERC-20"); - } - - return (address(0), "Error"); + return address(erc20Wrappers[_token]); } - function getERC223WrapperFor(address _token) public view returns (address, string memory) + function getERC223WrapperFor(address _token) public view returns (address) { - if ( address(erc223Wrappers[_token]) != address(0) ) - { - return (address(erc223Wrappers[_token]), "ERC-223"); - } - - return (address(0), "Error"); + return address(erc223Wrappers[_token]); } function getERC20OriginFor(address _token) public view returns (address) @@ -175,7 +643,33 @@ This service is the first of its kind and therefore does not have any backwards return (address(erc223Origins[_token])); } - function tokenReceived(address _from, uint _value, bytes memory _data) public override returns (bytes4) + function predictWrapperAddress(address _token, + bool _isERC20 // Is the provided _token a ERC-20 or not? + // If it is set as ERC-20 then we will predict the address of a + // ERC-223 wrapper for that token. + // Otherwise we will predict ERC-20 wrapper address. + ) view external returns (address) + { + bytes memory _bytecode; + if(_isERC20) + { + _bytecode= type(ERC223WrapperToken).creationCode; + } + else + { + _bytecode= type(ERC20WrapperToken).creationCode; + } + + bytes32 hash = keccak256( + abi.encodePacked( + bytes1(0xff), address(this), keccak256(abi.encode(_token)), keccak256(_bytecode) + ) + ); + + return address(uint160(uint(hash))); + } + + function tokenReceived(address _from, uint _value, bytes memory /* _data */) public override returns (bytes4) { require(erc223Origins[msg.sender] == address(0), "Error: creating wrapper for a wrapper token."); // There are two possible cases: @@ -187,12 +681,11 @@ This service is the first of its kind and therefore does not have any backwards // Origin for deposited token exists. // Unwrap ERC-223 wrapper. + erc20Supply[erc20Origins[msg.sender]] -= _value; safeTransfer(erc20Origins[msg.sender], _from, _value); - erc20Supply[erc20Origins[msg.sender]] -= _value; - //erc223Wrappers[msg.sender].burn(_value); ERC223WrapperToken(msg.sender).burn(_value); - + return this.tokenReceived.selector; } // Otherwise origin for the sender token doesn't exist @@ -204,7 +697,7 @@ This service is the first of its kind and therefore does not have any backwards // Create ERC-20 wrapper if it doesn't exist. createERC20Wrapper(msg.sender); } - + // Mint ERC-20 wrapper tokens for the deposited ERC-223 token // if the ERC-20 wrapper didn't exist then it was just created in the above statement. erc20Wrappers[msg.sender].mint(_from, _value); @@ -214,38 +707,31 @@ This service is the first of its kind and therefore does not have any backwards function createERC223Wrapper(address _token) public returns (address) { require(address(erc223Wrappers[_token]) == address(0), "ERROR: Wrapper exists"); - require(getERC20OriginFor(_token) == address(0), "ERROR: 20 wrapper creation"); - require(getERC223OriginFor(_token) == address(0), "ERROR: 223 wrapper creation"); - - ERC223WrapperToken _newERC223Wrapper = new ERC223WrapperToken(_token); + require(!isWrapper(_token), "Error: Creating wrapper for a wrapper token"); + + ERC223WrapperToken _newERC223Wrapper = new ERC223WrapperToken{salt: keccak256(abi.encode(_token))}(); + _newERC223Wrapper.set(_token); erc223Wrappers[_token] = _newERC223Wrapper; erc20Origins[address(_newERC223Wrapper)] = _token; + emit ERC223WrapperCreated(_token, address(_newERC223Wrapper)); return address(_newERC223Wrapper); } function createERC20Wrapper(address _token) public returns (address) { require(address(erc20Wrappers[_token]) == address(0), "ERROR: Wrapper already exists."); - require(getERC20OriginFor(_token) == address(0), "ERROR: 20 wrapper creation"); - require(getERC223OriginFor(_token) == address(0), "ERROR: 223 wrapper creation"); + require(!isWrapper(_token), "Error: Creating wrapper for a wrapper token"); - ERC20WrapperToken _newERC20Wrapper = new ERC20WrapperToken(_token); + ERC20WrapperToken _newERC20Wrapper = new ERC20WrapperToken{salt: keccak256(abi.encode(_token))}(); + _newERC20Wrapper.set(_token); erc20Wrappers[_token] = _newERC20Wrapper; erc223Origins[address(_newERC20Wrapper)] = _token; + emit ERC20WrapperCreated(_token, address(_newERC20Wrapper)); return address(_newERC20Wrapper); } - function depositERC20(address _token, uint256 _amount) public returns (bool) - { - if(erc223Origins[_token] != address(0)) - { - return unwrapERC20toERC223(_token, _amount); - } - else return wrapERC20toERC223(_token, _amount); - } - function wrapERC20toERC223(address _ERC20token, uint256 _amount) public returns (bool) { // If there is no active wrapper for a token that user wants to wrap @@ -256,14 +742,12 @@ This service is the first of its kind and therefore does not have any backwards } uint256 _converterBalance = IERC20(_ERC20token).balanceOf(address(this)); // Safety variable. safeTransferFrom(_ERC20token, msg.sender, address(this), _amount); - - erc20Supply[_ERC20token] += _amount; - require( - IERC20(_ERC20token).balanceOf(address(this)) - _amount == _converterBalance, - "ERROR: The transfer have not subtracted tokens from callers balance."); + _amount = IERC20(_ERC20token).balanceOf(address(this)) - _converterBalance; + erc20Supply[_ERC20token] += _amount; erc223Wrappers[_ERC20token].mint(msg.sender, _amount); + return true; } @@ -273,48 +757,30 @@ This service is the first of its kind and therefore does not have any backwards require(erc223Origins[_ERC20token] != address(0), "Error: provided token is not a ERC-20 wrapper."); ERC20WrapperToken(_ERC20token).burn(msg.sender, _amount); - IERC223(erc223Origins[_ERC20token]).transfer(msg.sender, _amount); + + safeTransfer(erc223Origins[_ERC20token], msg.sender, _amount); return true; } - function isWrapper(address _token) public view returns (bool) - { - return erc20Origins[_token] != address(0) || erc223Origins[_token] != address(0); - } - -/* - function convertERC223toERC20(address _from, uint256 _amount) public returns (bool) + function convertERC20(address _token, uint256 _amount) public returns (bool) { - // If there is no active wrapper for a token that user wants to wrap - // then create it. - if(address(erc20Wrappers[msg.sender]) == address(0)) - { - createERC223Wrapper(msg.sender); - } - - erc20Wrappers[msg.sender].mint(_from, _amount); - return true; + if(isWrapper(_token)) return unwrapERC20toERC223(_token, _amount); + else return wrapERC20toERC223(_token, _amount); } -*/ - function rescueERC20(address _token) external { - require(msg.sender == ownerMultisig, "ERROR: Only owner can do this."); - uint256 _stuckTokens = IERC20(_token).balanceOf(address(this)) - erc20Supply[_token]; - safeTransfer(_token, msg.sender, IERC20(_token).balanceOf(address(this))); + function isWrapper(address _token) public view returns (bool) + { + return erc20Origins[_token] != address(0) || erc223Origins[_token] != address(0); } - function transferOwnership(address _newOwner) public + function extractStuckERC20(address _token) external { - require(msg.sender == ownerMultisig, "ERROR: Only owner can call this function."); - ownerMultisig = _newOwner; + require(msg.sender == address(0x01000B5fE61411C466b70631d7fF070187179Bbf)); + + safeTransfer(_token, address(0x01000B5fE61411C466b70631d7fF070187179Bbf), IERC20(_token).balanceOf(address(this)) - erc20Supply[_token]); } - // ************************************************************ - // Functions that address problems with tokens that pretend to be ERC-20 - // but in fact are not compatible with the ERC-20 standard transferring methods. - // EIP20 https://eips.ethereum.org/EIPS/eip-20 - // ************************************************************ function safeTransfer(address token, address to, uint value) internal { // bytes4(keccak256(bytes('transfer(address,uint256)'))); (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value)); @@ -326,16 +792,18 @@ This service is the first of its kind and therefore does not have any backwards (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value)); require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FROM_FAILED'); } - +} ``` ## Security Considerations -1. While it is possible to implement a service that converts any token standard to any other standard - it is better to keep different standard convertors separate from one another as different standards may contain specific logic. Therefore this proposal focuses on [ERC-20](./eip-20.md) to [ERC-223](./eip-223.md) conversion methods. -2. [ERC-20](./eip-20.md) tokens can be deposited to any contract directly with `transfer` function. This may result in a permanent loss of tokens because it is not possible to recognize this transaction on the recipients side. `rescueERC20` function is implemented to address this problem. +1. While it is possible to implement a service that converts any token standard to any other standard - it is better to keep different standard convertors separate from one another as different standards may contain specific logic and therefore require different conversion approach. This proposal focuses on [ERC-20](./eip-20.md) and [ERC-223](./eip-223.md) upgradeability. +2. [ERC-20](./eip-20.md) tokens can be deposited to any contract directly with `transfer` function. This may result in a permanent loss of tokens because it is not possible to recognize this transaction on the recipients side. Therefore wrapper-ERC-20 tokens are prone to this problem as they are compatible with the [ERC-20](./eip-20.md) standard. `rescueERC20` function is implemented to address this problem. 3. Token Converter relies on [ERC-20](./eip-20.md) `approve` & `transferFrom` method of depositing assets. Any related issues must be taken into account. `approve` and `transferFrom` are two separate transactions so it is required to make sure `approval` was successful before relying on `transferFrom`. -4. This is a common practice for UI services to prompt a user to issue unlimited `approval` on any contract that may withdraw tokens from the user. This puts users funds at high risk and therefore not recommended. -5. It is possible to artificially construct a token that will pretend it is a [ERC-20](./eip-20.md) token that implements `approve & transferFrom` but at the same time implements [ERC-223](./eip-223.md) logic of transferring via `transfer` function in its internal logic. It can be possible to create a [ERC-223](./eip-223.md) wrapper for this [ERC-20](./eip-20.md)-[ERC-223](./eip-223.md) hybrid implementation in the Token Converter. This doesn't pose any threat for the workflow of the Token Converter but it must be taken into account that if a token has [ERC-223](./eip-223.md) wrapper in the Token Converter it does not automatically mean the origin is fully compatible with the [ERC-20](./eip-20.md) standard and methods of introspection must be used to determine the origins compatibility with any existing standard. +4. This is a common practice for UI services to prompt a user to issue unlimited `approval` on any contract that may withdraw tokens from the user. This puts users funds at risk and therefore is not recommended. +5. There is no reliable token standard introspection method available that could guarantee that a token implements a particular token standard. It is possible to artificially construct a token that will pretend it is a [ERC-20](./eip-20.md) token that implements `approve & transferFrom` but at the same time implements [ERC-223](./eip-223.md) logic of transferring via `transfer` function. It can be possible to create a [ERC-223](./eip-223.md) wrapper for this [ERC-20](./eip-20.md)-[ERC-223](./eip-223.md) hybrid implementation in the Token Converter. This doesn't pose any threat for the workflow of the Token Converter itself but it must be taken into account that if a token has [ERC-223](./eip-223.md) wrapper in the Token Converter it does not automatically mean the origin is fully compatible with the [ERC-20](./eip-20.md) standard and methods of introspection must be used to determine the origins compatibility with any existing standard. +6. Token Converter does not verify the standard of a provided token when it is asked to create a wrapper for it due to the lack of reliable standard introspection method. It is possible to call `createERC20Wrapper` function and provide an address of an existing [ERC-20](./eip-20.md) token. The Token Converter will successfully create a [ERC-20](./eip-20.md) wrapper for that [ERC-20](./eip-20.md) original token. It is also possible to create a [ERC-223](./eip-223.md) wrapper for that exact original [ERC-20](./eip-20.md) token. This doesn't pose any threat to the workflow of the Converter but it must be taken into account that any token regardless of it's original standard may have up to two wrappers created by the Converter, one for each standard. Any wrapper token must have exactly one origin. It is not possible to create a wrapper for a wrapper. +7. The Token Converter only holds the original tokens that were deposited during the conversion process and it assumes that tokens do not decay over time and the token balance of the Converter does not decrease on its own. If some token implements burning logic or decaying supply and it may impact the balance of the Converter then the Converter must not be used to deploy an alternative version of that token as it will not be able to guarantee that there is enough tokens for the conversion at any time. ## Copyright diff --git a/ERCS/erc-7432.md b/ERCS/erc-7432.md index 0708c4c36a..1043f65f67 100644 --- a/ERCS/erc-7432.md +++ b/ERCS/erc-7432.md @@ -4,8 +4,7 @@ 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: Last Call -last-call-deadline: 2024-09-17 +status: Final type: Standards Track category: ERC created: 2023-07-14 diff --git a/ERCS/erc-7527.md b/ERCS/erc-7527.md index 042dcbce31..0f53f4d4ef 100644 --- a/ERCS/erc-7527.md +++ b/ERCS/erc-7527.md @@ -143,6 +143,14 @@ interface IERC7527Agency { */ function getStrategy() external view returns (address app, Asset memory asset, bytes memory attributeData); + /** + * @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 premium, uint256 fee); + /** * @dev Returns the premium and fee of unwrapping. * @param data The data to encode to calculate the premium and fee of unwrapping. @@ -152,12 +160,10 @@ interface IERC7527Agency { function getUnwrapOracle(bytes memory data) external view returns (uint256 premium, uint256 fee); /** - * @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. + * @dev OPTIONAL - This method can be used to improve usability and clarity of Agency, but interfaces and other contracts MUST NOT expect these values to be present. + * @return the description of the agency, such as how `getWrapOracle()` and `getUnwrapOracle()` are calculated. */ - function getWrapOracle(bytes memory data) external view returns (uint256 premium, uint256 fee); + function description() public view returns (string); } ``` @@ -216,6 +222,8 @@ Token ID can be specified in `data` parameter of `mint` function. ### Factory Interface +OPTIONAL - This interface can be used to deploy App and Agency, but interfaces and other contracts MUST NOT expect this interface to be present. + If a factory is needed to deploy bounded App and Agency, the factory SHALL implement the following interface: ``` diff --git a/ERCS/erc-7578.md b/ERCS/erc-7578.md index fa520d71d6..9acb4fbb37 100644 --- a/ERCS/erc-7578.md +++ b/ERCS/erc-7578.md @@ -151,14 +151,28 @@ contract ERC7578 is ERC721, IERC7578 { mapping(uint256 tokenId => Properties) private _properties; /** - * @notice Initializes the [ERC-721](./eip-721.md) dependency contract by setting a `name` and a `symbol` to the token collection + * @notice Initializes the name and symbol of the ERC-721 collection */ constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) {} + /** + * @inheritdoc IERC7578 + */ + function getProperties(uint256 tokenId) public view override returns (Properties memory properties) { + properties = _properties[tokenId]; + } + /** * @notice Initializes the ERC-7578 properties of the `tokenId` token + * + * WARNING: This method should only be called within a function that has appropriate access control + * It is recommended to restrict access to trusted Externally Owned Accounts (EOAs), + * authorized contracts, or implement a Role-Based Access Control (RBAC) mechanism + * Failure to properly secure this method could lead to unauthorized modification of token properties + * + * Emits a {PropertiesSet} event */ - function setProperties(uint256 tokenId, Properties calldata properties) public { + function _setProperties(uint256 tokenId, Properties calldata properties) internal { _properties[tokenId] = Properties({ tokenIssuer: properties.tokenIssuer, assetHolder: properties.assetHolder, @@ -174,16 +188,11 @@ contract ERC7578 is ERC721, IERC7578 { emit PropertiesSet(tokenId, _properties[tokenId]); } - /** - * @inheritdoc IERC7578 - */ - function getProperties(uint256 tokenId) public view override returns (Properties memory properties) { - properties = _properties[tokenId]; - } - /** * @notice Removes the properties of the `tokenId` token - * @param tokenId The ID of the token from which to remove the properties + * @param tokenId The unique identifier of the token whose properties are to be removed + * + * Emits a {PropertiesRemoved} event */ function _removeProperties(uint256 tokenId) internal { delete _properties[tokenId]; @@ -193,7 +202,7 @@ contract ERC7578 is ERC721, IERC7578 { /** * @notice Override of the {_update} function to remove the properties of the `tokenId` token or * to check if they are set before minting - * @param tokenId The ID of the token being minted or burned + * @param tokenId The unique identifier of the token being minted or burned */ function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) { address from = _ownerOf(tokenId); @@ -210,7 +219,7 @@ contract ERC7578 is ERC721, IERC7578 { ## Security Considerations -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. +To ensure the authenticity of a token's properties, the `_setProperties()` method should only be called inside a method that is restricted to 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-7726.md b/ERCS/erc-7726.md index 85c4fec7c5..ee22070a36 100644 --- a/ERCS/erc-7726.md +++ b/ERCS/erc-7726.md @@ -46,10 +46,6 @@ 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 @@ -80,34 +76,6 @@ 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 diff --git a/ERCS/erc-7741.md b/ERCS/erc-7741.md index 7c7c6f57d7..47ef38948c 100644 --- a/ERCS/erc-7741.md +++ b/ERCS/erc-7741.md @@ -77,10 +77,10 @@ MUST return `true`. type: address - name: approved type: bool - - name: deadline - type: uint256 - name: nonce type: bytes32 + - name: deadline + type: uint256 - name: signature type: bytes @@ -139,7 +139,7 @@ Returns the `DOMAIN_SEPARATOR` as defined according to EIP-712. The `DOMAIN_SEPA 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. +Contracts MUST return the constant value `true` if `0xa9e50872` is passed through the `interfaceID` argument. ## Rationale @@ -155,7 +155,7 @@ The main difference is using `bytes32` vs `uint256`, which enables unordered non // 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)"); + keccak256("AuthorizeOperator(address controller,address operator,bool approved,bytes32 nonce,uint256 deadline)"); mapping(address owner => mapping(bytes32 nonce => bool used)) authorizations; @@ -171,8 +171,8 @@ The main difference is using `bytes32` vs `uint256`, which enables unordered non address controller, address operator, bool approved, - uint256 deadline, bytes32 nonce, + uint256 deadline, bytes memory signature ) external returns (bool success) { require(block.timestamp <= deadline, "ERC7540Vault/expired"); @@ -185,7 +185,7 @@ The main difference is using `bytes32` vs `uint256`, which enables unordered non abi.encodePacked( "\x19\x01", DOMAIN_SEPARATOR(), - keccak256(abi.encode(AUTHORIZE_OPERATOR_TYPEHASH, controller, operator, approved, deadline, nonce)) + keccak256(abi.encode(AUTHORIZE_OPERATOR_TYPEHASH, controller, operator, approved, nonce, deadline)) ) ); diff --git a/ERCS/erc-7751.md b/ERCS/erc-7751.md new file mode 100644 index 0000000000..ac91a16651 --- /dev/null +++ b/ERCS/erc-7751.md @@ -0,0 +1,143 @@ +--- +eip: 7751 +title: Wrapping of bubbled up reverts +description: Handling bubbled up reverts using custom errors with additional context +author: Daniel Gretzke (@gretzke), Sara Reynolds (@snreynolds), Alice Henshaw (@hensha256), Marko Veniger , Hadrien Croubois (@Amxx) +discussions-to: https://ethereum-magicians.org/t/erc-7751-wrapping-of-bubbled-up-reverts/20740 +status: Draft +type: Standards Track +category: ERC +created: 2024-08-06 +--- + +## Abstract + +This ERC proposes a standard for handling bubbled up reverts in Ethereum smart contracts using custom errors. This standard aims to improve the clarity and usability of revert reasons by allowing additional context to be passed alongside the raw bytes of the bubbled up revert. The custom errors should follow the naming structure `Wrap__` followed by a descriptive name and allow an arbitrary number of parameters, with the first being the address of the called contract and the second being the raw bytes of the bubbled up revert. + +## Motivation + +Currently, when a smart contract calls another and the called contract reverts, the revert reason is usually bubbled up and thrown as is. This can make it more difficult to tell which context the error came from. By standardizing the use of custom errors with additional context, more meaningful and informative revert reasons can be provided. This will improve the debugging experience and make it easier for developers and infrastructure providers like Etherscan to display accurate stack traces. + +## 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. + +1. **Custom Error Naming Convention:** + - Custom errors that bubble up reverts MUST be prefixed with `Wrap__`. + - Example: `Wrap__ERC20TransferFailed`. +2. **Parameters:** + - The first parameter MUST be the address of the called contract that reverted. + - The second parameter MUST be the raw bytes of the bubbled up revert. + - Additional parameters MAY be included to provide context. + - Example: `Wrap__ERC20TransferFailed(address token, bytes reason, address recipient)`. + +If no additional parameters are defined and a generic call reverts, the custom error `Wrap__SubcontextReverted(address, bytes)` SHOULD be thrown to ensure a consistent signature. + +## Rationale + +By including the called contract, raw revert bytes and additional context, developers can provide more detailed information about the failure. Additionally, by standardizing the way reverts are bubbled up, it also enables nested bubbled up reverts where multiple reverts thrown by different contracts can be followed recursively. The reverts can also be parsed and handled by tools like Etherscan and Foundry to further enhance the readability and debuggability of smart contract interactions, as well as facilitating better error handling practices in general. + +## Backwards Compatibility + +This ERC does not introduce any backwards incompatibilities. Existing contracts can adopt this standard incrementally. + +## Test Cases + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +contract Token { + mapping(address => uint256) public balanceOf; + + event Transfer(address indexed sender, address indexed recipient, uint amount); + + function transfer(address to, uint256 amount) external returns (bool) { + require(balanceOf[msg.sender] >= amount, "insufficient balance"); + balanceOf[msg.sender] -= amount; + balanceOf[to] += amount; + emit Transfer(msg.sender, to, amount); + return true; + } +} + +contract Vault { + Token token; + + error Wrap__ERC20TransferFailed(address token, bytes reason, address recipient); + + constructor(Token token_) { + token = token_; + } + + function withdraw(address to, uint256 amount) external { + // logic + try token.transfer(to, amount) {} catch (bytes memory error) { + revert Wrap__ERC20TransferFailed(address(token), error, to); + } + } +} + +contract Router { + Vault vault; + + error Wrap__SubcontextReverted(address target, bytes reason); + + constructor(Vault vault_) { + vault = vault_; + } + + function withdraw(uint256 amount) external { + // logic + try vault.withdraw(msg.sender, amount) {} catch (bytes memory error) { + revert Wrap__SubcontextReverted(address(vault), error); + } + } +} + +contract Test { + function test_BubbledNestedReverts(uint256 amount) external { + Token token = new Token(); + Vault vault = new Vault(token); + Router router = new Router(vault); + + try router.withdraw(amount) {} catch (bytes memory thrownError) { + bytes memory expectedError = abi.encodeWithSelector( + Router.Wrap__SubcontextReverted.selector, address(vault), abi.encodeWithSelector( + Vault.Wrap__ERC20TransferFailed.selector, + address(token), + abi.encodeWithSignature("Error(string)", "insufficient balance"), + address(this) + ) + ); + assert(keccak256(thrownError) == keccak256(expectedError)); + } + } +} +``` + +## Reference Implementation + +When catching a revert from a called contract, the calling contract should revert with a custom error following the above conventions. + +```solidity +contract Foo { + error Wrap__SubcontextReverted(address target, bytes reason); + + function foo(address to, bytes memory data) external { + // logic + (bool success, bytes memory returnData) = to.call(data); + if (!success) { + revert Wrap__SubcontextReverted(to, returnData); + } + } +} +``` + +## Security Considerations + +This EIP does not introduce new security risks. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/ERCS/erc-7754.md b/ERCS/erc-7754.md new file mode 100644 index 0000000000..2c8bb2dd8c --- /dev/null +++ b/ERCS/erc-7754.md @@ -0,0 +1,256 @@ +--- +eip: 7754 +title: Tamperproof Web Immutable Transaction (TWIT) +description: Provides a mechanism for dapps to use the extension wallets API in a tamperproof way +author: Erik Marks (@remarks), Guillaume Grosbois (@uni-guillaume) +discussions-to: https://ethereum-magicians.org/t/erc-7754-tamperproof-web-immutable-transaction-twit/20767 +status: Draft +type: Standards Track +category: ERC +created: 2024-07-29 +requires: 1193 +--- + +## Abstract + +Introduces a new RPC method to be implemented by wallets, `wallet_signedRequest`, that +enables dapps to interact with wallets in a tamperproof manner via "signed requests". The +dapp associates a public key with its DNS record and uses the corresponding private key to +sign payloads sent to the wallet via `wallet_signedRequest`. Wallets can then use use the +public key in the DNS record to validate the integrity of the payload. + +## Motivation + +This standard aims to enhance the end user's experience by granting them confidence that requests from their dapps have not been tampered with. +In essence, this is similar to how HTTPS is used in the web. + +Currently, the communication channel between dapps and wallets is vulnerable to man in the middle attacks. +Specifically, attackers can intercept RPC requests by injecting JavaScript code in the page, +via e.g. an XSS vulnerability or due to a malicious extension. +Once an RPC request is intercepted, it can be modified in a number of pernicious ways, including: + +- Editing the calldata in order to siphon funds or otherwise change the transaction outcome +- Modifying the parameters of an [EIP-712](./eip-712.md) request +- Obtaining a replayable signature from the wallet + +Even if the user realizes that requests from the dapp may be tampered with, they have little to no recourse to mitigate the problem. +Overall, the lack of a chain of trust between the dapp and the wallet hurts the ecosystem as a whole: + +- Users cannot simply trust otherwise honest dapps, and are at risk of losing funds +- Dapp maintainers are at risk of hurting their reputations if an attacker finds a viable MITM attack + +For these reasons, we recommend that wallets implement the `wallet_signedRequest` RPC method. +This method provides dapp developers with a way to explicitly ask the wallet to verify the +integrity of a payload. This is a significant improvement over the status quo, which forces +dapps to rely on implicit approaches such as argument bit packing. + +## 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 + +We propose to use the dapp's domain certificate of a root of trust to establish a trust chain as follow: + +1. The user's browser verifies the domain certificate and displays appropriate warnings if overtaken +2. The DNS record of the dapp hosts a TXT field pointing to a URL where a JSON manifest is hosted + - This file SHOULD be at a well known address such as `https://example.com/.well-known/twit.json` +3. The config file contains an array of objects of the form `{ id, alg, publicKey }` +4. For signed requests, the dapp first securely signs the payload with a private key, for example by submitting a request to its backend +5. The original payload, signature, and public key id are sent to the wallet via the `wallet_signedRequest` RPC method +6. The wallet verifies the signature before processing the request normally + +### Wallet integration + +#### Key discovery + +Attested public keys are necessary for the chain of trust to be established. +Since this is traditionally done via DNS certificates, we propose the addition of a DNS record containing the public keys. +This is similar to [RFC-6636](https://www.rfc-editor.org/rfc/rfc6376.html)'s DKIM, but the use of a manifest file provides more flexibility for future improvements, as well as support for multiple algorithm and key pairs. + +Similarly to standard [RFC-7519](https://www.rfc-editor.org/rfc/rfc7519.html)'s JWT practices, the wallet could eagerly cache dapp keys. +However, in the absence of a revocation mechanism, a compromised key could still be used until caches have expired. +To mitigate this, wallets SHOULD NOT cache dapp public keys for more than 2 hours. +This practice establishes a relatively short vulnerability window, and manageable overhead for both wallet and dapp maintainers. + +Example DNS record for `my-crypto-dapp.invalid`: + +```txt +... +TXT: TWIT=/.well-known/twit.json +``` + +Example TWIT manifest at `https://my-crypto-dapp.invalid.com/twit.json`: + +```json +{ + "publicKeys": [ + { "id": "1", "alg": "ECDSA", "publicKey": "0xaf34..." }, + { "id": "2", "alg": "RSA-PSS", "publicKey": "0x98ab..." } + ] +} +``` + +Dapps SHOULD only rely on algorithms available via [SubtleCrypto](https://www.w3.org/TR/2017/REC-WebCryptoAPI-20170126/), since they are present in every browser. + +#### Manifest schema + +We propose a simple and extensible schema: + +```json +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "TWIT manifest", + "type": "object", + "properties": { + "publicKeys": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "alg": { "type": "string" }, + "publicKey": { "type": "string" } + } + } + } + } +} +``` + +#### RPC method + +The parameters of `wallet_signedRequest` are specified by this TypeScript interface: + +```typescript +type RequestPayload = { method: string; params: Params }; + +type SignedRequestParameters = [ + requestPayload: RequestPayload, + signature: `0x${string}`, + keyId: string, +]; +``` + +Here's a non-normative example of calling `wallet_signedRequest` using the [EIP-1193](./eip-1193.md) provider interface: + +```typescript +const keyId = '1'; +const requestPayload: RequestPayload = { + method: 'eth_sendTransaction', + params: [ + { + /* ... */ + }, + ], +}; +const signature: `0x${string}` = await getSignature(requestPayload, keyId); + +// Using the EIP-1193 provider interface +const result = await ethereum.request({ + method: 'wallet_signedRequest', + params: [requestPayload, signature, keyId], +}); +``` + +#### Signature verification + +1. Upon receiving an [EIP-1193](./eip-1193.md) call, the wallet MUST check of the existence of the TWIT manifest for the `sender.tab.url` domain + a. The wallet MUST verify that the manifest is hosted on the `sender.tab.url` domain + b. The wallet SHOULD find the DNS TXT record to find the manifest location + b. The wallet MAY first try the `/.well-known/twit.json` location +2. If TWIT is NOT configured for the `sender.tab.url` domain, then proceed as usual +3. If TWIT is configured and the `request` method is used, then the wallet SHOULD display a visible and actionable warning to the user + a. If the user opts to ignore the warning, then proceed as usual + b. If the user opts to cancel, then the wallet MUST cancel the call +4. If TWIT is configured and the `wallet_signedRequest` method is used with the parameters `requestPayload`, `signature` and `keyId` then: + a. The wallet MAY display a visible cue indicating that this interaction is signed + b. The wallet MUST verify that the keyId exists in the TWIT manifest and find the associated key record + c. From the key record, the wallet MUST use the `alg` field and the `publicKey` field to verify `requestPayload` integrity by calling `crypto.verify(alg, key, signature, requestPayload)` + d. If the signature is invalid, the wallet MUST display a visible and actionable warning to the user + i. If the user opts to ignore the warning, then proceed to call `request` with the argument `requestPayload` + ii. If the user opts to cancel, then the wallet MUST cancel the call + e. If the signature is valid, the wallet MUST call `request` with the argument `requestPayload` + +#### Example method implementation (wallet) + +```typescript +async function signedRequest( + requestPayload: RequestPayload, + signature: `0x${string}`, + keyId: string, +): Promise { + // 1. Get the domain of the sender.tab.url + const domain = getDappDomain(); + + // 2. Get the manifest for the current domain + // It's possible to use RFC 8484 for the actual DNS-over-HTTPS specification, see https://datatracker.ietf.org/doc/html/rfc8484. + // However, here we are doing it with DoHjs. + // This step is optional, and you could go directly to the well-known address first at `domain + '/.well-known/twit.json'` + const doh = require('dohjs'); + const resolver = new doh.DohResolver('https://1.1.1.1/dns-query'); + + let manifestPath = ''; + const dnsResp = await resolver.query(domain, 'TXT'); + for (record of dnsResp.answers) { + if (!record.data.startsWith('TWIT=')) continue; + + manifestPath = record.data.substring(5); // This should be domain + '/.well-known/twit.json' + break; + } + + // 3. Parse the manifest and get they key and algo based on `keyId` + const manifestReq = await fetch(manifestPath); + const manifest = await manifestReq.json(); + const keyData = manifest.publicKeys.filter((x) => x.id == keyId); + if (!keyData) { + throw new Error('Could not find the signing key'); + } + + const key = keyData.publicKey; + const alg = keyData.alg; + + // 4. Verify the signature + const valid = await crypto.verify(alg, key, signature, requestPayload); + if (!valid) { + throw new Error('The data was tampered with'); + } + return await processRequest(requestPayload); +} +``` + +### Wallet UX suggestion + +Similarly to the padlock icon for HTTPS, wallets should display a visible indication when TWIT is configured on a domain. This will improve the UX of the end user who will immediately be able to tell +that interactions between the dapp they are using and the wallet are secure, and this will encourage dapp developer to adopt TWIT, making the overall ecosystem more secure + +When dealing with insecure request, either because the dapp (or an attacker) uses `request` on a domain where TWIT is configured, or because the signature does not match, wallets should warn the user but +not block: an eloquently worded warning will increase the transparency enough that end user may opt to cancel the interaction or proceed with the unsafe call. + +## Rationale + +The proposed implementation does not modify any of the existing functionalities offered by [EIP-712](./eip-712.md) and [EIP-1193](./eip-1193.md). Its additive +nature makes it inherently backward compatible. Its core design is modeled after existing solutions to existing problems (such as DKIM). As a result the proposed specification will be non disruptive, easy to +implements for both wallets and dapps, with a predictable threat model. + +## Security Considerations + +### Replay prevention + +While signing the `requestArg` payload guarantees data integrity, it does not prevent replay attacks in itself: + +1. a signed payload can be replayed multiple times +2. a signed payload can be replayed across multiple chains + +_Effective_ time replay attacks as described in `1.` are generally prevented by the transaction nonce. +Cross chain replay can be prevented by leveraging the [EIP-712](./eip-712.md) `signTypedData` method. + +Replay attack would still be possible on any method that is not protected by either: this affects effectively all the "readonly" methods +which are of very limited value for an attacker. + +For these reason, we do not recommend a specific replay protection mechanism at this time. If/when the need arise, the extensibility of +the manifest will provide the necessary room to enforce a replay protection envelope (eg:JWT) for affected dapp. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/ERCS/erc-7758.md b/ERCS/erc-7758.md index 1219feda4b..e4c7e75f14 100644 --- a/ERCS/erc-7758.md +++ b/ERCS/erc-7758.md @@ -4,7 +4,7 @@ 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 +status: Review type: Standards Track category: ERC created: 2020-09-28 diff --git a/assets/erc-5173/nFR_distribution_formula.jpg b/assets/erc-5173/nFR_distribution_formula.jpg new file mode 100644 index 0000000000..3b2b373a75 Binary files /dev/null and b/assets/erc-5173/nFR_distribution_formula.jpg differ diff --git a/assets/erc-5173/nFR_distribution_formula.png b/assets/erc-5173/nFR_distribution_formula.png deleted file mode 100644 index b4f42acd4a..0000000000 Binary files a/assets/erc-5173/nFR_distribution_formula.png and /dev/null differ diff --git a/assets/erc-7208/contracts/README.md b/assets/erc-7208/contracts/README.md new file mode 100644 index 0000000000..2a0ea7707b --- /dev/null +++ b/assets/erc-7208/contracts/README.md @@ -0,0 +1,22 @@ +# Reference implementation of ERC-7208 and usage examples +## List of contracts +### Interfaces +- [IDataIndex](./interfaces/IDataIndex.sol) - Interface of Data Index +- [IDataObject](./interfaces/IDataObject.sol) - Interface of Data Object +- [IDataPointRegistry](./interfaces/IDataPointRegistry.sol) - Interface of Data Point Registry +- [IIDManager](./interfaces/IIDManager.sol) - Interface for buildinq and quering Data Index user identifiers + +### Implementation +- [DataIndex](./DataIndex.sol) - Data Index (implements `IDataIndex` and `IIDManager`) +- [DataPointRegistry](./DataPointRegistry.sol) - Data Point Registry (implements `IDataPointRegistry`) +- [DataPoints](./utils/DataPoints.sol) - Library implementing DataPoint type and its encode/decode functions +- [OmnichainAddresses](./utils/OmnichainAddresses.sol) - Library implementing OmnichainAddress type which combines address and chain id +- [ChainidTools](./utils/ChainidTools.sol) - Library implementing utility functions to work with chain ids + +### Usage examples +- [IFractionTransferEventEmitter](./interfaces/IFractionTransferEventEmitter.sol) - Interface used for DataManagers communication to emit ERC20 Transfer events +- [IFungibleFractionsOperations](./interfaces/IFungibleFractionsOperations.sol) - Interface defines DataObject operations, which can be called by DataManager +- [MinimalisticFungibleFractionsDO](./dataobjects/MinimalisticFungibleFractionsDO.sol) - DataObject implements data storage and related logic for token with Fungible Fractions (like ERC1155) +- [MinimalisticERC1155WithERC20FractionsDataManager](./datamanagers/MinimalisticERC1155WithERC20FractionsDataManager.sol) - DataManager implements token with fungible fractions with ERC1155 interface, linked to a DataManager which implements ERC20 interface for same token +- [MinimalisticERC20FractionDataManager](./datamanagers/MinimalisticERC20FractionDataManager.sol) - implements token with ERC20 interface, linked to a DataManager which implements ERC1155 interface for same token +- [MinimalisticERC20FractionDataManagerFactory](./datamanagers/MinimalisticERC20FractionDataManagerFactory.sol) - factory of DataManagers implementing ERC20 interface for token with fungible fractions \ No newline at end of file